All Posts
AI Engineering July 24, 2026

Local OCR on Apple Silicon: Surya vs docTR vs PaddleOCR on M5 Max (2026)

There is a real reason to run OCR on your own hardware rather than a cloud document API: invoices, purchase orders, and contracts are exactly the documents you do not want leaving your network, and per page cloud pricing turns a back office batch job into a recurring bill. The question is whether a Mac can do it fast and accurately enough to replace the API. We put Surya, docTR, and PaddleOCR on an M5 Max, measured pages per second and character error rate on real business documents, and compared them against a local vision language model doing the same job, so you can see which engine fits which document and where the accuracy actually falls apart.

Local OCR on Apple Silicon: Surya vs docTR vs PaddleOCR on M5 Max (2026)

If you are pulling structured data out of invoices, purchase orders, or scanned contracts, the first decision is not which parsing schema to use. It is whether the pixels ever leave your machine. A cloud document API is easy to call and expensive to keep calling, and it means every invoice a supplier sends you gets uploaded to someone else's server. Running OCR locally on Apple Silicon removes both problems at once, but only if the engine you choose is fast enough to clear the day's document queue and accurate enough that the downstream extraction does not inherit garbage. We tested the three OCR engines people actually reach for, plus a local vision model, on an M5 Max. Here is what each one is good for.

Headline Comparison

Engine Approach Pages/sec (M5 Max) Character error rate, clean scans Character error rate, noisy scans Peak memory
PaddleOCR (PP-OCRv4) detection plus recognition, CNN 6.8 1.4% 5.9% 2.1 GB
docTR detection plus recognition, configurable 4.1 1.6% 6.7% 3.4 GB
Surya transformer detection and recognition 1.9 1.1% 3.8% 5.2 GB
Qwen2.5-VL 7B (4-bit, MLX) vision language model 0.4 1.3% 3.1% 9.8 GB

Read the throughput column and the noisy scan column together, because that pairing is the whole decision. PaddleOCR is roughly three times faster than Surya and clears clean documents at nearly the same accuracy, but its error rate more than quadruples on degraded scans. Surya is slower and heavier and holds its accuracy when the input gets ugly. The vision model is the most robust on messy documents and by far the slowest, which tells you it belongs on the exceptions, not the main line. The methodology, the hardware, and where each engine breaks are below.

Test Setup

Every number here came from the same machine and the same corpus so the comparison is honest. Hardware was a MacBook Pro with an M5 Max, 64 GB of unified memory, running each engine single stream with no other heavy process resident. The corpus was 500 pages split into two halves: 250 clean pages rendered from digital PDFs at 200 DPI, and 250 noisy pages that were photographed or faxed then rescanned, carrying skew, shadow, and compression artifacts. Character error rate was computed against a hand corrected ground truth using standard edit distance over the recognized text, so a lower number means fewer wrong, missing, or extra characters. Throughput is end to end wall clock, including image load and layout detection, not just the recognition forward pass, because that is the number that governs how long your batch takes.

PaddleOCR and docTR ran on their default CPU and Metal accelerated paths. Surya ran on its MPS backend so the transformer used the GPU. The Qwen2.5-VL 7B model ran 4-bit quantized through MLX, which is the configuration that fits comfortably in unified memory and keeps decode responsive. That last point matters: a vision model does OCR by generating text token by token, so its speed is bounded by decode throughput the same way any local language model is, and the local vision model benchmarks apply directly here.

Where Each Engine Wins

PaddleOCR is the throughput champion and the right default for high volume, reasonably clean input. Its PP-OCR pipeline pairs a light detection model with an efficient recognizer, so it moves fast and stays small in memory, which means you can run several worker processes on one machine without exhausting unified memory. The catch is fragility. On the noisy half of the corpus its character error rate climbed to almost 6 percent, and the failures were the expensive kind: dropped decimal points, merged digits, and misread currency symbols, exactly the characters that matter most on an invoice. If your documents are born digital or scanned under controlled conditions, PaddleOCR is hard to beat. If they arrive as phone photos, it will quietly corrupt the fields you care about.

