Umm… this has gotten a lot better, but I still have this vague feeling that something may or may not be missing here
:
I spent some more time trying to reproduce the v2.3 result from the public artifacts.
The short version is: I do not think model quality itself is a reason to hold up closing TIS 2.0. The released v2.3 checkpoint looks healthy in an independent rerun. What I still cannot quite close is the exact evaluator/provenance bridge between that checkpoint and the published headline result.
With the dataset, tokenizer/base model, checkpoint, prompt, separator logic, library versions, and scoring path pinned, my clean public-contract rerun lands around MRR 0.55. The most source-faithful run I have now — BF16 head plus position-corrected batched Mistral inference — gave approximately:
| Evaluation | MRR | Hit@1 | Hit@3 | Hit@5 |
|---|---|---|---|---|
| public-contract rerun | 0.5546 | 169/483 | 323/483 | 416/483 |
| published v2.3 result | 0.5102 | 146/483 | 300/483 | 390/483 |
So this does not look to me like “the published checkpoint falls apart when somebody else runs it.” If anything, I am getting a stronger ranking result from the public checkpoint.
The remaining question is why the published evaluator produces a materially different result.
What seems to be the missing link
The published result/log describes an optimized, batch-tokenized evaluation path and reports:
3935 separator hits + 37 midpoint fallbacks
The release material also refers to an optimized evaluator / batch-scoring implementation. But the reproducible paths I can currently connect do not seem to be the same thing:
README rerun path ≠ committed serial v2.3 evaluator ≠ published optimized-result path
I can find the serial v2.3 evaluator, and I can follow the README route, but I have not been able to connect either one exactly to the optimized evaluator that generated the headline 0.5102.
If that exact optimized evaluator exists somewhere I simply missed, then that is probably the highest-value artifact to point at. Otherwise, committing it — or regenerating the headline table with the evaluator that is actually committed — would close a large part of this by itself.
I tried to rule out several simpler explanations before landing there.
The 37 separator fallbacks are not enough by themselves
Using the pinned tokenizer/base and the exact 500-row MS MARCO slice, I get 3972/3972 separator hits, including through the normal batched-tokenizer route.
I then went further and calculated, for every passage, both:
- the score using the correct
Passage:boundary; - the score obtained if that passage is deliberately forced through the midpoint fallback.
That makes it possible to solve the counterfactual problem exactly: choose exactly 37 passages to fall back and see whether the published metric vector can be reproduced.
It cannot.
Even choosing the 37 fallbacks that hurt MRR the most only gets down to about:
MRR 0.5163
rather than 0.5102.
More importantly, I cannot simultaneously reproduce the published Hit counts 146 / 300 / 390 with any set of exactly 37 midpoint fallbacks. Even allowing very favorable tie ordering does not make the full metric vector line up.
So I think the safe conclusion is:
the 37 midpoint fallbacks may be part of the difference, but they cannot be the whole difference.
There was a real batched-position issue, but fixing it does not explain 0.5102 either
One useful thing did turn up while checking the batching path.
For a left-padded Mistral batch, directly calling the model with the attention mask but without padding-corrected position_ids gave hidden states that differed from serial inference.
Using position IDs derived from the attention mask:
position_ids = attention_mask.long().cumsum(-1) - 1
and masking the padding positions fixes that. This is also consistent with the way Transformers constructs position IDs for generation.
After that correction, my smoke cases became exactly serial-equivalent:
- hidden-state max difference:
0 - BF16 passage-score difference:
0
That is the evaluation route behind the 0.5546 result above.
So the padding/position issue is real and worth making explicit in an optimized evaluator, but it still does not explain the published 0.5102.
BF16 vs FP16 is also too small to explain it
I checked that direction as well.
The pinned prequantized Unsloth Mistral config specifies NF4 4-bit quantization with BF16 compute, and the committed v2.3 importance head is also naturally evaluated in BF16.
Switching between the relevant BF16/FP16 head variants moves the ranking somewhat, but only by a few thousandths of MRR — nowhere near the roughly 0.044 gap to the published result.
So at this point I do not see tokenizer batching, dtype, position IDs, or the 37 fallbacks individually or together as a sufficient explanation. Some additional difference in the optimized scoring/evaluation path still seems necessary.
There is a second, more mechanical cleanup I think would be useful before calling the evaluation side completely finished: regenerate the metric/comparison table from one evaluator contract.
Metric and comparison-table cleanup
Recall@k looks more like Hit@k / Success@k
The MS MARCO v1.1 slice actually contains queries with multiple selected passages.
In the 483 evaluable queries here, 46 have more than one positive passage.
The current metric called Recall@k effectively asks whether at least one designated relevant passage occurs in the top-k. That is a useful metric, but it is normally easier to interpret as Hit@k or Success@k.
I would probably report both:
- Hit@k: did any relevant passage appear in the top-k?
- Recall@k: what fraction of all relevant passages appeared in the top-k?
That avoids a small terminology trap for future readers.
NDCG deserves one clean recomputation
The current NDCG path also appears not to implement standard binary NDCG: the relevance/DCG side and ideal denominator do not use the actual set of positive passages in the usual way.
Using the position-corrected public ranking and standard all-positive binary NDCG, I get roughly:
- NDCG@5 ≈ 0.606
- NDCG@10 ≈ 0.659
So I would not interpret the very low published NDCG values as evidence that v2.3 ranking quality collapses. They look much more like a metric-definition issue.
I would regenerate the whole v2.2 / v2.3 / BM25 row together
There also seem to be multiple v2.2 evaluation families in the published artifacts.
The +25.6% figure makes sense for approximately:
0.4063 → 0.5102
but not for:
0.471 → 0.5102
which is only around an 8% increase.
That is not necessarily a problem if 0.4063 is the intentionally chosen common-evaluator baseline — in fact using a common evaluator is preferable — but then I would make that lineage explicit in the table.
BM25 MRR is comparatively easy to close: with the same MS MARCO slice and the apparent rank_bm25 contract I can independently reproduce approximately:
BM25 MRR = 0.4315
The secondary BM25 columns are less clean, though, so again I think rebuilding the entire comparison table from one evaluator is safer than correcting cells individually.
A small generated table carrying an evaluator/run ID would make this almost self-auditing:
| system | evaluator | MRR | Hit@1 | Hit@3 | Hit@5 | NDCG@5 | NDCG@10 |
|---|---|---|---|---|---|---|---|
| BM25 | same frozen evaluator | … | … | … | … | … | … |
| TIS v2.2 | same frozen evaluator | … | … | … | … | … | … |
| TIS v2.3 | same frozen evaluator | … | … | … | … | … | … |
Then all percentage improvements can be generated from that same table rather than maintained separately.
One other wording point: I would probably describe the 0.5137 validation → 0.5102 test relationship as encouraging, rather than lean too hard on the very small “generalization gap.”
As I understand the training path, the best checkpoint was selected from repeated checks on the same fixed 50-query validation subset. That is useful for checkpoint selection, but it means 0.5137 is itself a selected value rather than an untouched estimate.
Likewise, the 500-query set is already useful as a stable regression benchmark across the TIS versions, which is valuable, but that makes it somewhat different from a completely fresh holdout opened once at the end.
This does not require retraining anything. The low-cost options seem to be either:
- just soften the generalization wording; or
- evaluate once on a previously unused fixed slice if a stronger generalization claim is useful.
What I would do before closing TIS 2.0
I do not think another major training cycle is necessary.
The shortest path I can see is something like:
-
Make one evaluator authoritative.
Either commit the exact optimized evaluator that generated0.5102, or rerun the release numbers with the committed evaluator. -
Have the README call that exact evaluator.
-
Regenerate BM25 / v2.2 / v2.3 from the same metric implementation.
-
Rename or split Hit@k vs literal Recall@k, and use standard NDCG.
-
When separator fallback happens, log the qid/passage index and detected boundary, not only the total fallback count.
-
Emit a tiny run manifest.
Something like source commit, checkpoint revision/hash, base-model revision, tokenizer revision, dataset revision/slice, Python/Transformers/tokenizers/bitsandbytes versions, and the effective quantization configuration.
That would turn most of the remaining uncertainty into things a future reader can check in a few minutes.
So my current impression is:
I would not block closing TIS 2.0 on the model. The v2.3 checkpoint itself looks quite good in an independently pinned evaluation. The remaining loose end is mostly the exact evaluation/provenance path between that checkpoint and the published headline table.
If the missing optimized evaluator explains that path, I think most of the remaining puzzle snaps into place — and then moving on to TIS 3.0 / TIS+PIN seems much cleaner.




