Tibetan-aware retrieval LoRA for Qwen3-Embedding-8B
Qwen3-Embedding-8B cannot see Tibetan: a Sanskrit query retrieves the
matching Wylie dictionary headword 4% of the time, an English query 0.4%
of the time, and even Wylie ↔ Tibetan-script (uchen) — a deterministic
transliteration — round-trips at only 43% R@1. Buddhist-text RAG systems
work around this with hand-maintained alias tables.
This LoRA teaches the mapping to the embedder itself: Wylie ↔ uchen ↔
Sanskrit (IAST) ↔ English, trained contrastively on 95k alias pairs
mined from a licensed copy of the Illuminator Tibetan-English
Encyclopaedic Dictionary (©Tony Duff, Padma Karpo Translation Committee
— the dictionary text itself is not included in this repo and cannot be
reconstructed from embedding weights).
Results — dictionary headword retrieval (held-out terms)
R@1 over the full 26,787-headword haystack. Held-out terms (300 Sanskrit
- 500 English) contributed zero training pairs.
| axis | base R@1 | +LoRA R@1 | +LoRA R@5 |
|---|
| sanskrit → wylie | 0.044 | 0.264 | 0.447 |
| sanskrit → uchen | 0.100 | 0.261 | 0.419 |
| english → wylie | 0.004 | 0.186 | 0.328 |
| english → uchen | 0.030 | 0.144 | 0.263 |
| wylie → uchen | 0.430 | 0.927 | 0.990 |
Results — passage retrieval (real practice-text corpus)
58 gold queries (14 dharma terms × up to 4 transliteration schemes + 4
end-to-end questions) against 32,607 passages (gold targets + 30k
distractors) from a 866-document practice-text collection. hit@5 = any
gold passage in top 5; cross-hit masks out every passage that
contains the query string literally — the score string-matching cannot
reach.
| scheme | base hit@5 | base cross | +LoRA hit@5 | +LoRA cross |
|---|
| wylie | 10/14 | 4/14 | 13/14 | 11/14 |
| phonetic | 4/14 | 2/14 | 12/14 | 10/14 |
| sanskrit | 7/12 | 2/12 | 4/12 | 2/12 |
| english | 5/14 | 1/14 | 2/14 | 0/14 |
| e2e | 1/4 | 1/4 | 0/4 | 0/4 |
Read this table honestly: it is a specialization trade-off, measured
on small n (12-14 queries per scheme). Tibetan-side retrieval jumps —
cross-hit (the case alias tables exist for) goes 4/14→11/14 on Wylie and
2/14→10/14 on phonetic. Phonetic renderings were learned transitively:
the dictionary contains no phonetics at all, yet "tongpa nyi" now finds
stong pa nyid passages. The price: general-semantic English retrieval
regresses (5/14→2/14, end-to-end questions 0/4) — the LoRA pulls English
glosses toward Tibetan headword space and away from general passage
space. Dictionary-level English→headword improved (0.004→0.186), so
the regression is specific to long-passage semantic matching.
Deployment recommendation: route queries. Adapter ON for
Tibetan-script/Wylie/phonetic/Sanskrit-term queries, adapter OFF (base
model) for general English questions — with PEFT this is
enable_adapter_layers()/disable_adapter_layers() per query, no extra
memory. In a hybrid stack (FTS + vector + reranker) the English
regression is further absorbed by the lexical side.
Usage
Exactly like base Qwen3-Embedding-8B (last-token pooling, L2-normalize,
instruction prefix on queries only) with the adapter attached:
1from transformers import AutoModel, AutoTokenizer
2from peft import PeftModel
3import torch, torch.nn.functional as F
4
5tok = AutoTokenizer.from_pretrained("Qwen/Qwen3-Embedding-8B", padding_side="right")
6model = AutoModel.from_pretrained("Qwen/Qwen3-Embedding-8B", torch_dtype=torch.bfloat16).cuda()
7model = PeftModel.from_pretrained(model, "anicka/qwen3-embedding-8b-tibetan-lora").eval()
8
9INSTRUCT = ("Instruct: Given a term in any transliteration scheme "
10 "(Wylie, Tibetan script, Sanskrit, English), retrieve the "
11 "matching Tibetan dictionary headword\nQuery: ")
12
13def embed(texts, prefix=""):
14 enc = tok([prefix + t for t in texts], padding=True, truncation=True,
15 max_length=256, return_tensors="pt").to("cuda")
16 h = model(**enc).last_hidden_state
17 idx = enc.attention_mask.sum(1) - 1
18 v = h[torch.arange(h.size(0)), idx]
19 return F.normalize(v.float(), dim=-1)
20
21q = embed(["tongpa nyi"], prefix=INSTRUCT) # phonetic query
22d = embed(["stong pa nyid", "སྟོང་པ་ཉིད་", "śūnyatā"]) # any scheme
23print(q @ d.T)
Training
- Data: 95,135 contrastive pairs from 26,787 dictionary entries:
wylie↔uchen (26k), english-gloss→wylie (36k), form→definition (22k),
abbreviation/misspelling→canonical (3.6k), sanskrit→wylie/uchen (4k),
verb-stem→lemma (3.2k). Held-out eval terms excluded from all pair types.
- Objective: symmetric InfoNCE, in-batch negatives, temperature 0.05.
Length-bucketed batches (terms vs definitions) — term batches give
all-term in-batch negatives, which are the hard ones.
- LoRA: r=16, α=32, all attention + MLP projections, 1 epoch (best val at end, no overfit),
lr 1e-4 cosine, batch 48, bf16 + gradient checkpointing.
- Trained with the query instruction prefix on anchors only, matching
inference usage.
Limitations
- General-English and question-style retrieval regress (see the
passage table above) — route those to the base model. The natural v2
fix is mixing general-retrieval pairs into training to pin the English
side in place.
- English glosses collide ("wisdom" maps to shes rab, ye shes, …);
english-axis R@1 undercounts real quality — check R@5.
- Trained on dharma vocabulary; no claim about secular modern Tibetan.
- Passage-eval n is small (12-14 queries/scheme); treat those fractions
as strong signal with wide error bars, not precise rates.
Acknowledgements
The Illuminator Tibetan-English Encyclopaedic Dictionary (Lotsawa Tony
Duff, Padma Karpo Translation Committee) — used under license as
training data. Motivated by a Buddhist-RAG builder's observation that
no neural embedder handles Tibetan transliteration schemes.