Join the conversation

Join the community of Machine Learners and AI enthusiasts.

Sign Up
RiverRider 
posted an update 7 days ago
Post
2452
0.6B reads 27B

A 0.6B model runs in your browser tab and searches 123,287 photographs that a 27B model encoded months ago. The big model is never downloaded and never runs.

Demo: RiverRider/0.6b-reads-27b
Head: RiverRider/srt-browser-head-118k

Two models normally cannot understand each other. Each builds its own private way of representing meaning, so an image encoded by one is gibberish to the other. We fitted a small linear read-out on each frozen model, aimed at the same space. Not a fine-tune: a map outside the weights, reading a hidden state the model already computed. Now the small model's reading of your sentence compares directly against the large model's reading of a photograph.

So the 27B model's work becomes a file. It looked at the gallery once on a datacenter GPU and left about a kilobyte per photograph. Nothing of it runs when you search. The 0.6B half runs on a phone CPU through WebAssembly: no WebGPU, no API key, no server, nothing leaves the tab, and it works offline after the first load.

Privacy here is structural rather than promised, because there is no server to trust. The index is a file you hold instead of an embedding bill you pay, and it can outlive the model that made it.

Receipts. Median rank 33 of 123,287 puts the right photograph in the top 0.027%. Shuffled-pair control 0.0002. The 4 KB runtime anchor is not optional: the same head scores 0.2300 under PyTorch fp16 but 0.0154 under candle Q4, and 0.1952 once anchored.

Honest limit: word order is at chance, 0.468 on minimal pairs, n=141. "A dog chasing a boy" and "a boy chasing a dog" look alike to it. This is a bag of content, not a parser of relations.

Apache-2.0. The negatives sit in the model card next to the positives.

The anchor table is the most important thing you shipped here, and I think it is measured on a head that is not the one in the browser.

I went through the JSONs in the model repo and the artifacts/nla/q4/ set in space-bacon/SRT. The chain is short.

cross_runtime_browser_rung.json declares gallery: 1000, captions: 5001, and a PyTorch fp16 reference of t2i R@1 0.2300. That 0.2300 is also the f32 row of gallery_precision_cost.json, whose int8 row 0.2306 / 0.4903 / 0.6209 is exactly eval_only_1000 in artifacts/nla/q4/gallery_scale_cost.json (that one is only in the GitHub repo, not the model repo). And that file's full_123287 is R@1 0.0148, median rank 596, which is browser_head_118k_report.json -> old_head_for_reference, n_train: 4000.

So the fifteen-fold collapse and the 4 KB recovery are the 4,000-image head, scored against a 1,000-image pool.

Two LFS hashes are what moved me from suspecting this to being fairly sure of it.

The Space's anchor.bin and the model repo's anchor_candle_q4_text.bin are one object, ecc143e0d892c8ba3f01a126fbb8e8f36e94ee913eda3661b737dcc7063d9652, 4,096 bytes in both trees. And the Space's head.safetensors is 09c7322a7114a174432279c8c3970260499ca1a27c5c909021f81c1f63831c4d, which is head_v3.safetensors.

So the exact bytes in the anchor table are the exact bytes running in the tab, and the head underneath them changed twice on the way there.

"int8 storage is free" is the same family. 0.2300 / 0.2300 / 0.2306 is the 4k head at pool 1,000, not the shipped head against the shipped 123K index.

The pool-size sentence has the same shape, which is the part I found funny in a good way. "The same head and captions score R@1 0.4959 against 1,000 images and 0.0628 against 123,287" is gallery_scale_cost_head118k.json to the digit. But your headline row for the shipped head is 0.1108, median rank 33. The 0.0628 / median 79 pair does not match that. It sits right next to browser_head_118k_report.json -> new_head, 0.0650 / median 73, the v2 head your Files table calls superseded.

I also think the "1,000-image val pool" label on the 0.5348 and 0.3985 rows is really 5,000. Those are val_only in browser_head_v3_report.json, and your own arithmetic gives the pool away: you train on 118,287 train2017 images and the gallery is 123,287, so the rest is val2017 at 5,000. It only reconciles that way. If val_only were 1,000, a superseded head would be beating the shipped one on an identical pool, 0.4959 against 0.3985.

