Results
Aksharantar Telugu test, all 10,260 words, greedy decoding. Reward is the environment's:
0.8 × (1 − CER) + 0.2 × exact match, and 0 for an empty or looping answer.
Table with columns: base, this adapter | base | this adapter |
|---|
| reward | 0.372 | 0.695 |
| character error rate (capped at 1 per word) | 0.547 | 0.204 |
| exact match | 4.6% | 29.1% |
| chrF | 0.269 | 0.527 |
By where the word comes from (reward / exact match):
Table with columns: origin, words, base, this adapter| origin | words | base | this adapter |
|---|
| Dakshina (common words) | 5,724 | 0.386 / 6.0% | 0.721 / 32.9% |
| AK-Freq (frequent words) | 2,241 | 0.413 / 3.8% | 0.776 / 37.1% |
| AK-NEI (Indian named entities) | 1,347 | 0.334 / 2.5% | 0.566 / 13.7% |
| AK-NEF (foreign named entities) | 948 | 0.243 / 1.3% | 0.526 / 8.9% |
Sampling 8 answers at temperature 0.9 on 1,000 test words, the best of 8 reaches 0.737 (base
0.509), and at least one answer is exactly right for 35% of words (base 8%).
The base numbers are the untuned model on the same words, scored the same way. They are
aggregates, not a word-by-word pairing.
What it does and doesn't get right
Twelve test words, three per origin, drawn at random (seed 1), not picked by hand:
Table with columns: origin, romanized, reference, this adapter, CER| origin | romanized | reference | this adapter | CER |
|---|
| Dakshina | saalid | సాలిడ్ | సాలిడ్ | 0.000 |
| Dakshina | chendinade | చెందినదే | చెందినడే | 0.125 |
| Dakshina | thevadam | తేవడం | తీవడం | 0.200 |
| AK-Freq | gurtunchukovadam | గుర్తుంచుకోవడం | గుర్తుంచుకోవడం | 0.000 |
| AK-Freq | padutunnaayi | పడుతున్నాయి | పదుతున్నాయి | 0.091 |
| AK-Freq | sariayina | సరిఅయిన | సరియిన | 0.143 |
| AK-NEI | nerolac | నేరోలాక్ | నేరోలచ్ | 0.250 |
| AK-NEI | nischal | నిశ్చల్ | నిశ్ల | 0.286 |
| AK-NEI | fatima | ఫాతిమా | ఫతిమ | 0.333 |
| AK-NEF | kenner | కెన్నర్ | కేన్నర్ | 0.143 |
| AK-NEF | smith | స్మిత్ | స్మింగ్ | 0.333 |
| AK-NEF | greenville | గ్రీన్విల్లే | గ్రీవలన్ | 0.500 |
Most remaining errors are the ones romanization itself makes ambiguous: vowel length (e/ee,
a/aa) and retroflex versus dental consonants (d → డ or ద). Named entities, foreign ones
most of all, stay hardest, because their spelling in Telugu follows pronunciation rather than
the Latin letters. The model sometimes ends a word with a zero-width non-joiner; scoring
removes it, as Telugu text normalization usually does.
Use it
import torchfrom peft import PeftModelfrom transformers import AutoModelForCausalLM, AutoTokenizer base = "Qwen/Qwen2.5-1.5B-Instruct"tokenizer = AutoTokenizer.from_pretrained(base)model = AutoModelForCausalLM.from_pretrained(base, dtype=torch.float16, device_map="auto")model = PeftModel.from_pretrained(model, "makireddyvighnesh/Qwen2.5-1.5B-Instruct-telugu-translit-grpo") prompt = ("Transliterate the following romanized Telugu into Telugu script. " "Output only the Telugu text.\n\nnakshatram")inputs = tokenizer.apply_chat_template([{"role": "user", "content": prompt}], add_generation_prompt=True, return_tensors="pt", return_dict=True).to(model.device)output = model.generate(**inputs, max_new_tokens=48, do_sample=False)print(tokenizer.decode(output[0, inputs["input_ids"].shape[1]:], skip_special_tokens=True))
Use the prompt exactly as above: it is the one the model was trained on. The adapter was
trained on single words; for a sentence, transliterate it word by word.
With vLLM:
vllm serve Qwen/Qwen2.5-1.5B-Instruct --enable-lora --max-lora-rank 16 \ --lora-modules telugu-translit=makireddyvighnesh/Qwen2.5-1.5B-Instruct-telugu-translit-grpo
How it was trained
Environment. An OpenEnv server that serves one transliteration task per episode: reset
returns a romanized word and the prompt, step grades one answer and ends the episode. It
holds the references; the trainer only ever sees task IDs. The task set is a snapshot of
Aksharantar Telugu pinned to dataset revision e418c1f
(snapshot 0936c463…, recorded in training/environment.json), keeping only the
human-romanized sources:
Table with columns: split, words| split | words |
|---|
| train | 50,927 (Dakshina) |
| validation | 3,716 (Dakshina) |
| test | 10,260 (Dakshina, AK-Freq, AK-NEI, AK-NEF) |
Aksharantar's Telugu train split is otherwise 95% IndicCorp, web-mined and machine-romanized,
with about 1 in 8 native words misspelled in a 40-row audit. Those 2.38M pairs were left out.
Reward. 0.8 × (1 − CER) + 0.2 × exact match. CER is the edit distance over Unicode code
points after normalization (NFKC/NFC, zero-width characters and quotes removed, Telugu digits
to ASCII), divided by the reference length and capped at 1. A word-level rate would score one
wrong vowel sign as a total miss. Empty answers and answers that loop or balloon score 0, so
the policy can't farm partial credit by repeating itself. <think> blocks and a wrapping code
fence are stripped; prose around the answer is not, so the policy learns to output only the word.
GRPO. TRL 1.16 GRPOTrainer with environment_factory: one environment client per
rollout, reset(task_id) before generation, step(completion) in the reward function.
Table with columns: setting, value| setting | value |
|---|
| steps | 1000 (about 1 epoch over 16,000 train words) |
| prompts × answers per step | 16 × 8 |
| learning rate | 5e-5, cosine, 50 warmup steps |
| LoRA | r 16, alpha 32, dropout 0, all linear layers |
| temperature | 0.7 |
| max completion length | 48 tokens (longest reference: 37) |
| loss | DAPO (TRL default), no KL term |
| precision | fp16 on 2 × NVIDIA T4 (Kaggle) |
| wall time | 2.8 h of training |
Validation reward (256 frozen validation words, 8 samples each) rose from 0.47 at step 50 to
0.70 by step 650, then stayed flat. By the end, 58% of groups gave no learning signal (all 8
answers scored the same) and policy entropy had fallen from 0.33 to 0.06, so more steps on
this data would add little.
A second run that called the same reward function in-process instead of through the
environment, with the same seed and words, produced the same test scores to four decimals.
That is the check that the environment serves and grades exactly what the reward function
computes.
Files
adapter_model.safetensors, adapter_config.json: the LoRA adapter
training/log_history.json: every logged metric, every step
training/test_report.json: the test evaluation above
training/environment.json: the environment snapshot and Aksharantar revision trained against
assets/curves.png: the figure at the top
Limitations
- Single words only. It was never trained on phrases or sentences.
- Romanization varies from person to person. The test references are one spelling per word,
so a valid alternative spelling counts as an error.
- Named entities, especially foreign ones, are much weaker than common words.
- Evaluated on Aksharantar only.
License and credit
The adapter is Apache-2.0, like the base model. Training and test words come from
Aksharantar by AI4Bharat, CC BY 4.0.
The environment's design follows the FineEnvs
multilingual OCR and ASR environments.