Skip to main content

savitr — fast Surya OCR on Apple Silicon, for Indian electoral rolls

PyPI CI Downloads Model Docs

savitr runs Surya OCR (datalab-to/surya-ocr-2) on Apple Silicon via MLX. It also ships an electoral-roll-specific model that emits one compact line per voter, plus a pipeline that turns scanned roll PDFs into the canonical voter CSV. It runs locally without a cloud GPU.

Install

Requirements: an Apple-Silicon Mac (M-series — the OCR runs on MLX) and poppler (used to read PDFs):

brew install poppler             # macOS  (Debian: sudo apt-get install poppler-utils)

Then:

pip install savitr               # MLX runtime + terse roll model (auto-downloaded from HF)
pip install "savitr[train]"      # + the distillation toolchain (transformers/peft/torch)

Latest from git: pip install "git+https://github.com/in-rolls/savitr"

The terse model is fetched from gojiberries/savitr on first use (~800 MB, one time). The pure-Python parsing API (parse_terse) imports on any platform; only the OCR itself needs Apple Silicon.

Quickstart

The distilled terse model is the default and auto-downloads on first run — a bundled sample roll lets you try it immediately:

# OCR the bundled sample roll -> per-page voter summary (works from any install)
savitr ocr "$(savitr sample)"

# ... or write the voter records straight to CSV
savitr ocr "$(savitr sample)" -o voters.csv

# whole rolls -> canonical voter CSV (a directory of *_ENG.pdf, or a single -f file)
savitr parse-rolls -d english/ -o voters.csv
from savitr import MLXSuryaOCR, parse_terse, resolve_terse_model
from savitr.rolls.parse import TERSE_PROMPT

eng = MLXSuryaOCR(
    resolve_terse_model(), prompt=TERSE_PROMPT
)  # downloads the model if not local
text, _ = eng.ocr_image("page.png")
voters = parse_terse(text)  # [{'id': 'KMY...', 'elector_name': ..., 'age': ..., ...}]

Two models, and which one you want

Electoral rolls → the terse model. Published at gojiberries/savitr and downloaded on first use, so everything above works with no setup. It was distilled to emit voter rows and will emit them whatever the page holds — it is not a general OCR.

Anything else → base Surya. Upstream publishes datalab-to/surya-ocr-2 but not an MLX build of it, so convert it once (~1.3 GB fetched, ~500 MB written):

python -m mlx_vlm convert --hf-path datalab-to/surya-ocr-2 \
    --mlx-path models/surya-mlx-4bit -q --q-bits 4
from savitr import MLXSuryaOCR

eng = MLXSuryaOCR()  # finds models/surya-mlx-4bit, or $SAVITR_BASE_PATH
eng = MLXSuryaOCR("/some/other/model")  # or say where
text, _ = eng.ocr_image("page.png")  # HTML: <table><tr><td>…

With no converted model, every entry point — the constructor, savitr ocr --html, and --cover-model — says the same thing and repeats the command above. savitr ocr --html needs it; --cover-model is optional metadata and carries on without it.

terse-Surya (gojiberries/savitr)

Surya self-distilled to emit pipe-delimited voter rows. The model card is the single source for checkpoint provenance, evaluation definitions, results, and limitations.

What's in the box

Three layers — two you install and use, one for reproducing the model:

src/savitr/            # the pip package (use it)
  mlx_ocr.py           # GENERIC MLX Surya engine (MLXSuryaOCR) — run any Surya OCR fast
  rolls/               # ELECTORAL-ROLL app: parse · fields · schema · pipeline · ocr
  cli.py, __init__.py
training/              # repo-only: build_corpus · train · eval · merge · kaggle_*  (reproduce the model)
  • Use it — electoral rolls (the product): savitr parse-rolls / savitr ocr run the distilled terse model (the default) on roll PDFs → voter records / canonical CSV (savitr.rolls).
  • Use it — generic fast Surya: savitr.MLXSuryaOCR runs any compatible Surya model on Apple Silicon.
  • Reproduce it — training/distillation: lives in top-level training/, not shipped in the wheel (install the [train] extra to run it). We ship code to use the model, not to train it.

How it was built / what was tried

See the findings for the measured baseline (decode, not cold-start, dominates; ~110 s/page on llama.cpp), the MLX win, the tested negatives (compact prompt, guided JSON, parallelism), and the distillation method and numbers.

Develop

uv sync --all-groups --all-extras
uv run pytest
uv run ruff check .
uv run ruff format --check .

Release files for savitr 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for savitr 0.3.0
File Size Uploaded
savitr-0.3.0.tar.gz 748.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for savitr 0.3.0
File Interpreter ABI Platform
savitr-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.5 MB

Release files / savitr-0.3.0.tar.gz

Download URL savitr-0.3.0.tar.gz
Size 748.6 kB
Tags Source
SHA-256 checksum
How to use checksums
dcd386167a7a5ade03b4058bd6c689233552296800675a7a4bea553e76dd8d8b
BLAKE2b-256 checksum
How to use checksums
af09a2e26d35d4539fc83ce2cec7daf33a4a4cdaac03ae47a3cbf44547fdf1f0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 17, 2026.

Transparency log

Release files / savitr-0.3.0-py3-none-any.whl

Download URL savitr-0.3.0-py3-none-any.whl
Size 751.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5f55afee9615d65e5ea1331cd7faf487dfb4bf2665ace69e9c5cb192ad65817f
BLAKE2b-256 checksum
How to use checksums
f29401d795b08077d3d9cc018fab7e073dc2275cec0b03afb5b1a4ea21044100
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 17, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page