рд╣рд┐рдиреНрджреА рдореЗрдВ рд╕реЛрдЪрдиреЗ рдФрд░ рдЬрд╡рд╛рдм рджреЗрдиреЗ рд╡рд╛рд▓реЗ рдЫреЛрдЯреЗ рднрд╛рд╖рд╛ рдореЙрдбрд▓
Three Hindi language models trained from random initialisation, without any pretrained weights, on a single 6 GB laptop GPU: Jatayu (11M), Hanuman (42M base) and Angad (42M, instruction-tuned to answer questions). The tokenizer, data pipeline, pretraining and fine-tuning were all built from scratch.
Each model is named after a figure from the Ramayana whose role matches what the model does.
A small bird who took on a much bigger enemy. Our first test run: grammatical Hindi, made-up facts.
Master of speech. Fluent, grammatical Hindi in an encyclopaedic style.
Rama's envoy. Hanuman instruction-tuned on 37k Hindi Q&A pairs, so it answers instead of just continuing.
The wise elder. Larger model, trained on 1тАУ2B tokens of the Sangraha corpus.
The bridge to Lanka. One model across Hindi, Marathi, Bengali, Tamil and more.
When Hanuman first meets Rama in the Kishkindha Kanda, Rama tells Lakshmana that this messenger must have mastered grammar, because he spoke at length without a single mistake. Getting grammar right is the first thing a language model learns, and it is what this model does best.
рдиреВрдирдВ рд╡реНрдпрд╛рдХрд░рдгрдВ рдХреГрддреНрд╕реНрдирдордиреЗрди рдмрд╣реБрдзрд╛ рд╢реНрд░реБрддрдореНред
рдмрд╣реБ рд╡реНрдпрд╛рд╣рд░рддрд╛рдиреЗрди рди рдХрд┐рдЮреНрдЪрд┐рджрдкрд╢рдмреНрджрд┐рддрдореНрее Valmiki Ramayana, Kishkindha Kanda 3.29: "Surely he has studied the whole of grammar many times; though he spoke much, not one word was wrong."
Unedited samples (temperature 0.8, top-p 0.95). Bold is the prompt and the rest is the model. The grammar is correct; facts are sometimes invented.
Angad is Hanuman fine-tuned for about an hour on 37k Hindi questionтАУanswer pairs. Answer perplexity fell from 58.4 тЖТ 16.9, and it now ends every answer cleanly. It has learned how to answer, but not yet what is true.
prompt = "<hi>рдкреНрд░рд╢реНрди: рд╣реЛрд▓реА рдХрд╛ рддреНрдпреЛрд╣рд╛рд░ рдХреНрдпреЛрдВ рдордирд╛рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ?\nрдЙрддреНрддрд░: " # model: ashishpatel26/angad-hi-42m ┬╖ generate until </s>
Measured on Wikipedia text the models never saw during training (lower is better). Making the model 4├Ч larger cut perplexity by more than half.
Measured on 380 held-out Hindi questionтАУanswer pairs, scoring the answer tokens only. Fine-tuning made answers 3.5├Ч more predictable.
| Type | Llama-style decoder (RMSNorm, SwiGLU, RoPE) |
| Hidden / FFN | 512 / 1408 |
| Layers / heads | 8 / 8 |
| Context | 512 tokens |
| Embeddings | Tied input/output |
| Vocabulary | 32,000 (byte fallback) |
| GPU | RTX 3060 Laptop, 6 GB |
| Precision | bf16 autocast, SDPA attention |
| Optimiser | AdamW (0.9, 0.95), wd 0.1, clip 1.0 |
| LR schedule | 6e-4 тЖТ 6e-5 cosine, warmup |
| Tokens seen | ~229M (~4 epochs) |
| Val loss | 3.84 |
Hanuman and Jatayu are base models: they continue the text you give them, so start the prompt with <hi>. Angad answers questions; use the template shown below.
# pip install torch transformers sentencepiece huggingface_hub
import torch, sentencepiece as spm
from huggingface_hub import hf_hub_download
from transformers import LlamaForCausalLM
repo = "ashishpatel26/hanuman-hi-42m"
model = LlamaForCausalLM.from_pretrained(repo)
sp = spm.SentencePieceProcessor(model_file=hf_hub_download(repo, "spm.model"))
x = torch.tensor([sp.encode("<hi>рд╣рд┐рдорд╛рд▓рдп рдкрд░реНрд╡рдд")])
y = model.generate(x, max_new_tokens=100, do_sample=True,
top_p=0.95, temperature=0.8, repetition_penalty=1.15)
print(sp.decode(y[0].tolist()))# Angad: ask a question
repo = "ashishpatel26/angad-hi-42m"
model = LlamaForCausalLM.from_pretrained(repo)
sp = spm.SentencePieceProcessor(model_file=hf_hub_download(repo, "spm.model"))
x = torch.tensor([sp.encode("<hi>рдкреНрд░рд╢реНрди: рд╕реНрд╡рд╕реНрде рд░рд╣рдиреЗ рдХреЗ рддреАрди рдЙрдкрд╛рдп рдмрддрд╛рдЗрдПред\nрдЙрддреНрддрд░: ")])
y = model.generate(x, max_new_tokens=150, do_sample=True, top_p=0.9, temperature=0.7,
repetition_penalty=1.15, eos_token_id=sp.eos_id(), pad_token_id=sp.pad_id())
print(sp.decode(y[0, x.shape[1]:].tolist()))