Telugu (Andhra Pradesh) property-document extraction β€” Gemma 3 12B QLoRA

A LoRA adapter for Gemma 3 12B Instruct that reads the OCR of an Andhra Pradesh property document and returns one JSON object: the parties and their identifiers, the property, the amounts and the registration details.

Sibling of the Kannada model anandkaman/gemma3_12b_deedexp. This is a separate specialist for AP documents, not a multi-state model β€” the two are trained apart on purpose, because the document language, the fee table and the role vocabulary differ.

Measured accuracy

11 held-out documents, none seen in training, scored against labels derived from the documents themselves rather than from the accompanying register.

result
buyer/seller role swaps 0
buyer count exact 11/11
seller count exact 11/11
sale consideration exact 11/11
registration date exact 11/11
registration office exact 11/11
state exact 11/11
stamp duty exact 9/11
registration fee exact 9/11
party names recovered 24/32
output failed to parse as JSON 0/11

Read the limits with the numbers. n = 11, against the Kannada model's 60. These are the harder documents by construction β€” they were held out for length, and length tracks party count β€” and 6 of the 11 prompts still exceed the 11k-token training window, so part of the OCR is truncated at generation and the model still recovers the fields above.

Two fields score 0 and are not model weaknesses: pincode and the schedule address. Both have an unsettled labelling convention in the source register (the pincode values trace to an architect's letterhead, a sub-registrar's address and a tax receipt, among others), so neither can be scored until the convention is fixed. The address field recovers ~0.4 of the gold's tokens while scoring 0 on exact match.

Prompts β€” full and subset

Three prompts are trained. All three use the same rules block; they differ in one sentence that names the schema to return.

file returns
PROMPT_full.txt everything: parties, property, document details
PROMPT_subset_1.txt parties only β€” buyer, seller, confirming party
PROMPT_subset_2.txt property and registration details only

Where the saving is. The subset prompts are not shorter on input β€” they are 66 characters longer. The saving is in the output: a subset answer averages 465 characters against 1017 for the full schema, 46 % of the size. Because decoding is sequential, that is roughly a 2x faster call when you only need one half of the record. Use a subset prompt when you only need parties, or only need the property and amounts; it is not a way to shorten the input.

18 % of the training rows are subset-schema variants, which also lifts per-field accuracy on the full schema rather than only enabling the short form.

Use

from unsloth import FastLanguageModel
from unsloth.chat_templates import get_chat_template

model, tok = FastLanguageModel.from_pretrained(
    "anandkaman/gemma3_12b_deedexp_te", max_seq_length=11008, load_in_4bit=True)
FastLanguageModel.for_inference(model)
tok = get_chat_template(tok, chat_template="gemma-3")

rules  = open("PROMPT_full.txt").read()          # or PROMPT_subset_1.txt / _2.txt
prompt = rules + "\n\nOCR:\n" + ocr_text

text = tok.apply_chat_template(
    [{"role": "user", "content": [{"type": "text", "text": prompt}]}],
    tokenize=False, add_generation_prompt=True)
ids = tok(text, return_tensors="pt", add_special_tokens=False).input_ids.to("cuda")
out = model.generate(input_ids=ids, max_new_tokens=2600, do_sample=False)
print(tok.decode(out[0][ids.shape[1]:], skip_special_tokens=True))

Greedy decoding. Trained at max_seq_length=11008; longer OCR should be cut from the middle, keeping the instruction block and the start and end of the document.

Runs in about 11.4 GB of VRAM in 4-bit.

Design rules it was trained to follow

  • Roles come from what a party does, never from where it appears on the page. The registration endorsement states the role directly (executant = seller, claimant = buyer, witness = neither).
  • A witness is never a party, and a power-of-attorney holder is not a party either.
  • People named in recited earlier transactions are not parties to this sale.
  • Missing data is acceptable; invented data is a critical error. Any field the document does not carry is null. A malformed identifier is null rather than a guess.

Base model, quantization and merging

Read MERGE.md before merging. The essentials:

base unsloth/gemma-3-12b-it-bnb-4bit β€” bitsandbytes NF4, double quant, bf16 compute. Not the dynamic -unsloth-bnb-4bit variant
identity verified by content hash against the Hub: config.json blob ce6ea5eb…, both shards byte-identical in size
adapter plain LoRA, r=16, alpha=32 β†’ merge scaling 2.0; no DoRA / rsLoRA / QALoRA, no modules_to_save
adapted 417 modules = 336 text (48 layers x 7 projections) + 81 vision (27 SigLIP layers x q/k/v)
merge target bf16 unsloth/gemma-3-12b-it (google/gemma-3-12b-it is gated)
lineage three phases, each loading the previous phase's weights into a fresh same-rank LoRA. This adapter is self-contained β€” there is no second adapter to stack

Three things that will bite you, all covered in MERGE.md:

  1. Attach to Gemma3ForConditionalGeneration, not the text-only class. 81 of the 417 adapted modules are in the SigLIP vision tower, matched by name. A text-only load leaves them unresolved.
  2. Key layout. The adapter was saved from the model.language_model.layers.* module tree; the Hub checkpoints still use language_model.model.layers.* on disk. Verified 417/417 resolved on transformers 5.5.0; run check_adapter_keys.py on your own stack first β€” it builds the model on the meta device, so it costs no download and no GPU.
  3. A bf16 merge is not the model that was evaluated. The accuracy table above was measured with this adapter on the 4-bit base. The LoRA was fitted against NF4-dequantized weights, so merging into bf16 is the standard QLoRA path but not numerically identical β€” re-score after merging. For exactly the evaluated model, do not merge: attach the adapter to the 4-bit base.

Two scripts ship here: check_adapter_keys.py (seconds, no weights) and merge_lora_streaming.py, which merges shard by shard so it runs on a 16 GB machine, refuses an ambiguous key mapping, and spot-checks the arithmetic afterwards.

Training

QLoRA (rank 16, alpha 32) on unsloth/gemma-3-12b-it-bnb-4bit, 49 documents / 60 rows, 36 steps, built on a Telugu-adapted base. max_seq_length=11008, batch 1 x grad-accum 8, lr 2e-4 cosine, warmup_ratio 0.03, weight_decay 0.01, adamw_8bit, seed 42, packing=False, bf16, unsloth offloading gradient checkpointing, on one 15.478 GiB RTX 5060 Ti. Stack: unsloth 2026.5.2 / torch 2.10.0 / transformers 5.5.0 / trl 0.24.0 / peft 0.19.1 / bitsandbytes 0.49.2. Labels were derived from each document's own registration endorsement rather than from the accompanying register β€” correcting 11 buyer/seller role swaps and removing 5 identifying witnesses that a register-derived label set had recorded as parties.

No document text, identifier or party name from the training corpus is published here.

Contact: kamananand98@gmail.com

Downloads last month
-
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Model tree for anandkaman/gemma3_12b_deedexp_te

Adapter
(9)
this model