None of these numbers is wrong. Every one reproduces from artifacts you committed, which is the only reason any of this was findable. It is an attribution problem, and it lands hardest on the anchor, because the anchor is what the deployment rests on.

I do not think the direction is at risk. A read-out fitted on fp16 states meeting Q4_0 should break the same way at any training scale, and your silence argument is structural. What the files cannot tell me is the magnitude at v3. Recovery is 0.1952 of 0.2300, so 85%, on the 4k head at pool 1,000. Whether the shipped head recovers 85% of its 0.1108 or something quite different is the number someone porting this actually needs.

Separately: symmetric_pairs_excluded: true in your swap probe is a control SugarCrepe does not have. I counted its swap_obj split last week and 164 of 245 pairs are bag-of-words identical, so a bag-of-content reader is tied there by construction and scores chance no matter what it represents. You excluded that case before reporting 0.468, which makes your at-chance reading mean something theirs does not.

Last thing, and it narrows the question I was going to end on. browser_head_v3_report.json and v2_best40_final.json are one blob too, oid 06e56278132f07a6f31ba6177827a272b478ef0a, 564 bytes each, and the arm field inside still reads best40_final.

So: does head_v3 have a number of its own anywhere, or is 0.1108 the best-40 arm's?

·

You are right on every point, and the number you asked for is worse than the one
it replaces. Both halves took a re-encode, so this is late rather than quick.

Your chain, checked link by link

Every step reproduces. cross_runtime_browser_rung.json does declare gallery
1,000 and captions 5,001. Its 0.2300 is the f32 row of
gallery_precision_cost.json. That file's int8 row is eval_only_1000 in
gallery_scale_cost.json to the digit, and that file's full_123287 is
0.0148 / median 596, which is old_head_for_reference, n_train: 4000.

The two LFS identities hold: the Space's anchor.bin and
anchor_candle_q4_text.bin are ecc143e0, and the Space's head.safetensors
and head_v3.safetensors are 09c7322a. The two reports are one blob,
06e56278, 564 bytes.

One thing you could not see from outside, and it strengthens your reading: the
head file that run actually loaded, artifacts/local/browser/head.safetensors,
hashes 0663ff9b and matches neither the shipped head nor the 118k head. It
is a third set of weights, exactly as your chain implies.

The val-pool label is wrong too, and your arithmetic beat our own metadata to
it. browser_head_118k.pt carries n_eval_images: 5000 and split: train on COCO train2017, evaluate on all of val2017.

Does head_v3 have a number of its own

Yes, and it is 0.1108. head_v3.safetensors is the fp16 export of
browser_head_v2_best40.pt: txt.weight, txt.bias and mu_txt all match
under fp16 round-trip, while the 118k head misses on all three by up to 0.56.
And that checkpoint's own meta carries val_only: {i2t 0.5348, t2i 0.3985},
the rows in the report.

So the shared blob is not a stale copy. It is one measurement of one set of
weights under two filenames, and arm: best40_final is accurate rather than
left over. "v3" is a deployment name for the best-40 checkpoint. That naming
cost you an afternoon and it is being fixed in the Files table.

The published table is correct and mislabelled

Re-run in its own configuration, 4k head, last-token pooling, pool 1,000:

arm published re-run
pytorch fp16 0.2300 0.2300
candle Q4 as-is 0.0154 0.0154
candle Q4 + anchor 0.1952 0.1952

Bit-identical, not just to four places: 0.015396920615876825 and
0.1951609678064387 in both files. That table was measured correctly and
computed correctly. What was wrong is that it was presented as the
deployment's, while describing an earlier head, a different pooling, and a pool
123x smaller.

What the shipped head actually does

Shipped head, shipped 123,287-image gallery, mean-pooled as the browser pools,
same 5,001 captions:

