Lab 4: Turn the knob, and the controls
On this page
Big thing: You will sweep the edit strength alpha from -4 to 12 across 256 unseen practice problems. You will also test each part of the edit (layer 15 vs layer 19, 64 neurons vs all 3,584 neurons, and same-size random controls) and classify how every answer ended.
Why it matters: One benchmark gain shows that an edit helped, while a dose sweep and matched controls test why it worked and where it breaks. In these runs, the gain needed both the loop direction u and neurons chosen because they fire during loops. At alpha = -4, accuracy even rises while the edit pushes the model harder into loops.
Time and hardware: Building the 12 variant models takes about 2 minutes on CPU. Running one variant on the 256 practice problems (dev256, 512 answers) takes 17 to 45 minutes on 1 NVIDIA A100 GPU, and the full plans/sampler.json plan takes about 10.6 GPU-hours across 7 GPUs. Classifying endings and inspecting answers with labs/lab4_endings.py and labs/peek.py takes a few seconds on CPU.
Theory guide: Section 8.5.
Before you run: predict
Write down a short guess for each question before running the commands or reading the tables.
- As
alphagrows through0, 1, 2, 4, 6, 8, 12, does accuracy keep rising to12, or plateau and drop? Why? - Keep the same 64 neurons and weight-change size (Frobenius norm) as the released edit, and replace only the loop direction
uwith a random direction at right angles tou(randdir). How many percentage points of the+49.4gain will survive? - What if you keep the loop direction
uand the same weight-change size, and apply it to 64 random neurons instead of the chosen 64 (randcols)? - At
alpha = -4, the edit multiplies the 64 neurons' push along the loop direction by1 - (-4) = +5, pushing 5 times harder toward loops. Willdev256accuracy be lower than the unedited model (12.1%), about the same, or higher?
Run it
1. How edit variants are defined
Every weight edit in this lab is a JSON entry in configs/variants.json, where _doc explains the schema:
- Each entry becomes a model folder
/nvme/wsp-lab/models/qwen35-<name>. - Inside
"layers", each edited layer ("15"or"19") specifies"alpha"(the strength knob). "dir"chooses the 1,024-number unit vector (a list of numbers scaled to have length one) in the residual stream. The residual stream is the running list of 1,024 numbers per token that every layer reads from and adds to. A token is a word piece."released"uses the paper's directionufor that layer (default), and"random_orth:SEED"picks a random unit vector at right angles tou."cols"chooses which columns ofdown_projto edit. This is the MLP output matrix of shape1,024 x 3,584; the MLP is the layer's bank of neurons. Columnjis the vector neuronjadds to the stream per unit of activation, or firing strength."released"uses the paper's 64 neurons (default),"all"edits all 3,584 neurons, and"random:SEED"picks 64 other neurons at random."norm_match": truerescalesalphaso the Euclidean size (Frobenius norm) of the matrix changeW' - Wmatches the released edit at thatalpha.
Two entries from configs/variants.json, for a4 (alpha = 4 on the released direction and neurons) and the random-direction control randdir:
"a4": {"about": "flip and triple the loop component",
"layers": {"15": {"alpha": 4}, "19": {"alpha": 4}}},
"randdir": {"about": "control: same 64 neurons, same size of change, but a random direction instead of u",
"layers": {"15": {"alpha": 6, "dir": "random_orth:15", "norm_match": true},
"19": {"alpha": 6, "dir": "random_orth:19", "norm_match": true}}}
2. Build the edited model folders on CPU
Connect to sampler, activate the environment, and run scripts/build_models.py on CPU (CUDA_VISIBLE_DEVICES="" hides the GPUs so PyTorch stays on CPU):
ssh sampler
. /nvme/wsp-lab/code/scripts/env.sh && cd /nvme/wsp-lab/code
CUDA_VISIBLE_DEVICES="" python scripts/build_models.py
For each entry in configs/variants.json, scripts/build_models.py symlinks the tokenizer and config files from /nvme/wsp-lab/models/Qwen3.5-0.8B and writes model.safetensors-00001-of-00001.safetensors with the edited down_proj tensors in layers 15 and 19. If a folder already matches its spec in edit_note.json, the script prints exists <name> and skips it.
3. Run the generation plan on sampler
The generation runs are in plans/sampler.json. Every dev/* run tests one model on dev256 (128 GSM8K-train and 128 MATH-train practice problems the agent never touched, with n = 2 answers per problem, or 512 answers per run). One entry from plans/sampler.json:
{"name": "dev/a4", "model": "qwen35-a4", "data": "dev256", "n": 2, "shards": 1, "est": 8}
Use nvidia-smi to pick a free GPU. One dev256 run on one A100 GPU takes 17 to 45 minutes depending on answer length (dev/a12 finishes in 16.8 minutes because its answers are shorter; dev/base takes 44.8 minutes because almost every answer hits the 32,768-token cap). The full plans/sampler.json plan (22 runs, including Lab 5 decoding runs and build/base) takes about 10.6 GPU-hours:
nvidia-smi
python scripts/run_jobs.py plans/sampler.json --gpus 0-6
python scripts/summarize.py /nvme/wsp-lab/runs
Note on rerunning finished jobs: scripts/run_jobs.py skips any shard that already has shard*.done.json, so once these runs have finished, the command above finishes immediately without generating new text. To regenerate a run, move its finished folder aside first:
mv /nvme/wsp-lab/runs/dev/a4 /nvme/wsp-lab/runs/dev/a4-ours
python scripts/run_jobs.py plans/sampler.json --gpus 7 --only dev/a4
Then dev/a4 holds the new run and dev/a4-ours keeps the earlier one.
4. Classify how every answer ended and inspect single answers
scripts/pull_runs.sh copies only per-answer summary numbers to the laptop, so the full answer texts (shard*.texts.jsonl.gz) stay on the VM that generated them (all dev/* runs in plans/sampler.json are on sampler). Run labs/lab4_endings.py on CPU on sampler to classify every answer in the sweep and controls, and to print examples where dev/a-4 closed its thinking out of a loop:
export CUDA_VISIBLE_DEVICES=""
python labs/lab4_endings.py
python labs/lab4_endings.py dev/base dev/p50c dev/l15only dev/l19only dev/all1 dev/all2 dev/randdir dev/randcols \
--out lab4/endings-controls
python labs/lab4_endings.py dev/a-4 --show 2
The first two commands write results/lab4/endings.json and results/lab4/endings-controls.json; the third writes results/lab4/try-dev_a-4/endings.json.
To inspect single answers on sampler, use labs/peek.py (CPU, a few seconds). Without --id, it lists all 512 answers in a run; with --id, it prints the problem, whether the answer looped, the token where the loop started, the text around </think>, and the ending:
python labs/peek.py dev/p50c
python labs/peek.py dev/p50c --id gsm8k_train_5628
python labs/peek.py dev/a-4 --id gsm8k_train_5628
What you should see
1. The dose curve (alpha from -4 to 12)
Every row below tests the same 64 neurons per layer and loop direction u at layers 15 and 19 on dev256 (256 problems, 2 answers each = 512 answers), varying only alpha. Accuracy and gain include their standard error (SE, the typical sampling uncertainty across 256 problems; a gap of at least 2 SE is unlikely to be chance). Gains in percentage points (pp) and rescued or lost problem counts are paired by problem against dev/base (alpha = 0). A rescued problem has at least one correct edited answer and no correct base answer; a lost problem is the reverse. The last column, from Lab 7 (results/lab7/side_effects.json), gives the percentage change in perplexity, a measure of how surprised the model is by the text. Perplexity is to the average negative log-probability of the true next token across 65,536 tokens of WikiText-2, so lower is better and +0.0% is the unedited base score of 20.009.
alpha |
What it does to the 64 neurons' push along u |
Accuracy % | Cut at cap % | Mean tokens | Correct when finished % | Gain vs base (pp), rescued / lost | WikiText perplexity change (Lab 7) |
|---|---|---|---|---|---|---|---|
-4 (dev/a-4) |
5 times stronger toward loops | 19.7 +/- 1.9 |
65.8 |
26,063 |
55.4 |
+7.6 +/- 2.2, 54 / 24 |
+5.39% |
0 (dev/base) |
Unedited | 12.1 +/- 1.6 |
88.7 |
29,683 |
100.0 |
- |
0.00% |
1 (dev/a1) |
Erased (1 - alpha = 0) |
22.7 +/- 2.1 |
77.1 |
26,964 |
96.6 |
+10.5 +/- 1.8, 42 / 7 |
-0.49% |
2 (dev/a2) |
Mirrored (1 - alpha = -1) |
35.2 +/- 2.6 |
62.7 |
23,911 |
92.1 |
+23.0 +/- 2.3, 71 / 5 |
-0.69% |
4 (dev/a4) |
Flipped, 3 times (1 - alpha = -3) |
59.2 +/- 2.8 |
29.1 |
17,730 |
82.9 |
+47.1 +/- 2.6, 118 / 0 |
-0.47% |
6 (dev/p50c) |
Flipped, 5 times (released edit) | 61.5 +/- 2.8 |
19.7 |
16,104 |
76.6 |
+49.4 +/- 2.6, 125 / 0 |
+0.79% |
8 (dev/a8) |
Flipped, 7 times (1 - alpha = -7) |
60.7 +/- 2.8 |
14.1 |
14,589 |
70.7 |
+48.6 +/- 2.6, 119 / 0 |
+3.33% |
12 (dev/a12) |
Flipped, 11 times (1 - alpha = -11) |
51.8 +/- 2.8 |
12.3 |
13,515 |
59.0 |
+39.6 +/- 2.7, 102 / 0 |
+14.34% |

How to read lab4_dose.png: the four panels plot accuracy, percentage of answers cut at the 32,768-token cap, mean answer length (thousand tokens), and Lab 7's WikiText perplexity change across alpha = -4, 0, 1, 2, 4, 6, 8, 12. The dose sweep is blue in the first three panels and red in the fourth. The three open markers to the right of alpha = 6 also use alpha = 6. They are a red square at x = 6.35 for rebuilt, and purple triangles at x = 6.7 and x = 7.05 for Lab 8's capA-a6 and capB-a6. The plot shifts them right of 6 so their error bars do not overlap p50c.
2. Which parts of the edit matter (layers, neuron subset, and random controls)
All runs below are at alpha = 6 unless noted, on the same 256 practice problems (512 answers per run):
| Model | What changes | Accuracy % | Cut at cap % | Mean tokens | Correct when finished % | Gain vs base (pp), rescued / lost | WikiText perplexity change (Lab 7) |
|---|---|---|---|---|---|---|---|
dev/p50c |
Released edit (alpha = 6, 64 neurons, layers 15 and 19) |
61.5 +/- 2.8 |
19.7 |
16,104 |
76.6 |
+49.4 +/- 2.6, 125 / 0 |
+0.79% |
dev/l15only |
Released edit on layer 15 only | 60.4 +/- 2.9 |
28.5 |
17,362 |
84.2 |
+48.2 +/- 2.7, 117 / 0 |
+0.05% |
dev/l19only |
Released edit on layer 19 only | 46.9 +/- 2.7 |
49.0 |
20,729 |
91.2 |
+34.8 +/- 2.4, 100 / 0 |
+1.65% |
dev/all1 |
alpha = 1 on all 3,584 neurons (layers 15 and 19) |
28.7 +/- 2.3 |
71.9 |
25,609 |
97.9 |
+16.6 +/- 2.0, 61 / 6 |
-0.42% |
dev/all2 |
alpha = 2 on all 3,584 neurons (layers 15 and 19) |
49.4 +/- 2.8 |
46.7 |
20,117 |
91.9 |
+37.3 +/- 2.5, 97 / 0 |
-0.36% |
dev/randdir |
Control: same 64 neurons, random orthogonal direction, same change size | 14.5 +/- 1.8 |
87.3 |
29,486 |
98.5 |
+2.3 +/- 1.6, 22 / 18 |
+0.30% |
dev/randcols |
Control: direction u, 64 random neurons, same change size |
14.6 +/- 1.9 |
86.7 |
29,113 |
100.0 |
+2.5 +/- 1.6, 23 / 20 |
-0.10% |

How to read lab4_controls.png: each horizontal bar is one model on dev256, grouped by color. Grey is the unedited base model (12.1%), and green is the released edit (61.5%) and your Lab 3 rebuild (61.5%). Purple shows the two Lab 8 capstone edits built from the model's own loops (capA-a6 at 43.0% and capB-a6 at 58.2%). Blue shows the ablations (l15only, l19only, all1, all2). Red at the bottom shows the two norm-matched random controls (randdir at 14.5% and randcols at 14.6%), which barely pass the grey bar.
3. How the agent discovered this path in its own notebook
You can read the agent's notebook in external/lm_understand-logs/exp8_math/runs/p50c/work_notes/experiment_history.md (lines 55 to 112 and 225 to 374; online at https://github.com/RikiyaT/lm_understand-logs/blob/main/exp8_math/runs/p50c/work_notes/experiment_history.md). The variants above retrace its main steps. Because failed requests did not count toward its budget, its pass number can trail the request file number in external/lm_understand-logs/exp8_math/runs/p50c/requests/. Each step lists both:
- 4th forward pass (
fwd_5): switching off one neuron (layer 19, neuron 11) gave+6.25 +/- 4.35percentage points, too noisy to trust on a small batch. - 10th forward pass (
fwd_13): removing the loop direction (alpha = 1) from all 3,584 columns of layers 15 and 19 raised correct answers from11 to 42 of 128(+24.22 +/- 3.96points). - 19th forward pass (
fwd_27): mirroring the direction (alpha = 2) across all 3,584 columns jumped from21 to 110 of 256(+34.77 +/- 3.53points). - 20th forward pass (
fwd_29): raisingalphato3across all columns reached+39.84points. - 23rd forward pass (
fwd_35): a random orthogonal direction of the same norm changed accuracy by only+1.56 +/- 3.52points, a sign that the gain came fromuand not random weight noise. - 27th and 28th forward passes (
fwd_41andfwd_42): atalpha = 4with a 16,384-token limit, editing the top 64 neurons per layer and editing all 3,584 both gained+26.56points, but the 64-neuron edit left fewer answers ending in long whitespace runs (2vs10). - 30th forward pass (
fwd_45): raising the 64-neuron edit toalpha = 6jumped from7/64 to 35/64(+43.75points), with answers cut at the limit falling from57to5. - 33rd and 34th forward passes (
fwd_50andfwd_52): the frozen edit raised 128 fresh practice problems from9 to 74correct (+50.78points). Later passes tested more fresh problems. On the 256 fresh problems offinal_1, it went from10.16%to58.98%(+48.83 +/- 3.23points,127rescued,2lost). Across704checked practice items it went from7.95%to57.53%(line 244), and across the final1,152items from10.59%to60.42%(line 374).
Our all1 (+16.6 +/- 2.0) and all2 (+37.3 +/- 2.5) apply the released direction u to all 3,584 columns, so they are close copies of the agent's 10th and 19th passes (fwd_13 and fwd_27) rather than exact ones (the same u, but tested on different problems). Our norm-matched controls randdir (+2.3 +/- 1.6) and randcols (+2.5 +/- 1.6) match the agent's 23rd pass (fwd_35, +1.56 +/- 3.52).
4. How every answer ended (results/lab4/endings.json and endings-controls.json)
labs/lab4_endings.py sorts the 512 answers in a run into four mutually exclusive groups by whether and how the model closed its <think> block:
- Cut while thinking: hit the 32,768-token limit before writing
</think>. (A cut answer still counts as correct if its last\boxed{}holds the right answer, even inside its scratchpad; if it wrote no\boxed{}, the grader checks its last\fbox{}.) - Stopped while thinking: emitted the end-of-sequence token before writing
</think>, leaving no final answer. - Closed out of a loop: wrote
</think>right after verbatim repetition (the 60-character snippet ending 100 characters before</think>appears at least 3 times in the 6,000 characters before</think>). - Closed normally: wrote
</think>and the group 3 check found no repeated snippet. That check inspects only the last 6,000 characters before</think>, so an answer that recovered from a loop more than 6,000 characters before closing, or wrote</think>before its 6,000th character, still counts as "closed normally".
Every cell below lists answers (correct answers):
| Run | Total correct (of 512) | Cut while thinking | Stopped while thinking | Closed out of a loop | Closed normally |
|---|---|---|---|---|---|
dev/base (alpha = 0) |
62 |
453 (4) |
0 (0) |
3 (2) |
56 (56) |
dev/a-4 (alpha = -4) |
101 |
255 (4) |
47 (0) |
197 (87) |
13 (10) |
dev/a1 (alpha = 1) |
116 |
394 (3) |
1 (0) |
31 (29) |
86 (84) |
dev/a2 (alpha = 2) |
180 |
306 (4) |
0 (0) |
70 (44) |
136 (132) |
dev/a4 (alpha = 4) |
303 |
83 (1) |
1 (0) |
172 (70) |
256 (232) |
dev/p50c (alpha = 6) |
315 |
72 (0) |
0 (0) |
96 (29) |
344 (286) |
dev/a8 (alpha = 8) |
311 |
51 (0) |
28 (0) |
69 (16) |
364 (295) |
dev/a12 (alpha = 12) |
265 |
43 (0) |
37 (0) |
19 (3) |
413 (262) |
dev/l15only |
309 |
81 (1) |
0 (0) |
136 (47) |
295 (261) |
dev/l19only |
240 |
196 (2) |
0 (0) |
151 (82) |
165 (156) |
dev/all1 |
147 |
362 (6) |
0 (0) |
30 (25) |
120 (116) |
dev/all2 |
253 |
200 (1) |
0 (0) |
93 (49) |
219 (203) |
dev/randdir |
74 |
440 (10) |
0 (0) |
14 (7) |
58 (57) |
dev/randcols |
75 |
443 (7) |
0 (0) |
6 (5) |
63 (63) |
5. Median length of correct answers and breakdown by problem source
dev256 is half GSM8K (grade-school word problems, 128 problems, 256 answers) and half MATH (competition math, 128 problems, 256 answers). results/summary.json breaks accuracy down by half and records the median token length of correct answers:
| Run | GSM8K accuracy % / cut % | MATH accuracy % / cut % | Median tokens of correct answers |
|---|---|---|---|
dev/base (alpha = 0) |
15.2 / 86.3 |
9.0 / 91.0 |
5,600.5 |
dev/a-4 (alpha = -4) |
23.0 / 55.9 |
16.4 / 75.8 |
12,987 |
dev/p50c (alpha = 6) |
74.6 / 4.7 |
48.4 / 34.8 |
7,998 |
dev/a12 (alpha = 12) |
55.5 / 0.8 |
48.0 / 23.8 |
5,799 |
In the unedited model, only short derivations finish before a loop starts, giving a median correct length of 5,600 tokens (5,600.5). With p50c, longer derivations can finish without looping, raising the median to 7,998 tokens. At alpha = -4, most correct answers (87 of 101) close right after repeated text. The median length of all correct answers is 12,987 tokens; the endings check does not measure loop length.
Why it works
1. The dose trade-off along u: why accuracy plateaus and then drops
In Lab 0, W' = W - alpha * u (u^T W_S) scales the chosen 64 neurons' push along u by 1 - alpha. Sweeping alpha from 0 to 12 takes that multiplier from +1 (no edit) through 0 (erase at alpha = 1) and -1 (mirror at alpha = 2) to -5 (alpha = 6) and -11 (alpha = 12).
alpha = 1 (erasing the 64 neurons' push along u) is not enough. In Lab 3, those 64 neurons held about half the positive loop score in layers 15 and 19 (49.8% and 55.5%), while the other 22 blocks and the attention and DeltaNet mixers also pushed toward u. Setting alpha = 1 zeroes out those 64 neurons' contribution (22.7% accuracy) and leaves the rest of the loop drift intact. Setting alpha > 1 turns them into a counter-push: when they fire, they subtract (alpha - 1) times their original loop push, cancelling drift from other neurons and layers.
Pushing alpha too high shows two signs of harm to reasoning; the first is clean and the second is partly muddied by which problems finish:
- Premature stop tokens during thinking: From
alpha = 0toalpha = 6, at most1of 512 answers emits an end-of-sequence token inside<think>. Atalpha = 8,28answers stop mid-thinking with no final answer; atalpha = 12,37do. - Lower accuracy among finished answers, and higher perplexity: Among answers that finish before the cap, accuracy drops as
alphagrows:100.0%(alpha = 0),96.6%(alpha = 1),92.1%(alpha = 2),82.9%(alpha = 4),76.6%(alpha = 6),70.7%(alpha = 8), and59.0%(alpha = 12). Among normal<think>closes without a detected loop, accuracy falls from286 / 344(83.1%) atalpha = 6to262 / 413(63.4%) atalpha = 12. These finished-answer rates mix reasoning harm with selection: atalpha = 0few answers finish, and they may come from easier problems. Higheralphalets harder problems finish too, and some of them are wrong. Thealpha = 6vsalpha = 12normal-close gap has the same issue because the two runs finish different problems. The cleaner signs of harm are the mid-thinking stops in item 1 and WikiText-2 perplexity (Lab 7): small edits (alpha = 1, 2, 4) slightly lower perplexity (-0.49%,-0.69%,-0.47%),alpha = 6adds+0.79%,alpha = 8adds+3.33%, andalpha = 12jumps to+14.34%.
Across alpha = 4 (59.2%), alpha = 6 (61.5%), and alpha = 8 (60.7%), fewer cut answers and lower finished-answer accuracy roughly balance out. The released alpha = 6 sits on that plateau: paired by problem, it leads alpha = 4 by +2.3 +/- 1.6 and alpha = 8 by +0.8 +/- 1.6 points. Mid-thinking stops and perplexity rise at alpha = 8 and 12.
2. Why both the direction u and the 64 neurons matter
Changing 64 columns by 10.6% and 8.1% of the matrix norm could in principle work by adding weight noise that breaks repetition. The controls randdir and randcols test this with "norm_match": true in wsp/edits.py, which rescales alpha so the Frobenius norm matches p50c at both layers:
randdiredits the same 64 neurons per layer by the same matrix norm along a random unit vector orthogonal tou. Accuracy is14.5 +/- 1.8%(+2.3 +/- 1.6points over12.1%,22rescued,18lost).randcolsappliesuwith the same matrix norm to 64 random neurons outside the chosen 64. Accuracy is14.6 +/- 1.9%(+2.5 +/- 1.6points,23rescued,20lost).
Both controls sit within noise of the unedited model, with 87.3% and 86.7% of answers still cut at the cap. Among our 64-neuron edits, the gain appeared only when u went into neurons chosen because they fire during loops. Two caveats apply: each control is a single random draw (one random direction, one random set of 64 neurons), and the neurons do not have to be the exact released 64. In Lab 3, rebuilding from only 2 of the agent's 4 text pairs picked neurons that shared 42 and 31 of the released 64 neurons at layers 15 and 19 and still scored 62.3%. In Lab 8, recipe B picked neurons from real loops, shared 42 and 28 of the 64, and scored 58.2%.
Comparing all1 and all2 with the 64-neuron edits shows what the 64-neuron restriction does. Editing all 3,584 neurons at alpha = 1 (all1, 28.7%) and alpha = 2 (all2, 49.4%) beats the 64-neuron edits at the same alpha (a1 at 22.7% and a2 at 35.2%), which suggests the other 3,520 neurons also push along u. In Lab 7, KL divergence measures how far the edited next-token probabilities move from the base model's. At alpha = 2, all2 shifts predictions on productive texts about 3.5 times as much as a2 (KL 0.0063 vs 0.0018). It is also less selective: its KL ratio between looping and productive texts is 1.45 (0.0092 vs 0.0063), compared with 2.49 for p50c (0.0241 vs 0.0097). We did not run the all-neuron edit above alpha = 2, so we do not know its peak accuracy, only that the 64-neuron edit concentrates more of its change on looping text.
3. Two likely mechanisms: fewer loops and more escapes (and layer 15 vs layer 19)
Compare dev/base and dev/p50c in the endings table:
The endings suggest the edit helps in two ways, but do not prove it, because the check inspects only the text right before </think>:
- Fewer answers end their thinking in a loop: Normal
<think>closes jump more than sixfold, from56indev/baseto344indev/p50c. Normal closes are usually right:56 / 56(100%) indev/baseand286 / 344(83.1%) indev/p50c. The simplest reading is that fewer loops start. - More answers escape from a loop: Answers that repeat verbatim and then emit
</think>("closed out of a loop") rise from3indev/baseto172atalpha = 4and96atalpha = 6. Loop escapes are noisier than normal closes: indev/p50c,29 of 96(30.2%) are correct, adding29to the286normal-close successes (286 + 29 = 315).
Comparing dev/l15only and dev/l19only in the endings table:
- Layer 15 alone mostly adds normal closes:
dev/l15onlyscores60.4%(309correct, near61.5%for both layers). Normal closes rise from56to295(261correct), cut answers fall from453to81, and136answers close out of a loop (47correct). - Layer 19 alone leaves more answers cut and gets more closes from loops:
dev/l19onlyscores46.9%(240correct), with196cut answers,165normal closes (156correct), and151closes out of a loop (82correct).
Lab 6 offers a hint, though not a proof, for why the layers differ. Across 48 real loops, it measures the trigger s(t) (how hard the 64 chosen neurons push along u on each token). Relative to its in-loop level, layer 15's trigger builds earlier: in the last 512 tokens before the loop it reaches 42% of its in-loop value (0.178 of 0.424), compared with 32% at layer 19 (0.241 of 0.758). Inside the loop, layer 19's trigger is larger (0.758 vs 0.424). This fits layer 15 acting earlier and layer 19 pushing harder once a loop starts, though we did not test that directly.
4. The alpha = -4 surprise: why you must read the answers
At alpha = -4 (dev/a-4), the edit multiplies the 64 neurons' push along u by 1 - (-4) = +5, pushing 5 times harder in the loop direction. On headline accuracy alone, dev/a-4 scores 19.7 +/- 1.9%, or +7.6 +/- 2.2 points above the unedited model (12.1%).
The endings table and labs/peek.py show what happened:
- Normal reasoning collapses: Normal
<think>closes drop from56indev/baseto13indev/a-4(10correct).dev/a-4loses24problems the base model solved (54rescued,24lost, whilea4,p50c,a8, anda12lose0). - Mid-thinking stops jump to 47:
47answers emit the end token inside<think>with no final answer, and WikiText perplexity worsens by+5.39%. - Most correct answers close their thinking right after repeated text:
197answers close this way; of101correct answers indev/a-4,87came fromclosed out of a loop.
One problem shows the pattern (gsm8k_train_5628: "Mr. Desmond bought three times as many toys for his younger son as he bought for his elder son. If the elder son received 60 toys, how many toys did Mr Desmond buy?", truth 240):
- Unedited (
quick/base, answer 0): Computes60 + 180 = 240early, then enters an 83-token verbatim loop at token8,568of32,768("Let's check if the answer is 240. / Let's check if the answer is 180...") that repeats about292times to the cap. - Released edit (
quick/p50c, answer 0): Finishes in5,825tokens, closes</think>, and writes\boxed{240}. - Alpha -4 (
dev/a-4, answer 0, onsampler): Runpython labs/peek.py dev/a-4 --id gsm8k_train_5628onsampler. The model repeats "Wait, is it possible and is the same?" over and over, breaks off mid-line with a stray backtick and</think>, and writes240(4,830tokens total).
Pushing harder in the loop direction cut normal closes from 56 to 13, lost 24 previously solved problems, and dropped finished-answer accuracy to 55.4%. Yet 87 correct answers closed their thinking right after repeated text, and total accuracy rose by +7.6 points.
Check yourself
- Look at the dose table between
alpha = 6(61.5%accuracy,19.7%cut) andalpha = 12(51.8%accuracy,12.3%cut). Fewer answers are cut atalpha = 12, so why is accuracy9.7percentage points lower?
Answer
Two things go wrong at alpha = 12. First, 37 answers emit an end token inside <think> (stopped while thinking), against 0 at alpha = 6, producing no final answer. Second, the -11 multiplier likely hurts reasoning: WikiText-2 perplexity rises +14.34%, and finished-answer accuracy falls from 76.6% at alpha = 6 to 59.0% at alpha = 12 (normal closes fall from 286 / 344 = 83.1% to 262 / 413 = 63.4%), though selection may contribute because the two runs finish different problems.
- Why do
randdirandrandcolsboth use"norm_match": trueinconfigs/variants.json? What would happen ifrandcolsused rawalpha = 6without"norm_match": true?
Answer
In Lab 1, the 64 chosen neurons aligned with u much more strongly than typical down_proj columns (median |cos(u, column)| is 0.095 on the 64 columns in layer 15 vs 0.018 on the rest). Without rescaling, plugging 64 random columns into W' = W - 6 u (u^T W_S) gives a Frobenius norm for W' - W several times smaller than the released edit's, so the control could fail from a smaller weight change alone. Setting "norm_match": true multiplies alpha by ||u_rel^T W[:, cols_rel]|| / ||u^T W[:, cols]|| so W' - W matches p50c in Frobenius norm.
- Compare
dev/l15only(60.4%accuracy,+0.05%perplexity change) anddev/l19only(46.9%accuracy,+1.65%perplexity change). Which single layer carries most of the benefit ondev256, and how do their answer endings differ?
Answer
Layer 15 alone (60.4%) nearly matches editing both layers (61.5%) with almost no WikiText perplexity change (+0.05%). In the endings table, dev/l15only mostly adds normal closes (295 normal, 81 cut while thinking), while dev/l19only leaves 196 cut, gets 165 normal closes, and closes out of a loop more often (151).
- In
dev/a-4,101of512answers are correct (19.7%), beatingdev/base(62of512,12.1%). Give three numbers from the tables that showdev/a-4damages the model instead of fixing the loop bug cleanly.
Answer
Any three of these confirm the damage:
- Normal closes fall from
56(56correct) indev/baseto13(10correct) indev/a-4. dev/a-4loses24problems that the unedited model solved (54 / 24rescued/lost), whereasdev/p50closes0(125 / 0).47answers stop dead in the middle of thinking (stopped while thinking), compared with0indev/baseand0indev/p50c.- Accuracy among finished answers drops from
100.0%indev/base(76.6%indev/p50c) to55.4%, and87of the101correct answers come from crashing out of a verbatim loop (closed out of a loop). - WikiText-2 perplexity rises by
+5.39%(compared with+0.79%forp50c).
Try this next
We did not run the experiments below. Write down your prediction first, then test on a 16-problem slice (--limit 16, about 3 to 5 minutes on 1 GPU) before running all 256 problems.
-
Test
alpha = 3oralpha = 5on the dose curve (not run). Betweenalpha = 2(35.2%) andalpha = 4(59.2%), accuracy jumps24.0points. From the project root on this machine, add"a3"or"a5"to a new fileconfigs/try_dose.json:json{ "a3": {"about": "flip and double the loop component (1 - alpha = -2)", "layers": {"15": {"alpha": 3}, "19": {"alpha": 3}}}, "a5": {"about": "between a4 and p50c (1 - alpha = -4)", "layers": {"15": {"alpha": 5}, "19": {"alpha": 5}}} }On this machine, create
plans/try_dose_quick.jsonin the format ofplans/quick.json(--limit 16runs the first 16 GSM8K problems ofdev256, 2 answers each = 32 answers):json{ "about": "Quick 16-problem check of alpha 3 and alpha 5", "runs": [ {"name": "quick/a3", "model": "qwen35-a3", "data": "dev256", "n": 2, "shards": 1, "est": 1, "args": ["--limit", "16"]}, {"name": "quick/a5", "model": "qwen35-a5", "data": "dev256", "n": 2, "shards": 1, "est": 1, "args": ["--limit", "16"]} ] }Sync both files:
bashbash scripts/sync_vm.sh samplerPick a free GPU with
nvidia-smi. Connect to the VM, activate the environment, build both models on CPU, and run the plan (replace7with your free GPU):bashssh sampler . /nvme/wsp-lab/code/scripts/env.sh && cd /nvme/wsp-lab/code CUDA_VISIBLE_DEVICES="" python scripts/build_models.py --variants configs/try_dose.json python scripts/run_jobs.py plans/try_dose_quick.json --gpus 7What to predict: In the agent's 20th pass (
fwd_29), raisingalpha = 2toalpha = 3on all columns added about 5 points (+34.77to+39.84), about a third of the way to the roughly+50points its 64-neuron edit later reached. Our 64-neuron curve climbs faster, froma2at35.2%toa4at59.2%, so a fair guess fora3on fulldev256is near the middle, around45% to 50%. Predicta5on fulldev256to land around60% to 61%(within noise ofa4at59.2%andp50cat61.5%). On these16problems alone, our full-run answers scorea2at37.5%(12/32correct) anda4,p50c, anda8at65.6%(21/32each). With 16 problems, the standard error is roughly 9 points or more, so the quick check gives only a rough placement on the curve. -
Edit only a subset of the 64 neurons at
alpha = 6(not run). Inwsp/edits.py,build_from_specaccepts a list of integer column indices for"cols". Inresults/lab3/rebuild.json, each layer stores"cols"(all 64 chosen neurons) and"top12"(the 12 highest-scoring neurons by loop score). From the project root on this machine, write a variant file with 32 of the 64 neurons ("cols"[:32], the 32 lowest-numbered of the 64, sincecolsis sorted by index, not score) or with the top 12 ("top12"):bashpython3 -c ' import json r = json.load(open("results/lab3/rebuild.json"))["layers"] spec = { "halfcols-a6": {"about": "alpha 6 on 32 of the 64 neurons per layer", "layers": {L: {"alpha": 6, "cols": r[L]["cols"][:32]} for L in ("15", "19")}}, "top12-a6": {"about": "alpha 6 on the top 12 loop-score neurons per layer", "layers": {L: {"alpha": 6, "cols": r[L]["top12"]} for L in ("15", "19")}} } json.dump(spec, open("configs/try_subset.json", "w"), indent=1) 'Copy the quick plan from Try 1 to
plans/try_subset_quick.json, replacing the two run names withquick/halfcols-a6andquick/top12-a6and the model names withqwen35-halfcols-a6andqwen35-top12-a6. Keep"args": ["--limit", "16"]and sync:bashbash scripts/sync_vm.sh samplerBuild on CPU and test on a free GPU (replace
7as needed):bashssh sampler . /nvme/wsp-lab/code/scripts/env.sh && cd /nvme/wsp-lab/code CUDA_VISIBLE_DEVICES="" python scripts/build_models.py --variants configs/try_subset.json python scripts/run_jobs.py plans/try_subset_quick.json --gpus 7What to predict: We did not measure the counter-push of each subset, so use Lab 3's loop scores as a rough guide. The 32 lowest-numbered columns are not an even half by score. At layer 19 they include the two highest scorers, neurons 1 and 11. Neuron 11 alone holds
24%of layer 19's positive score, so this half probably carries well over half of layer 19's push. At layer 15 no neuron dominates (the top holds5.6%). If that half carried exactly half the push,alpha = 6on 32 columns would act likealpha = 3or4on all 64. Predict roughly45% to 55%on fulldev256with under+0.79%WikiText perplexity change, and somewhat higher if layer 19's half carries more. The top 12 neurons hold more positive score than the top 8 (19.3%at layer 15,39.7%at layer 19) and less than the top 32 (38.9%,49.8%), compared with49.8%and55.5%for all 64. That share is large, especially at layer 19, sotop12-a6could land anywhere from neara2(35.2%) to neara4(59.2%) on fulldev256. The16-problem slice is not the full-set comparison: our full-run answers on these problems scorea2at37.5%anda4,p50c, anda8at65.6%.