Tools,

LightOnOCR-3, tested on a real scanned PDF

1,953

of 1,955 scan words, read in order

Get the setup

On a Mac, use llama.cpp

LightOnOCR-3-4B is the new 4B model of LightOnOCR. A GGUF copy runs in llama.cpp on the Mac GPU.

1,953 of 1,955 words, in order

130 s for 10 pages, Mac GPU

Install Poppler for pdftoppm, and jq. Put llama.cpp and the two GGUF files next to the PDF, then run sh lighton3.sh. You get one Markdown file, book.md.

The full lighton3.sh script
pdftoppm -scale-to 2048 -png book.pdf page
./llama-b12316/llama-server -m gguf/LightOnOCR-3-4B.Q8_0.gguf --mmproj gguf/LightOnOCR-3-4B.mmproj-f16.gguf \
  --reasoning off --host 127.0.0.1 --port 8094 --threads 2 --no-warmup > server.log 2>&1 &
S=$!
until curl -sf http://127.0.0.1:8094/health > /dev/null; do kill -0 $S || exit 1; sleep 1; done
for p in page-*.png; do
  { printf '{"messages":[{"role":"user","content":[{"type":"image_url","image_url":{"url":"data:image/png;base64,'
    base64 -i "$p" | tr -d '\n'
    printf '"}}]}],"max_tokens":4096,"temperature":0.2,"top_p":0.9,"seed":42}'; } > req.json
  curl -sf http://127.0.0.1:8094/v1/chat/completions -H "Content-Type: application/json" -d @req.json \
    | jq -er '.choices[0].message.content' > "${p%.png}.md" || { kill $S; wait $S; exit 1; }
done
kill $S; wait $S
cat page-*.md > book.md

The settings come from the model card

The script uses the settings on the model card:

In llama-server, --reasoning off turns thinking off. No output had a think tag. A fixed seed gives the same text on a rerun.

Its 2 misses were double dashes

Both missed words ended in a long double dash, and the model wrote a single dash. Every other word matched the proofread text.

It also kept only 1 running page header. The older LightOnOCR-2 kept 3. Fewer headers means cleaner text for search.

Typed pages: all 13 cover words

LightOnOCR-3 reads every page as a picture, typed pages too. It read all 13 words of the typed cover page, COVER SHEET included.

In the 11-page file, the scan words stayed at 1,953, in 143 s.

The older 1B model is faster here

The same 10 scanned pages, scorer and Mac GPU:

Scan words in order, of 1,955
Model Words Time
LightOnOCR-3 1,953 130 s
LightOnOCR-2 1,954 72 s
GLM-OCR 1,950 118 s
DeepSeek OCR 1,948 83 s
dots.ocr 1,946 168 s

Of the 130 s, llama-server took 62 s, model start included. The rest was pdftoppm, which draws the pages at 2,048 px.

German: better than LightOnOCR-2

The script has no language option. I gave it 3 German pages. One is a magazine page with two columns. It kept the column order.

202 of 206 umlauts

961 of 972 words in order, two columns

Next to LightOnOCR-2 on the same files: 3 more umlauts, 2 more two-column words.

dots.ocr read 2 more umlauts and 8 more two-column words.

It took 80 s for the 3 pages. dots.ocr took 157 s.

LightOnOCR-3 and our Actor: 3 wins, 2 losses

Our Actor, from a file URL, with no install:

curl -s -X POST "https://api.apify.com/v2/acts/brasspage~pdf-ocr-to-markdown/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"documentUrls": ["https://api.apify.com/v2/key-value-stores/sTnmNrGoSsi7tLQvs/records/anythingllm-mixed-11-pages.pdf"]}' \
  | jq -r '.[].markdown' > mixed.md
Same files, LightOnOCR-3 on the Mac GPU
Test LightOnOCR-3 Actor
Scan words in order 1,953 1,874
Typed cover words 13 13
Umlauts 202 192
Two columns, in order 961 954
Time, 11 pages 143 s 133 s
Time, 3 German pages 80 s 59 s

LightOnOCR-3 read more words. The Actor was faster, and both read all 13 cover words.

The Actor needs no GPU and no 5.2 GB download. It costs $0.006 a page.

The license allows business use

The model card says Apache 2.0, for research and commercial use. The GitHub repo has the plain Apache 2.0 license. I read both before the download.

I planned to test Nanonets-OCR2 first. It has no license, and its base model allows only non-commercial use. So I did not test it.

How I tested it
  • Scan: the 10 pages of OCR in n8n, proofread Wikisource book scans, 1,955 true words. The 11-page file adds the 13-word typed cover page of AnythingLLM OCR.
  • German: the 3 pages of Paperless-ngx OCR, 1,887 words, 206 umlauts.
  • Score: true words found in order in book.md, with the scorer of the tessdata test. German: the scorer of Tesseract German.
  • llama.cpp b12316 for macOS on an Apple Silicon Mac GPU, at the lowest priority, 2 threads. GGUF revision d73ad16828c6, Q8_0 model and f16 mmproj. Times include one model start.
  • The GGUF copy is by mradermacher. ggml-org has none. Its files matched the Hub checksums.
  • One run each. The 11-page file holds the same 10 scan pages, and their text was the same in both runs, page for page.
  • The other models: the same files and scorer, on the same Mac, on November 12. Each page has its own setup.
  • The script above is the file that ran. The Actor: two real runs earlier the same day, build 0.1.65, on the same files.
What I did not test
  • Grounding mode, which adds a box to each block.
  • The 0.8B and 1B models, the BF16 files and the MTP copies.
  • Transformers, vLLM and NVIDIA GPUs.
  • Handwriting, tables, charts, math and forms.
Sources

Two ways to skip the work

Run it yourself. Our Apify Actor turns scanned PDFs into Markdown, tables included.

Try the Actor

Or send us the scan. Tack, our AI, does the job for $29, up to 50 pages. First page free.

Get one page free

Questions people ask

Does LightOnOCR-3 run on a Mac?

Yes. With a GGUF copy in llama.cpp on the Mac GPU, it read 10 scanned pages in 130 s. The two GGUF files are 5.2 GB.

Is LightOnOCR-3 better than LightOnOCR-2?

Not on our English scan. It read 1 word less, and the older model was faster. On German pages, it read 3 more umlauts.

Can a business use LightOnOCR-3?

Yes. The model card says Apache 2.0, for research and commercial use, with no added terms. Keep the license and notices if you share the model.