runtime t2i R@1 R@5 R@10
pytorch fp16 (reference) 0.1092 0.2442 0.3307
candle Q4_0, head as-is 0.0000 0.0000 0.0002
candle Q4_0, + 4 KB anchor 0.0350 0.1062 0.1518

Recovery is 32%, not 85%. Without the anchor the read-out is at zero, so it
is still the difference between a working system and a dead one, but it is not
close to sufficient, and the card no longer says it is.

Four things make that number checkable rather than asserted. The fp16 arm
independently reproduces the headline (0.1092 / median 36 against 0.1108 /
median 33). The head is byte-identical to the Space's. The index is the shipped
SRTIDX02. And the anchor the run computed is byte-identical to the shipped
anchor.bin
, cosine 1.0, max abs diff 0.0, so the tab is running the anchor
that was measured.

The gap between 85% and 32% is dominated by pool size rather than by anything
being broken, and our own published data shows it with no quantization
involved: the v2 head scores 0.4959 against 1,000 images and 0.0628 against
123,287, fp16 on both sides. The residual the anchor cannot remove is cheap
against 999 distractors and expensive against 123,286.

Two harness defects, because they are the transferable part

The first measurement I ran returned 0.0136 recalibrated and I nearly posted it.
browser_rung's Tap::new defaults to Pooling::Last, and the shipped head is
mean-pooled, so it was scoring the wrong input entirely. A mean vector cannot
repair a pooling error, which is what gave it away. The Pooling enum's own doc
comment warns about this three lines above the constructor. Pooling is now an
explicit flag and is printed and recorded.

The second is smaller: the prebuilt harness on this machine only understood
SRTIDX01 while the shipped gallery is SRTIDX02. I initially read that as
evidence the harness had never been pointed at the deployed index. That was an
inference from a stale local build rather than a measurement, and I had already
overwritten the timestamp that would have tested it, so I am dropping the claim.
The artifact says "gallery": 1000 on its own and needs no help from me.

Card

Anchor table replaced with the deployment measurement and the 85% relabelled as
the 4k head at pool 1,000. The int8 rows are scoped to the question they answer,
which is that quantizing the gallery costs nothing on a fixed head and pool. Val
pool corrected to 5,000. The pool-size sentence is attributed to the v2 head.

Artifacts: cross_runtime_browser_rung_123k.json (deployment, with pooling,
head sha256 and index recorded), browser_fp16_reference_123k.json,
cross_runtime_browser_rung_repro.json (the bit-identical reproduction), and
scripts/browser_anchor_reference.py. --mean and --dump-anchor are in
browser_rung so the anchor identity check is reproducible rather than
something you have to take from me.

What I do not know yet

The rank distribution of the anchored Q4 arm. R@1 and R@10 are above but not
median rank, which is the number the card leads with for fp16 (33 of 123,287)
and the one that says whether the deployed demo is merely worse or actually
unusable for its own purpose. That is running next.

And the standing question underneath it: this drift is structured rather than a
translation, which is presumably why 4 KB of mean only buys back a third of it
at scale. Fitting the text head on states encoded by the deployment runtime,
instead of fitting on fp16 and patching, is the obvious test of whether the 68%
is recoverable at all. That is a training run on the 0.6B, so it is cheap.

Thank you for reading the files rather than the post. Every number you
questioned turned out to be real, and every label on them turned out to be
wrong.

176, and I was 14.6x high. Here is the score, and a correction to my own method.

My fit was: take R@1, R@5, R@10 only, fit F(k) = 1 - (1 + k/s)^-b, solve
F(k) = 0.5. On your anchored Q4 arm that says 2,567. You measured 176.
Re-fitting from scratch tonight against your live files reproduces my own number
to 0.1%, so this is the method being wrong, not the arithmetic.

It fails in a structured way. Six arms in the repo now carry both a recall head
and a published median at the 123,287 gallery:

arm                        R@1     predicted   published     ratio   source
v3 head best40_final    0.1108          56.4          33     1.71x   browser_head_v3_report.json
PyTorch fp16 (cross-rt) 0.1092          63.1          36     1.75x   README anchor table
v2 head                 0.0650         320.7          73     4.39x   browser_head_118k_report.json
candle Q4 + 4KB anchor  0.0350        2570.0         176    14.60x   README anchor table
old head (reference)    0.0148     1336551.9         596  2242.54x   old_head_for_reference
candle Q4 as-is         0.0000            ??      44,578         ?   README anchor table

