Введение: от демо IDP-системы к production-реализации

В 2023 году мы начали перерабатывать enterprise-продукт для интеллектуальной обработки документов (IDP). В его основе был зрелый, но устаревающий NLP-движок на Java — точный, надёжный, но не способный извлекать сложные сущности или рассуждать над контекстом. Решение казалось очевидным: добавить LLM.

Первый прототип на Python мы собрали за вечер. Он впечатлял: понимал свободные формулировки, находил неявные связи, генерировал структурированные ответы. Но когда мы попытались интегрировать его в существующую Java-экосистему с требованиями к SLA, полной изоляции данных и нагрузке в несколько тысяч RPS — начались проблемы. Python-стек не проходил нагрузочные тесты, внешний API был запрещён политикой безопасности, а попытки интегрировать LLM в монолит приводили к нестабильности.

Мы поняли: прототип и production — это две разные вселенные. Чтобы масштабировать LLM в enterprise-продукте, нужна не просто модель, а новая архитектура.

В этой статье делюсь нашим опытом перехода от Python-демок к полиглотной системе. Вы узнаете:

  • почему Python остаётся в исследованиях, но уходит из критического пути инференса;

  • как мы снизили стоимость обработки документов в 40 раз, отказавшись от облачных API и крупных моделей;

  • какие паттерны повысили точность на 10%;

  • и как запустить fine-tuned 3B-модель на CPU-сервере с полным контролем над данными.

Если вы выводите LLM из Jupyter Notebook в production — этот материал для вас.


Контекст: от MVP к промышленному RAG

На начальном этапе внедрения LLM ключевым фактором успеха была скорость проверки гипотез. Python позволял за несколько часов собрать рабочий прототип с использованием LangChain, Chroma и OpenAI API. Однако в enterprise-среде LLM-системы быстро эволюционировали в сложные data pipelines, включающие:

  • извлечение и нормализацию структурированных/неструктурированных данных;

  • семантическое чанкирование с перекрытием;

  • гибридный поиск (лексический + векторный + графовый);

  • динамическую фильтрацию на основе метаданных и прав доступа;

  • реранкинг и постобработку ответов;

  • аудит, трассировку и кэширование на всех уровнях.

Эти задачи связаны с производительностью центрального процессора (CPU-bound) и требуют предсказуемой производительности, что ставит под сомнение применимость интерпретируемого Python в высоконагруженных сценариях.

При этом важно отметить, что даже при использовании оптимизированных форматов вроде ONNX, значительная часть вычислительной нагрузки в CPU-bound сценариях приходится не на инференс самой модели, а на сопутствующие операции: токенизацию (преобразование текста в идентификаторы токенов), постобработку логитов (сэмплинг следующего токена через greedy, top-k, top-p стратегии) и детокенизацию (обратное преобразование идентификаторов токенов в текст). В сценариях с высокой частотой малых запросов (например, chat completion) и CPU-only инференсом, накладные расходы на токенизацию/детокенизацию могут составлять десятки процентов от времени обработки запроса, особенно при неоптимизированном маршалинге между Python и нативными библиотеками. Для моделей >7B параметров в batch-режиме преобладает вычислительная нагрузка самого трансформера.

Библиотеки вроде tokenizers от Hugging Face используют Rust-бэкенд через PyO3 — фреймворк для интеграции Rust-кода в Python, что минимизирует влияние GIL на производительность. Однако при очень высокой частоте вызовов (порядка тысяч RPS — requests per second) накладные расходы на маршалинг данных Python↔Rust и контекстные переключения могут стать узким местом. Для достижения таких показателей RPS монолитное Python-приложение может перестать быть эффективным, и требуются выделенные высокопроизводительные сервисы на Rust/Go.

Два ключевых тренда в индустрии:

  1. Безопасность данных: Компании с критическими данными (финансовый сектор, здравоохранение, госучреждения) избегают внешних API типа OpenAI, предпочитая локальное развертывание моделей.

  2. Доступность инференса: Растущая популярность fine-tuning моделей 1–7B параметров, которые эффективно работают на CPU или маломощных GPU, делает AI доступным для среднего бизнеса.

Мы не стали выбирать между Python и JVM. Вместо этого — разделили ответственность.

Каждый этап LLM-пайплайна теперь выполняется на языке, который оптимален для его задач: Python для исследований, Rust/Go/C++ для инференса, JVM для оркестрации.

Вот как выглядят три ключевых архитектурных паттерна, через которые прошла наша система.


Архитектурные паттерны

2.1. Прототип на Python (исследовательская фаза)

Стек:
FastAPI / Streamlit → LangChain → Chroma / FAISS → OpenAI / vLLM

Стек при работе с конфиденциальными данными:
FastAPI → sentence-transformers → Qdrant локально → локальная LLM (Llama, Qwen)

