India's paperwork and class-board LPs are small, local, and checkable. IndicQuant is a CPU harness for that: Indic document extractors with tests, plus AutoOpt (arXiv:2510.21436) as a checked loop — image → mathematical understanding → latex → optimization IR → validation → code generation → solver selection → optimize → independent verification. Not image-to-answer. A tiny CNN+CTC is trained here and shipped as ONNX int8. This machine does not train or serve a 30B VLM, AutoOpt-M1 (393M), or DeepSeek-Coder 1.3B.
Identity numbers in evals/ are SAMPLE / fake.
CLI, CPU — uv run indicquant autoopt --sample opt_lp2 then uv run indicquant eval --suite autoopt:
opt_lp2 |
vertex-lp, (x=2, y=3), objective 18.0, sub-ms |
| documents golden set | field accuracy 1.0 on the labelled rows (hi/ta/te/kn/bn) |
| autoopt golden set | 10/10, including incomplete + infeasible checkpoints |
| tiny MER | 56,021 params, int8 65 KB, ~8 ms/page; 31.1% CER on 64 held-out lines (256 train lines, zero overlap) |
| photos | 11 phone shots of the SAMPLE sheet on a laptop screen (evals/photos/real/); 2 synthetic JPEGs stay labelled as such |
| pytest | uv run pytest -q |
Live Compose (http://127.0.0.1:7861) is postgres + redis; uv run indicquant serve is the single-process UI.
Per-field tables, CER, and confusion: uv run indicquant eval, uv run indicquant eval --suite scans, uv run indicquant eval --suite mer, uv run indicquant eval --suite photos. Paper protocol: docs/comparison.md. On-device envelope: docs/BENCHMARKS.md. Architecture: ARCHITECTURE.md.
UI and job JSON live under results/demo/.
| File | What |
|---|---|
| walkthrough.gif | skim: PAN → Aadhaar → GST → AutoOpt → CLI |
| 00_testing_report.png | pytest + evals |
| 02_pan_extract.png | PAN hi → JSON |
| 06_aadhaar.png | Aadhaar hi |
| 03_autoopt_lp2.png | mer checkpoint + obj 18 |
Golden sets are the test distribution. Adding rows raises coverage without a GPU.
| Set | Path | Notes |
|---|---|---|
| Documents | evals/documents.json |
hi, ta, te, kn, bn, native digits, IGST, two-page GST, DL |
| AutoOpt | evals/autoopt.json |
LP, LaTeX, BOBD, equality, compile fail, infeasible |
| Scans | evals/scans.json + evals/scans/* |
labelled pages, ocr_noise, camera-style JPEGs; grammar compile-solve |
| MER split | evals/mer_split.json |
256 train / 64 held-out synthetic lines + 12 scan-instance strings, zero overlap |
| Photos | evals/photos/ |
11 phone photos of the SAMPLE sheet on a laptop screen + 2 JPEG-degraded renders. Not printed paper. IDs are fake. |
A scanned AutoOpt-11k dump would be the next data step. It needs their 11k pages, not this laptop.
Same loop: understand → latex → IR → validate → codegen → select solver → optimize → verify. Grammar repair still sits in front of the IR. CTC is an extra, measured path. There is no image-to-answer shortcut.
Split (auditable in evals/mer_split.json): trained on 256 unique synthetic LP lines (seed 7). Evaluated on 64 held-out synthetic lines (seed 123) plus 12 scan-instance strings from evals/scans.json. Zero string overlap. Shared boilerplate (subject to, x >= 0, y >= 0, p >= 0, q >= 0) is train-only and may still appear on eval pages.
| Piece | What shipped | What it is not |
|---|---|---|
| Pages | evals/scans/* + gold in scans.json |
AutoOpt-11k |
| Grammar MER | mer.recognise — sense + ≤ / s.t. |
a net |
| Tiny CTC | CNN+CTC → ONNX fp32/int8 | ResNet+Swin+Nougat 393.3M |
| Metric | CER + compile-solve | paper BLEU on 11k |
uv sync --extra mer --extra train
uv run python scripts/train_mer_ctc.py --steps 1200
uv run python scripts/eval_mer_cer.py
uv run python scripts/bench_mer.py
uv run indicquant eval --suite scans
uv run indicquant eval --suite merHeld-out CTC: 31.1% CER (0.3114 micro) on 64 lines; 37.0% CER on the 12 scan-instance strings. Grammar MER on stored ocr_noise still compile-solves 8/8. Full-page CTC is not AutoOpt-M1. Source: results/mer/cer.json.
document ingest → split → preprocess → infer → validate → assemble
autoopt ingest → preprocess → understand → latex → ir → validate
→ codegen → select_solver → optimize → verify
flowchart LR
img["image"] --> understand["mathematical understanding"]
understand --> latex["latex"]
latex --> ir["optimization IR"]
ir --> val["validation"]
val -->|incomplete| stop["stop"]
val --> codegen["code generation"]
codegen --> pick["solver selection"]
pick --> opt["optimize"]
opt --> verify["independent verification"]
GST validate: taxable + cgst + sgst + igst ≈ total. The AutoOpt IR fails closed-loop if min/max or constraints are missing. Solver selection is vertex-lp (≤2 linear vars) or bobd-grid (quadratic / extra vars). Verify re-evaluates the IR at (x) and rejects a lying objective.
uv venv --python 3.11 && uv pip install -e '.[dev,mer]'
uv run indicquant extract --sample pan_hi
uv run indicquant autoopt --sample opt_lp2
uv run indicquant eval
uv run indicquant eval --suite autoopt
uv run indicquant eval --suite scans
uv run indicquant eval --suite mer
uv run indicquant eval --suite photos
uv run pytest -qUI: uv run indicquant serve → http://127.0.0.1:7860
Compose: docker compose up --build → http://127.0.0.1:7861.
curl -s http://127.0.0.1:7861/api/jobs -H 'content-type: application/json' -d '{"sample_id":"opt_lp2"}'- Printed paper (or more phone shots). We now have 11 phone photos of the SAMPLE sheet on a laptop screen (
evals/photos/real/). That is not a field dump and not printed paper. More angles of paper would still help. - A better line CTC — more synthetic data, still CPU, still ONNX. Keep compile as the checkpoint. Held-out CER is 31.1%; that is the number to beat.
- Optional HiGHS/CBC behind M2; vertex/BOBD stays the no-dep path.
What we will not do on this laptop: train 393M Nougat or fine-tune 1.3B DeepSeek-Coder.
- https://www.kaggle.com/datasets/shivanibhandari767/indicquant
- https://www.kaggle.com/code/shivanibhandari767/indicquant-mer-compile-solve-on-cpu
uv run python scripts/push_kaggle.pyevals/documents.json document golden set
evals/autoopt.json AutoOpt golden set
evals/scans.json labelled pages + grammar CER
evals/mer_split.json 256/64 CTC split, zero overlap
evals/photos.json 11 phone-of-screen + 2 synthetic JPEGs
src/indicquant/harness/ ingest → assemble
src/indicquant/autoopt/ mer, ctc, m1–m3, ONNX weights
src/indicquant/eval/ scoring
docs/comparison.md vs arXiv:2510.21436
docs/BENCHMARKS.md size / CPU ms / held-out CER
results/demo/ screenshots + walkthrough.gif
results/mer/cer.json held-out CTC CER

