OCR models for invoices and receipts: local hardware and hybrid OCR deployment
Here's the rule I use: start deterministic and cheap, and pay for a vision-language model only on pages that earn it. That's the whole decision. The rest of this is me justifying that one sentence.
The inputs to that decision are pages per hour, image resolution, privacy, latency, and exception frequency, not model fashion. Invoices and receipts arrive as supplier invoices, retail receipts, credit notes, and phone photos of crumpled slips: faded thermal paper, shadows, skewed and logo-stamped, totals disagreeing across the page. What you owe the downstream system is vendor, dates, totals, tax, currency, line items, and a pointer back to the source, inside a schema you can validate.
Tier one is boring on purpose: OCR, vendor templates, anchors, and arithmetic checks that flag a total that doesn't sum. A vision model earns a place on top only where it beats that, on bad handwriting, a template-free layout, or a label sitting fields from its value. It has no business replacing a stage that already works. Pointing every page at the biggest vision model, or reaching for an accelerator a CPU engine doesn't need, isn't ambition. It's waste.
The number that looks right is the dangerous one
The failure worth losing sleep over isn't garbled text, it's a syntactically valid amount copied from the subtotal, the balance-forward line, or a neighboring row. Language models continue plausible patterns for a living, so a damaged total gets completed into something that reads fine and is wrong. You catch malformed output on sight. Confident, well-formatted, wrong output sails through review because nobody flags a number that looks like a number.
Build an abstention path: low-resolution regions, clipped pages, glare, bad handwriting, and contradictory totals should raise an exception, not a guess dressed as an answer. Confidence scores are miscalibrated and vary engine to engine, so pair them with image-quality checks, format validators, cross-field arithmetic, and disagreement between passes.
Store the page number and bounding box for every field that matters, and show the crop beside the value: review becomes verification, not retyping. A person settles an ambiguous digit in seconds against the crop, not the whole page. Never let normalization quietly overwrite what the recognizer saw, an O read as a 0. Keep the raw read and the corrected value side by side, always.
What local hardware buys you and what it doesn't
Judge the pipeline by field and line-item accuracy, arithmetic reconciliation, duplicate detection, and exception rate, not character error rate alone. CER treats a missing comma and a wrong account number as the same mistake. Add exact-match scoring for identifiers, dates, and amounts.
Version the model, preprocessing, dictionaries, and validation together, and replay a stratified test set whenever one changes. Keep any VLM reasoning separating observation from interpretation, and keep schema validation outside the model: text on a document is untrusted input, not an instruction, and a hostile string on a fake invoice is not permission to change what happens next. Split multi-page documents on your own terms and attach identifiers before a model sees them, rather than handing over a bundle and hoping it keeps two invoices apart.
Local deployment keeps documents under your own roof and gets cheaper as steady volume climbs, but you're carrying storage, backups, model memory, and the accelerator behind it. A hosted API or frontier vision model buys elasticity and a better exception ceiling, at the cost of upload latency, retention terms, and an ongoing per-page bill. Hybrid routing earns its complexity when privacy rules are non-negotiable and escalation is visible. I wouldn't chase zero exceptions either way; a review queue catching the odd stray page beats a model trained never to admit doubt.
Before you buy an accelerator or sign up for a hosted API, pull last month's exception log and sort it by cause. If most of it is bad photographs, hardware won't fix that. If most of it is your pipeline missing a field it should have caught, that's a template problem, not a compute problem, so fix that first.