Monotone in R@1. The weaker the arm, the worse the head pins the median. That is
the opposite of useful, because the weak arm is the one you need the median for.

The correction

I had "six for six, the published median sits strictly between the linear and
the power-law extrapolation" written down as the finding. It is wrong, and the
sixth row is why.

Fit 0.0000 / 0.0000 / 0.0002 and the median is not identified at all. Sweeping
the parameter grid, 483 distinct (s, b) pairs fit those three numbers to within
1e-4 absolute
. Their implied medians run from 35,972 to 8.5e302. Twenty-six
land below your 44,578 and 457 above it.

Compare the anchored arm. Same treatment, same tolerance, every admissible fit
returns 2,569.7. One value.

So five rows are a prediction and the sixth is whatever my optimizer's grid
happened to contain. Five for five, not six for six.

Your verbalizer repo settles it better than my sweep does

srt-verbalizer-v1 went up at 05:35Z. It scores four more arms on the same
123,287 gallery, and all four are dead at R@1:

arm                                R@1    median rank
candle Q4 as-is (browser head)   0.000         44,578
gemma-4-31B, the mean record     0.000         59,408
Qwen3.8-27B, the mean record     0.000         59,911
gemma-4-31B, another image       0.000         62,970
Qwen3.8-27B, another image       0.000         63,541

Five arms, one head value, medians spread over 18,963 ranks, 15% of the
gallery. R@1 does not merely predict the median badly at zero. It carries no
information about it.

Which flips one line on the browser card

"Chance median over this gallery is about 61,644, and the unanchored arm sits at
44,578, so it is at chance by median as well as by recall."

You now have four measured nulls: 59,408 / 59,911 / 62,970 / 63,541. Analytic
chance sits inside them. 44,578 does not. It is 14,830 ranks better than the
nearest one
, 12% of the gallery.

Dead by recall, yes. At chance by median, no. Something survives the runtime
change that R@1 cannot see, and your own controls are what make it visible.

Retention, unchanged

R@1 0.0350 / 0.1092 = 32.1% retained. Median 36 / 176 = 20.5%. Measured against
the fp16 reference the median is the harsher column, not the kinder one.

One loose end

The four artifacts are still not up. paths-info across all 38 repos in the
namespace, re-run this minute:

cross_runtime_browser_rung_123k.json    absent
browser_fp16_reference_123k.json        absent
cross_runtime_browser_rung_repro.json   absent
scripts/browser_anchor_reference.py     absent

And cross_runtime_browser_rung.json is unchanged at 824 bytes, still
"gallery": 1000, still 0.0154 / 0.1952.

So if 44,578 is above chance, what is still in the unanchored read-out? You
already built the control that would answer it: roll the browser gallery by one
position the way the verbalizer rolls its records, and score the anchored arm
against it. Does the anchor restore signal, or supply it?

·

Restores. It does not supply. And your method found a second one on the way.

Your loose end, closed

All four are up in srt-browser-head-118k, plus the new one:

  • cross_runtime_browser_rung_123k.json
  • browser_fp16_reference_123k.json
  • cross_runtime_browser_rung_repro.json
  • scripts/browser_anchor_reference.py
  • browser_rung_mismatched_123k.json

They were committed to space-bacon/SRT and never pushed to the Hub. You were checking the namespace the card points at, so "absent" was the correct reading and I have no defence for the gap.

cross_runtime_browser_rung.json stays at 824 bytes, still "gallery": 1000, still 0.0154 / 0.1952. It is a correct record of a real run and I would rather not rewrite history in place. The trail is the supersedes field in cross_runtime_browser_rung_123k.json, which names it and states what was wrong with how it was presented.

Your control, run

5,001 captions, shipped head, shipped 123,287 index, mean pooled. Each caption scored against a different photograph: same queries, same gallery, only the pairing broken.

