Visitar URL original
GitHub - raihan-js/tiny-bilingual-retriever: Distill bge-m3 into a 30M JA-EN encoder for cross-lingual search on CPU · GitHub
Skip to content

About

Distill bge-m3 into a 30M JA-EN encoder for cross-lingual search on CPU

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

14 Commits

Folders and files

Repository files navigation

Tiny Bilingual Retriever

tiny-bilingual-retriever results

Write-up: I Distilled a 568M Multilingual Model Into a 37M Japanese-English Encoder — Here's What Survived

Distill BAAI/bge-m3 (568M) into sbintuitions/modernbert-ja-30m (the "30M" model; 36.7M parameters including embeddings) for English-Japanese cross-lingual retrieval on CPU.

The problem

Tokyo companies with global customers need English queries to find Japanese documents. A 568M model is too big for CPU serving. Can a ~37M student match the teacher's cross-lingual quality?

The approach

  1. Distill bge-m3 into modernbert-ja-30m using EN-JA sentence pairs
  2. Compress with Matryoshka truncation, int8/binary quantisation, ONNX Runtime
  3. Fuse with MeCab-segmented BM25 via RRF
  4. Measure quality kept vs index MB vs CPU ms

Results

Baselines (500 queries, 5,000 passages)

Model JA-JA nDCG@10 EN-JA nDCG@10 Search p50 Index MB
cl-nagoya/ruri-v3-30m 0.9604 0.5418 1.0ms 4.9
BAAI/bge-m3 (teacher) 0.9421 0.6742 6.1ms 19.5
intfloat/multilingual-e5-small 0.9113 0.4795 1.9ms 7.3
sbintuitions/modernbert-ja-30m 0.3223 0.0373 1.1ms 4.9

All values are in results/baselines.json (a tracked copy of data/results/baselines.json, which is git-ignored; re-run 2026-10-05; the nDCG values reproduced the numbers first reported here exactly). Search p50 is the brute-force cosine search over the 5,000 pre-encoded passages for one query (JA-JA run, CPU, single run), not query-encoding time, so it mostly tracks embedding size (1,024-d bge-m3 vs 256-d for the 30M models). The latency column of the first run (3.6 to 9.7 ms) differed by up to ~5x from the re-run on the same machine, so read these as order-of-magnitude.

Which checkpoint is on Hugging Face

HF repo What it is EN-JA nDCG@10 vs teacher
tiny-rerank-ja-en-30m-distilled the distilled student (data/models/student-distilled) 0.4809 71%
tiny-rerank-ja-en-30m that student after the Matryoshka fine-tune, plus ONNX for dims 256/128/64 (student-matryoshka, verified by SHA-256 of the weights) 0.4158 62%

The 71% headline is the first row. Until 2026-10-05 only the second row was on the Hub, so its card now says so.

Train/eval overlap check. The 50k distillation pairs and the EN-JA eval are both seed-0 samples of the OPUS-100 en-ja train split; 27 of the 500 eval queries (5.4%, chance level) were also training pairs. Scoring only the 473 unseen queries (scripts/leakage_check.py, results/leakage_check.json): distilled 0.4838 (vs 0.4809 overall), Matryoshka 0.4139 (vs 0.4158), untrained base 0.0371 (vs 0.0373). The overlap does not drive the result.

After distillation (50k pairs, 3 epochs, cosine similarity loss)

Model JA-JA nDCG@10 EN-JA nDCG@10 vs teacher Index MB
BAAI/bge-m3 (teacher) 0.9421 0.6742 100% 19.5
student-distilled 0.2179 0.4809 71% 4.9

After Matryoshka fine-tune (2 epochs)

dim EN-JA nDCG@10 vs dim=256 Index MB Search p50 (single run, noisy)
256 0.4158 100% 4.9 4.5ms
128 0.3989 96% 2.4 4.7ms
64 0.3613 87% 1.2 0.5ms

Key finding: The distilled student captures 71% of the teacher's EN-JA quality with 15× fewer parameters (36.7M vs 568M) and a 4× smaller index (4.9 vs 19.5 MB). Matryoshka truncation lets you choose the quality/size trade-off at serving time: dim=64 keeps 87% of the dim-256 Matryoshka model's score at 1/4 its index. Two caveats on that number: the Matryoshka fine-tune itself cost quality at full width (EN-JA nDCG 0.4809 distilled → 0.4158 at dim 256), and measured against the teacher, dim 64 is 54% (0.3613 vs 0.6742).

Hybrid fusion (RRF, k=60)

eval Dense BM25 (MeCab) RRF fusion
JA-JA 0.1545 0.8123 0.4334
EN-JA 0.4158 0.0166 0.2049

Fusion finding: RRF hurts when one method dominates. The student is strong at EN-JA but weak at JA-JA; BM25 is the reverse. Fusing a strong method with a weak one drags the strong method down. Hybrid only helps when both methods are reasonably good on the same query type.

After JA-JA training (JQaRA, 3 epochs)

Model JA-JA nDCG@10 EN-JA nDCG@10
student-matryoshka 0.1545 0.4158
student-ja-en 0.4348 0.2632

Trade-off: JA-JA training improves monolingual retrieval (0.15→0.43) but degrades cross-lingual (0.42→0.26). The student becomes more balanced but loses its EN-JA specialty.

Quantisation (int8 and binary)

Method EN-JA nDCG@10 vs float32 Index MB
float32 0.4158 100% 4.9
int8 0.4143 99.6% 1.2
binary 0.0322 7.7% 0.2

int8 is nearly free — 99.6% quality at 1/4 the index size. Binary is catastrophic — the sign of each element loses too much information.

Usage

# Baselines
PYTHONPATH=src python scripts/run_baselines.py

# Train student
PYTHONPATH=src python scripts/train_student.py

# Compress
PYTHONPATH=src python scripts/compress.py

# Hybrid fusion
PYTHONPATH=src python scripts/run_fusion.py

Limitations

  • EN-JA eval is synthetic (opus-100 pairs), not a standard benchmark
  • Absolute nDCG inflated by subsampling; only relative comparisons meaningful
  • Student starts with no retrieval training; distillation quality depends on teacher
  • The public cl-nagoya/ruri-v3-30m (same size) scores higher on both JA-JA (0.96 vs 0.22) and EN-JA (0.54 vs 0.48): this is a measured recipe and cost table, not a state-of-the-art model
  • Latency is PyTorch on CPU, single measurement; the p50 drop from 4.7 ms (dim 128) to 0.5 ms (dim 64) looks like measurement noise, not a 10× speed-up. scripts/compress.py contains an ONNX export path; no ONNX numbers are reported here

License

MIT

About

Distill bge-m3 into a 30M JA-EN encoder for cross-lingual search on CPU

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages