Both of your asks ran, and the second one takes your retraction further than you took it.
Your direct question first. Neither. The 0.001–0.002 shuffled control is run(..., shuffle=True) in gemma4_mlp_align.py, which permutes Ytr and then calls train_heads. It is a freshly initialised head fit on destroyed correspondence, so it prices whether the fitting procedure can manufacture alignment from noise. It is not an eval-time null for a given head, and the per-cell null you are asking for did not exist anywhere in the repo.
Worse, and you have not caught this one yet: rounds 12 and 13 shipped their controls as result files with no code path. blind_bigram_prior, margin_over_blind, prior_comparison and mean_image_control are all JSON with nothing behind them. That is the same class of thing as the dead HEAD_REPO constant you opened with, two rounds running, and it is ours. Every null now lives in one script with named arms and one scorer: scripts/sugarcrepe_controls.py.
Reproduction against the banked numbers, fresh box, transformers 5.15.1:
| cell |
flips vs banked |
rate |
| qwen-img × qwen-txt (cached states) |
0 / 7511 |
0.00% |
| gemma-img × qwen-txt (fresh gemma image encode) |
9 / 7511 |
0.12% |
| qwen-img × gemma-txt (fresh gemma text encode) |
20 / 7511 |
0.27% |
The matched cell reproduces exactly on all seven splits; the mixed cell reproduces exactly on three. That is the fresh-box re-encode floor, disclosed rather than explained away.
Your derangement null, all four cells
Real images, trained head, pairing deranged, 20 seeds. Clean five macro:
| cell |
real |
deranged |
margin |
| gemma-img × gemma-txt |
0.6861 |
0.5643 |
+12.2 |
| qwen-img × gemma-txt |
0.6952 |
0.5648 |
+13.0 |
| gemma-img × qwen-txt |
0.6643 |
0.5989 |
+6.5 |
| qwen-img × qwen-txt |
0.6611 |
0.5911 |
+7.0 |
The margin sorts by text tower and not at all by image tower. Swapping the image tower moves it 0.8 and 0.5 points. Swapping the text tower moves it about 6. Ratio 1.86x.
So the tower attribution survives, but now against a null that takes a different value in every cell, which is precisely what the blind prior could not do and what you correctly said it could not do. Per split on the matched qwen cell: replace_obj +20.7, replace_rel +8.6, replace_att +7.4, swap_obj −0.2, swap_att −1.4. The entire margin is object identity, and both swap splits sit at or below their own scrambled control.
Your 17.8 points, where I think you stopped one arm short
You gave two readings of the mean-image ablation, word-order competence or a candidate-set artifact, and proposed position exchange to separate them. There is a third, and it falls out of the scorer. score() is a cosine, so holding the image constant makes zi one fixed direction and the whole ablation reduces to which caption embeds closer to it. That is a typicality test, and typicality is invariant to position exchange, so your test would have scored it as genuine competence.
Three arms, all text-side, no re-encode:
| arm |
gemma/gemma |
qwen-img × gemma-txt |
gemma-img × qwen-txt |
qwen/qwen |
| mean image, own split |
0.6241 |
0.6251 |
0.6925 |
0.6868 |
| mean image, another split |
0.6270 |
0.6301 |
0.6904 |
0.6887 |
| random head-space direction |
0.505 |
0.503 |
0.495 |
0.505 |
Another split's mean image reproduces the ablation everywhere, within 0.5 points in all four cells. A random direction gives chance. So the ablation is not measuring the images it replaces, and it is not measuring fixed directions in general. It is measuring the one direction image means occupy, along which SugarCrepe's human captions outscore its generated foils. A regression of that fixed-direction score on caption length and mean unigram log-frequency accounts for the add splits almost exactly (0.993, 0.994) but not the swaps (0.563, 0.590 against the ablation's 0.716, 0.714), so it is not length either, and your normalized prior already showed that.
That is your round-13 control failing for exactly the reason you named in round 14, confirmed from the inside. The reading I banked off it, "image content degrades a text prior everywhere subtler", is withdrawn. It never had an image in it.
And the four-cell view explains the inversion completely: the ablation beats real images only under the weaker text tower. Under gemma text it loses (0.624, 0.625 against 0.686, 0.695). Under qwen text it wins (0.693, 0.687 against 0.664, 0.661). It was a property of the text tower, not a fact about image content.
Position exchange cannot decide this
Each candidate is scored independently against the image and the two scores are compared, so the scorer carries no positional term and exchange maps accuracy to its complement identically. Verified to 1e-9 on every split, both arms, all four cells. The test only bites on a scorer that sees both candidates jointly, like a cross-encoder. Ours is not that.
One more thing we found, before you do
Building the control script, I went to load the gemma/gemma v6 head and it does not exist. Not in the repo, not in RiverRider/srt-nla-gemma4-artifacts. The paper's headline cell had no retrievable head behind it.
Rebuilt it on a fresh box from the published procrustes/train_pairs chunks plus a seed-0 regeneration of the v6 negatives. The negative set came back at 473,148 with prep_replace 75,786 and adj_transfer 54,894, matching the published counts to the item, so it is a reproduction rather than a fresh recipe.
| arm |
rebuild |
published |
| baseline_centered |
0.288 |
0.2880 |
| linear_n117787 |
0.620 |
0.622 |
| mlp_n117787 |
0.558 |
0.567 |
| mlp_shuffled |
0.001 |
0.001 |
Note the linear comparison is against the w=1.0 hard-negative row of the weight sweep, not mlp_align_full's 0.661, which is the no-hard-negative recipe. Comparing to the wrong row would have understated the rebuild by four points. Baseline and shuffled reproduce exactly.
It is published now, with the state caches, so the whole control script runs from published artifacts alone: q4/gemma_v6_head.pt, q4/sugarcrepe_img_states_gemma_L47.npz, q4/sugarcrepe_txt_states_gemma_L47.npz, q4/mlp_align_gemma_v6_rebuild.json, and q4/controls_*.json for the four cells.
What survives, stated against the null that varies per cell
Image identity is worth +6.5 to +13.0 clean-five points depending on the text tower, concentrated on replace_obj at +20.7, and worth nothing on either swap split. That is the inventory-versus-arrangement division of labour from round 11 arriving from a completely different direction, which is the first time two independent instruments in this program have agreed on it.
Both papers are corrected and the PDF is rebuilt. Ledger entry 9 records that three of our four SugarCrepe nulls were blind to the thing they were nulling, and that the sign of our conclusion was being set by null selection rather than by measurement. The acknowledgment credits the mean-image ablation as a control you proposed and then withdrew, whose withdrawal is what forced the derangement null that replaced it. That sequence is worth more than a control that had worked.
Code: scripts/sugarcrepe_controls.py, artifacts/nla/q4/controls_*.json, artifacts/nla/q4/mlp_align_gemma_v6_rebuild.json.
And your standing question is now the binding one
Every claim on swap_obj, mine and yours, the 17.8 and the floor and the margins, is read off 245 items with a 95% interval of about six points. Your arithmetic says resolving an eight-point effect needs roughly 584 per cell. I do not think SugarCrepe can be re-split into that, so it means generating a larger swap set.
Worth doing, or is swap_obj a split we should both stop quoting?