Характеристики:

  • Время создания рабочего прототипа: от часов до 1–2 дней

  • Подходит для: валидации бизнес-гипотез, внутренних демо, доказательства концепции (PoC)

  • Ограничения безопасности: Внешние API неприемлемы для конфиденциальных данных

  • Ограничения масштабируемости: Отсутствие строгой типизации, сложности с наблюдаемостью (observability)

Поток данных:

  1. Токенизация через tokenizers (Rust-бэкенд, вызываемый из Python)

  2. Инференс через PyTorch/TensorFlow или локальные оптимизированные модели

  3. Сэмплинг через встроенные методы библиотек

  4. Декодирование через те же transformers

Обработка запроса в Transformers
Обработка запроса в Transformers

2.2. Production-система на статически типизированных языках — JVM, Go, Rust (промышленная фаза)

Стек JVM для enterprise:
Spring Boot / Quarkus → Apache Lucene (HNSW) / Opensearch + ONNX Runtime → Schema-guided reasoning (SGR) → локальная LLM через внешний сервис инференса / fine-tuning моделей 1–7B на CPU/ONNX → Micrometer + OpenTelemetry

Характеристики:

  • Время разработки production-решения: от 2–4 недель (при наличии зрелой ML-инженерной команды)

  • Подходит для: систем с SLA менее 300 мс, высокой нагрузкой (>5K RPS), требованиями к безопасности и аудиту

  • Преимущества безопасности: Полный контроль над данными, соответствие ФЗ-152/GDPR/HIPAA

  • Экономическая эффективность: Модели 1–7B параметров после квантизации экспортируются либо в ONNX (для ONNX Runtime), либо в GGUF (для llama.cpp), что позволяет запускать их на CPU без дорогих GPU

Production-паттерны:

  • Schema-guided reasoning (SGR) — подход, при котором генерация LLM ограничивается заранее заданной схемой (например, JSON Schema), что обеспечивает воспроизводимость и типобезопасность вывода, повышает точность на 5–10%.

  • Статическая типизация — предотвращает ошибки на этапе компиляции

Библиотеки для JVM-стека:

  • Deep Java Library (DJL) (от AWS) — инференс (TensorFlow, PyTorch, ONNX) и базовые NLP-операции

  • ONNX Runtime Java — для моделей в ONNX формате. Подходит для batch-инференса и умеренных RPS, но не рекомендуется для low-latency сценариев с тысячами RPS, из-за накладных расходов JNI и отсутствия zero-copy передачи тензоров, где предпочтительнее развертывание ONNX Runtime как отдельного gRPC-сервиса на C++ с лёгковесным клиентом на Java.

  • llama.cpp (C++ native) и Ollama (Go-обёртка над llama.cpp) — CPU-инференс GGUF-моделей с предсказуемой задержкой (latency)

  • vLLM (Python/CUDA) — высокопроизводительный инференс-сервер с ядром на CUDA и Python API.

  • TensorFlow Java — имеет ограниченную поддержку; может использоваться для инференса устаревших моделей в формате SavedModel, но не рекомендуется для новых проектов.

  • Spring AI (от Spring Team) — высокоуровневая абстракция для LLM-интеграции в Spring Boot приложениях с поддержкой нескольких провайдеров (OpenAI, Anthropic, Hugging Face, локальные модели)

Выбор формата модели:

  • GGUF (через llama.cpp): Оптимален для CPU-инференса, встроенная поддержка квантизации, быстрой загрузки, идеален для развертываний, чувствительных к затратам

  • ONNX (через ONNX Runtime Java): Часто предпочтительнее в корпоративной среде благодаря строгой стандартизации, мощной экосистеме инструментов (включая ONNX Runtime) и лучшей поддержке со стороны коммерческих вендоров.

Инфраструктурный контекст:

  • Kubernetes для оркестрации микросервисов и управления ресурсами инференса

  • Kubeflow для организации end-to-end MLOps пайплайнов (обучение, валидация, развертывание)

  • Service Mesh (Istio/Linkerd) для управления трафиком и обеспечения отказоустойчивости

Особенности оптимизации:

  • Токенизация на Java/Rust: Использование биндингов к Rust-токенизаторам или нативных реализаций BPE/WordPiece

  • Эффективная работа с логитами: Прямой доступ к выходным тензорам и оптимизированные операции сэмплинга (greedy, top-k, top-p, temperature)

  • Локальный инференс: Полный контроль над данными, отсутствие внешних вызовов

  • Ресурсная эффективность: Квантизированные модели 1–7B на CPU снижают инфраструктурные затраты до 80%

Обработки запроса в JVM Production-системе
Обработки запроса в JVM Production-системе

2.3. Гибридная архитектура (рекомендуемый подход)

