ms-marco-MiniLM-L6-v2 fine-tuned as an Indian tax-law reranker
A cross-encoder reranker, fully fine-tuned from
cross-encoder/ms-marco-MiniLM-L6-v2
for
second-stage reranking over Indian tax-law judgments. It reads a (query, passage)
pair together and scores how well the passage answers the question.
Used to rerank the top 50 results from
bge-small-en-v1.5-indian-tax-law-lora,
it improves
nDCG@10 by +12.4% and
Recall@10 by +11.4% over that retriever alone.
The finding you should know before using this
A zero-shot MS-MARCO reranker makes this task worse, not better. On the same
shortlists, the un-tuned base model scores nDCG@10 0.4837 — below the 0.5015 you get
by not reranking at all. The gain here comes from domain fine-tuning, not from adding a
reranker.
A paired bootstrap over the same 256 queries and identical shortlists (10,000 resamples)
puts the fine-tuned-versus-zero-shot gap at +0.0798 nDCG@10, 95% CI [+0.0516, +0.1092],
p < 0.0001, better on 83 queries and worse on 33. The interval excludes zero.
So: do not substitute the base model here and expect similar behaviour.
Usage
The reranker takes bare query text — unlike the bi-encoder, it needs no prefix.
1from sentence_transformers import CrossEncoder
2
3model = CrossEncoder("FlorianTraugott/ms-marco-MiniLM-L6-v2-indian-tax-law", max_length=512)
4
5query = "Can a reassessment notice be issued after four years?"
6shortlist = ["<passage 1>", "<passage 2>", "..."] # top-50 from the bi-encoder
7
8scores = model.predict([(query, p) for p in shortlist])
9reranked = [p for _, p in sorted(zip(scores, shortlist), key=lambda x: -x[0])]
Scores are raw logits (the model uses an identity activation), so they are unbounded
and only meaningful for ordering — not as calibrated probabilities.
Results
256 held-out queries against a 3,868-passage corpus, reranking the top N=50.
| metric | off-the-shelf bi-encoder | fine-tuned bi-encoder | + this reranker | + zero-shot reranker |
|---|
| Recall@10 | 0.4844 | 0.6875 | 0.7656 | 0.6875 |
| MRR@10 | 0.2996 | 0.4431 | 0.4989 | 0.4193 |
| nDCG@10 | 0.3444 | 0.5015 | 0.5635 | 0.4837 |
| comparison | Recall@10 | MRR@10 | nDCG@10 |
|---|
| this reranker vs the retriever alone | +11.4% | +12.6% | +12.4% |
| full two-stage stack vs the off-the-shelf baseline | +58.1% | +66.5% | +63.6% |
Choosing the rerank depth
A reranker permutes a shortlist; it cannot add to it. So post-rerank Recall@10 can never
exceed the retriever's Recall@N, and at N=10 it is provably unchanged.
| N | Recall@10 | ceiling (retriever Recall@N) | MRR@10 | nDCG@10 |
|---|
| 10 | 0.6875 | 0.6875 | 0.4875 | 0.5365 |
| 25 | 0.7383 | 0.7969 | 0.4926 | 0.5521 |
| 50 | 0.7656 | 0.8633 | 0.4989 | 0.5635 |
| 100 | 0.7578 | 0.9180 | 0.4969 | 0.5599 |
N=50 is the operating point: it maximises every metric measured, and N=100 is both
slower and worse. At N=10, Recall is frozen by construction while MRR@10 still rises
+10.0% and nDCG@10 +7.0% on the identical ten documents — the reranker doing purely its
reordering job.
Training
| |
|---|
| Base model | cross-encoder/ms-marco-MiniLM-L6-v2 (Apache-2.0, 22.7M params, 6 layers) |
| Method | full fine-tune — all 105 tensors including the classification head |
| Loss | pointwise BinaryCrossEntropyLoss, unweighted |
| Data | 6,792 labeled pairs — 1,132 positives + 5,660 mined hard negatives (5:1) |
| Schedule | 2 epochs, 382 steps, batch 32, lr 2e-5, 38 warmup |
| Sequence | 512 tokens, train and eval — 0 pairs truncated (longest 455) |
| Precision | fp32 on Apple Silicon MPS — 12.5 minutes |
LoRA was tried and rejected: under task_type=SEQ_CLS, PEFT attaches without error but
leaves modules_to_save=None, so the classification head stays frozen while the body
adapts underneath it. Full fine-tuning was verified live instead.
Training data
Same corpus and split as the companion retriever: 3,868 passages from 400 Indian tax
judgments filtered from
opennyaiorg/InJudgements_dataset
(Apache-2.0), with synthetic queries generated by OpenAI
gpt-4.1-mini under anti-echo
constraints, and 5 hard negatives per query mined with the un-tuned retriever.
The reranker trains on the same hard negatives the retriever saw, so its difficulty is
calibrated to the shortlists it actually sits behind. Eval positives are excluded from
training in both label slots; all leak checks measure 0.
Intended use and limitations
Intended for reranking a retrieval shortlist over Indian tax-law judgments, at N≈50,
behind the companion retriever
bge-small-en-v1.5-indian-tax-law-lora.
Neither model is the full story alone: the retriever sets the recall ceiling this model
reorders within.
Not intended for other legal domains or jurisdictions, general-purpose reranking, or
scoring passages outside a retrieved shortlist. It is a ranking component, not a
legal-advice system.
- Reranking is net-positive, not uniformly safe. Of 221 queries with a positive in the
top 50: 91 improved, 69 unchanged, 61 got worse. One positive fell from rank 1 to 22.
Net top-10 swing was +20.
- This checkpoint was selected on the evaluation set (by nDCG@10 at N=50), so its
figures carry mild selection optimism. The effect is bounded — only three candidates were
compared and the two trained ones differ by 0.0008 nDCG@10.
- The in-training validation metric was saturated (0.8983 zero-shot on a 1-in-6
ranking proxy), which is why checkpoint selection was redone on the real task.
- The eval queries are synthetic, each with a single labelled correct passage, so the
attainable ceiling is below 1.0 and unknown.
- Latency was never benchmarked. N=50 means 50 cross-encoder forward passes per query,
serially, after retrieval. Batched throughput during evaluation was 139–179 pairs/second
on an Apple Silicon laptop, which is not a served single-query path.
- About 44% of the available headroom is converted: at N=50 the ceiling is Recall@10
0.8633 and this model reaches 0.7656.
Licence
Apache-2.0, inherited from cross-encoder/ms-marco-MiniLM-L6-v2 (Apache-2.0); this is
a full fine-tune of those weights, so the derivative carries the same terms and its notice
requirements. The training corpus derives from opennyaiorg/InJudgements_dataset
(Apache-2.0). Synthetic training queries were generated using OpenAI gpt-4.1-mini; if you
redistribute or build on this work, review OpenAI's terms for your own use case.
Author: Ayush Padhy (GitHub/HF:
@FlorianTraugott)
Citation
1@software{legal_retrieval_finetune_2026,
2 title = {Fine-tuning a two-stage retrieval stack for Indian tax law},
3 year = {2026},
4 url = {https://github.com/FlorianTraugott/legal-retrieval-finetune}
5}