envoy¶
envoy
¶
The Envoy — nnsight's window into a running PyTorch model.
An Envoy wraps a torch.nn.Module and mirrors its submodule
tree, so every module in the model has a matching envoy reachable by the same
attribute path (model.transformer.h[0].mlp). Envoys are the objects you
interact with when tracing: they expose each module's live input/output
during a forward pass, let you overwrite those values, read gradients, skip
whole modules, and reach individual operations inside a forward via
source.
You open a trace with with model.trace(x): and, inside the block, read or
write envoy attributes as if the forward pass had paused at each module for you.
Capture a value with .save() to use it after the trace:
.. code-block:: python
from nnsight.intervention.envoy import Envoy
model = Envoy(my_module)
with model.trace(x):
hidden = model.layer1.output.save() # captured mid-forward
model.layer2.output[:] = 0 # overwrite layer2's output in place
print(hidden.shape) # available after the block
Gradients are available the same way. Call .backward() on a captured value
as a context manager and, inside it, read .grad on tensors you captured
earlier in the forward — you can edit gradients too:
.. code-block:: python
with model.trace(x):
a1 = model.fc1.output
loss = model.output.sum()
with loss.backward():
g = a1.grad.save() # gradient flowing into fc1's output
a1.grad = a1.grad * 2 # and it can be edited in place of autograd's
Locations must be read in execution order: asking for an earlier module's output
after a later one has already run raises
OutOfOrderError.
Envoy
¶
Envoy(module: Module, path: str = 'model', interleaver: Interleaver | None = None, rename: dict[str, str | list[str]] | None = None, envoys: dict | None = None)
Wraps a torch.nn.Module to expose and edit its values during a trace.
One envoy mirrors one module and reads or overwrites that module's live
input/output (and gradients) as the forward pass runs, driving those
interventions through a shared Interleaver.
The child envoys mirror the module's submodule tree, so the whole model is
reachable by attribute path from the root envoy. See the module docstring for
the mental model.
| ATTRIBUTE | DESCRIPTION |
|---|---|
path |
The module's dotted location in the tree, e.g.
|
interleaver |
The
|
_module |
The wrapped
|
_edits |
TYPE:
|
_children |
The child envoys this envoy owns, in module order — one per module, so a module the tree already wraps elsewhere is not in it.
TYPE:
|
_child_map |
Every entry of the wrapped module's
TYPE:
|
interleaver
instance-attribute
¶
interleaver = interleaver if interleaver is not None else Interleaver()
iter
property
¶
Deprecated: use tracer.iter.
An alias for tracer.iter[...]; the iteration API lives on the tracer.
source
property
¶
source: Source
Get the source code representation of the module.
Examples:
>>> model = TransformersModel("openai-community/gpt2", dispatch=True)
>>> print(model.transformer.h[0].attn.source) # list the operations
>>> with model.trace("Hello World"):
... attn = model.transformer.h[0].attn.source.attention_interface_1.output.save()
| RETURNS | DESCRIPTION |
|---|---|
Source
|
A |
device
property
¶
The device of the module's first parameter, or None if it has none.
devices
property
¶
The set of devices the module's parameters live on (empty if it has none).
trace
¶
trace(*args: Any, fn: Any = None, backend: Any = None, tracer_cls: type[InterleavingTracer] | None = None, trace: bool = True, **kwargs: Any) -> InterleavingTracer
Open a trace: a with block that runs the module and lets you read and
edit its intermediate values.
Inside the block, read an envoy's .output/.input to capture a value,
or assign to it to overwrite what the module passes on. Mark a value with
.save() to keep it past the block.
Examples:
>>> model = TransformersModel("openai-community/gpt2", dispatch=True)
>>> with model.trace("Hello World"):
... model.transformer.h[0].attn.output[0][:] = 0 # zero the attn output
... output = model.output.save()
>>> print(output)
| PARAMETER | DESCRIPTION |
|---|---|
*args
|
Arguments to pass to the tracer
TYPE:
|
fn
|
What the trace runs, defaulting to the module's
TYPE:
|
backend
|
What running the captured block means, defaulting to local
execution. Set by
TYPE:
|
tracer_cls
|
Tracer class to construct instead of the default
TYPE:
|
trace
|
If
TYPE:
|
**kwargs
|
Keyword arguments to pass to the tracer
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
InterleavingTracer
|
An InterleavingTracer for this module, or — when |
InterleavingTracer
|
the module's output directly. |
edit
¶
edit(*, inplace: bool = False, backend: Any = None) -> EditingTracer
Open an editing tracer: capture interventions and store them as defaults.
The block is not executed against a live forward; instead the interventions
it captures are stored and replayed on every later trace of the (edited)
model. Clear them with clear_edits.
Examples:
>>> model = TransformersModel("openai-community/gpt2", dispatch=True)
>>> # The first layer's attention output will always be zeroed.
>>> with model.edit() as (tracer, edited_model):
... edited_model.transformer.h[0].attn.output[0][:] = 0
>>> with model.trace("Hello World"):
... output = model.output.save() # original model, unedited
>>> print(output)
>>> with edited_model.trace("Hello World"):
... edited_output = edited_model.output.save() # edit applied
>>> print(edited_output)
| PARAMETER | DESCRIPTION |
|---|---|
inplace
|
If
TYPE:
|
backend
|
Backend for the underlying trace.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
EditingTracer
|
An |
clear_edits
¶
Drop every edit stored on this envoy, restoring its unedited behavior.
session
¶
Open a session: a scope enclosing several traces that share values.
Inside with model.session(): you can open multiple with
model.trace(...) blocks and pass values between them — a value read in
one trace is available in a later trace without an explicit .save(),
because the session (not each individual trace) is the save boundary. Only
values marked with nnsight.save survive past the session itself.
Ordinary Python — loops, conditionals, building lists — runs natively in
the session body.
Examples:
>>> with model.session():
... with model.trace(x):
... hidden = model.layer1.output # no .save() needed
... with model.trace(x):
... out = (hidden * 2).save() # `hidden` flows in
>>> print(out)
| PARAMETER | DESCRIPTION |
|---|---|
backend
|
What runs the captured session block. Defaults to running it in place (which executes the nested traces as it reaches them).
TYPE:
|
tracer_cls
|
Tracer class to construct instead of the default
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Tracer
|
A |
inputs
¶
The module's forward inputs as an (args, kwargs) tuple.
Read or replace the whole input during a trace::
with model.trace("Hello World"):
args, kwargs = model.transformer.h[0].attn.inputs
output
¶
output(value: Any) -> Object
The module's forward output — read or replace it during a trace::
with model.trace("Hello World"): attn = model.transformer.h[0].attn.output[0].save()
skip
¶
Skip this module's execution, returning replacement as its output.
The module's forward is not run; replacement is used in its place.
Examples:
>>> model = TransformersModel("openai-community/gpt2", dispatch=True)
>>> with model.trace("Hello World"):
... # Skip the first layer, passing its input through as its output.
... model.transformer.h[0].skip(model.transformer.h[0].input)
... output = model.output.save()
>>> print(output)
| PARAMETER | DESCRIPTION |
|---|---|
replacement
|
The value to use as the module's output; must match the
shape the module would return. Read it from anywhere — including
this module's own
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
None
|
Nothing; the skip takes effect when the module runs. |
to
¶
to(device: device) -> Envoy
Move the wrapped module to device (in place).
| PARAMETER | DESCRIPTION |
|---|---|
device
|
The device to move the module to.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Envoy
|
This envoy, for method chaining. |
cpu
¶
cpu(*args: Any, **kwargs: Any) -> Envoy
Move the wrapped module to the CPU (in place); return this envoy.
cuda
¶
cuda(*args: Any, **kwargs: Any) -> Envoy
Move the wrapped module to the GPU (in place); return this envoy.
interleave
¶
Run fn interleaved with the interleaver's registered workers.
This is the low-level driver behind trace; you rarely call it
directly. The workers (edits + per-invoke interventions, with their batch
groups) are set up on the interleaver first; this runs fn(*args, **kwargs)
alongside them and clears them afterward.
| PARAMETER | DESCRIPTION |
|---|---|
fn
|
The callable to run, or a method name resolved against the module.
TYPE:
|
*args
|
Positional inputs for a direct (untraced) call. They're wrapped as
a single implicit invoke and assembled like a one-invoke trace
(tokenized/collated), then moved to
TYPE:
|
batcher
|
The per-invoke inputs to combine into one call. When given, it's
assembled into
TYPE:
|
**kwargs
|
Keyword inputs — part of the direct call's single invoke when no
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Any
|
Whatever
TYPE:
|
__call__
¶
Run this module's forward — applying it ad hoc, out of execution order.
Inside a trace you can feed a module any input to compute with it away from
its place in the forward pass — e.g. the logit lens, running lm_head on
an intermediate hidden state::
with model.trace(prompt):
hidden = model.transformer.h[-1].output
logits = model.lm_head(model.transformer.ln_f(hidden))
While interleaving the module is called the ordinary way, with this
trace stood down for the duration. Its own hooks and wrappers still fire
— a runtime that keeps collectives around the forward (transformers
tensor parallelism wraps module.forward with them) needs them to —
while nnsight serves nothing and spends no occurrence, for this
module or anything under it. That keeps the call from switching into
the very worker greenlet making it, and leaves the module's real place in
the forward pass untouched: calling a whole layer ad hoc does not consume
the occurrence its children's real visit was going to fill. Outside a
trace it is an ordinary module call.
Pass hook=True to let the trace watch the call too. Use it for a
module attached to the tree that isn't part of the real forward pass —
an adapter, LoRA, or SAE applied in an edit — so its internals become
observable at .submodule.output::
model.transformer.h[0].adapter = MyAdapter()
with model.edit() as (tracer, edited):
acts = edited.transformer.h[0].output
edited.transformer.h[0].output = edited.transformer.h[0].adapter(acts, hook=True)
with edited.trace(prompt):
inner = edited.transformer.h[0].adapter.inner.output.save()
The block above applies the adapter once, at the first time the layer runs.
To apply it every time — each step of a generation loop — put the
passthrough under the edit tracer's iter::
with model.edit(inplace=True) as tracer:
for _ in tracer.iter[:]:
acts = model.transformer.h[0].output
model.transformer.h[0].output = model.transformer.h[0].adapter(acts, hook=True)
| PARAMETER | DESCRIPTION |
|---|---|
*args
|
Inputs to run the module's forward on.
TYPE:
|
hook
|
Whether nnsight watches the call. If
TYPE:
|
**kwargs
|
Keyword inputs to the module's forward.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Any
|
The module's output for these inputs. |
__iter__
¶
__iter__() -> Iterator[Envoy]
Iterate over this envoy's direct children.
Yields each immediate child envoy — e.g. the blocks of a
ModuleList, so for layer in model.model.layers:
walks the layers. This is not recursive; use modules to walk the
whole subtree.
| YIELDS | DESCRIPTION |
|---|---|
Envoy
|
Each direct child envoy, in order.
TYPE::
|
Example
::
for layer in model.model.layers:
print(layer.path)
__getitem__
¶
__getitem__(key: Any) -> Envoy
Index into direct child envoys, e.g. for a ModuleList.
An int or str key resolves by name, the way the wrapped module
indexes it — layers[2] is the module layers holds at "2", even
when an earlier entry is a module the tree already wraps elsewhere and so
has no envoy of its own here.
| PARAMETER | DESCRIPTION |
|---|---|
key
|
An index the wrapped module accepts (an int, or a str), or a slice over this envoy's children in module order.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Envoy
|
The child envoy at
TYPE:
|
Envoy
|
first block of a |
__len__
¶
The number of entries in the wrapped module (e.g. a ModuleList's length).
get
¶
Resolve a dotted path from this envoy, e.g. "transformer.h.0.mlp".
A programmatic alternative to attribute access, useful when the path is
built at runtime. Outside a trace it returns the descendant envoy; inside
one, a trailing .output/.input resolves through to the live value.
Examples:
>>> model = TransformersModel("openai-community/gpt2", dispatch=True)
>>> module = model.get("transformer.h.0.mlp")
>>> with model.trace("Hello"):
... value = model.get("transformer.h.0.mlp.output").save()
| PARAMETER | DESCRIPTION |
|---|---|
path
|
A
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Any
|
The resolved child |
Any
|
ends in an intervention attribute during a trace. |
modules
¶
modules(include_fn: Callable[[Envoy], bool] | None = None, names: bool = False) -> list[Any]
Flatten this envoy's whole subtree (children first, then self) into a list.
| PARAMETER | DESCRIPTION |
|---|---|
include_fn
|
Optional predicate on an envoy; only those it returns
TYPE:
|
names
|
If
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
list[Any]
|
A list of |
named_modules
¶
Flatten the subtree into (path, envoy) tuples: modules with names=True.
| PARAMETER | DESCRIPTION |
|---|---|
include_fn
|
Optional predicate on an envoy; only those it returns
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
list[tuple[str, Envoy]]
|
A list of |
traceable
¶
Make an Envoy method usable as a trace context.
with envoy.method(...): traces the method (runs it interleaved with the
block's interventions); envoy.method(...) just calls it. While already
interleaving, it always just calls the method (we're inside a trace).