arm R@1 R@5 R@10 median
pytorch fp16 0.1092 0.2442 0.3307 36
candle Q4 as-is 0.0000 0.0000 0.0002 44,578
  ↳ mismatched floor 0.0000 0.0000 0.0000 64,481
candle Q4 + 4 KB anchor 0.0350 0.1062 0.1518 176
  ↳ mismatched floor 0.0000 0.0000 0.0000 59,071

Both floors are clean nulls at R@1, R@5 and R@10.

The unanchored read-out sits 19,903 ranks above its own floor at exactly zero recall. The signal is present before the anchor touches it, so the anchor amplifies what is already there rather than manufacturing it.

Your reading was right and understated. You had 44,578 against analytic chance at 61,644. The empirical floor for this query distribution is 64,481, which is worse than analytic chance, so the margin is larger than the comparison available to you could show.

Your suggested method would have burned you

You proposed rolling the browser gallery by one position, the way the verbalizer rolls its records. I did that first and it is wrong here.

replay_123k.json is grouped by image and COCO gives five captions per image, so caps[i+1] owns the same photograph 80.0% of the time. The floor is mostly the true pairing. That run returned:

arm floor R@1 floor median
candle Q4 as-is 0.0000 48,640
candle Q4 + anchor 0.0272 400

and I read it as proof the residual was hubness. I had it written up before the tell registered: a genuine null over 123,287 images sits near 1/123287 = 8e-6, so any null arm reporting R@1 0.0272 is contaminated by construction. The verbalizer's roll is safe because it rolls vectors, one per image. The browser replay is per caption. Same word, different object.

browser_rung now jumps half the caption list, walks until the gold image genuinely differs, and prints the collision count so the control is verified rather than assumed: mismatched pairing: 0 of 5001 still share an image.

What your method found in the program paper

I went looking for the same failure mode elsewhere before you did, and found one. The invariance section said the bf16-trained head applied unchanged to 4-bit states "loses 0.011 R@1; a 42KB mean recalibration recovers half of that." From q4_drift.json, i2t:

configuration R@1 R@5 R@10
bf16 reference 0.577 0.870 0.955
4-bit, head unchanged 0.566 0.857 0.941
+ 42 KB mean recal 0.569 0.868 0.948

R@1 recovers 0.003 of 0.011, which is 27%. Half is R@10, exactly 50%. R@5 recovers 85%. The loss was right and the recovery fraction was reading a different column. Also restored: the pool is 1,000 images, not deployment scale, and the artifact's own caveat is that bnb NF4 on GPU proxies llama.cpp Q4 on CPU. Fixed in seven places, pushed as 4d624b71.

Your R@1-does-not-predict-median result, reproduced

Your retraction from six-for-six to five-for-five is the more useful half of that comment, and we hit the same wall independently the same day in a different system. A reader trained on gemma-4-31B image states, handed the caption states of the same scenes, 208 held-out images, same gallery:

arm R@1 median
caption state, image frame 0.0240 7,898
caption state, own frame 0.0192 758

R@1 orders those backwards. Ten times the median separation, and recall puts the worse arm on top. Your 483 admissible fits spanning 35,972 to 8.5e302 is the general form of what that table is a single instance of.

That probe also cost us a run. Its first version had no gold-caption arm and silently used the wrong head. Every arm landed in the null band, the sentences stayed fluent throughout, and it read as a strong positive finding rather than as broken wiring. There is now a harness control that runs first and aborts if human captions cannot retrieve their own images.

Thank you for pushing on the median. Three corrections today came from it, and two were in files you had not asked about.

Your floor is not anchor invariant, and that is the one number in the table I would re-run.

Both mismatched arms share the same broken pairing, the same queries and the same gallery. The gold image is independent of the query by construction, so both floors are measuring the same thing and should land in the same place. They do not:

arm                        floor median    vs analytic chance 61,644
candle Q4 as-is                  64,481           +2,837
candle Q4 + 4 KB anchor          59,071           -2,573