Поток данных для enterprise:

  1. Data Science-команда выполняет fine-tuning моделей 1–7B параметров → экспортирует в ONNX/GGUF

  2. Инженерная команда использует ONNX Runtime (Java/Go/Rust) или llama.cpp/ollama → интегрирует в production-сервис

  3. Локальный LLM-инференс на CPU или маломощных GPU

  4. Постобработка и оркестрация — на JVM/Go/Rust с полной трассировкой через OpenTelemetry

Преимущества:

  • Безопасность: Данные не покидают периметр организации

  • Экономика: Стоимость инференса снижается на порядок и более по сравнению с крупными облачными моделями

  • Производительность: Предсказуемая latency без сетевых задержек

Примерная архитектура production-системы
Примерная архитектура production-системы

Сравнительный анализ: Python vs JVM/Go/Rust в контексте RAG

Критерий

Python (с нативными расширениями)

JVM (Java/Kotlin)

Go / Rust

Скорость реализации

Очень высокая (для прототипа), низкая (для production)

Средняя

Средняя

Безопасность данных

Стандартные стеки уязвимы, требует усиления (изоляция, аудит, TEE)

Высокая (полный контроль)

Очень высокая

Экосистема ML/LLM

Очень высокая

Растущая

Растущая

Производительность CPU-bound задач

Ограничена накладными расходами на маршалинг между Python и нативными расширениями

Высокая (JIT + нативные библиотеки)

Очень высокая

Типобезопасность

Низкая

Высокая

Очень высокая

Поддержка локальных моделей

Отличная

Хорошая

Хорошая (особенно Rust)

Интеграция с enterprise

Умеренная

Отличная

Растущая

Оптимизация токенизации

Хорошая (Rust-биндинги)

Высокая

Очень высокая

> Вывод: для компаний с чувствительными данными и ограниченным бюджетом на GPU выбор локальных fine-tuning моделей на JVM/Go/Rust стеке становится оптимальным решением.


Рекомендации

Сценарий

Рекомендуемый стек

Обоснование выбора

Валидация идеи, некритичные данные

Python (FastAPI + внешние API)

Минимальное время реализации

Чувствительные данные, PoC

Python + локальные модели

Безопасность без потери скорости

Enterprise с конфиденциальными данными

JVM + локальные модели 1–7B на CPU

Безопасность, контроль, стоимость

Высокая нагрузка, умеренные требования к безопасности

Go/Rust + квантизированные модели

Производительность и безопасность

Бюджетные решения для среднего бизнеса

JVM/Go + квантизированный fine-tuning моделей 1–3B

Баланс стоимости и качества

Ключевые пункты коммерческого контракта на поставку LLM-решения

  1. Технические спецификации:

    • Точность модели на валидационных данных

    • Производительность (токенов/сек)

    • Совместимость с инфраструктурой заказчика

  2. Юридические положения:

    • Права на дообучение модели

    • Ответственность за качество предсказаний

    • Процедура разрешения споров

  3. Операционные аспекты:

    • Процесс обновления моделей

    • Мониторинг производительности и качества (сдвиги данных, галлюцинации)

    • Поддержка и обслуживание

Критические рекомендации для промышленного внедрения:

  1. Безопасность данных

    • Избегайте внешних API для конфиденциальных данных

    • Используйте локальные модели с полным контролем над пайплайном

    • Внедряйте шифрование данных на всех этапах

  2. Экономическая эффективность

    • Рассматривайте fine-tuning небольших моделей (1–7B параметров) вместо использования крупных моделей

    • Оптимизируйте модели через квантизацию (GGUF) и экспорт в ONNX

    • Используйте кэширование эмбеддингов и результатов для снижения нагрузки

  3. Техническая архитектура

    • Начинайте с Python, но проектируйте систему как набор слабосвязанных сервисов

    • Используйте Structured Output паттерны (SGR) для критически важных задач — это обеспечит 95%+ воспроизводимость и повысит точность на 5–10%

    • Внедряйте валидацию по JSON Schema на уровне API

    • Выбирайте стеки со статической типизацией для предсказуемости

    • Заранее проектируйте под контейнерную оркестрацию (Kubernetes) для упрощения масштабирования

    • Рассматривайте MLOps платформы (Kubeflow) для автоматизации жизненного цикла моделей

    • Разделяйте ответственность: токенизатор → инференс → сэмплинг → декодирование

    • Используйте специализированные библиотеки (tokenizers, llama.cpp, DJL)

    • Профилируйте каждый этап отдельно

    • Внедряйте единые форматы трассировки (OpenTelemetry)


Заключение

Python доминирует на этапах исследования благодаря экосистеме и скорости итераций, однако в производственных средах с требованиями к задержке (latency) и масштабируемости часто применяются статически типизированные языки. Три ключевых фактора (безопасность, экономическая эффективность, производительность) определяют выбор в сторону локальных развертываний на JVM, Go и Rust.