Surya is the engine to reach for when accuracy on imperfect input is the requirement. Its transformer detection and recognition hold up under skew and noise far better than the CNN pipelines, and it handles multi column layouts and mixed scripts with less hand tuning. You pay for that with throughput and memory. At under 2 pages per second it is the slowest of the three dedicated engines, and at north of 5 GB resident it limits how many parallel workers you can stack. For a nightly batch of a few thousand pages where correctness beats speed, that trade is usually worth it.

docTR sits in the middle on every axis, and its real value is flexibility rather than a top score on any single metric. It lets you swap detection and recognition models independently, so you can tune the pipeline to your document class rather than accepting a fixed recipe. If you have an unusual layout or a specific font family that the packaged models mishandle, docTR gives you the most room to fix it. If you just need good numbers out of the box, PaddleOCR or Surya will get you there with less assembly.

The Vision Model Exception

Qwen2.5-VL 7B was the most robust engine on the noisy corpus, and it was also more than four times slower than Surya and needed nearly twice the memory. That profile is not a general purpose OCR engine, it is a specialist for the documents that break the others. A vision language model does not just transcribe, it can read a semi structured layout and return the fields you ask for, which means for a mangled invoice you can skip separate OCR and extraction steps and ask directly for the totals and line items. The cost is throughput that makes it unusable as your primary path.

The pattern that works is a cascade. Run PaddleOCR or Surya over the whole queue, score confidence per page, and route only the low confidence pages to the vision model. On our corpus that routed roughly 8 percent of pages to the slow path while lifting overall field accuracy close to the vision model's level, at a fraction of its total runtime. Here is the routing skeleton.

def extract_page(image, threshold=0.80):
    result = fast_ocr(image)          # PaddleOCR or Surya
    if result.mean_confidence >= threshold:
        return result.text
    # low confidence: fall back to the vision model
    return vlm_extract(image, prompt="Return all text, preserve line items.")

The threshold is the tuning knob. Set it high and you route more pages to the slow model, buying accuracy with runtime. Set it low and the batch finishes faster but more errors slip through. Measure it against your own documents rather than trusting a default, because the confidence distribution depends heavily on scan quality.

Decision Table

Your situation Use
High volume, clean or digital scans PaddleOCR
Noisy, skewed, or photographed documents Surya
Unusual layouts or fonts needing custom models docTR
Semi structured extraction where fields matter more than raw text Vision model on the exceptions only
Mixed quality queue at scale Fast engine plus vision model fallback

When This Applies to Your Stack

If you are extracting data from documents that carry supplier, customer, or financial information, local OCR on Apple Silicon is a serious option, not a compromise. An M5 Max clears thousands of clean pages an hour with PaddleOCR while keeping the data on your own hardware, and a Surya or vision model fallback catches the pages that would otherwise poison your extraction. The mistake is picking one engine and running your whole queue through it. Clean and noisy documents want different engines, and the cheapest reliable pipeline routes between them on a confidence signal rather than paying vision model latency on every page or accepting fast engine errors on the hard ones. Once the text is clean, the same machine can run the retrieval and extraction stages of a local pipeline without a round trip to a cloud API.

If your team is wiring document extraction into an accounting or commerce backend, for example turning supplier invoices into NetSuite records through an integration layer, Contra Collective builds AI infrastructure that runs the OCR and extraction where the data lives. Keeping the pixels on your own hardware is a policy decision that a local pipeline finally makes cheap.

FAQ

Is local OCR on a Mac accurate enough to replace a cloud document API? On clean, digitally rendered documents, yes. PaddleOCR and Surya both landed near or below 1.5 percent character error rate on our clean corpus, which is competitive with cloud OCR. The gap opens on degraded scans, where cloud engines and local vision models both hold accuracy better than fast local CNN pipelines. A cascade that falls back to a local vision model on low confidence pages closes most of that gap while keeping data on your hardware.

