中文翻译暂不可用,显示俄语原文。

Как вы обеспечиваете низкую задержку (<500ms) для LLM?

Краткий тезис

latency|Низкая задержка LLM-сервиса достигается сочетанием нескольких техник: стриминг токенов, выбор модели меньшего размера (1B–3B вместо 70B) для простых задач, кэширование частых запросов (Redis), batching нескольких запросов в один проход, а также использование специализированных инференс-движков (vLLM) с PagedAttention и Flash Attention 2. Ключевой ориентир: комбинация стриминга, меньшей модели и кэша позволяет уложиться в 200 мс для типовых запросов.


1. Термин: Задержка (Latency)

Задержка (latency) — время от отправки запроса пользователем до получения первого или последнего токена ответа. Для LLM часто различают:

  • Time to First Token (TTFT) — время до начала генерации (включает препроцессинг, KV-cache инициализацию, первый forward pass). Должен быть ≤ 100–200 мс.
  • Time per Output Token (TPOT) — время генерации одного следующего токена. Для маленьких моделей (1–3B) может быть 2–5 мс/токен, для больших (70B) — 20–50 мс/токен.

Цель <500 мс обычно означает, что пользователь видит первый токен быстро, а затем получает поток токенов (стриминг), не дожидаясь полного ответа.


2. Почему низкая задержка критична

  • UX Если ответ дольше 500 мс, пользователь воспринимает сервис как «тормозящий». Для чат-ботов и ассистентов каждая миллисекунда влияет на удержание.
  • Real-time сценарии Голосовые ассистенты, live-транскрибация — требуют TTFT < 200 мс.
  • Экономика Медленный сервис требует больше ресурсов на ожидание (keep-alive соединения, параллельные запросы). Высокая задержка может привести к таймаутам и loss запросов.

3. Streaming (потоковая передача)

Streaming — сервер отправляет каждый сгенерированный токен сразу, как только он готов, вместо того чтобы ждать полный ответ. Снижает perceived latency (воспринимаемую задержку): пользователь начинает читать через 100–300 мс, хотя полный ответ может генерироваться 2–3 секунды.

Как работает

  • LLM генерирует первый токен → сразу отправляется клиенту (WebSocket, Server-Sent Events — SSE).
  • Последующие токены отправляются по мере генерации.
  • Клиент отображает их инкрементально.

Реализация на Python (FastAPI + vLLM):

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from vllm import LLM, SamplingParams

app = FastAPI()
llm = LLM(model="meta-llama/Llama-3.2-3B-Instruct")

async def stream_response(prompt: str):
    sampling_params = SamplingParams(max_tokens=256, stream=True)
    async for output in llm.generate_async(prompt, sampling_params):
        yield output.text

@app.post("/chat")
async def chat(prompt: str):
    return StreamingResponse(stream_response(prompt), media_type="text/plain")

Streaming обязателен для любого production-сервиса LLM, если не требуется полный ответ сразу (например, для дальнейшей обработки).


4. Выбор меньшей модели

Меньшая модель (1B–3B параметров) даёт задержку в 5–10 раз ниже, чем модель 70B, при близком качестве на простых задачах (суммаризация, фактологический ответ, извлечение).

Размер моделиПримерыTTFT (1 токен)TPOTLatency 100 токенов
1-3BTinyLlama, Qwen2.5-1.5B, Phi-3.5-mini30-80 мс2-5 мс~250-600 мс
7-8BLlama 3.1-8B, Mistral 7B80-200 мс5-12 мс600-1400 мс
70B+Llama 3.1-70B, DeepSeek-V3300-1000 мс20-50 мс2-6 с

Когда использовать меньшие модели

  • Специализированная задача (только классификация, только суммаризация).
  • Большинство пользовательских запросов не требует «интеллекта» гигантской модели.
  • Допустимо использовать router (маршрутизатор) — для сложных запросов отправлять на 70B, для простых — на 3B.

Практический паттерн

if complexity_score(prompt) < 0.7:
    model = "tinyllama-1.1b"
else:
    model = "llama-70b"

5. Batch inference (пакетный инференс)

Batch inference — объединение нескольких независимых запросов в один forward pass на GPU. GPU лучше утилизирована при обработке батча, что уменьшает задержку на один запрос за счёт амортизации накладных расходов.

  • Dynamic batching: сервер собирает запросы в течение небольшого таймаута (например, 50 мс) и отправляет батч на инференс.
  • Continuous batching (vLLM): позволяет добавлять новые запросы и завершать старые во время прохода — ещё более эффективная загрузка.

Задержка при батче TTFT для каждого запроса в батче увеличивается незначительно (в пределах 10-20%), а пропускная способность (throughput) растёт в 2-4 раза. Для низкой задержки критичен маленький батч (до 4-8 запросов) — большее замедлит TTFT.


6. Кэширование (Redis)

Кэширование — хранение результатов частых (шаблонных) запросов в быстром хранилище, например Redis in-memory database. Позволяет вернуть ответ за 1-5 мс без вызова LLM.

Какие запросы кэшировать

  • Повторяющиеся вопросы (FAQ, справка).
  • Запросы с одинаковыми контекстами (системные промпты, приветствия).
  • Результаты, которые одинаковы для многих пользователей (документация, описание продуктов).

Реализация

import hashlib, redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)

def get_cached_response(prompt: str) -> str | None:
    key = hashlib.sha256(prompt.encode()).hexdigest()
    return r.get(f"llm_cache:{key}")