Новая реальность enterprise LLM:

  • Безопасность превыше всего: Компании предпочитают локальные развертывания с полным контролем над данными

  • Доступность через оптимизацию: fine-tuning моделей 1–7B параметров на CPU делает AI доступным для бизнеса любого масштаба

  • Платформенный подход: Kubernetes и Kubeflow становятся стандартом для оркестрации сложных LLM-пайплайнов

Для поставщиков ML-решений критически важно разрабатывать не только точные модели, но и архитектуру защиты данных, включающую технические механизмы анонимизации и шифрования, а также платформенные компоненты для управления всем жизненным циклом.

Оптимальной стратегией становится полиглотная архитектура, где Python остаётся в исследованиях и MLOps, а production инференс и оркестрация строятся на JVM, Go и Rust — с акцентом на безопасность, экономическую эффективность и защиту данных на всех уровнях стека.


А вы как решаете проблему перехода от LLM-прототипа к production?

Используете ли Java, Go или Rust в инференс-пайплайнах? Сталкивались ли с ограничениями Python при высокой нагрузке?

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


  1. muhachev
    11.11.2025 04:34

    Сладкий звон жэпэтэллера.


    1. KastorTroy Автор
      11.11.2025 04:34

      Жэпэтэллер пишет сказки.
      Я — собираю систему.
      Бинарник. Индекс. Поды.
      Не GPT — инфраструктура.


  1. Elaugaste
    11.11.2025 04:34

    Много воды, почти никакой конкретики.

    Очень хотелось бы посмотреть на то как приложение для прода собирается за 2-4 недели, без возможности использовать фреймворки. Под go с этим вообще весьма туго, есть всего пара фреймворков


    1. KastorTroy Автор
      11.11.2025 04:34

      Не пишут с нуля — интегрируют.
      llama.cpp/ollama — готовый инференс.
      Go: библиотек мало, но ollama — весь стек на Go.
      Java: Spring Boot, DJL, ONNX Runtime — готовый фреймворк.
      gin — 200 строк API.
      Opensearch — векторный и классический поиск за день.
      OpenTelemetry, Kubernetes/Helm — всё быстро и надёжно.
      2–4 недели — сборка из готовых кирпичей.
      Go/Java — не для обучения, а для надёжного инференса.


      1. Elaugaste
        11.11.2025 04:34

        Окей, тоесть вы просто берете ollama, раните там какой то gguf. И видимо далее используете ollama api чтобы просто кидать запросики из java (или чего то еще) ?

        Я почему то подумал что речь про оптимизацию и приложение целиком собрано в go монолит, вероятно с использованием langchaingo/eino и вот это все за 2-4 недели


        1. KastorTroy Автор
          11.11.2025 04:34

          Да, именно так — но это один из сценариев:
          — ollama — CPU-инференс без Python
          — Java/Go — аудит, SLA, безопасность
          — Kubernetes, OpenTelemetry — стабильность, наблюдаемость

          Это и есть 2–4 недели на готовый стек — когда вам не нужен GPT, а нужна надёжность, контроль и цена.

          Есть попытки запускать GGUF напрямую в Java — через экспериментальные API, например, Vector API (JEP 469) или TornadoVM, но пока с ограниченной поддержкой.
          Мы также тестировали Qwen3 4B в ONNX — инференс из Java дал прирост скорости по сравнению с Python-аналогом.

          Вывод: Go/Java — не для всех моделей, но для production-оркестрации — лучший выбор.


  1. Gnet21
    11.11.2025 04:34

    Компании, которые не могут себе позволить нормальные GPU, в состоянии оплатить разработку и поддержку такого стэка технологий?

    ИМХО тут описаны работы сильно дороже, чем стоит RTX 5090 и возможно дороже А100. А если эти разработчики ещё и сторонние(что часто дешевле), то с их пропажей поддержка такого зоопарка будет отдельным приключением. Есть сомнения, что малый бизнес такое способен поиянуть


    1. KastorTroy Автор
      11.11.2025 04:34

      Вы правы — стек сложнее, чем RTX 5090.
      Но инференс на GPU — это не только карта за 300–800 тыс. ₽: это электричество, охлаждение, сопровождение, и — если вы работаете с конфиденциальными данными — ещё и привязка к поставщику внутри ЦОДа, где GPU не всегда доступен.
      CPU-сервер с GGUF-моделью — прост, универсален и его может обслужить любой, кто работает с локальной инфраструктурой.
      Поддержка — не «зоопарк», а изолированные сервисы: инференс через llama.cpp или Ollama, оркестрация — на Java/Go.
      Не все могут.
      Но тем, у кого данные важны — это осознанный выбор.