PaddleOCR-VL, tested on a real scanned PDF
1,947
of 1,955 scan words, read in order
On a Mac, use llama.cpp
PaddleOCR-VL 1.6 has a GGUF model. llama.cpp runs it on the Mac GPU. The layout model stays on the CPU.
1,947 of 1,955 words, in order
73 s for 10 pages, Mac GPU
Install paddlepaddle,
paddleocr[doc-parser] and python-docx. Put
llama-server and the two GGUF files next to the PDF, then run
sh llama.sh. Each page gets a Markdown file in out/.
The full llama.sh script
./llama-b12316/llama-server -m gguf/PaddleOCR-VL-1.6-GGUF.gguf --mmproj gguf/PaddleOCR-VL-1.6-GGUF-mmproj.gguf --port 8091 --temp 0 --threads 2 2> server.log &
SERVER=$!
until curl -sf http://127.0.0.1:8091/health > /dev/null; do sleep 1; done
paddleocr doc_parser -i book.pdf --pipeline_version v1.6 --vl_rec_backend llama-cpp-server --vl_rec_server_url http://127.0.0.1:8091/v1 --save_path out --cpu_threads 2
CODE=$?
kill $SERVER
exit $CODE
Two stock problems on a Mac
- The model card's own command runs the model in Paddle on the CPU. With 2 threads, no page was done after 10 minutes. I stopped it.
-
The CLI also saves each page as a Word file, and
paddleocr[doc-parser]3.7.0 does not install python-docx. It wrote page 1, then stopped: No module named 'docx'. Exit code 1.
The second one leaves a good Markdown file for page 1 only. Count the pages before you trust the output.
Typed pages: 13 of 13 cover words
PaddleOCR-VL reads every page as a picture, typed pages too. In the 11-page file, it read all 13 words of the typed cover page. The scan words stayed at 1,947, in 75 s.
German worked with no language setting
The doc parser has no language option. I gave it 3 German pages. One is a magazine page with two columns.
188 of 206 umlauts
956 of 972 words in order, two columns
PaddleOCR-VL beat our Actor here
Our Actor makes Markdown 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
| Test | PaddleOCR-VL | Actor |
|---|---|---|
| Scan words in order | 1,947 | 1,874 |
| Typed cover words | 13 | 13 |
| Umlauts | 188 | 192 |
| Two columns, in order | 956 | 954 |
| Time, 11 pages | 75 s | 133 s |
| Time, 3 German pages | 40 s | 59 s |
The Actor read 4 more umlauts. It needs no GPU and no 3.2 GB setup. It costs $0.006 a page.
The license allows business use
The model, the layout model and the code are Apache 2.0. I read the full license file before the download. It adds no use terms.
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 the Markdown pages, with the scorer of the tessdata test. German: the scorer of Tesseract German.
- paddlepaddle 3.3.1, paddleocr 3.7.0, paddlex 3.7.2, Python 3.12 arm64, llama.cpp b12316 for macOS, on an Apple Silicon Mac GPU, at the lowest priority. GGUF revision 511b09642bb3, layout model PP-DocLayoutV3. One run each. Times include the model start.
- 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
- The CPU path with more than 2 threads. I cap jobs at 2 threads on this Mac.
- The mlx-vlm-server backend, the transformers script and the Ollama community upload.
- The GGUF card's tip to raise image_max_pixels. I used the files as shipped.
- vLLM, NVIDIA GPUs, handwriting, tables, math and forms.
Two ways to skip the work
Run it yourself. Our Apify Actor turns scanned PDFs into Markdown, tables included.
Or send us the scan. Tack, our AI, does the job for $29, up to 50 pages. First page free.
Questions people ask
Does PaddleOCR-VL run on a Mac?
Yes. With its GGUF model in llama.cpp on the Mac GPU, it read 10 scanned pages in 73 s. The Paddle CPU path with 2 threads did not finish page 1 in 10 minutes.
Why does paddleocr doc_parser say No module named 'docx'?
The CLI also saves each page as a Word file, and paddleocr[doc-parser] does not install python-docx. It wrote page 1, then stopped. Run pip install python-docx.
Can a business use PaddleOCR-VL?
Yes. The model, the layout model and the code are Apache 2.0, with no added use terms. Keep the license and notices if you share the model.
Related guides
- MinerU on a Mac: setup and a scanned PDF test
- PaddleOCR on a Mac: CPU setup and results, tested
- GLM-OCR on a scanned PDF: Mac setup and results, tested
- DeepSeek OCR on a scanned PDF: Mac setup and results, tested
- dots.ocr on a scanned PDF: Mac setup and results, tested
- Chandra OCR on a scanned PDF: Mac setup and results, tested
- Surya OCR on a scanned PDF: setup and results, tested
- EasyOCR vs Tesseract vs PaddleOCR vs RapidOCR, tested
- PDF to Markdown in Python: 5 libraries tested