AI / ML

Как я строил AI-инфраструктуру для игрового сервиса с 2M DAU

Реальный кейс: от постановки задачи до production-деплоя. Какие модели выбрали, почему отказались от облачных API и как добились задержки ответа менее 80мс на P99.

80мс
P99 latency
Задержка ответа ассистента на 99-м перцентиле
2M
DAU
Без деградации производительности
−74%
Стоимость
Снижение расходов относительно облачного API
Архитектура AI-инфраструктуры

С чего всё началось

В начале 2024 года ко мне пришёл клиент — мобильный издатель с флагманской игрой, которая держит около 2M DAU. Задача казалась простой: «хотим добавить AI-ассистента, который помогает новым игрокам освоиться». На деле это оказалось задачей, которая коснулась всей backend-архитектуры.

В этой статье я разберу весь путь: от первоначального анализа до production-деплоя. Покажу, какие решения принимали и почему, включая те, от которых в итоге отказались.

Почему облачные API не подошли

Первым порывом было взять OpenAI API или Anthropic и быстро интегрировать. Мы провели нагрузочное тестирование и получили цифры, которые нас остановили:

  • Средняя задержка — 340мс, P99 — 1.2с
  • При пике 50k одновременных пользователей — rate limiting и очереди
  • Стоимость при текущем объёме — ~$28k/месяц
  • GDPR не позволял отправлять игровой контекст наружу

Последний пункт стал ключевым. Игровой контекст — прогресс игрока, история действий, инвентарь. Всё это PII в трактовке европейского регулятора.

80мс
P99 latency
Задержка ответа ассистента на 99-м перцентиле
2M
DAU
Без деградации производительности
−74%
Стоимость
Снижение расходов относительно облачного API

Глоссарий

  • DAU

    Daily Active Users — количество уникальных пользователей за сутки. Ключевая метрика нагрузки на backend.

  • P99 latency

    99-й перцентиль задержки ответа: 99% запросов обрабатываются быстрее этого значения. Жёстче среднего, показывает реальный worst case.

  • RAG

    Retrieval-Augmented Generation — подход, при котором LLM получает релевантный контекст из базы знаний перед генерацией ответа.

  • vLLM

    Высокопроизводительный inference-движок для LLM с поддержкой continuous batching и PagedAttention. Де-факто стандарт для self-hosted inference.

  • Quantization

    Сжатие весов модели с float32/float16 до int4/int8. Q4_K_M — формат llama.cpp: 4-битные веса, минимальная потеря качества.

  • Prefix caching

    Кэширование KV-состояния общего префикса промптов (системный промпт, контекст игры). Резко снижает время до первого токена при повторяющихся запросах.

Архитектура до: облачный API
Архитектура после: self-hosted inference
Слева — первоначальная схема с облачным API. Справа — итоговая с self-hosted inference.

Выбор модели и inference-инфраструктура

Нам нужна была модель, которая умеет: отвечать на вопросы по контексту игры (RAG), генерировать подсказки на русском языке, работать в пределах 4k токенов контекста. При этом — помещаться на GPU, которые мы могли себе позволить.

После сравнения Mistral 7B, Llama-3 8B и Phi-3 mini остановились на Llama-3 8B в quantized версии (Q4_K_M через llama.cpp). На A10G она держала ~180 tok/s, чего нам хватало с запасом.

Inference-кластер

Развернули vLLM с continuous batching на 4×A10G. Перед ним — Ray Serve как маршрутизатор с балансировкой по queue depth. Кэш промптов через prefix caching (системный промпт с описанием игрового мира повторялся в 94% запросов).

vllm_config.py — конфиг inference-сервера

from vllm import AsyncLLMEngine, AsyncEngineArgs

engine_args = AsyncEngineArgs(
    model="meta-llama/Meta-Llama-3-8B-Instruct",
    quantization="awq",
    max_model_len=4096,
    gpu_memory_utilization=0.90,
    # Prefix caching — ключевое для нашего use-case
    enable_prefix_caching=True,
    # Continuous batching
    max_num_seqs=256,
    max_num_batched_tokens=8192,
)

