Sarvam-1 · Hindi → Citizen Profile JSON (LoRA)
A QLoRA fine-tune of
sarvamai/sarvam-1
that converts spoken-Hindi self-descriptions into a strict 20-field
CitizenProfile JSON schema. Designed for form-filling assistants at
government service centers, banking KYC, and welfare-scheme applications,
where applicants describe themselves in natural Hindi and the downstream
form needs structured fields.
Headline (290-entry held-out test set):
| Metric | Value |
|---|
| Parse rate | 290 / 290 (100.0%) |
| Schema validation rate | 290 / 290 (100.0%) |
| Field accuracy (strict exact-match) | 5692 / 5800 (98.1%) |
synthetic_dense gold-NN field acc (target distribution) | 98.3% |
Source code, data preparation pipeline, and training/eval scripts:
github.com/subidhkhanal/hindi-form-agent.
Quick start
1import json
2import torch
3from peft import PeftModel
4from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
5
6BASE = "sarvamai/sarvam-1"
7ADAPTER = "subidhkhanal/sarvam-1-hindi-citizen-profile-lora"
8
9tokenizer = AutoTokenizer.from_pretrained(BASE)
10if tokenizer.pad_token is None:
11 tokenizer.pad_token = tokenizer.eos_token
12
13base = AutoModelForCausalLM.from_pretrained(
14 BASE,
15 quantization_config=BitsAndBytesConfig(
16 load_in_4bit=True, bnb_4bit_quant_type="nf4",
17 bnb_4bit_compute_dtype=torch.float16, bnb_4bit_use_double_quant=True,
18 ),
19 device_map="auto",
20)
21model = PeftModel.from_pretrained(base, ADAPTER)
22model.eval()
23
24# Exact prompt format used at training time
25INPUT_DELIMITER = "हिंदी पाठ:"
26OUTPUT_DELIMITER = "संरचित JSON:"
27
28hindi_text = (
29 "मेरा नाम रामलाल है। मेरी उम्र चालीस साल है। "
30 "मैं बिहार के मधुबनी जिले में रहता हूं। खेत मजदूरी का काम करता हूं, "
31 "महीने में लगभग पच्चीस हजार रुपये कमाता हूं। आधार कार्ड और बैंक खाता है।"
32)
33prompt = f"{INPUT_DELIMITER}\n{hindi_text}\n\n{OUTPUT_DELIMITER}\n"
34
35inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
36with torch.no_grad():
37 out = model.generate(
38 **inputs, max_new_tokens=512, do_sample=False,
39 pad_token_id=tokenizer.pad_token_id,
40 eos_token_id=tokenizer.eos_token_id,
41 )
42gen = tokenizer.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
43
44# Model may emit trailing junk after the JSON; raw_decode handles it
45predicted, _ = json.JSONDecoder().raw_decode(gen)
46print(json.dumps(predicted, ensure_ascii=False, indent=2))
Expected output:
1{
2 "full_name": "रामलाल",
3 "age": 40,
4 "gender": "male",
5 "marital_status": null,
6 "district": "मधुबनी",
7 "state": "बिहार",
8 "occupation": "खेत मजदूर",
9 "monthly_income_inr": 25000,
10 "has_aadhaar": true,
11 "has_bank_account": true,
12 "bank_name": null
13}
(plus null for the remaining fields)
Output schema
20-field Pydantic
CitizenProfile (
full definition in repo).
All fields are
Optional — the model is trained to emit
null for fields the
input does not mention, rather than hallucinate.
Closed-set (Literal) fields use canonical labels:
gender ∈ {"male", "female", "other"}
marital_status ∈ {"single", "married", "widowed", "divorced"}
caste_category ∈ {"general", "obc", "sc", "st"}
has_aadhaar, has_pan, has_voter_id, has_ration_card, has_bank_account ∈ {true, false}
Government ID fields are booleans only — actual ID numbers are never
captured in training data (privacy by design).
Training
| Knob | Value |
|---|
| Base model | sarvamai/sarvam-1 (2.5 B params) |
| Trainable params | 6,422,528 (0.25%) |
| Method | QLoRA: 4-bit NF4 + LoRA |
LoRA r / α / dropout | 16 / 32 / 0.05 |
| LoRA target modules | q_proj, k_proj, v_proj, o_proj |
| Epochs | 3 |
| Effective batch | 8 (2 × grad-accum 4) |
| LR | 2e-4 (cosine, 5% warmup) |
| Max seq length | 1024 |
| Mixed precision | fp16 (T4 = Turing, no bf16) |
| Optimizer | paged AdamW 8-bit |
| Loss masking | Completion-only (DataCollatorForCompletionOnlyLM) |
| Hardware | Kaggle T4 single GPU, ~58 min |
Training data
2,270 train / 290 val / 290 test entries from four sources:
| Source | n | Description |
|---|
handcrafted_seed | 10 | Dense first-person anchor examples |
hiner (subsampled) | 2,000 | AI4Bharat's HiNER-collapsed — real-Hindi distributional signal, sparse |
synthetic_dense | 800 | First-person Devanagari citizen profiles generated against a persona matrix |
hardcases | 40 | Stress tests: disfluency, negation, approximate values, mixed scripts, etc. |
Full data prep methodology and the persona/domain references live in
data_prep/
in the GitHub repo.
Evaluation
Strict exact-match field accuracy on a held-out test set (290 entries, never
seen during training). Test set is itself produced by a deterministic
random.seed(42) split, fully reproducible.
By source
| Source | n | Parse | Schema | Field acc | Gold-NN field acc |
|---|
synthetic_dense (target distribution) | 80 | 100.0% | 100.0% | 98.7% | 98.3% |
hardcases | 10 | 100.0% | 100.0% | 95.5% | 93.4% |
hiner | 200 | 100.0% | 100.0% | 98.0% | 80.8% |
By field (gold-non-null accuracy, sorted)
| Field | Overall | Gold-NN | n (gold-NN) |
|---|
marital_status | 100.0% | 100.0% | 80 |
father_or_husband_name | 100.0% | 100.0% | 59 |
number_of_dependents | 99.3% | 100.0% | 3 |
pincode | 100.0% | 100.0% | 10 |
caste_category | 100.0% | 100.0% | 78 |
religion | 100.0% | 100.0% | 80 |
has_aadhaar | 100.0% | 100.0% | 81 |
has_pan | 100.0% | 100.0% | 9 |
has_voter_id | 100.0% | 100.0% | 9 |
has_ration_card | 100.0% | 100.0% | 26 |
has_bank_account | 100.0% | 100.0% | 51 |
bank_name | 100.0% | 100.0% | 25 |
age | 99.7% | 98.9% | 90 |
gender | 99.7% | 98.9% | 90 |
state | 99.0% | 97.8% | 90 |
village_or_town | 99.0% | 95.1% | 41 |
monthly_income_inr | 98.6% | 94.3% | 70 |
full_name | 88.6% | 88.6% | 290 |
occupation | 94.8% | 86.0% | 86 |
district | 84.1% | 82.4% | 250 |
Highlights:
- Every closed-set (
Literal-typed) field at 100% gold-NN — the model
canonicalizes free Hindi text to enum values reliably (अनुसूचित जाति → sc,
मुस्लिम → इस्लाम, विधवा → widowed).
bank_name at 100% (25/25) — the conditional rule (extract iff the
speaker named the bank, else null) generalized perfectly.
- Devanagari number → int: चौवालीस → 44, इकहत्तर → 71, बावन → 52, etc.
Weak spots (district, full_name, occupation) are bottlenecked by:
- HiNER label noise (party names mistagged as PER, locations truncated).
- Strict exact-match: predicting
"स्टेशनरी दुकानदार" when gold is
"दुकानदार" counts as wrong even though both are correct. Real semantic
accuracy is higher.
Intended use
Demo / research / educational. Drop-in inference component for Hindi
form-filling assistants, voice-to-form pipelines, and structured-extraction
research.
Not for production use without further validation. Government ID-related
fields are booleans only (never extracts ID numbers — that's a deliberate
privacy boundary, not an oversight).
Limitations
- Strict exact-match underreports. Predictions semantically equivalent to
gold (canonical spelling variants, more-specific occupations) score as
wrong. A normalized or semantic-match metric would raise the field-accuracy
number meaningfully without changing the underlying capability.
- HiNER label noise. The 2,000 HiNER training entries provide real-Hindi
distributional signal but carry inherited labeling quirks (party names as
full_name, multi-word locations truncated). The data-prep stoplist filters
the worst cases; some residual noise reaches the model.
- Generation may over-run past the JSON. The model occasionally emits
duplicate JSON blocks or Hindi narrative after the closing
}. Always parse
with json.JSONDecoder().raw_decode() (takes the first valid object) or use
the StoppingCriteria provided in the repo's evaluate.py.
- First-person dense inputs are the sweet spot. Sparse third-person news
sentences (HiNER-style inputs) still produce well-formed JSON but value
accuracy drops — that's a property of HiNER labels, not a model failure.
License
The adapter inherits the base model's license:
Sarvam non-commercial license.
Not for commercial use.
Citation / acknowledgement
Built on top of
Sarvam-1 by Sarvam AI,
and the
HiNER-collapsed
NER dataset by AI4Bharat / CFILT. See the GitHub repo for the full data prep
acknowledgements and per-source methodology.
Full source, data, and reproducible training scripts:
github.com/subidhkhanal/hindi-form-agent.