Tools,

LightOnOCR, tested on a real scanned PDF

1,954

of 1,955 scan words, read in order

Get the setup

On a Mac, use llama.cpp

LightOnOCR-2 is a small model, 1B. Its GGUF model runs in llama.cpp on the Mac GPU.

1,954 of 1,955 words, in order

72 s for 10 pages, Mac GPU

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

The full lighton.sh script
pdftoppm -scale-to 1540 -png book.pdf page
./llama-b12316/llama-server -m gguf/LightOnOCR-2-1B-Q8_0.gguf --mmproj gguf/mmproj-LightOnOCR-2-1B-Q8_0.gguf \
  --host 127.0.0.1 --port 8093 --threads 2 --no-warmup > server.log 2>&1 &
S=$!
until curl -sf http://127.0.0.1:8093/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:8093/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:

llama-mtmd-cli cannot send an image with no text. So the script starts llama-server once and sends each page to it. A fixed seed gives the same text on a rerun.

Typed pages: all 13 cover words

LightOnOCR 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,954, in 72 s.

The top scan score in our tests

The same 10 scanned pages, scorer and Mac GPU:

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

All five models are close on words. LightOnOCR was also the fastest.

German: two columns in order

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.

199 of 206 umlauts

959 of 972 words in order, two columns

Next to GLM-OCR on the same files: 8 more umlauts, 8 more two-column words.

dots.ocr read 5 more umlauts and 10 more two-column words.

It took 157 s for the 3 pages. LightOnOCR took 41 s.

LightOnOCR and our Actor: 5 wins, 1 tie

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 on the Mac GPU
Test LightOnOCR Actor
Scan words in order 1,954 1,874
Typed cover words 13 13
Umlauts 199 192
Two columns, in order 959 954
Time, 11 pages 72 s 133 s
Time, 3 German pages 41 s 59 s

LightOnOCR read more words, and faster. Both read all 13 cover words.

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

The license allows business use

The LightOnOCR-2 model card says Apache 2.0. I read it before the download. It adds no use terms.

I planned to test HunyuanOCR first. Its license does not apply in the EU, the UK and South Korea, and it bans use of the output there. 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 7d4daf70c285, Q8_0 model and Q8_0 mmproj. Times include one model start.
  • 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
  • LightOnOCR-3, the newer 4B model. It has its own test: LightOnOCR-3.
  • The f16 files, the bbox models and the base model.
  • Transformers, vLLM and NVIDIA GPUs.
  • Handwriting, tables, 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 run on a Mac?

Yes. With its GGUF model in llama.cpp on the Mac GPU, LightOnOCR read 10 scanned pages in 72 s. The two GGUF files are 1.1 GB.

What prompt does LightOnOCR need?

None. The model card sends only the page image, with the longest side at 1,540 px, at temperature 0.2 and top_p 0.9.

Can a business use LightOnOCR?

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