engine = AsyncLLMEngine.from_engine_args(engine_args)

RAG-пайплайн: что работает в игровом контексте

Классический RAG с векторным поиском по всей базе знаний игры давал плохое качество — слишком много нерелевантных чанков попадало в контекст. Пришлось выстроить двухуровневую систему ретривала.

Уровень 1 — структурированный контекст: прогресс игрока, текущий квест, инвентарь. Это всегда присутствует в запросе и передаётся напрямую, без векторного поиска.

Уровень 2 — семантический поиск: только если вопрос выходит за рамки текущего контекста. Индекс в Qdrant (~180k чанков по игровой документации), re-ranking через cross-encoder.

Дашборд мониторинга качества AI-ответов

Мониторинг качества ответов

Один из самых недооценённых аспектов RAG — evaluation в production. Мы настроили три уровня проверки:

  • Online: thumbs up/down от игроков (13% response rate)
  • Async: LLM-as-a-judge на 5% сэмпле запросов
  • Offline: еженедельный прогон по golden set из 800 вопросов

Это позволило поймать регрессию при обновлении модели прежде, чем она попала бы в production.

Схема inference-кластера

Как устроен кластер изнутри

Четыре A10G-ноды за Ray Serve. Каждая нода — отдельный vLLM-процесс с prefix caching и continuous batching. Маршрутизатор смотрит на queue depth и шлёт запрос на наименее загруженную ноду.

При деградации одной ноды Ray Serve автоматически перераспределяет трафик на оставшиеся три. Среднее время восстановления — менее 2 секунд.

RAG pipeline: retrieval + reranking

RAG pipeline: два уровня retrieval

Первый уровень — всегда в контексте: персонаж, локация, инвентарь. Передаётся напрямую без векторного поиска.

Второй уровень подключается только если вопрос выходит за рамки текущего игрового состояния. Qdrant + cross-encoder re-ranking дают precision@5 = 0.87 на нашем тестовом сете.

"

Самый дорогой урок: начинать с evaluation-фреймворка, а не с самой модели. Иначе не знаешь, стало ли лучше.

— из ретроспективы проекта
GPU утилизация
Latency P50/P95/P99
Удовлетворённость пользователей
Метрики через 3 месяца: GPU ~68% утилизации, P99 latency стабильна, удовлетворённость выросла с 61% до 84%.

Итоги и выводы

Через три месяца в production мы получили систему, которая отвечает 400k запросов в день, держит P99 latency в 80мс и стоит ~$7.2k/месяц вместо прогнозируемых $28k.

Что сработало лучше всего:

  • Prefix caching дал −40% к нагрузке на inference одним конфиг-параметром
  • Двухуровневый RAG поднял accuracy с 61% до 84% на golden set
  • LLM-as-a-judge автоматизировал evaluation почти полностью

Что сделал бы иначе: начал бы с evaluation-фреймворка на день 1, а не строил его параллельно с системой. Это сэкономило бы 2 недели работы и один неловкий инцидент в production.

Что дальше

Смотрим на fine-tuning Llama-3 на игровых диалогах, чтобы убрать system-промпт полностью и освободить контекстное окно. Напишу отдельно, как пойдёт.

Частые вопросы

Три причины: стоимость при 2M DAU была бы в районе $80k/мес, latency облачных API на P99 не укладывалась в наш SLA 80мс, и мы не могли допустить передачу игровых данных пользователей на внешние серверы.

У нас 4×A10G (24GB VRAM каждая). Llama-3 8B в Q4_K_M занимает ~5GB, остаток идёт под KV-cache. При пиковой нагрузке ~1200 запросов/мин утилизация GPU держалась на уровне 65–75%.

LLM-as-a-judge на 5% трафика + golden set из 800 вопросов запускается еженедельно через CI. Если метрика падает — PR не проходит.

При 100k DAU скорее всего хватит одной A10G. Но при таком масштабе облачный API будет дешевле — self-hosted inference окупается примерно от 500k DAU при активном использовании AI-фич.

Источники