Skip to main content

Qwen3-VL vs EasyOCR in Docling: 10x Slower, No Boxes

· 7 min read
Bogdan Varlamov
Bogdan Varlamov
Technologist

Docling's vision-language model pipeline, running the 4B-parameter Qwen3-VL model locally, processed 444 cookbook page images for the soviet.recipes project at a 99.5% success rate. Compared to the earlier EasyOCR run, it gave up every piece of debugging output that run had: no text-region overlays, no per-page confidence score, no reading-order artifacts. It also ran far slower, and Docling still processes one-page image documents one at a time, so the batch took about 26.3 hours.

The run

The pipeline ran Docling's VLM backend against unsloth/Qwen3-VL-4B-Instruct-GGUF:Q8_0, a Q8_0-quantized 4B-parameter Qwen3-VL model, with workers=1 and vlm_scale=2.0. It processed 444 images one at a time, against the pipeline's log output:

MetricValue
Total images444
Successful442
Failed2
Success rate99.5%
Total processing time94,676.15s (about 26.3 hours)
Average per imageabout 213s (about 3.5 minutes)

No debug output this time

The EasyOCR proof-of-concept produced a reading-order debug view: numbered boxes over every detected region, with the sequence Docling read them in overlaid as arrows. Where a page curved away from the camera, that overlay showed exactly which region got dropped, and EasyOCR reported a confidence score for what it did read.

A vision-language model reads the whole page image and generates text directly instead of detecting regions first, so that debug output has nothing to attach to. The Qwen3-VL pipeline returns page text with no bounding boxes, no confidence score, and no artifact showing which part of the page produced which line of output. When the model misses or misreads something, there's nothing to point at, just the text it decided to write.

The reason is baked into how the pipeline works. Docling's VlmPipeline is end-to-end: the page image goes straight to the model, and the model returns DocTags. Coordinates only exist in that output if the model writes them. Docling's export_to_doctags() takes each element's bounding box, normalizes it to the page, and scales it to a 0-500 grid, so a real box turns into real location tokens. Qwen3-VL is a general-purpose VLM that was never trained on that coordinate grid, so a plain "convert to DocTags" prompt returns structure and text but no reliable location tokens. Every element comes through as loc_0, which is why there are no regions to draw.

Serial batching, still

The batch-processing pipeline post already flagged this: Docling's convert_all() batches across documents, and each page here is its own one-page document, so there's no parallelism for the wrapper to reach through. That post estimated the Docling VLM path at roughly three minutes a page against a local server, projecting to about ten hours across 224 pages.

The 444 in this run isn't the same 224 from that estimate. The original 224 DSLR captures are open-book spreads with two text pages per photo, minus 2 cover images that carry no recipe text. A preprocessing step splits each spread down the middle into two single-page images, which is where 444 comes from, twice the page count behind the same ten-hour estimate.

This run processed those 444 images in 94,676.15 seconds, about 26.3 hours, averaging around 213 seconds (about 3.5 minutes) per image. Docling's internal concurrency fans out across the pages within one document, and every image here is a single-page document on its own, so that concurrency has nothing to distribute across. The batch ran the same way the earlier estimate assumed: one page after another.

I re-ran the Docling w/EasyOCR pass over the same 444 images. It processed all 444 at a 100% success rate in 9,274.52 seconds, about 2.6 hours, averaging roughly 21 seconds per image. That's about ten times faster than the VLM pipeline's 26.3 hours, and it completed all 444 pages while the VLM run failed 2. That EasyOCR pass ran on the CPU, and on this machine it has no other option. EasyOCR runs on PyTorch, which only accelerates through CUDA on NVIDIA hardware. This machine has an AMD Radeon 8060S, the integrated GPU on the Ryzen AI Max+, with no NVIDIA card, so PyTorch has no usable Windows GPU backend to fall back on. EasyOCR is CPU-bound here regardless of configuration, which is why the run reports Accelerator device: 'cpu' and the roughly 21-seconds-per-page timing. The VLM pipeline used the GPU while EasyOCR could not, so the timing gap reflects the hardware each run had available rather than the two engines' raw compute cost.

Two bar charts comparing Docling's Qwen3-VL 4B VLM pipeline against EasyOCR on 444 Soviet cookbook pages. Total processing time was 26.3 hours for Qwen3-VL versus 2.6 hours for EasyOCR, and average time per page was 213 seconds versus 21 seconds. EasyOCR ran about ten times faster on CPU.

Next steps

The next step is a comparison script that diffs the VLM output against the EasyOCR output for every page and flags where the two disagree. Pages where both methods extracted the same text are the working assumption for "probably accurate," since agreement between two independently-run engines is a cheaper signal than grading each page by hand. The diverging pages are the ones worth a closer look, since a disagreement means at least one engine got something wrong.

I might also extend the pipeline with a DocTags-native VLM. SmolDocling and Granite-Docling are trained to emit <loc_x> location tokens as part of DocTags, so pointing the VLM pipeline at one of them instead of Qwen3-VL would carry real bounding boxes through the output. The existing get_visualization('reading_order') code would start producing meaningful debug images again with no other changes, which brings back the region overlays the EasyOCR run had.

Both are small enough that hardware is a non-issue here. SmolDocling is 256M parameters and Granite-Docling is 258M, roughly a fifteenth the size of the 4B Qwen3-VL this run used. The weights land around 256MB at 8-bit and about 512MB at bf16, so they fit in memory with room to spare and run locally without the multi-hour batch times the 4B model needed. The tradeoff is that a 256M-class model may transcribe less accurately than the 4B, so this is a swap for layout visibility, not a bet on cleaner text.

Real bounding boxes would also make the comparison script itself better. Right now the plan diffs raw VLM text against EasyOCR text, and the two engines lay out their output differently, so some disagreements are formatting noise rather than genuine misreads. If the VLM emits regions the way EasyOCR does, the diff can align text region by region instead of comparing two flat blobs, which is a closer apples-to-apples read on where the engines actually disagree.

Related Posts

This work is part of the soviet.recipes project. See the phase 2 planning post, the EasyOCR proof-of-concept, and the batch-processing pipeline post for the earlier steps, and the soviet-recipes-project tag for everything related.