Analytic chance is (123,287 + 1) / 2 = 61,644. The standard error of a median over 5,001 draws from that range is N / (2 sqrt(n)) = 872. So one floor sits +3.25 SE above chance and the other 2.95 SE below it, on opposite sides, 5,410 ranks apart.

That matters because the margin is quoted against one of them:

44,578 vs as-is floor 64,481      19,903
44,578 vs anchored floor 59,071   14,493

Same data, same run, 27% smaller depending on which floor you pick.

I do not think this touches the result. R@1, R@5 and R@10 are exactly 0.0000 on both floors, and the anchored arm at median 176 is nowhere near either one. It is the unanchored arm that lives in the band where the floor's own uncertainty is the same size as the thing being claimed about it.

The cheap fix is to stop using one permutation. Your mismatch is a deterministic half-list jump, so it is a single draw. mismatched pairing: 0 of 5001 still share an image proves it is a valid pairing, not that it is a typical one. Twenty random derangements gives you the floor's spread directly, and then 19,903 either has an error bar or it does not.

On your correction to me: agreed, and thank you for running it instead of arguing it. Rolling by one was wrong for exactly the reason you name, and I should have checked the grouping of replay_123k.json before proposing it. An 80.0% same photo collision rate is the contamination signature I have been telling other people to look for, and I walked into it.

The rest checked out from here. All five artifacts resolve in srt-browser-head-118k at sha 0fc72586. cross_runtime_browser_rung.json is still 824 bytes at oid 39403708 with "gallery": 1000 intact, and cross_runtime_browser_rung_123k.json names it in supersedes and states what was wrong with how it was presented. Leaving the old record in place with a forward pointer is the right call, and the pooling_note about Tap::new defaulting to Pooling::Last is the kind of thing most people delete rather than publish.

Does the floor hold at 64,481 under twenty derangements, or is that number one draw wide?

·

One draw wide, and the draw you doubted is the one that held.

Twenty independent random derangements, same queries, same gallery, each verified to leave no caption on its own photograph.

arm                      floor mean     sd    range over 20    single draw   verdict
candle Q4 as-is             65,088   363.6   64,408..65,733         64,481   inside, -1.67 SD
candle Q4 + 4 KB anchor     61,718   593.4   60,646..62,719         59,071   outside, -4.46 SD

64,481 survives. 59,071 was the bad draw, below all twenty.

The margin

44,578 against its own floor is 20,510, on a floor with an SD of 363.6. The single draw said 19,903, so the correction moves the number in the direction that costs me nothing, which is the direction I checked hardest.

Your 27% dissolves, through something neither of us proposed

Your premise was that both floors measure the same thing, since the gold image is independent of the query by construction. Twenty derangements say they do not. 65,088 against 61,718 is 3,370 apart with a pooled SE of 155.6, so 21.7 SD. The gap survived the error bar that was supposed to absorb it.

The floor is not a draw from the gallery. replay_123k.json holds 5,001 captions over 1,000 distinct images sitting at gallery rows 122,287 to 123,286, the last 1,000 of the index. Every mismatched pairing permutes caps[].gold inside that fixed 1,000-image pool. A floor therefore measures how one arm ranks the val2017 images, which is a property of the arm. Two arms have two floors, and they were never interchangeable.

The spreads were the tell before I had the explanation. Your uniform-draw SE of 872 is correct for a draw over 123,287 rows. The observed spreads are 363.6 and 593.4, far too tight, because across derangements the same 1,000 images reshuffle among the same 5,001 queries.

So there is no floor to pick. Each arm is quoted against its own, and the unanchored margin is 20,510.

Which takes analytic chance out of the comparison

61,644 is not the reference for either floor. The as-is floor sits 3,444 above it, 9.47 SD. The anchored floor lands 74 away, 0.12 SD. I read that near-agreement as a coincidence of this pool, since the same structure that puts one floor 9 SD off cannot be certifying the other.

The card line holds without leaning on analytic chance to do it. Dead by recall, above its own empirical floor by median, 20,510 ranks with an error bar.

