Недавно получил задачу сделать автоматизированную оцифровку характеристик из паспортов товаров в БД, а не изменение параметров вручную в ERP. Я подумал, было бы здорово поделиться, как я это сделал, с вами на Хабре!
Базовые задачи:
Нужно, чтобы это все работало локально
Система должна принимать разные форматы (.doc, .pdf, .png)
Возможность создавать динамические таблицы, куда ИИ будет заполнять сама информацию, а не хардкодить для каждой категории паспорта свои отчеты
Желательно, чтобы все работало на одной видеокарте (в моем случае 3090 на 24GB VRAM)
Для реализации задачи, я решил развернуть 2 ИИ модели — deepseek-ocr-7b и Qwen-1.5. Первая будет считывать текст из изображений, а вторая преобразовывать сырой текст в json, который, затем, будет распределяться по полям таблицы.
Скачать весь проект вы можете тут — https://github.com/Chashchin-Dmitry/llm-ocr-handmade
Шаг 1. Подготовка системы
1.1 Проверка драйвера NVIDIA
Для начала, нам нужно установить драйвер NVIDIA. В моем случае, это 576.66 серия, но если у вас другая видеокарта, то введите свою модель и серию тут.

Перезагружаем комп/сервер и пишем команду:
nvidia-smi
Если команда показала что-то с Nvidia и номер модели, значит мы удачно скачали драйверы.
1.2 Установка Docker с GPU поддержкой
Windows:
-Docker Desktop + включить WSL2 backend
-В настройках Docker Desktop включить "Use WSL 2 based engine"
Linux:
# NVIDIA Container Toolkit
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
sudo systemctl restart docker
1.3 Проверка GPU в Docker
docker run --rm --gpus all nvidia/cuda:12.1-base nvidia-smi
Должна показать твою видеокарту.
Шаг 2: Выбор версии vLLM
Очень важно подобрать под себя верную версию vLLM под вашу видеокарту. Немаловажно, она должна быть совместима с вашей версией драйвера.
Для DeepSeek-OCR и Qwen-1.5, я выбрал vLLM v0.11.2 с CUDA 12.9 (с версией ниже, у вас ничего не запустится)
# Скачивание образа (43GB!)
docker pull vllm/vllm-openai:v0.11.2
Подробнее, как подобрать нужную версию vLLM под вашу GPU можно тут.
Шаг 3: Выбор моделей
OCR
Мне понравилось отзывы от новой deepseek-ocr, а еще она очень компактная, поэтому, я решил, что она идеально подойдет к моему кейсу. Вот ее требования:
DeepSeek-OCR:
Модель:
deepseek-ai/DeepSeek-OCRРазмер: 3B параметров
VRAM: ~8-10GB
Задача: Извлечение текста из изображений
Модель для структуризации
Моя задача состоит развернуть все разом на одной видеокарте, поэтому я выбирал самые маленькие LLM, которые только есть. Более того, мне надо сырой текст преобразовывать в json, с чем микро модели будут справляться идеально и, самое главное, быстро.
Вот какие могут быть варианты для вас:
Модель |
Параметры |
VRAM |
Качество |
|---|---|---|---|
Qwen2.5-1.5B-Instruct |
1.5B |
~5GB |
Базовое |
Qwen2.5-3B-Instruct |
3B |
~8GB |
Хорошее |
Qwen2.5-7B-Instruct |
7B |
~16GB |
Отличное |
Мы выбрали 1.5B — влезает вместе с OCR на 24GB и достаточен для структуризации JSON.
Шаг 4: Конфигурация docker-compose.yml
version: '3.8'
services:
# DeepSeek-OCR
vllm-ocr:
image: vllm/vllm-openai:v0.11.2
container_name: vllm-ocr
runtime: nvidia
environment:
- NVIDIA_VISIBLE_DEVICES=all
ports:
- "8001:8000"
volumes:
- ~/.cache/huggingface:/root/.cache/huggingface
command: >
--model deepseek-ai/DeepSeek-OCR
--trust-remote-code
--max-model-len 4096
--gpu-memory-utilization 0.30
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
# Qwen для структуризации
vllm-qwen:
image: vllm/vllm-openai:v0.11.2
container_name: vllm-qwen
runtime: nvidia
environment:
- NVIDIA_VISIBLE_DEVICES=all
ports:
- "8002:8000"
volumes:
- ~/.cache/huggingface:/root/.cache/huggingface
command: >
--model Qwen/Qwen2.5-1.5B-Instruct
--trust-remote-code
--max-model-len 16384
--gpu-memory-utilization 0.35
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
# FastAPI Backend
backend:
build: .
container_name: ocr-backend
ports:
- "8000:8000"
volumes:
- ./backend:/app/backend
- ./frontend:/app/frontend
- ./uploads:/app/uploads
environment:
- DATABASE_URL=mysql+pymysql://root:password@host.docker.internal:3306/ocr_documents
- VLLM_OCR_URL=http://vllm-ocr:8000
- VLLM_QWEN_URL=http://vllm-qwen:8000
extra_hosts:
- "host.docker.internal:host-gateway"
command: uvicorn backend.main:app --host 0.0.0.0 --port 8000 --reload
Ключевые параметры:
Параметр |
Значение |
Зачем |
|---|---|---|
|
0.30 / 0.35 |
Делим GPU между моделями |
|
4096 / 16384 |
Максимальный контекст |
|
- |
Для кастомного кода моделей |
Шаг 5: Формат запросов к DeepSeek-OCR
Важнейший нюанс! DeepSeek-OCR использует особый формат промпта, не стандартный OpenAI.
Неправильно (400 Bad Request):
messages = [
{"role": "system", "content": "You are OCR assistant"},
{"role": "user", "content": "Extract text from image"}
]
Правильно:
messages = [
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {"url": f"data:image/png;base64,{image_base64}"}
},
{
"type": "text",
"text": "<|grounding|>Convert the document to markdown."
}
]
}
]
Варианты промптов:
Промпт |
Результат |
|---|---|
|
Текст + координаты элементов |
|
Только текст |
Шаг 6: Лимиты токенов
Потенциальная проблема, которая у вас может возникнуть — "max_tokens is too large"
DeepSeek-OCR имеет контекст 4096 токенов. Если запросить max_tokens=4096, а input занимает 100 токенов — ошибка.
Решение:
# OCR Service
payload = {
"model": "deepseek-ai/DeepSeek-OCR",
"max_tokens": 3500, # Оставляем запас для input
"temperature": 0.0,
...
}
# Qwen Structurizer
payload = {
"model": "Qwen/Qwen2.5-1.5B-Instruct",
"max_tokens": 1024, # Достаточно для JSON
"temperature": 0.1,
...
}
Шаг 7: Контекст для структуризации
Проблема: OCR возвращает 8000+ токенов из многостраничного PDF, а Qwen имеет контекст 2048.
Решение: Увеличить --max-model-len в docker-compose:
vllm-qwen:
command: >
--model Qwen/Qwen2.5-1.5B-Instruct
--max-model-len 16384 # Было 2048
--gpu-memory-utilization 0.35 # Увеличили с 0.20
Важно: Больший контекст = больше VRAM. На 24GB влезает 16K контекст для 1.5B модели.
Шаг 8: Промпт для структуризации
STRUCTURING_SYSTEM_PROMPT = """Ты - эксперт по структуризации данных из документов.
Твоя задача - извлечь конкретные данные из текста документа и вернуть их в JSON формате.
Правила:
1. Извлекай ТОЛЬКО те поля, которые указаны в схеме
2. Если поле не найдено в документе - укажи null
3. Сохраняй форматирование дат и чисел как в оригинале
4. Не выдумывай данные - только то, что есть в документе
5. Возвращай ТОЛЬКО валидный JSON, без пояснений"""
def build_structuring_prompt(columns, ocr_text):
fields = "\n".join([f'- "{col["name"]}": {col["description"]}' for col in columns])
return f"""Извлеки данные из документа по следующей схеме:
ПОЛЯ ДЛЯ ИЗВЛЕЧЕНИЯ:
{fields}
ТЕКСТ ДОКУМЕНТА:
\"\"\"
{ocr_text}
\"\"\"
Верни JSON. Если поле не найдено - укажи null."""
Шаг 9: Запуск системы
# 1. Запуск моделей (первый раз скачивает веса ~10GB)
docker-compose up -d vllm-ocr vllm-qwen
# 2. Ожидание загрузки (5-10 минут)
docker logs vllm-ocr -f
# Ждём: "Uvicorn running on http://0.0.0.0:8000"
# 3. Проверка моделей
curl http://localhost:8001/v1/models # OCR
curl http://localhost:8002/v1/models # Qwen
# 4. Запуск backend
docker-compose up -d backend
# 5. Открыть UI
# http://localhost:8000
Шаг 10: Использование
Пора зайти и посмотреть что получилось. Сразу скажу — весь фронт я навайбкодил, потому не ругайтесь на английский язык.

