Это исходный отчёт фазы как есть — со всеми таблицами и методикой. Живая версия: 📄 Документы и OCR.
Фаза 6: документы и OCR
Прогон: 20–22 октября 2026
Хост: UGREEN DXP4800 Pro, i3-1315U, 32 GB, iGPU Intel RPL-U (Xe-LP, 64 EU)
Что меряли: насколько хорошо локальные модели читают русские документы (сканы счетов, квитанций ЖКХ, писем, рецептов, объявлений) и превращают их в JSON. Сравнивались классический OCR, специализированные OCR-модели и универсальные VLM.
Коротко
- Лучший OCR для русских документов на NAS — HunyuanOCR (1B) на iGPU.
- CER 0.1% на чистых страницах и 0.2% на сканах, все ячейки таблиц на месте (таблицы выдаёт в Markdown).
- ~11 с на страницу, 5.3 GiB.
- Так же точно читают Qwen3-VL 4B/8B и Qwen3.5-4B с визуальным адаптером (0.1–0.2%), но медленнее: 18–31 с на страницу.
- dots.ocr тоже точен (0.2%), но визуальная часть у него работает только на CPU, и выходит 87 с на страницу.
- Самый быстрый приличный вариант — PP-OCRv5 ESLAV из Immich на CPU: CER 1.5–2.1% за 0.4 с на страницу.
- Минусы: таблицу он отдаёт просто строками и иногда путает похожие латинские и кириллические буквы («ИHH»).
- GLM-OCR: 1.2–1.3% за 10 с, тоже без структуры таблиц.
- Tesseract — тот, что стоит в Paperless-ngx/OCRmyPDF, — здесь худший из рабочих. CER 8–15%, а таблицы рассыпаются («Удлинитель» → «Yanmwrens»). PaddleOCR-VL на целых страницах со сплошным текстом зацикливается на пустых строках. DeepSeek-OCR на сканах заметно хуже (11.5%).
- Документ → JSON.
- Одна модель, сразу с картинки: Qwen3.5-4B с визуальным адаптером даёт 98.1% верных полей и 77% документов без единой ошибки за 24 с.
- Две модели, OCR → Qwen3.5-4B: HunyuanOCR → Qwen3.5-4B даёт 98.5% и 80% за ~27 с (11 + 16). Это почти потолок: на исходном тексте без OCR те же 99.0% и 87%.
- Качество OCR передаётся дальше напрямую:
- PP-OCRv5 → 91.5% полей, квитанции ЖКХ 83%: строки таблицы теряют столбцы;
- Tesseract → 55%, квитанции 16%.
- Qwen3.6-35B и Gemma 4 26B в этой фазе не досчитаны. С картинками обе модели на этой сборке llama.cpp залипают через 3–20 запросов к одному серверу: Qwen3.6 после ~12 страниц отвечает пустотой или «
````» под грамматикой, Gemma 26B на 8K контекста выдаёт «de de de», а на 16K её убивает OOM. Причины и рабочую настройку нашли в фазе 7 (фото ≤1024 px, Gemma на 12K, свежий сервер каждые несколько запросов). Повторять здесь не стали: HunyuanOCR и Qwen3.5-4B и так почти у потолка (0.1–0.2% CER, 98% полей), большая модель выводов не изменит. Строки Qwen3.6 из таблиц убраны, сырые ответы сбойного прогона лежат вraw/qwen36-35b_*.jsonl`.
Как мерили
-
Документы (
make_docs.py). 30 страниц с точно известным содержимым:- 24 документа фазы 5 (счета с таблицей, заявки в управляющую компанию, письма о встречах, рецепты, объявления, отзывы);
- 6 квитанций ЖКХ с таблицей услуг (услуга, единица, объём, тариф, начислено).
Шрифты Liberation Sans/Serif/Mono и Open Sans, ширина 1240 px (A4 при 150 DPI), текст 26 px (~12 pt). Знак «₽» записан как «руб.»: в этих шрифтах его нет. - Два варианта каждой страницы: - «чисто» — цифровая отрисовка в PNG; - «скан» — серая бумага, поворот до 1.5°, размытие, шум, JPEG с качеством 60. - Движки: - классический OCR: Tesseract 5 (Debian,
rus+eng,--psm 3) и PP-OCRv5 ESLAV через ML-контейнер Immich v3.3.3 (тот, что читал кириллицу в фазе 1; детектор на 2048 px вместо 736 у Immich); - OCR-модели в llama.cpp: PaddleOCR-VL 1.6, GLM-OCR, HunyuanOCR, dots.ocr, DeepSeek-OCR, с промптами из их PR в llama.cpp (OCR:,OCR,OCR markdown); - VLM общего назначения: Qwen3-VL 4B и 8B, а также Qwen3.5-4B, Qwen3.6-35B-A3B и Gemma 4 26B-A4B с визуальными адаптерами (mmproj F16 от unsloth) поверх уже скачанных весов. Им давался русский промпт: «распознай весь текст…, таблицы в Markdown». - Сервер: llama.cppserver-vulkanbuild 11718,-c 16384 -ngl 99 -fa off --jinja --cache-ram 0,temperature 0. - Метрики: - CER по символам в порядке чтения и F1 по словам без учёта порядка. Разметка Markdown/HTML, кавычки и тире перед сравнением нормализуются. - Ячейки таблиц — доля ячеек эталона, найденных на своей строке в таблице ответа. - JSON — правила фазы 5: валидность по схеме, доля верных полей, документы без ошибок, выдуманные значения вместоnull. - Документ → JSON двумя путями: - VLM получает картинку и схему (response_format); - OCR-текст отдаётся Qwen3.5-4B (лучший быстрый извлекатель фазы 5).Потолок — тот же Qwen3.5-4B на исходном тексте документа. - Воспроизведение: - на NAS:
run_docs.sh <движок> | text2json <движки…> | score, вся цепочка —chain.sh; - клиентыdocs_client.py,tess_runner.py, оценкаscore_docs.py; - сырые ответы лежат вraw/, таблицы генерируетpython3 make_report.py.
Распознавание текста
30 документов, «чисто» — цифровая отрисовка, «скан» — поворот до 1.5°, размытие, серая бумага, шум, JPEG 60. CER — по символам в порядке чтения (ниже лучше), F1 — по словам без учёта порядка (выше лучше); разметка Markdown/HTML перед сравнением убирается. «Ячейки» — доля ячеек 10 таблиц (4 счёта, 6 квитанций ЖКХ), найденных в таблице ответа на своей строке; у классического OCR структуры таблиц нет. VLM общего назначения читали только сканы. «Стр., с» — медиана на страницу (скан).
| Движок | Тип | Где | CER чисто / скан, % | F1 слов чисто / скан, % | Ячейки таблиц, % | Стр., с | RAM, GiB |
|---|---|---|---|---|---|---|---|
| Tesseract 5 (rus+eng) | классический OCR | CPU | 8.2 / 15.3 | 91.7 / 83.9 | 0.0 | 0.3 | — |
| PP-OCRv5 ESLAV (через Immich ML) | классический OCR | CPU | 1.5 / 2.1 | 95.7 / 95.0 | 0.0 | 0.4 | 0.5 |
| PaddleOCR-VL 1.6 (0.9B) | OCR-модель | iGPU | 60.0 / 60.5 | 39.9 / 39.8 | 0.0 | 47.2 | 5.2 |
| GLM-OCR (0.9B) | OCR-модель | iGPU | 1.2 / 1.3 | 95.6 / 95.3 | 0.0 | 10.3 | 5.2 |
| HunyuanOCR (1B) | OCR-модель | iGPU | 0.1 / 0.2 | 99.9 / 99.9 | 100.0 | 10.9 | 5.3 |
| dots.ocr (1.7B) | OCR-модель | iGPU | — / 0.2 | — / 99.8 | 100.0 | 87.0 | 6.1 |
| DeepSeek-OCR (3B MoE) | OCR-модель | iGPU | 3.7 / 11.5 | 95.9 / 90.7 | 80.0 | 18.6 | 7.9 |
| Qwen3-VL-4B | VLM | iGPU | — / 0.2 | — / 99.7 | 99.3 | 17.9 | 8.8 |
| Qwen3-VL-8B | VLM | iGPU | — / 0.1 | — / 100.0 | 100.0 | 30.6 | 11.1 |
| Qwen3.5-4B + mmproj | VLM | iGPU | — / 0.2 | — / 99.7 | 100.0 | 19.6 | 7.4 |
Документ → JSON
Те же 30 документов (сканы), схемы и эталоны из фазы 5 плюс квитанции ЖКХ (лицевой счёт, период, плательщик, услуги с объёмом, тарифом и суммой, итог). «Сразу из картинки» — VLM получает изображение и JSON Schema (response_format, [0-9] вместо \d). «OCR → LLM» — текст движка OCR отдаётся Qwen3.5-4B с тем же промптом; «без OCR» — Qwen3.5-4B получает исходный текст документа, это потолок для конвейера. «Время» — медиана на документ: только сама модель извлечения (для OCR → LLM без времени OCR).
| Путь | Валидно, % | Поля, % | Документ без ошибок, % | Выдумано вместо null | Квитанции ЖКХ, % полей | Время, с |
|---|---|---|---|---|---|---|
| без OCR (исходный текст) → Qwen3.5-4B | 100 | 99.0 | 87 | 0 из 11 | 100.0 | 16.0 |
| Qwen3-VL-4B, сразу из картинки | 100 | 92.9 | 53 | 1 из 11 | 89.7 | 20.4 |
| Qwen3-VL-8B, сразу из картинки | 100 | 95.6 | 67 | 1 из 11 | 93.4 | 34.0 |
| Qwen3.5-4B + mmproj, сразу из картинки | 100 | 98.1 | 77 | 0 из 11 | 100.0 | 23.9 |
| Tesseract 5 (rus+eng) → Qwen3.5-4B | 100 | 55.1 | 43 | 0 из 11 | 16.5 | 15.9 |
| PP-OCRv5 ESLAV (через Immich ML) → Qwen3.5-4B | 100 | 91.5 | 60 | 0 из 11 | 83.1 | 15.9 |
| PaddleOCR-VL 1.6 (0.9B) → Qwen3.5-4B | 100 | 30.6 | 33 | 0 из 11 | 4.3 | 12.2 |
| GLM-OCR (0.9B) → Qwen3.5-4B | 100 | 96.1 | 60 | 0 из 11 | 98.1 | 16.3 |
| HunyuanOCR (1B) → Qwen3.5-4B | 100 | 98.5 | 80 | 0 из 11 | 100.0 | 16.0 |
| dots.ocr (1.7B) → Qwen3.5-4B | 100 | 98.5 | 80 | 0 из 11 | 100.0 | 16.0 |
| DeepSeek-OCR (3B MoE) → Qwen3.5-4B | 100 | 78.2 | 70 | 0 из 11 | 67.1 | 15.1 |
Выводы и рекомендации
Что ставить
| Задача | Рекомендация | Почему |
|---|---|---|
| Текстовый слой для архива документов (поиск по сканам) | HunyuanOCR на iGPU, или PP-OCRv5 ESLAV на CPU, если важна скорость | 0.1–0.2% CER и таблицы в Markdown за ~11 с; или 1.5–2% за 0.4 с |
| Разбор документов в JSON (счета, квитанции, заявки) | Qwen3.5-4B + mmproj, сразу с картинки, json_schema с [0-9] |
одна модель на всё: 98% полей, 24 с на документ, та же модель, что для голосового ассистента |
| То же, но с сохранением текста | HunyuanOCR → Qwen3.5-4B | 98.5% полей и чистый текст страницы, ~27 с |
| Не брать | Tesseract, PaddleOCR-VL (без своего конвейера разметки), DeepSeek-OCR на сканах | ошибки и развал таблиц |
Для Paperless-ngx это означает: штатный Tesseract плохо читает русские сканы с таблицами. Лучше подать документы, распознанные HunyuanOCR или PP-OCRv5, через свой предобработчик или API.
Что видно в цифрах
- Специализированная маленькая OCR-модель (1B) не уступает универсальным VLM в 4–8 раз больше. HunyuanOCR читает так же точно, как Qwen3-VL-8B, втрое быстрее и вдвое легче.
- Скан против чистой страницы. Поворот, размытие и JPEG почти не влияют на сильные модели (+0–0.1 п.п. CER). А у Tesseract ошибок вдвое больше, у DeepSeek-OCR втрое.
- Структура таблиц решает для JSON. Классический OCR отдаёт таблицу строками без разделителей, и LLM по ним путает столбцы: квитанции ЖКХ 83% против 100% после HunyuanOCR.
- Скорость упирается в генерацию текста, а не в разбор картинки. На страницу уходит 200–400 токенов ответа, а модели генерируют 8–25 tok/s.
Ограничения
- Документы синтетические: напечатаны известными шрифтами, без рукописного текста, печатей, многоколоночной вёрстки и фотографий с телефона.
- «Скан» — программная имитация: поворот до 1.5°, размытие, JPEG. Настоящие плохие сканы и фото под углом могут оказаться тяжелее.
- Промпты для OCR-моделей взяты из их PR в llama.cpp. Их собственные конвейеры (разметка страницы, затем распознавание блоков) могли бы работать лучше, особенно у PaddleOCR-VL.
Заметки для фреймворка
- dots.ocr на этой сборке и Vulkan выдаёт мусор («tasktasktask…») при любом промпте. С визуальным энкодером на CPU (
--no-mmproj-offload) он читает правильно, но страница занимает ~3 мин. - PaddleOCR-VL нужен свой шаблон чата. Это
--chat-template-fileиз репозитория GGUF, без него llama-server работает с ошибками, как сказано в PR. - Gemma 4 26B с картинками:
--swa-full,cache_prompt: falseи ровно 12K контекста: на 8K ответы — мусор («de de de»), на 16K с полным SWA-кэшем и адаптером процесс упирается в лимит 20 GB и его убивает OOM-киллер. - Qwen3.6-35B и Gemma 4 26B с картинками залипают через 3–20 запросов к одному серверу (пустые ответы, «
````», одна и та же буква),cache_prompt: falseне спасает,/slots/0?action=eraseв этой сборке не реализован. Рабочий обход — перезапуск сервера каждые несколько запросов (CHUNKвrun_docs.shиrun_vlm.sh`).