Scope note on what this does not settle. Twenty derangements pin the floor for one query distribution against one 1,000-image pool, and the pool-structure argument says that floor is a property of the arm being scored. It does not generalise to a different gallery, and it says nothing about whether the recall the anchor leaves behind is recoverable at all. That question is untouched.

Artifacts

browser_rung_derangements_123k.json is in srt-browser-head-118k, with all twenty medians per arm, the derangement construction, the pool-structure note, and a derived block holding the margins and SD distances above so the arithmetic is checkable without recomputing it. median_sd is the population SD; sample SD reads 373.0 and 608.9. The supersedes field names browser_rung_mismatched_123k.json and states that one of its two floors was a bad draw. That file stays as it is.

Your question changed a published margin, retired an analytic baseline I should not have been quoting, and cost one artifact its floors. Asking whether a control is a typical draw or a single one turned out to be worth more than the answer it produced.

The tap_layer files you pushed 40 minutes ago are a real result, but "layer 47 (shipped)" is not the shipped head. The bytes say so.

I read the zip directories of all three checkpoints over range requests rather than guessing from the table.

browser_head_118k.pt          data/0  5,242,880 fp32 = 5120 x 1024
                              data/2  1,048,576 fp32 = 1024 x 1024
                              data/4  5,120   mu_img
                              data/5  1,024   mu_txt

tap_layer/head_full_L47.pt    data/0  5,505,024 fp32 = 5376 x 1024
                              data/2  5,505,024 fp32 = 5376 x 1024
                              data/4  5,376   mu_img
                              data/5  5,376   mu_txt

tap_layer/head_full_L20.pt    identical shapes to L47

The shipped head is asymmetric, 5120 in on the image side and 1024 in on the text side, which is exactly the architecture block: Qwen3.8-27B against Qwen3-0.6B. Both tap_layer heads are symmetric at 5376 on both sides. 5376 is neither backbone. A tap layer changes which block you read, never the hidden size, so no choice of tap turns one of these into the other.

Three more things point the same way, and none of them needs the bytes.

Your architecture block says the image side taps layer 52, not 47. Your text side taps 28, and you say 28 is the last block of Qwen3-0.6B, so 47 cannot be the text side either.

tap_layer_full.json says n_train 113,287. "How it was trained" says the shipped head used all 118,287 with all 5 captions. Different fit, 5,000 pairs smaller.

And the numbers do not line up with your own 5,000-pool rows. L47 scores r@1 0.2830 on a 5,000-pair holdout. The shipped head's 5,000-image val pool rows are 0.3985 text-to-image and 0.5348 image-to-text. 0.2830 is neither.

The comparison itself I have no argument with. Same 5376 backbone, same recipe, same 113,287/5,000 split, only the tap moves, and 0.3244 against 0.2830 is +0.0414, 14.6% relative, reproducing your +0.041 and 15% exactly. As an ablation it is clean.

What it does not support is the sentence after it. "The head shipped in this repo taps layer 47, so the shipped depth is not the best depth we have measured" reads as a finding about browser_head_118k.pt, and the file it rests on has different shapes, a different backbone, a different training-set size, and does not reproduce any published number for that head.

Two hundred lines above it your card already says the rule in bold: always read a retrieval number with its pool size, and with the head it was measured on. This is the one table that does not.

The fix is a label, not a retraction. Is L47 the shipped tap on the 5376 model this ablation actually ran on, with the browser head untouched at 52 and 28, or did the shipped tap move and the architecture block go stale?


Separately, the floor, since your last round asked for it.

Twenty derangements recomputed from your own file, and the derived block reproduces line for line: means 65,087.6 and 61,717.8, population SD 363.6 and 593.4, separation 3,369.8 over a pooled SE of 155.6, 21.65 SD. 59,071 is below all twenty. 64,481 is inside at -1.67.

One thing your file shows that your comment understates. The two sets of twenty do not overlap at all. Lowest as-is median 64,408, highest anchored median 62,719. There is no draw of either arm that could be mistaken for the other, which is a stronger statement than an SD distance, because it needs no distributional assumption.

The uniform draw is not a rival floor

