Interventions: ablation, edits, steering, patching¶
Change something inside a model, see what happens, and put it back. Every intervention runs in memory; the original GGUF is never modified. Only bake writes anything, and it always writes a new file.
Runtime interventions need a build with --features trace and a supported architecture (see
Limitations). Static impact and bake work for any model.
The edit stack¶
Each model has an edit stack. An edit is one operation with an id, a label and an on/off switch.
| Action | Web (edit panel) | REPL | API |
|---|---|---|---|
| add | Add edit form, or the Edit: buttons in the graph side panel | edit … |
POST /api/models/{id}/edits |
| switch one off / on | checkbox | toggle <id> |
POST …/edits/{n}/toggle |
| remove one | × | — | DELETE …/edits/{n} |
| undo / redo | ↶ / ↷ | undo, redo |
POST …/edits/undo, …/redo |
| back to the original model | restore all | restore |
POST …/edits/restore or DELETE …/edits |
| named snapshot | save / load | snapshot save\|load <name> |
POST …/snapshots |
| portable patch | export / import edits.json |
edits export\|import <file> |
GET …/edits/export, POST …/edits with {"edits": […]} |
| write a new GGUF | Bake | bake <file> |
POST …/bake |
Undo and redo cover every change, including restore and import: restore all, then undo brings the edits back. A change that would not compile (an unknown node, a token outside the vocabulary) is refused and the stack stays as it was.
The stack lives in the running process and is shared by its front ends: the web UI started with the
REPL web command sees the REPL's edits and the other way round. Snapshots are saved to disk
(<output_dir>/edits/<digest>/<name>.json) and survive restarts.
Edit operations¶
Node ids are the graph's ids (neurondump graph <model> --expand … shows them).
op |
Fields | Effect at run time |
|---|---|---|
scale |
target, factor |
multiplies the node's contribution to the residual stream: a layer (both blocks), a block (layer.7.attn, layer.7.ffn), a KV group (its heads), a head (layer.7.attn.head.3), a neuron bucket (layer.7.ffn.n.0-63), a single neuron (layer.7.ffn.neuron.42) or an expert (bake only) |
zero |
target |
scale by 0; also accepts tensors, sections and vocab:a-b token ranges (same as ablate) |
skip |
layer |
the layer becomes the identity |
ablate |
cut |
exactly the cut of neurondump ablate --cut |
embed |
token, scale?, replace_with? |
the token's input embedding row is scaled, or replaced by another token's row |
ban |
token |
the token can no longer be predicted (logit −∞) |
boost |
token, bias |
adds bias to the token's logit (negative discourages it) |
patch |
layer, head?, source, all_positions? |
activation patching: the layer's output (or one head's attention output) is replaced by the one from the source prompt |
steer |
layer, a, b, strength |
steering vector: adds strength × (mean h_layer(a) − mean h_layer(b)) to the residual stream after the layer, at every position |
sampling |
temperature, top_k?, top_p?, seed? |
sampling of the edited side (temperature 0 = greedy) |
force |
step, token |
the token chosen at that generation step, whatever the logits say |
system |
text |
replaces the system prompt (empty = none) |
template |
text |
replaces the chat template (Go template syntax, as in Ollama: {{ .System }}, {{ .Prompt }}, {{ range .Messages }}) |
Example edits.json (also accepted: a bare list of edits, or a list of ops). Token ids are
vocabulary ids, as shown by neurondump tokens; the REPL and the web panel also accept a token's
text and resolve it.
{
"schema_version": 1,
"model": "llama3.2:1b",
"edits": [
{ "id": 1, "enabled": true, "label": "zero layer 7", "op": "zero", "target": "layer.7" },
{ "id": 2, "enabled": true, "op": "steer", "layer": 6, "a": "Dear Sir or Madam, I am writing to formally request", "b": "hey dude, just wanna ask", "strength": 4 },
{ "id": 3, "enabled": false, "op": "ban", "token": 22463 }
]
}
How they run¶
The enabled edits compile to one intervention for the forward pass:
- Scales and zeros multiply activations: the head's slice of the attention output before
attn_output, the neuron's activation beforeffn_down, or the block's output after its post-norm. That is mathematically the same as scaling the touched weights, without copying any. A factor of 0 also skips computing the block. - Embedding edits patch only the touched rows when the input is embedded. With tied embeddings the output head keeps the original rows at run time (bake changes both; see below).
- Logit edits are applied to the next-token logits before sampling, the logit lens and the KL.
- Steering runs prompts
aandbon the clean model once, takes the mean residual after the layer, and adds the scaled difference at every position. - Patching runs
sourceon the clean model once and copies its activations, aligned from the end of the sequence: the last position only, or every position withall_positions. - Prompt edits (
system,template) change the text: prompts are then always wrapped in the chat template, on both sides of a comparison, so the only difference is the edit.
Compare: before and after¶
Every comparison runs baseline (no edits, greedy) and edited on the same prompt(s):
- generated text side by side (
tokensper side); - top-k next tokens and KL(baseline ‖ edited);
- perplexity and mean KL on a corpus, and top-1 agreement;
- the logit lens of both sides per layer;
- the decision breakdown of both sides and which layers changed their push;
- the changed nodes, outlined in orange in the 2D and 3D graph.
neurondump edit llama3.2:1b --apply edits.json --prompt "The name of the star that Earth orbits is" --tokens 12
neurondump edit llama3.2:1b --apply edits.json --corpus texts.txt --tokens 0
With no enabled edit, both sides are identical (KL 0, same text): that is how restore is checked.
Ablation is a special case¶
neurondump ablate, the graph's Cut this + Simulate and sweeps are the zeroing / skipping
subset of the same machinery. In the graph, Apply as edit puts the current cut on the edit
stack as an ablate edit, where it can be toggled, undone and baked like any other.
Bake¶
neurondump edit llama3.2:1b --apply edits.json --bake llama-edited.gguf --no-compare
# REPL: bake llama-edited.gguf
# web: edit panel → Bake (written to <output_dir>/bake/<name>)
ollama create llama-edited -f llama-edited.Modelfile
ollama run llama-edited "What is the capital of Italy?"
Bake writes the enabled edits into a new GGUF (it refuses to overwrite the source):
| Edit | Baked as |
|---|---|
| scale / zero of a block or layer | scales attn_output / ffn_down (and biases), or the post-norm weight when the block has one |
| skip a layer | zeroes the layer's block outputs (the layer becomes the identity; the file size stays the same: use slim to remove layers) |
| scale a head | scales the head's columns of attn_output |
| scale neurons | scales their columns of ffn_down |
| scale an expert | scales its slice of ffn_down_exps |
| embedding edit | rewrites the token's rows of token_embd (with tied embeddings this also changes the output head) |
| ablate / zero of tensors | the same zero operations as slim |
| sampling, system prompt, template | written to the Modelfile (PARAMETER, SYSTEM, TEMPLATE) |
| ban, boost, patch, steer, force | not baked (they only exist at run time); listed as skipped with the reason |
Only touched tensors are rewritten. Float tensors keep their type; quantized tensors that are scaled
are re-encoded as Q8_0 (reported as requantized); pure zeroing keeps the type when the zeroed ranges
are block-aligned. The result is validated like a slim output, and the edits are stored in the
neurondump.edits metadata key.