Skip to content

eproperty

eproperty

A descriptor for a value the interleaver serves during a trace.

An eproperty turns a plain attribute — model.h[0].output, tracer.result, a source op's .input — into a handoff point in the run: reading it parks the worker until the model reaches that location and returns the value there; writing it swaps the worker's value in. The location is "{obj.path}.{key}" (or just key when the host has no path, as for tracer.result).

The decorated stub is the preprocess: it takes the raw value the interleaver served and returns what the user reads, so an identity view is just def output(self, value): return value. Two more callbacks refine it:

  • postprocess — runs on a written value before it's swapped in (e.g. repack a lone input back into the (args, kwargs) the model wants).
  • transform — the write-back half of preprocess. When preprocess hands back a reshaped/sliced view, the user's in-place edits to it are invisible to the model (which still holds the original). A transform maps the edited view back to the model's layout; it fires once, after the block is done with that read, and the result is spliced in as if swapped.

    class Heads(Envoy): @eproperty(key="output") def heads(self, value): # preprocess: [B,S,H] -> heads b, s, h = value.shape return value.view(b, s, self.n_heads, h // self.n_heads).transpose(1, 2)

    @heads.transform
    def heads(self, value):                     # write the edited heads back
        b, nh, s, hd = value.shape
        return value.transpose(1, 2).reshape(b, s, nh * hd)
    

    with model.trace(prompt): model.attn.heads[:, 5] = 0 # zero head 5; transform swaps it back

Both callbacks work in the shape of the location, which is not always a bare tensor. key="output" on a tuple-returning module serves — and must be handed back — the whole tuple. key="input" always serves the raw (args, kwargs) pair, the same value .inputs gives you, so the example above written against c_proj's input destructures on the way in and repacks on the way out::

@eproperty(key="input")
def heads(self, value):
    (x,), _ = value
    ...

@heads.transform
def heads(self, value):
    ...
    return ((flat,), {})

One failure mode is worth knowing before you meet it: an eproperty is a property, and a property getter that raises AttributeError falls through to __getattr__. A preprocess that raises one — a typo, a wrong unpack — is swallowed and resurfaces as 'Heads' object (nor its module) has attribute 'heads', blaming the attribute rather than the line inside it.

IEnvoy

Bases: Protocol

Interface for objects that host eproperty descriptors.

An eproperty reads and writes its value through the interleaver at a location derived from the host, so a host must provide:

ATTRIBUTE DESCRIPTION
interleaver

The Interleaver managing execution flow (used by eproperty.provide to serve a value from the model side).

TYPE: Interleaver

path

Optional location prefix used to build the eproperty's location ("{path}.{key}"). May be None / empty — eproperty._location then falls back to the key alone. This is how tracer-level eproperties such as InterleavingTracer.result work without a path prefix.

TYPE: Optional[str]

Notes

Hosts with no meaningful path (e.g. tracers) need not declare path at all — eproperty._location uses getattr(obj, "path", ""), so a missing attribute is treated the same as None / "". It is declared Optional[str] here only for type clarity.

interleaver instance-attribute

interleaver: Interleaver

path instance-attribute

path: Optional[str]

eproperty

eproperty(key: Optional[str] = None, description: Optional[str] = None)

Bases: property

A served value on an interleaving host (Envoy, SourceEnvoy, tracer, ...).

Define one by decorating a stub with @eproperty (or @eproperty(key=...)); the stub is the preprocess. The host only needs a path attribute (and an interleaver for provide); the value is read/written through Mediator, which raises outside a trace.

PARAMETER DESCRIPTION
key

The location suffix appended to the host's path ("{path}.{key}"). Defaults to the stub's name. Several eproperties may share a key to give different views of the same location (input and inputs).

TYPE: Optional[str] DEFAULT: None

description

A short label; only used to surface the attribute in a repr.

TYPE: Optional[str] DEFAULT: None

name instance-attribute

name: Optional[str] = None

key instance-attribute

key: Optional[str] = None

description instance-attribute

description = description

__call__

__call__(preprocess: Callable) -> 'eproperty'

Register the decorated stub as the preprocess and adopt its name/doc.

postprocess

postprocess(func: Callable) -> 'eproperty'

Register the write-side callback, run on a value before it's swapped in.

transform

transform(func: Callable) -> 'eproperty'

Register the write-back for an edited preprocess view (see class doc).

__get__

__get__(obj: Optional[IEnvoy], owner: Any = None) -> Any

__set__

__set__(obj: IEnvoy, value: Any) -> None

provide

provide(obj: IEnvoy, value: Any) -> Any

Serve this eproperty's value into the run from the model side.

The counterpart to a worker reading it: hands value to the interleaver at this location so a worker parked there is resumed with it. Used for values the model produces outside any module — e.g. a driver feeding tracer.result.