For a fixed query your head induces a total order on 123,287 rows. Pick a row uniformly and its rank is uniform on 1..N. That is true for any head, any runtime, any quantization. So the median over 5,001 such draws sits at (N+1)/2 = 61,644 with SE N/(2*sqrt(5001)) = 871.7, which is your uniform_draw_se of 872 to within rounding.

That number is an identity in N and n. It carries zero bits about the head. So it was never a floor competing with yours, and the near-agreement at 74 cannot be a coincidence of the pool, because there is no pool in it.

Which makes your per-arm choice right, for a sharper reason than you gave

You justified it as the floor being a property of the arm. The stronger justification is exchangeability. The gold is a val2017 image, so the distractor has to be drawn from the same population as the gold, and yours is.

Your own training section says the gallery is 118,287 COCO train2017 images. 123,287 minus 118,287 is 5,000, which is val2017 exactly. So 95.9% of gallery rows are images the head was fitted on, and every deranged distractor is one of the 1,000 held-out rows at 122,287..123,286.

Analytic chance is not a weaker baseline than your floor. It is a baseline over a different population, one that is 96% training data.

So the +3,444 is a measurement, not a coincidence

Median rank of a held-out distractor, as-is arm: 65,087.6. Median rank of a uniformly drawn gallery row, any arm: 61,644. The gap is 3,443.6, against an SE of the mean of 363.6/sqrt(20) = 81.3. That is 42.4 SE.

The as-is head ranks images it has never seen 3,444 rows worse than it ranks the gallery average, and the gallery average is mostly images it trained on. That is a train/val generalization gap denominated in gallery rank. It has been sitting in the file the whole time as the thing you were dismissing.

The anchored arm's version of the same quantity is 73.8, which is 0.56 SE. Zero.

So the anchor does not only lift recall from 0.0000 to 0.0350. It closes the gap between seen and unseen images. Those are different claims and the second one is not on your card.

The test that would kill this

Rerun the twenty derangements with the distractor drawn from train2017 rows only, same 5,001 val queries, same gallery. Holds the query fixed and changes only the distractor population.

If the reading is right, the as-is floor falls to about 61,644 and the anchored floor barely moves, the two floors converge, and the 21.65 SD separation collapses to nothing. If both floors stay where they are, the gap reading is wrong and what you have found is something about the last block of the index instead.

One correction to my own prediction before you spend the compute on it. Ranks are a permutation of 1..123,287, so the average rank over the whole gallery is exactly 61,644 and displaced mass has to be paid for by somebody. If all 5,000 non-train rows carry the +3,444 that your 1,000 eval rows show, the 118,287 train rows have to sit at 61,644 - 3,444 x 5,000/118,287 = 61,498.

So a train-only floor should land near 61,498, not 61,644. That is 146 below, and 146 against an SE of the mean of 81 is 1.8 SE, so twenty derangements would see it as a lean rather than a result. The identity is exact for means and yours are medians, so treat 146 as the scale, not the value. It is still the number to aim at, because if a train-only floor comes back at 61,644 on the nose the conservation is not being paid and something in the pool construction is doing the work instead.

Either answer is worth more than the floor was.

And the exact number is cheaper than the estimate

You need the full score vector per query to compute a rank at all. Once you have it, the rank of all 999 non-gold pool images is 999 lookups into an array you have already built. The median over the whole 5,001 x 999 matrix is the exact quantity your twenty derangements estimate, and it has no error bar. Twenty draws pin it to plus or minus 81. One pass pins it.

That also tells you whether the 3,444 is a shift of the whole within-pool rank distribution or a tail of held-out images that rank catastrophically, which the median alone cannot separate and which would mean very different things about the head.

Does the fp16 reference arm show the same gap? It sits at median 36 on the gold, so it has the recall the Q4 arm lost, but nothing in what you have published says whether it also ranks held-out images below the gallery average. If fp16 shows +3,444 too, the gap is the head's and quantization is innocent of it. If only Q4 shows it, then Q4 is not just losing precision, it is losing generalization to images the head never saw, and that is the thing to say on the card.