Уже неплохо. Можно создать свою схему-таблицу и настроить в ней колонки. Давайте это и сделаем.

Добавим поля-колонки

Вот так выглядит настроенная схема

Отлично, теперь давайте запустим первый PDF файл. В качестве примера, я зашел по первой ссылке в Яндекс Маркет и распечатал характеристики ведра

Примерно такой результат должна выдать программа (у меня не сохранился оригинал оцифровки ведра выше).


Заключение
По моему очень даже неплохой результат для такого серьезного кейса. А главное, работает все локально без запросов в облако.
Делитесь своим опытом настройки моего контейнера.
Буду рад за лайк и подписку на канал :) https://t.me/notes_from_cto
Комментарии (4)

maxwolf
11.12.2025 15:35Спасибо! А вы пытались, хотя бы приблизительно, оценить достоверность и устойчивость результатов? Типа, сколько на 100 обработанных пдфок, получается описаний собственно вёдер, сколько из них - реальных, а сколько - "скидок по альфа-карте" с длиной шириной и массой по 40%?

Dmitrii-Chashchin Автор
11.12.2025 15:35Спасибо за комментарий! Слушай, руки не дошли, но есть очень простая, но неплохая статья сравнения deepseek / qwen vl / mistral ocr - https://www.analyticsvidhya.com/blog/2025/11/deepseek-ocr-vs-qwen-3-vl-vs-mistral-ocr/
veriga
Судя по формату входных данных, DeepSeek-OCR спилена у Google, я в Gemini API такие промпты писал. И Gemini как раз читает документы этих форматов: pdf, doc и картинки. Возможно, DeepSeek-OCR -- это урезанная Gemma 3 4B. Только у Gemma контекст 128K. Я ее тоже на 24GB GPU ставил и тюнил
Dmitrii-Chashchin Автор
Интересное наблюдение, никогда не думал об этом :)