Деградация продового LLM-инференса: как KV-cache, фрагментация памяти и p99 подрывают стабильность сервисов
Демонстрационные запуски моделей обманчиво быстры, но под реальной нагрузкой инференс постепенно деградирует: p99 растёт, память утекает, а OOM приходит на ровном месте. Технологический евангелист VK Cloud Стас Погоржельский разбирает четыре ключевых механизма, которые приводят к деградации производственных сред на длинном горизонте.
Демонстрационные запуски больших языковых моделей почти всегда впечатляют: первые запросы обрабатываются быстро, а время до первого токена (TTFT) остаётся в приятных пределах. Однако когда сервис неделю живёт под реальной нагрузкой, картина меняется: латентность на уровне 99-го перцентиля (p99) неуклонно растёт, память «утекает», и в один момент система падает с ошибкой нехватки памяти (OOM) на запросе, который ещё вчера выполнялся без проблем. Стас Погоржельский, технологический евангелист VK Cloud, неоднократно наблюдал этот сценарий в демонстрационных средах и подчёркивает: инференс редко выходит из строя сразу и явно, обычно он деградирует постепенно, оставаясь незамеченным до достижения критической точки. В своём материале он выделяет четыре основных механизма такой деградации: фрагментацию KV-кэша, нехватку памяти при работе с длинным контекстом, блокировку очереди внутри батча (head-of-line blocking) и расхождение метрик p50 и p99. Для каждого случая автор описывает внутреннее поведение системы, способы воспроизведения проблемы и метрики, которые сигнализируют о надвигающемся инциденте, а также предоставляет скрипты для воспроизведения в репозитории-харнесе.
Ключевая идея, которую необходимо принять при проектировании инференс-систем, — это то, что продовый инференс упирается в память сильнее, чем в вычисления. Префилл, то есть обработка промпта, активно нагружает вычислительные ядра, тогда как декодирование, когда модель генерирует токены по одному, постоянно читает и пишет память, упираясь именно в её пропускную способность. Именно на длинной дистанции деградация возникает из-за декодирования вместе с растущим KV-кэшем. В свежей работе UC Berkeley отмечается, что по мере роста контекста основной источник трафика памяти смещается с весов модели на KV-кэш, и интеграторы фиксируют, что KV-кэш в проде часто начинает превышать по объёму сами веса модели. Конкретные цифры выглядят отрезвляюще: модель на 13B параметров на A100 40GB занимает примерно 26 Гб под веса, оставляя около 14 Гб под KV-кэш. При расходе порядка 1 Мб состояния на токен это даёт примерно 14 тысяч токенов на всю карту, то есть при длине последовательности 512 в батч помещается около 28 запросов, а при 2048 — только 7 (по данным Anyscale). Для старых моделей с multi-head attention KV-кэш для 70B и контекста 8K достигает примерно 20 Гб на запрос, а батч на 32 запроса требует порядка 640 Гб, тогда как у современных 70B (например, Llama-3) с 8 KV-головами вместо 64 та же память падает примерно в 8 раз: около 2,6 Гб на запрос при 8K и около 10 Гб при 32K (данные Symphony). Пропускную способность сервиса задаёт число последовательностей, одновременно помещающихся в память под KV-кэш, а скорость «размышления» модели вторична.
Одним из главных источников деградации является фрагментация KV-кэша. Классическая реализация KV-кэша резервирует непрерывный кусок памяти под максимальную длину последовательности заранее, чтобы не докупать память по ходу генерации, но большинство запросов до максимума не доживают. Например, запрос, который генерирует 100 токенов при max_len 2048, приводит к тому, что около 1948 слотов из 2048 зарезервированы и простаивают — это примерно 95% впустую. Авторы vLLM замерили, что существовавшие на тот момент системы теряли 60–80% памяти KV-кэша на фрагментацию и избыточное резервирование. Решение этой проблемы — PagedAttention, который выделяет память под KV-кэш не одним куском, а блоками по требованию, по аналогии с виртуальной памятью и таблицей страниц в ОС. Логические позиции токенов сопоставляются физическим блокам через block table, что позволяет снизить потери на фрагментацию до менее чем 4%, а throughput вырастает в 2–4 раза при том же уровне латентности по сравнению с FasterTransformer и Orca (PagedAttention, SOSP 2023), а против HuggingFace Transformers прирост доходит до 24-кратного. Однако на практике paged attention скорее смещает проблему, чем полностью её устраняет: со временем проявляется внешняя фрагментация пулов блоков и начинает сказываться работа механизма вытеснения (eviction). Например, в TensorRT-LLM KV-кэш организован иерархически: блок — минимальная единица аллокации, пул — буфер памяти (primary на GPU и secondary с выгрузкой на CPU), а отдельный менеджер отслеживает состояния блоков (Created, Updated, Removed, Stored). При постоянных аллокациях, вытеснениях и повторном использовании блоков фактическая доступная ёмкость под новые запросы начинает заметно колебаться, и именно в этой динамике занятости пула часто скрывается первый источник деградации: код остаётся неизменным, но поведение системы со временем ухудшается.
Второй механизм — нехватка памяти (OOM) при работе с длинным контекстом. Когда контекст запроса превышает запланированные лимиты, KV-кэш может расти непредсказуемо, особенно если модель поддерживает окно внимания больше, чем зарезервировано. В таких случаях система может попытаться выделить дополнительную память, но если свободного места нет, возникает OOM. Проблема усугубляется тем, что современные модели с длинным контекстом (например, 32K или 128K токенов) требуют значительных объёмов памяти под KV-кэш, и если не предусмотреть механизмы сжатия или вытеснения, сервис может упасть в самый неподходящий момент. Третий механизм — блокировка очереди (head-of-line blocking) внутри батча. Когда один запрос в батче требует непропорционально много времени на декодирование (например, из-за длинного контекста или сложного промпта), он может заблокировать обработку остальных запросов, что приводит к росту p99 и ухудшению общего восприятия сервиса. Это особенно критично при потоковой генерации, когда пользователи ожидают быстрых ответов. Четвёртый механизм — расхождение метрик p50 и p99: если средняя латентность остаётся низкой, но хвост p99 растёт, это сигнализирует о том, что отдельные запросы сталкиваются с непредвиденными задержками, которые могут быть вызваны фрагментацией, вытеснением блоков или конкуренцией за память. Эти метрики необходимо отслеживать в динамике, а не только в моменте, и для этого автор предоставляет скрипты, например kv_usage_probe.py, который снимает занятость KV-кэша во времени под нагрузкой.
Для российского рынка эта тема особенно актуальна: многие компании активно внедряют LLM-решения, но часто недооценивают сложности эксплуатации. Демо-стенды и тестовые запуски создают ложное ощущение простоты, а когда сервис выходит в прод, начинаются проблемы с производительностью и стабильностью. Стас Погоржельский отмечает, что интеграторы фиксируют случаи, когда KV-кэш в проде превышает объём весов модели, что приводит к неожиданным OOM и росту затрат. Реакция отрасли включает переход на более эффективные архитектуры внимания (например, grouped-query attention), использование PagedAttention и других методов оптимизации памяти, а также внедрение мониторинга, который отслеживает не только средние значения, но и хвостовые перцентили. Российские облачные провайдеры, такие как VK Cloud, активно развивают инструменты для дезагрегированного инференса и управления ресурсами, что позволяет частично смягчить проблемы, но полностью их не устраняет.
Сравнение с альтернативами показывает, что выбор подхода к инференсу зависит от конкретных задач. Если сервис работает с короткими запросами и небольшим контекстом, можно обойтись без сложных механизмов управления памятью, но для длинных контекстов и высоких нагрузок необходимы PagedAttention, эффективные стратегии вытеснения и, возможно, дезагрегированный инференс, когда префилл и декодирование выполняются на разных сервисах. Однако такие решения добавляют сложность в эксплуатацию и требуют тщательного мониторинга. В то же время существуют подходы, основанные на сжатии KV-кэша или использовании более компактных моделей, что снижает требования к памяти, но может ухудшить качество генерации. Выбор оптимального решения — это всегда компромисс между стоимостью, латентностью и качеством.
Перспективы развития этой области связаны с дальнейшим совершенствованием алгоритмов управления памятью, появлением более эффективных архитектур внимания и развитием аппаратных решений с большим объёмом памяти. Открытыми остаются вопросы о том, как предсказывать деградацию заранее и как автоматически адаптироваться к изменяющимся нагрузкам. Автор подчёркивает, что необходимо не только полагаться на метрики, но и проводить регулярные нагрузочные тесты, имитирующие реальные сценарии, а также внедрять алерты на основе p99 и занятости KV-кэша. В будущем можно ожидать появления более интеллектуальных систем мониторинга, которые будут предсказывать OOM и другие проблемы на основе анализа исторических данных. Пока же инженерам остаётся следовать рекомендациям: измерять, мониторить и заранее проектировать инференс-системы с учётом всех четырёх механизмов деградации, чтобы избежать неприятных сюрпризов в проде.