2026-11-09 · 36 min · interpretability · qwen · small-models · agents · reasoning
Why read this
Solidtop 85%Diffs the edited and base Qwen3.5-0.8B bit for bit, reads the agent's log, and measures loops in published and CPU-run samples next to the critics' claims.
- Original analysis
- Runs on a laptop CPU
- A new technique
InterpretabilityApache-2.0Practitioner teardown
How this was scored
- Is it new?
- 2 of 3: A real new idea, method or capability
- Can I trust it?
- 2 of 3: Measures key facts from files, code or configs
- Can I run it?
- 2 of 3: Open code or weights with real limits
- Will I understand it?
- 2 of 3: Mechanism from first principles with figures
- Can I act on it?
- 1 of 3: General advice
- Will it last?
- 1 of 3: Relevant for months
- Does it affect many?
- 1 of 3: A specialist community
- Only here?
- 2 of 3: A teardown or measurement few others did
Score 59 of 100, ranked 423 of 701 rated articles. Each question is answered 0–3 by hand, and a 3 is rare. How articles are scored
On 2 November 2026 four MIT CSAIL researchers (Rikiya Takehi, Jayden Teoh, Alex Zhang and Omar Khattab) published Sparks of Weight-Space Programming. The claim is that a frontier model, GPT-6 Astra, used as it is, can read and write neural network weights: name the game a policy was trained on from its raw weights, repair a stuck car controller, write game-playing networks from zero, and, in the experiment that went viral, make Qwen3.5-0.8B better at math by editing its weights directly. Reported: MATH-500 went from 7.9% to 48.1%, GSM8K from 13.8% to 67.1%, IFEval from 21.0% to 62.3%, by changing "only 0.016% of weights", with no gradients and no training.
Within a day the replies split. One reply called it a loop fix dressed up: Qwen3.5-0.8B runs in non-thinking mode by default, and in thinking mode it loops, so of course a fix for looping helps. Another said Gaussian noise on the last four layers doubles the score too. Hugging Face's Ahsen Khaliq rebuilt the checkpoint from the published logs and re-ran MATH-500 at 51.0%.
Everything needed to check the core claim is public: both checkpoints, the edit as a diff, and a log of every message, every Python run and every forward pass of the agent's 20-hour session. So I checked it. This page labels each claim measured (I ran it), reported (a source says so) or reasoned (my inference).
- architecture
- Qwen3_5ForConditionalGeneration
- task
- image-text-to-text
- library
- transformers
- license
- apache-2.0
- safetensors
- 1 shard
- largest file
- 1.75 GB
- files
- 17
- downloads
- 3.5K
- likes
- 132
repo last modified 2026-11-02
What the experiment was
Reported, from the post and exp8_math/README.md in the logs. Astra got a persistent Python kernel over
Qwen3.5-0.8B's weights (CPU only, no GPU, no internet), practice problems with answers (7,469 from GSM8K train, 7,275
from MATH train), and a budget of 10, 25 or 50 forward passes. A pass is either a generation (the model answers up to
512 practice problems with the evaluation's sampling, original and edited side by side if asked) or a teacher-forced
read of up to 1,024 texts that returns log-probs, hidden states and MLP activations. Gradient descent, autograd and
any iterative fitting were forbidden. So were edits to the embedding, the output head and the final norm, and any
edit that just multiplies a whole tensor by a constant. Astra never saw the benchmarks.
The evaluation is fixed: thinking on, temperature 0.6, top-p 0.95, top-k 20, presence penalty 1.5, at most 32,768 new
tokens, last \boxed{} graded with math-verify. Those are the thinking-mode settings Qwen's own card uses for its
benchmarks.
The three runs ended with three different edits:
| Budget | Turns | Hours | Final edit (from each run's final_answer.md) | MATH-500 |
|---|---|---|---|---|
| none | the base model | 7.9% | ||
| 10 passes | 74 | 11.6 | subtract 1.5x a loop direction from 30 output matrices in layers 8-22 | 12.3% |
| 25 passes | 86 | 8.5 | zero attention head 7 of layer 15 | 14.1% |
| 50 passes | 158 | 20.3 | subtract a loop direction from 64 MLP output columns in layers 15 and 19 | 48.1% |
Only the 50-pass edit is published as a checkpoint, so that is the one I took apart.
The model and the edit, term by term
Before the measurements, the object. Measured, from the base model's config.json: Qwen3.5-0.8B's language model
is 24 decoder layers over a 1,024-wide residual stream. Three of every four layers use Gated DeltaNet, a linear
attention with 16 heads of width 128; layers 3, 7, 11, 15, 19 and 23 use ordinary softmax attention with 8 query heads
and 2 key-value heads of width 256. Every layer has the same gated MLP:
The vocabulary is 248,320 tokens, and the output head reuses the embedding table. Both layers the edit touches, 15 and 19, are softmax-attention layers; the 25-pass run's edit, a zeroed attention head, is in layer 15 too.
Read by columns. The MLP's hidden vector has 3,584 entries, one per neuron, and the output is where is column . So column is the fixed vector neuron adds to the residual stream, scaled by how strongly it fires. Editing a column changes what a neuron says, never when it fires.
Now the edit, with every symbol:
- , unit length: the loop direction. Astra got it by teacher-forcing matched pairs of texts, the same problem continuing productively and continuing into repetition, and taking the difference of what layers 15 and 19 write on the two branches.
- : for each neuron, how far its write points along . Call it .
- : a 3,584 x 3,584 diagonal 0/1 mask with 64 ones. It keeps the update to the chosen neurons.
- : a rank-one matrix whose column is for a chosen neuron and zero otherwise.
- : the dose. Column becomes , so its component along goes from to . At the neuron stops writing along ; at it writes .
Everything orthogonal to in those 64 columns is untouched, and so is every other column. Reasoned: that is why this is cheap to search over. Once and the 64 indices are fixed, there is one number left to choose, and the whole forward pass up to layer 15 does not depend on it.
What the rules allowed shaped it as much as anything. Reported, from the task prompt: no gradients, no optimisers, no iterative fitting, no edits to the embedding, the output head or the final norm, nothing that just multiplies a tensor by a constant. Closed-form linear algebra on activations Astra had read was allowed. A projection like this one is about the most expressive thing those rules leave: it uses a direction estimated from data and puts it into weights without a training loop.
The edit, measured from the two checkpoints
Measured. I streamed both safetensors files over HTTP range requests (base Qwen/Qwen3.5-0.8B at commit
2fc0636, edited at d1671a1) and compared all 488 tensors byte for byte. Exactly two differ:
model.language_model.layers.15.mlp.down_proj.weight and …layers.19.mlp.down_proj.weight, both [1024, 3584] in
bfloat16. Every other tensor, vision tower included, is identical.
Inside each, 64 of the 3,584 columns changed. A column of down_proj is what one MLP neuron writes into the
1,024-wide residual stream. The bits differ in 65,260 values in layer 15 and 64,928 in layer 19, 130,188 in all. That is
a little under the 131,072 values the edit touched, because some updated values round back to the same bfloat16
number.
The change in each layer is one outer product. I took an SVD of the difference restricted to the 64 columns: 99.9995% of its energy is in the first singular value in layer 15 (99.9991% in layer 19), and the next singular values (0.001) are the size of bfloat16 rounding. So the edit is
where is a unit "loop direction" in the residual stream and keeps only the 64 chosen columns. That matches the
formula in Astra's final report, W = W0 - 6*outer(u, mask64*(u@W0)). I measured what it does to each changed column:
its component along goes from to (the median ratio is -5.00 in both layers). Removing the component
would be . This edit overshoots it, so those 64 neurons now write against the loop direction, five times
as hard as they used to write along it. The relative Frobenius change is 10.64% of layer 15's matrix and 8.11% of
layer 19's. The two directions are not the same vector: their cosine is 0.85.
The "0.016%" is honest arithmetic, with a choice of denominator. Measured: the release has 873,438,784 parameters, the language model 752,393,024 of them, and 254,279,680 of those are the tied embedding table. 131,072 touched weights is 0.0150% of the release, 0.0174% of the language model and 0.0263% of the language model without its embedding. The post's 0.016% is 131,072 over a round 0.8 billion. Any of these is small. The more useful way to say it is that the edit is described by two 1,024-number directions and 128 column indices; the strength is one constant.
One more measured detail. The 64 columns are not simply the 64 with the largest : only 31 of them in layer
15, and 18 in layer 19, are in that top 64. Reported, from work_notes/experiment_history.md: they were chosen
from activations on a pass of the original model, as the neurons that carry most of the difference between what is
written on looping and on productive text ("~91%/98% of transfer repetitive-minus-productive writing contrast, but
50%/40% of full directional weight norm"). Measured: the second half of that checks out. The chosen columns hold
25.2% of in layer 15 and 16.0% in layer 19; the square roots are 50% and 40%.
How big is the change per weight? Measured, from the diff: the median changed entry moves by 0.0020 in layer 15 and 0.0017 in layer 19, against a median weight of 0.0052 and 0.0058 in those matrices, and the largest single change is 0.146 and 0.215. Per column it is large: the median chosen column changes by 57% of its own norm in layer 15 and 43% in layer 19. So this is not a small nudge to many weights. It is a large change to a few neurons' outputs, along one axis.
One pattern I did not expect. Measured: 40 of layer 15's 64 neurons, and 35 of layer 19's, have an index below 512. If the choice ignored the index, about 9 would. The low-index neurons do not have larger write vectors (their mean column norm is 0.236 against 0.277 for the rest in layer 15); their mean is only about 25% higher. I come back to this below, where I recompute the selection from activations.
How Astra found it
Reported, from the p50c run's log. The log reads like an ordinary interpretability workflow, run by a model with a tight forward-pass budget.
- Diagnose. Pass 1 ran the original model on 96 practice problems: 11 right, 85 cut at the token limit, no finished wrong answers. Astra wrote that the dominant failure was "pathological long/repetitive reasoning", not math.
- Find a direction. Teacher-forced reads of matched contexts, the same problem continuing productively or into repetition, gave a common difference vector in the residual stream. A first try ablated a single neuron in layer 19 (+6.25 points on 32 problems, within noise).
- Turn the dose up. Removing the direction from both layers' full
down_projat α = 1 took 128 fresh problems from 8.59% to 32.81%. α = 2 on a 256-answer set: 29 right to 92. Restricting it to 64 neurons per layer at α = 4: 13 of 128 to 47 of 128. At α = 6 with the full 32,768 tokens: 7 of 64 to 35 of 64, with the share cut at the limit falling from 89% to 8%. - Control. A norm-matched random direction, orthogonal to the loop direction, at the same dose: +1.56 points with a standard error of 3.52, on 128 answers. Not the effect.
- Confirm on held-out problems. Frozen before this step, the edit was run against the original on 1,152 practice problems not used to choose it: 10.6% to 60.4%, 579 rescues and 5 losses. Of the 579 rescues, 576 were answers the original had not finished. Accuracy on answers the original did finish did not change.
Astra's own reading of that last line is the honest one: the edit lets the model finish answers it could already work out. The post says the same thing in its Gemma follow-up: on Gemma 3 270M, which reaches its token limit on only 8.5% of answers, the same procedure found ordinary reasoning errors and no component to switch off, and MATH-500 stayed between 6.8% and 7.7%.
The task prompt matters for judging how much was discovered. Reported, from turns/t001/prompt.txt: it names the
target as an example, "if the model often fails because it repeats itself and never finishes, finding and fixing what
drives that repetition counts", and tells Astra to "go after the largest category you can explain". It also rules out
the cheap fix: "making the model skip its thinking or close it early regardless of the problem" is not wanted. So
Astra did not have to find out that the model loops. It had to find where, in weights, and do it within 50 passes.
The dose ledger
Reported, from work_notes/experiment_history.md. Pulled out of the narrative, the generation passes that tested
a candidate read like a dose-response table. Each is paired: original and edited model on the same problems in the
same pass.
| Pass | Edit | Problems x samples, cap | Original → edited | Gain (SE) |
|---|---|---|---|---|
| 4 | ablate one neuron (layer 19, #11) | 32 x 1, 32,768 | 3 → 5 | +6.25 (4.35) |
| 5 | remove , layer 19 only, α = 1 | 32 x 1, 32,768 | 4 → 6 | +6.25 (4.35) |
| 10 | remove , layers 15 + 19, all columns, α = 1 | 128 x 1, 32,768 | 11 → 42 | +24.22 (3.96) |
| 12 | same, α = 2 | 64 x 4, 32,768 | 29 → 92 | +24.61 (3.96) |
| 19 | same, α = 2, fresh problems | 128 x 2, 32,768 | 21 → 110 | +34.77 (3.53) |
| 20 | same, α = 3 | 128 x 2, 32,768 | 21 → 123 | +39.84 (3.82) |
| 23 | norm-matched random direction | 64 x 2, 32,768 | 14 → 16 | +1.56 (3.52) |
| 27 | 64 chosen columns, α = 4 | 64 x 2, 16,384 | 13 → 47 | +26.56 (4.45) |
| 28 | all columns, α = 4 | 64 x 2, 16,384 | 14 → 48 | +26.56 (4.59) |
| 30 | 64 chosen columns, α = 6 | 64 x 1, 32,768 | 7 → 35 | +43.75 (6.63) |
| 34 | same, frozen, fresh problems | 64 x 1, 32,768 | 2 → 34 | +50.00 (6.30) |
Two things stand out. The gain grows with the dose all the way from α = 1 to α = 6, with no sign of a peak; Astra stopped raising it because it had to freeze an edit and confirm it, not because a higher dose hurt. And pass 28 is the ablation for the 64-neuron mask: at α = 4, editing all 3,584 columns and editing the 64 chosen ones gave the same gain on the same problems. The mask halves the size of the change (relative Frobenius change 7.09% against 14.11% in layer 15, from the same notes); it was not needed for the effect. Reasoned: that makes "0.016% of weights" partly a presentation choice. The full-column version of the same edit changes 7.3 million weights across the two matrices and, at that dose, does the same thing.
Three budgets, three edits
The two smaller budgets ended somewhere else. Reported, from each run's edit/manifest.json: the 10-pass run
shipped 30 rank-one edits, one per attention or MLP output matrix in layers 8-22, at α = 1.5, which changed the bits
of 72,986,982 weights. The 25-pass run zeroed the 256 output columns of attention head 7 in layer 15, 262,144
weights. Its own notes call the head a retriever of "repeated continuations": weakening it reduced loops, amplifying
it made them worse.
The logs keep, for every run, the grade of all 2,000 MATH-500 answers and the full text of 200 of them (the first sample of the first 20 problems in each of ten evaluation shards). That is enough to compare the three edits by difficulty and by loop rate, which the post does not do. Measured, with the loop rule from the samples section below, re-implemented and applied to all 800 texts. (On the 20 published samples per model the re-implementation marks 19 base and 7 edited answers as looping, against 19 and 9 for the first version, so if anything it undercounts the edited model's loops.)
The base model's failure is the same at every level. Even on level 1, 16 of 21 first samples loop and never close. It solves what it can solve before it loops: the 13 base answers that close their thinking are all right. The two small-budget edits move that a little: 41 and 29 closed answers of 200, of which 33 and 25 are right. The 50-pass edit changes the picture: 152 of 200 close, and of the 71 that still loop, 54 recover and close. Its closed answers are now wrong 40% of the time (92 right of 152), which is what happens when a model starts finishing problems it cannot do.
Two paired facts settle what kind of gain this is. Measured, from the per-problem grades: the base model solves 77 problems at least once in four samples. The 50-pass edit solves every one of those 77 at least once, and on them its accuracy is 96.4% against the base's 51.3%. It solves 320 problems at least once in all. Nothing the base could do was lost. The gain is largest in the middle: level 3 goes from 7.6% to 56.7%, level 5 only from 0.9% to 23.1%.
What the edit does to the answers
Measured, from the 20 published samples per model in the edited repository's eval/ folder (the first sample of
each of the first 20 problems in the re-run's smoke test, thinking on, 32,768-token cap). I counted closed </think>
tags, right \boxed{} answers and occurrences of "wait", and marked a loop onset where at least 90% of the next 3,000
characters' lines repeat a line already written.
The base model closes its thinking in 1 of 20 answers and gets that one right. The other 19 loop: their tails are
lines like Wait, $f(-1) = 5/3$. repeated until the cap, and the median answer says "wait" 764.5 times. The edited
model closes in 18 of 20 and is right in 10. It still falls into a loop in 9 of 20, with a later onset, and all 9 of those
eventually close. Its two capped answers fail in a new way: each ends in a run of more than three million whitespace characters.
The logs flag the same thing ("whitespace degeneration").
Twenty answers per model are an illustration. The re-run behind them is the real check. Reported, from the model repository: 500 problems x 4 samples, the post's protocol on vLLM, scored 51.0% with 31.6% of answers hitting the token limit. Its 25-problem smoke test of the base model scored 5.0% with 95% at the limit.
Scoring the loop under both models (CPU)
Measured. To see the mechanism rather than its outcome, I took base-model answers from those samples, split each at its loop onset, and scored the text before and after the onset under both sets of weights, teacher-forced, on a CPU (bfloat16, PyTorch 2.14, transformers 5.20.2, two threads). If the edit works the way the log says, the looping tail should become much less likely and the productive head should barely change.
Five base answers, mean log-probability per token (nats) of the text before the onset and of about 3,000 characters after it:
| Problem | Tokens before onset | Tokens after | Before onset: base → edited | Loop: base → edited |
|---|---|---|---|---|
precalculus/807 | 7,068 | 1,367 | -0.346 → -0.362 (-0.016) | -0.030 → -0.076 (-0.045) |
intermediate_algebra/1994 | 9,046 | 1,461 | -0.277 → -0.294 (-0.017) | -0.018 → -0.061 (-0.043) |
algebra/2584 | 7,778 | 1,285 | -0.309 → -0.325 (-0.016) | -0.072 → -0.129 (-0.056) |
number_theory/572 | 8,232 | 1,083 | -0.169 → -0.201 (-0.032) | -0.031 → -0.121 (-0.090) |
algebra/1349 | 4,130 | 1,609 | -0.367 → -0.382 (-0.015) | -0.056 → -0.086 (-0.030) |
The loop tail is cheap for the base model: -0.03 nats per token means each next token of the loop has about a 97%
chance. The edit makes every loop token less likely in all five answers, by 0.053 nats on average, and costs the
productive text before the onset 0.019 nats per token (about 2% in perplexity). That is selective, but less than
Astra's own measurement on its chosen cases (-0.103 against -0.009 at the same dose, from final_report.md).
Reasoned: a per-token penalty of 0.05 looks small, but it compounds. Summed over 1,000 tokens of loop, the edited model gives that exact
continuation about times the probability the base model gave it. Each step of the loop is a little more
likely to be left, sampling gets many steps to leave it, and presence penalty 1.5 pushes the same way. That is why a change too small to notice on productive text moves the share of answers cut at the limit from
93% to 33%.
Turning the one knob
The published checkpoint is one point on a curve. Because the edit is , 64 indices and , and I recovered all three from the diff, I can rebuild it at any dose and with any part swapped out. Measured. I rebuilt the two matrices from the base weights (at this reproduces the published tensors to within bfloat16 rounding: the largest difference is 0.0005), and scored six base answers teacher-forced under each version: 43,214 tokens of text before the loops and 7,327 inside them. The edit cannot change anything below layer 15, so I cached the residual stream entering layer 15 once per answer and re-ran only layers 15-23 for each setting, about 30 seconds each on two threads.
What the sweep shows, in order of how much it changed my reading:
- The random-direction control is flat. A random unit vector orthogonal to , in the same 64 columns at , changes the loop tail by -0.0001 nats per token and the text before it by -0.0003. Astra's logged random control (+1.56 points, SE 3.52) agrees. The effect is the direction, not the act of disturbing 64 neurons.
- Selectivity rises with the dose. At the edit costs the productive text almost as much as the loop (-0.0023 against -0.0026). At 6 the loop pays 2.3 times as much, at 8 it pays 3.7 times, at 12 it pays 7.5 times. But the cost to normal text rises too: -0.021 nats per token at 6, -0.035 at 8, -0.084 at 12. The last is about 9% on perplexity across all reasoning, which I would not ship without the accuracy run Astra had and I do not.
- The activation-chosen mask matters on real text. Swapping the 64 neurons for the 64 with the largest , same dose, halves the loop penalty (-0.024 against -0.050) and is less selective (1.5x against 2.3x). Removing from every column at is not selective at all here (-0.0044 on the productive text, -0.0037 on the loop), although in Astra's generations that edit gained 24 points. Reasoned: a small, unselective change can still break loops in generation, because a loop is a fixed point that any perturbation makes a little less stable; the teacher-forced number measures selectivity, not that.
- The two layers are not additive. Layer 15 alone does -0.027 on the loop, layer 19 alone -0.012, both together -0.050, more than the sum. Layer 19 reads a stream layer 15 has already changed. Astra's own single-layer probes at ranked them the same way (-0.081 against -0.034 on its short pairs).
There is also a gap between Astra's teacher-forced numbers and mine. On its hand-written pairs, about 200 tokens each, the full-column edit at raised the productive branch's log-prob by 0.002 and lowered the repetitive one by 0.024. On real 7,000-token answers I get no selectivity at that setting. Reasoned: short clean pairs make a contrast look sharper than it is inside a long, half-repetitive answer.
Why six
The probe that explains the dose is the residual stream itself. Measured, on the same six answers with the base weights: projected onto , the residual stream after layer 15 averages 0.93 on productive text and 2.10 inside a loop. The 64 chosen neurons write 0.11 of that on productive text and 0.34 in a loop, which is 80% of what all 3,584 neurons together write along there (0.43). They are mostly silent along until the model loops.
Now apply the edit. Each chosen neuron's contribution goes from to , a change of inside a loop, and the stream along falls from 2.10 to 0.04, measured. On productive text it falls from 0.93 to 0.30. After layer 19 the same thing happens: 3.82 to 0.29 in the loop, 1.86 to 0.64 outside it.
So is close to the dose that cancels the loop direction exactly when the model is looping: would zero it on average, and 6 leaves 0.04. Reasoned: the "minus five times" overshoot that looked strange in the diff is not the neurons pushing against loops. It is the amount needed to cancel what the whole residual stream carries along at that point, using the 64 neurons whose activity rises when it does. Astra did not compute it this way; it found 6 by raising the dose until the results said stop.
What does mean in tokens? Measured: pushed through the final norm and the tied unembedding, neither layer's
reads as anything about repetition. The tokens it promotes most are fragments (...*, urada, ossid) and
Chinese administrative titles; the ones it suppresses lean towards connectives ( first, thus, however,
rather, accordingly, using). "Wait" is in neither top 25. That is the expected null result for a mid-stack
direction, which feeds later layers rather than the output, and it is a warning against naming the direction from
its logit-lens reading. What it does is clear from the probe above; what it represents is not.
Recomputing the selection
Last, the low-index pattern from the diff. Measured: on four of the same answers I recomputed Astra's selection rule myself: for every neuron, its mean write along inside the loop minus its mean write before it. The 64 chosen neurons carry 97% of that net contrast in layer 15 and 102% in layer 19 (some unchosen neurons push the other way), close to the 91% and 98% Astra logged on its own texts. My own top 64 by contrast share 38 neurons with Astra's set in layer 15 and 27 in layer 19, from different texts, which is reasonable agreement for a choice made on one forward pass.
My top 64 are low-index too: 38 below 512 in layer 15, 52 in layer 19. The reason is in the activations. Neurons 0-511 fire about twice as hard as the rest on these texts (mean 0.126 against 0.058 in layer 15, 0.152 against 0.060 in layer 19). So the skew is a property of Qwen3.5-0.8B's MLPs, not of the agent's tooling. Why the first 512 neurons of a layer should be the busy ones I can only guess; the checkpoint's index order is whatever training left it.
The criticism: thinking mode, and noise
Reported, from Qwen's model card: "Qwen3.5-0.8B operates in non-thinking mode by default", and in thinking mode
"Qwen3.5-0.8B is more prone to entering thinking loops compared to other Qwen3.5 models". The card's own IFEval for
this model is 44.0 in thinking mode and 52.1 in non-thinking mode. The post's base IFEval is 21.0 with thinking on and
a 32,768-token cap; 74.9% of those answers were cut off (eval/unedited/merged.json) and an answer cut inside its
thinking scores zero. Reasoned: Qwen probably ran its card with a larger budget, so a cut-off loop hurt it less;
the card does not say.
The sharpest reply, from @dummerspast39, says that in default non-thinking mode the unedited model scores 47% on MATH-500 against 48% for the edit. Reported, unverified: the post gives no sample count, prompt or sampling settings, and I found no files behind it.
Measured, small. I could not run MATH-500 with thinking off at full size on a CPU, but I could look for loops. I
sampled 12 MATH-500 problems (seed 7) and ran both checkpoints twice on each with enable_thinking=False and Qwen's
non-thinking settings (temperature 0.7, top-p 0.8, top-k 20, presence penalty 1.5), capped at 1,536 new tokens:
48 answers, about 1.4 hours on three CPU threads. Neither model loops. Across 24 answers each, the base said "wait" 16
times and the edit 18; no answer from either repeats a line more than 8 times, against hundreds in the thinking-mode
samples above. The answers that hit the cap (12 of 24 for the base, 9 for the edit) were still working through the
problem, not repeating it. Right answers, by last \boxed{} and counting a vector written as a tuple as right: 10 of 24 for the base, 8 for the edit. That is too small a sample to separate
the two, and the short cap makes both scores low; it says only that the edit neither breaks non-thinking mode nor has
a loop there to fix, which is what @dummerspast39's numbers would predict.
The authors' own harness was ready to answer this. Measured, by reading exp8_math/code/evaluate.py: it
defines a nothink35 protocol, Qwen's non-thinking setting with presence penalty 1.5 and an 8,192-token cap, under
the comment "but thinking looped at 0.8B; the evaluation uses the model card's NON-thinking benchmark setting
instead". The file's docstring still describes a non-thinking evaluation of a Qwen3-1.7B checkpoint, left over from
an earlier experiment. Every MATH-500 shard behind the published numbers, though, records "protocol": "think35"
and "thinking": true in its scores.json, for the base and all three edits. So the published numbers are what the
post says they are, thinking on; the non-thinking comparison was planned in code and never published.
The other reply, from @yavuzbakman, shows MATH-500 going from 8.8% to 16.2% after adding Gaussian noise to the last four layers ("seed 11, 20%"), one sample per problem over all 500. Reported. One seed, named, out of an unknown number tried; at one sample per problem the base's own run-to-run spread is not small. Set it beside Astra's logged control: a norm-matched random direction at the edit's dose gained +1.56 points (SE 3.52) on paired practice problems. Reasoned: both can be true. Noise that big perturbs the late layers enough to knock some answers out of a loop; a single random direction in two layers does not. Neither is close to the targeted edit's 40-point gain under the same protocol. The arXiv paper the reply points to, Evolution Strategies at Scale by Qiu and colleagues, is about fine-tuning with many such perturbations and a reward, not about one random draw.
So both sides are right about something. Reasoned:
- The edit is real, small and specific. It is the same two tensors in both public checkpoints, it is rank one per layer, it was found by contrasting activations and confirmed on held-out problems against a random control, and it generalizes past math because loops are not specific to math (IFEval, GPQA Diamond and MMLU-Pro all went up).
- The "up to 6x" is a fix for a failure that this model's default mode does not have. The relevant comparison is not 7.9% but the base model with thinking off, and there the reply's numbers say the edit adds about one point. Nothing here shows Astra made the model better at math than it already is; the post's own Gemma result says it could not.
- Some of what is new is the operator, not the operation. Finding a direction from contrasting activations and projecting it out of a weight matrix is an established interpretability edit; the agent ran that workflow on its own, with a 50-pass budget, and picked the layers, the neurons and the dose.
The background paper: the other way to change weights without gradients
The noise reply cited Evolution Strategies at Scale: LLM Fine-Tuning Beyond Reinforcement Learning (Qiu, Gan, Hayes, Liang, Xu, Dailey, Meyerson, Hodjat and Miikkulainen; Cognizant AI Lab, MIT, UCLA and UT Austin). It is worth reading next to Astra's edit, because it also changes an LLM's weights with no backpropagation, and it does it in the opposite way.
Reported, from the paper's Algorithm 1 and 2. Each iteration samples Gaussian noise vectors the size of the whole model and scores each perturbed copy with a reward:
- is every parameter of the model. Nothing is masked.
- is the perturbation size; the paper uses per parameter.
- is the task reward of the -th perturbed model, a full generation and grading.
- is z-scored across the population, so the update is a reward-weighted average of the noise: directions that scored above average are added, those below are subtracted.
- is the step size, . The usual factor is folded into it.
The engineering is in Algorithm 2: noise is never stored. Each perturbation is regenerated from its random seed, added layer by layer in place, scored, and subtracted again, so a population of 30 costs one model's memory. is small next to the 10,000 or more of earlier ES work, and it is the paper's main surprise that this works on billions of parameters. On the Countdown arithmetic task, reported from its Table 1, ES took Qwen2.5-0.5B-Instruct from 0.1% to 14.4% (best RL baseline 13.5%) and Qwen2.5-3B-Instruct from 10.0% to 60.5% (best RL 43.8%), with one set of hyperparameters for every model.

The paper's math result is on a different model and scale from anything above: Qwen2.5-Math-7B, which does not loop the way Qwen3.5-0.8B does in thinking mode. What makes it useful here is the picture of what ES does to weights.

So ES spreads its change over every parameter, and the change looks like noise even though it is not. Astra's edit
is the other extreme: 131,072 weights, one direction, and a change you can name. Measured, to put them on one
scale: spread evenly over all 3.67 million entries of layer 15's down_proj, the edit's Frobenius norm (1.75) would
be a per-entry change of , about of a single ES perturbation; in layer 19 it is
. The two methods move these matrices by about the same total amount. ES points it in a random
direction and keeps it if the reward goes up; Astra pointed all of it along one direction it had measured.
Reasoned: that also frames the noise reply. Adding one Gaussian draw to four layers is ES with a population of one and no update rule. In a model whose main failure is a self-reinforcing loop, a random push can break enough loops to double a score near 8%; ES selects among such pushes with a reward, and Astra skipped the search by reading where the loop lives. None of the three, as run here, shows the base model learning math it did not already know.
The rest of the week's weight-space programming
Reported. Three other things were filed under the same name, and they are different kinds of object.
TensorSlay's fact writer (areu01or00/qwen0.8B-wsp) writes a short
profile of its author into Qwen3.5-0.8B: five down_proj tensors (layers 17-21), each changed by at most 25% of its
norm. I read its README and wsp.py. The update is a closed-form, MEMIT-style least-squares solve,
ΔW = R Kₚᵀ (λ·Kₙ Kₙᵀ + Kₚ Kₚᵀ + μI)⁻¹, with protected keys from about 40 everyday questions. Its own results table
is candid: on five unseen phrasings, two return the bio word for word, one starts right and then loops, one gets a
clause, one misses. It is a known editing algorithm that an agent set up, not weights the agent reasoned out.
Chandan Singh's toy programs (report) gave Claude, as a coding agent, a blank transformer and the rule "set every weight by hand". Across 13 tasks it wrote 959 programs; the ten algorithmic ones (addition, multiplication, sorting, parity, GCD and more) reached 100% accuracy within three attempts. The parity model has 174 weights, 35 of them nonzero. This is the forward direction the post's title promises, at a scale where every weight can be checked by hand.
Weight-space learning, which Boris Knyazev pointed to, is the other route: train a model whose outputs are weights, "Stable Diffusion for weights" in his phrase. Weight-space programming asks a general model to reason its way to an edit; weight-space learning trains a generator for one.
The closest page on this site is Program-as-Weights, where a compiler model turns a spec into a LoRA, trained for that job. The edit here is also what the neuron pages do by hand: neuron steering scales a single neuron's write, neuron finding searches for which one, and neuron lens reads what a neuron's weights say. An agent that picks 64 writer neurons by their activation contrast and rescales their write along one direction is doing the same thing, in two layers at once. Neuron tuning is the trained version of the same idea: it picks neurons that fire in the target context and are quiet elsewhere, and trains only their output columns. Astra's selection rule is that, done with one activation read instead of a training run, and its update is that, restricted to one direction.
Limits, and what I could not check
- No accuracy run of my own at full length. The level breakdown and the dose ledger use the authors' generations and grades; I counted, I did not re-grade 8,000 answers. The 43.4 million tokens of one MATH-500 re-run are out of reach on a CPU, and a thinking-mode answer from the base model typically runs to the 32,768-token cap, so even ten problems would take the better part of a day here.
- Teacher-forced log-probs are a mechanism check, not a benchmark. The sweep says which settings are selective on six real answers; it does not say what accuracy each would reach. Item 3 in the sweep is the case where they disagree.
- Six answers. The sweep's per-answer spread is in the widget (worst and best loop tail); the ordering of the settings is the same on every answer, the magnitudes are not.
- Only the 50-pass edit is published as weights. The 10- and 25-pass edits I describe from their manifests and grades, not from tensors.
- The direction is unnamed. I can show what does and when it is large. I cannot tell you what it represents.
- The non-thinking question is still open. One GPU run would close it (below), and the authors' harness already has the protocol for it.
- My runs were heavier than they needed to be. Without the fused linear-attention kernels, PyTorch's reference path held the six 8,500-10,600-token sequences in about 6 GB of RAM at peak.
What I ran, and what I did not
Ran: a byte-for-byte diff of all 488 tensors of both checkpoints over HTTP range requests; an SVD of each changed
matrix's difference; counts on the 20 published samples per model; teacher-forced scoring of base-model loops under
both weights; and both checkpoints with thinking off on 12 sampled problems, two answers each, at 1,536 tokens. Read: the post, the edited model's card and eval/ files, the logs' exp8_math
README, the p50c run's final answer, history notes, manifest and first prompt, TensorSlay's README, Singh's report,
and Qwen's model card.
Added on 10 November: a rebuild of the edit from the recovered , indices and dose, checked against the
published tensors; a teacher-forced dose sweep ( = 0-12) and five controls on six base answers; a probe of
the residual stream and the chosen neurons' write along ; the logit lens of ; a recomputation of the neuron
selection on four answers; loop and level counts over the 800 full texts and 8,000 grades the logs keep for the base
model and all three budgets; and a read of evaluate.py, apply_edit.py, the three edit manifests and the p50c
history. Read, for the background: the ES paper's algorithms, Table 1 and Figures 2 and 9.
Did not run: the full MATH-500 protocol in either mode. At 32,768 tokens per answer the re-run took 43.4 million generated tokens on an H200; on this CPU that is out of reach, so the 7.9%, 48.1%, 51.0%, 47% and 16.2% are all reported. I did not re-run Astra's search, the noise experiment or TensorSlay's writer.
- license
- MIT
- branch
- main
- tests
- none found
- source
- 581.3 kB
- commit date
- 2026-10-29
by size of tracked source at this commit, file counts in brackets; docs, data and vendored trees excluded
local clone, 2026-11-10 at 4d09ba6 — branch, commit, commitDate, fileCount, hasTests, languages, license, licenseFile, shallow
shallow clone: counts describe the pinned tree, not the history
If you have a GPU and want to settle the open question, the experiment is one vLLM run: the base and edited
checkpoints on MATH-500 with enable_thinking=False and Qwen's non-thinking settings, four samples per problem. If the
edit holds the base's non-thinking score, it is a free loop fix for anyone who wants thinking mode on this model. If it
lowers it, it is a fix for thinking mode only.