Недавно получил задачу сделать автоматизированную оцифровку характеристик из паспортов товаров в БД, а не изменение параметров вручную в ERP. Я подумал, было бы здорово поделиться, как я это сделал, с вами на Хабре!

Базовые задачи:

  1. Нужно, чтобы это все работало локально

  2. Система должна принимать разные форматы (.doc, .pdf, .png)

  3. Возможность создавать динамические таблицы, куда ИИ будет заполнять сама информацию, а не хардкодить для каждой категории паспорта свои отчеты

  4. Желательно, чтобы все работало на одной видеокарте (в моем случае 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 серия, но если у вас другая видеокарта, то введите свою модель и серию тут.

Шаг 1.2 Найдите свой драйвер Nvidia
Шаг 1.2 Найдите свой драйвер Nvidia

Перезагружаем комп/сервер и пишем команду:

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

Ключевые параметры:

Параметр

Значение

Зачем

--gpu-memory-utilization

0.30 / 0.35

Делим GPU между моделями

--max-model-len

4096 / 16384

Максимальный контекст

--trust-remote-code

-

Для кастомного кода моделей

Шаг 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."
            }
        ]
    }
]

Варианты промптов:

Промпт

Результат

<|grounding|>Convert the document to markdown.

Текст + координаты элементов

Free OCR.

Только текст

Шаг 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: Использование

Пора зайти и посмотреть что получилось. Сразу скажу — весь фронт я навайбкодил, потому не ругайтесь на английский язык.

Шаг 10. Первый экран
Шаг 10. Первый экран

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

Шаг 10. Создаем таблицу для заполнения с Qwen-1.5
Шаг 10. Создаем таблицу для заполнения с Qwen-1.5

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

Шаг 10. Добавляем поля-колонки
Шаг 10. Добавляем поля-колонки

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

Шаг 10. Пример настроенных полей
Шаг 10. Пример настроенных полей

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

Шаг 10. Пример товара для оцинковки
Шаг 10. Пример товара для оцинковки

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

Шаг 10. Процесс оцифковки
Шаг 10. Процесс оцифковки
Шаг 10. Пример заполнения таблицы с помощью ИИ
Шаг 10. Пример заполнения таблицы с помощью ИИ

Заключение

По моему очень даже неплохой результат для такого серьезного кейса. А главное, работает все локально без запросов в облако.

Делитесь своим опытом настройки моего контейнера.


Буду рад за лайк и подписку на канал :) https://t.me/notes_from_cto


Комментарии (4)


  1. veriga
    11.12.2025 15:35

    Судя по формату входных данных, DeepSeek-OCR спилена у Google, я в Gemini API такие промпты писал. И Gemini как раз читает документы этих форматов: pdf, doc и картинки. Возможно, DeepSeek-OCR -- это урезанная Gemma 3 4B. Только у Gemma контекст 128K. Я ее тоже на 24GB GPU ставил и тюнил


    1. Dmitrii-Chashchin Автор
      11.12.2025 15:35

      Интересное наблюдение, никогда не думал об этом :)


  1. maxwolf
    11.12.2025 15:35

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


    1. 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/