Which local OCR engine is fastest on Apple Silicon? PaddleOCR was the fastest in our testing at about 6.8 pages per second on an M5 Max, roughly three times Surya's throughput, because its detection and recognition models are lightweight. That speed comes with more fragility on noisy input, so the fastest engine is not automatically the right one for your document mix.

Why is a vision language model so much slower than a dedicated OCR engine? Because a vision model produces text by generating tokens one at a time, so its speed is bounded by decode throughput just like any local language model, while a dedicated OCR engine runs a single detection and recognition pass. That is why the 7B vision model ran under half a page per second while the CNN pipelines cleared several pages per second on the same machine.

How much memory do I need to run local OCR? The dedicated engines are modest: PaddleOCR peaked near 2 GB and Surya near 5 GB in our tests, so a 16 GB or 24 GB Mac handles them comfortably. A 7B vision model quantized to 4-bit needs closer to 10 GB resident, so plan for a machine with 32 GB or more if the vision fallback runs alongside other work.

Can these engines handle tables and multi column layouts? Surya and docTR handle complex layouts more reliably than PaddleOCR out of the box, and a vision model can be prompted to preserve table structure directly. For invoices with line item tables, either run a layout aware engine or route those pages to the vision model, which can return structured line items rather than a flat text dump.

[ 02 ] — Keep Reading

More from the lab.

Oct 4, 2026 AI Engineering

The Model Context Protocol's STDIO Problem: Inside the Latest Remote Code Execution Disclosure

Security researchers at OX Security found that the Model Context Protocol's STDIO transport interface runs any process command passed to it on the host system before it ever confirms the subprocess is a valid MCP server, an execute first, validate never pattern that completes an injected command even when the caller eventually gets an error message back. The scope is not small: 150 million downloads across package registries, more than 7,000 publicly accessible MCP servers, and up to 200,000 vulnerable instances across every officially supported SDK, Python, TypeScript, Java, and Rust. Anthropic's response, after OX disclosed in January 2026, was to update documentation and call the behavior intentional. Here is what that actually means if your team runs MCP servers over STDIO.

Sep 30, 2026 AI Engineering

OpenAI's Decisions API: Real-Time Agentic Decision-Making for Enterprise Workflows, Explained

Buried inside OpenAI's DevDay 2026 announcement list alongside GPT-6.1 Sol and Dots agents was a smaller, more technically specific release: a Decisions API built for real time decision making against predefined answer sets. Where the Agents API and a general purpose agent loop are built for open ended reasoning across an unbounded task, the Decisions API is built for the opposite case, a small number of valid outcomes and a requirement that the choice happen fast. This post works through what that distinction actually means in practice and where a bounded decision primitive fits into an existing agentic workflow.

Aug 8, 2026 AI Engineering

Offline Batch Inference on an M5 Max: Maximizing Overnight Throughput for a Prompt Backlog (2026)

Most writing about local inference assumes a human is on the other end, watching tokens appear and judging the model by how fast the first one lands. Batch work is the opposite situation, and it is more common than the interactive framing suggests: you have a backlog of tens or hundreds of thousands of prompts, nobody is waiting on any single one, and the only thing that matters is how much of the pile you can clear before morning. Product descriptions for a catalog, classification over a support archive, embeddings and summaries for a corpus, synthetic data for a fine tune. In that regime latency is irrelevant and throughput is everything, and the settings that make a local server feel responsive to a person actively work against you, because they optimize for the wrong number. Running one prompt at a time on an M5 Max leaves most of the machine idle, since a single decode stream cannot saturate the memory bandwidth the chip has to offer. The job is to keep many sequences in flight so the expensive weight reads are amortized across all of them, and to arrange the backlog so the GPU never waits on padding, scheduling, or a slow tail. This post is about doing that deliberately: what actually bounds batch throughput on Apple Silicon, how far batching takes you before memory stops you, and the operational scaffolding that turns a fragile overnight run into one you can resume when it dies at 3am.

Ready when you are

Want to discuss this topic?

Start a Conversation