def set_cached_response(prompt: str, response: str, ttl=3600):
    key = hashlib.sha256(prompt.encode()).hexdigest()
    r.setex(f"llm_cache:{key}", ttl, response)

Политика ключей TTL (time-to-live) — 15-60 минут; кэшировать только для статических промптов; если запрос содержит персонализированные данные (имя пользователя) — не кэшировать.


7. Специфика железа и оптимизация инференса

7.1 vLLMProduction-инференс-движок

vLLM — библиотека для быстрого инференса LLM. Основные фичи для снижения задержки:

  • PagedAttention — эффективное управление KV-кэшем (деление на страницы). Снижает фрагментацию памяти, позволяет поддерживать большие батчи и уменьшает паузы на освобождение памяти.
  • Flash Attention 2 — техника, ускоряющая attention в 2-3 раза за счёт лучшего использования кэша GPU и избежания записи промежуточных матриц.
  • Continuous batching — динамическое добавление/завершение запросов без остановки.

Результат на одной GPU (A100 80GB) vLLM с Llama 3.1-8B даёт TTFT 80 мс и TPOT 6-8 мс, что укладывается в 500 мс для 50-токенового ответа.

7.2 Quantization (квантование)

Quantization — снижение точности весов модели (например, FP16INT8, INT4). Уменьшает размер модели и время forward pass.

Тип квантованияРазмер (на параметр)Скорость (token/s)Потеря качества
FP162 байта100% (база)0%
INT8 (GPTQ)1 байт~120-150%~0.5%
INT4 (AWQ)0.5 байта~150-200%~1-2%

Для low-latency часто используют INT4 (AWQ) — модель помещается в VRAM с запасом, скорость выше, а качество приемлемо.

7.3 Speculative Decoding (спекулятивное декодирование)

Speculative decoding — приём, когда маленькая «драфт-модель» (например, 0.5B) генерирует несколько токенов, а большая модель проверяет их. Если драфт-модель верна, принимается сразу несколько токенов за один forward pass большой модели. Это ускоряет генерацию в 2-3 раза без изменения качества.


8. Асинхронный стек (FastAPI + asyncio + очередь)

Асинхронный стек (FastAPI + asyncio + очередь задач) позволяет не блокировать I/O и эффективно управлять соединениями.

  • FastAPI — асинхронный фреймворк, способный обрабатывать тысячи одновременных соединений.
  • asyncio — библиотека для конкурентного выполнения. LLM-инференс сам может быть CPU/GPU-интенсивным, но асинхронные паузы (загрузка данных, отправка ответов) не блокируют ядра.
  • Очередь задач (например, Celery или Redis Queue) — когда запросы приходят быстрее, чем их может обработать GPU, ставим их в очередь и обрабатываем с приоритетами. Для low-latency лучше использовать встроенную очередь с asyncio (asyncio.Queue), чтобы не добавлять сетевые задержки.

Примерная архитектура

Client → FastAPI → asyncio.Queue → Worker (vLLM) → StreamingResponse

Worker запускается в отдельном процессе/потоке с одной GPU. FastAPI принимает запросы, кладёт в очередь, worker забирает и генерирует стрим.


9. Дополнительные техники

  • Prompt caching — кэширование KV-кэша для одинаковых префиксов (system prompt, несколько первых токенов). vLLM поддерживает KV-cache sharing между запросами.
  • Prefix caching — если system prompt не меняется, вычисляем его в начале сессии и переиспользуем.
  • Input compression — сокращение длины промпта: удаление стоп-слов, перефразирование короткими фразами (с помощью LLM меньшего размера).
  • Edge deployment — маленькие модели (1B) могут работать на CPU с ONNX Runtime или на мобильных чипах, убирая задержки сети.

Пет-проект для закрепления

Задача построить микросервис чат-бота на LLM с гарантированной задержкой < 500 мс для 95% запросов.

Инструменты Python, FastAPI, vLLM, Redis, Locust (для нагрузочного тестирования), Docker.

Шаги:

  1. Установить vLLM, загрузить модель Qwen2.5-1.5B (INT4 AWQ).
  2. Написать FastAPI-приложение с эндпоинтом /chat, который принимает prompt и возвращает токены через SSE.
  3. Внедрить кэш Redis: перед генерацией проверять хеш промпта, при находке отдавать кэш.
  4. Настроить continuous batching в vLLM (параметр --max-num-batched-tokens = 4096, --max-num-seqs = 32).
  5. Запустить Locust для стресс-теста: 50 одновременных пользователей, случайные промпты (50-200 символов). Измерить TTFT, TPOT, полное время ответа.
  6. Оптимизировать: если latency > 500 мс для части запросов — уменьшить max_num_seqs, увеличить размер батча, добавить speculative decoding (если поддерживается).

Ожидаемый результат

  • TTFT: < 100 мс
  • TPOT: < 3 мс/токен
  • 95-й перцентиль полного времени ответа (на 200 токенов): < 500 мс
  • Пропускная способность: > 100 requests/min на одной A100.

Связь с другими вопросами

ВопросТема
61Как вы деплоите LLM в production?
62Что такое model serving и какие есть инструменты?
63Как вы реализуете batch inference для LLM?
65Как вы тестируете latency и throughput LLM-сервиса?
66Какие стратегии кэширования вы используете для LLM?
67Что такое квантование моделей и как оно влияет на latency?

Навигация