editing¶
editing
¶
Editing: interventions that stick.
A normal trace runs the model once and forgets your interventions. Editing lets you record a block of interventions once and have it replayed automatically on every future trace of that envoy — no need to repeat it each time.
The edit lives on the envoy, not in the module. Two boundaries follow from that,
and both are silent. An edit stored on a child envoy is not replayed by a trace
rooted at the parent, because
Envoy.interleave takes the run's
mediators from the traced envoy's own _edits; the parent's clear_edits
does not reach it either. And an edit only applies to work that goes through the
interleaver: calling the wrapped torch.nn.Module directly runs the model as it
was.
By default Envoy.edit leaves the original envoy clean and returns an
edited copy, so you opt into the edited behavior through the copy that
with model.edit() as (tracer, edited): binds. Pass inplace=True to edit
the envoy itself, in which case only the tracer is bound. Stored edits can be
removed with Envoy.clear_edits.
Example
import torch.nn as nn from nnsight.intervention.envoy import Envoy
model = Envoy(some_module)
Store an edit;
modelitself is left untouched.¶with model.edit() as (tracer, edited): ... edited.layer1.output[:] = 0
The stored edit is replayed on every trace of
edited.¶with edited.trace(x): ... out = edited.output.save() # reflects layer1.output == 0 print(out)
modelstill behaves normally.¶with model.trace(x): ... clean = model.output.save() # unedited print(clean)
EditingTracer
¶
Bases: InterleavingTracer
Record a block of interventions and replay it on every future trace.
Returned by Envoy.edit. An edit block is written like a
with envoy.trace(): block, but instead of running once it is stored and
replayed automatically on every later trace of the envoy. With
inplace=False (default) the original envoy is left clean and an edited copy
is returned alongside the tracer; inplace=True edits the envoy itself and
binds only the tracer. Edits stack and persist until Envoy.clear_edits.
Being a trace block, it carries the tracer's iter API — use it to re-apply
an edit at every occurrence of a location, e.g. each generation step.
Examples:
>>> with model.edit() as (tracer, edited):
... edited.layer1.output[:] = 0
>>> with edited.trace(x):
... out = edited.output.save() # reflects the stored edit
>>> print(out)
inplace: If False (default), edit a shallow copy so the original
envoy is left untouched; if True, store the edit on envoy
itself.
backend: Optional backend for the underlying trace.
__enter__
¶
__enter__() -> EditingTracer | tuple[EditingTracer, Envoy]
Enter the edit block.
Returns the tracer — an edit block is a trace block, so it has the same
tracer.iter / tracer.all() API, needed to re-apply an edit at every
occurrence of a location (e.g. each step of a generation loop)::
with model.edit(inplace=True) as tracer:
for _ in tracer.iter[:]:
model.h[0].output[0][:] = model.h[0].adapter(
model.h[0].output[0], hook=True
)
With inplace=False (the default) the edit is stored on a copy, so the
copy is returned alongside the tracer — bind both and write the block's
interventions against the copy::
with model.edit() as (tracer, edited):
edited.h[0].output[0][:] = 0
execute
¶
Store the block instead of running it.
Overrides the base trace behavior: rather than executing code against a
live model run, append it to envoy._edits so Envoy.interleave
replays it on every later trace.