В крупномасштабных системах обслуживания LLM уровень оркестрации имеет решающее значение для общей эффективности системы. То, насколько эффективно уровень оркестрации распределяет неоднородные потоки запросов между парком реплик движка LLM, напрямую влияет на TTFT, TPOT и пропускную способность обслуживания. При работе с кластером реплик vLLM самое важное решение, которое принимает ваш маршрутизатор, просто: кто получит следующий запрос? В этом блоге мы разберем заблуждение об оптимизации исключительно ради повторного использования KV-кэша, представим маршрутизацию с учетом токеновой нагрузки и объясним, почему это критически важно для эффективного обслуживания LLM.
Сложности маршрутизации запросов LLM
Обслуживание LLM фундаментально отличается от традиционной маршрутизации микросервисов из-за авторегрессионной природы вывода и высокой вариативности запросов к LLM. Традиционные микросервисы обычно обрабатывают запросы независимо, почти не получая выгоды от маршрутизации связанных запросов на одну и ту же реплику. Обслуживание LLM, напротив, привносит состояние, высокую неоднородность и непредсказуемые затраты на выполнение.
- Состояние выполнения: движок может повторно использовать KV-кэши, вычисленные предыдущими запросами. Маршрутизация запроса на реплику, которая уже содержит его префикс, может значительно сократить вычисления префилла и TTFT.
Состояние выполнения: движок может повторно использовать KV-кэши, вычисленные предыдущими запросами. Маршрутизация запроса на реплику, которая уже содержит его префикс, может значительно сократить вычисления префилла и TTFT.
- Высокая неоднородность: запросы сильно различаются по длине входных и выходных последовательностей, что приводит к большим различиям в использовании ресурсов GPU и времени выполнения.
Высокая неоднородность: запросы сильно различаются по длине входных и выходных последовательностей, что приводит к большим различиям в использовании ресурсов GPU и времени выполнения.
- Недетерминированная генерация: даже для одного и того же входного сигнала количество сгенерированных выходных токенов может варьироваться, что затрудняет предварительное прогнозирование общей стоимости запроса.
Недетерминированная генерация: даже для одного и того же входного сигнала количество сгенерированных выходных токенов может варьироваться, что затрудняет предварительное прогнозирование общей стоимости запроса.
В совокупности эти свойства создают уникальную проблему для уровня оркестрации: как использовать преимущества повторного использования KV-кэша, балансируя при этом неоднородность между репликами движка.
Наивная оптимизация: максимизация повторного использования KV-кэша
Максимизация повторного использования KV-кэша — это самый наивный подход к сокращению TTFT и оптимизации эффективности обслуживания. Существует три распространенных способа максимизации повторного использования KV-кэша: аффинити сессий, аффинити префиксов и аффинити KV-кэша.
Аффинити сессий
Ray Serve LLM поддерживает аффинити сессий через консистентное хеширование, которое требует от клиентов связывать запросы из одной сессии с идентификатором сессии. Для многоходовых рабочих нагрузок это направляет последующие ходы на ту же реплику, позволяя им повторно использовать KV-кэши, вычисленные на более ранних этапах.
Дополнительным преимуществом консистентного хеширования для аффинити сессий является балансировка нагрузки на уровне сессий: при достаточном количестве виртуальных узлов оно равномерно распределяет сессии между репликами.
Рисунок 1: Консистентное хеширование с виртуальными узлами обеспечивает балансировку нагрузки на уровне сессий.
Аффинити префиксов
Основываясь на входящих запросах, маршрутизатор аффинити префиксов Ray Serve LLM строит префиксное дерево, которое отслеживает, какие префиксы были отправлены на каждую реплику. Последующие запросы проходят по дереву, чтобы найти реплику с наибольшим совпадением префиксов.
- Приблизительность: префиксное дерево отслеживает строки запросов, а не фактический KV-кэш. Кэширование KV работает на уровне токенов, поэтому представление маршрутизатора может отличаться от фактического состояния кэша движка.
Приблизительность: префиксное дерево отслеживает строки запросов, а не фактический KV-кэш. Кэширование KV работает на уровне токенов, поэтому представление маршрутизатора может отличаться от фактического состояния кэша движка.
- Отсутствие учета вытеснения: маршрутизатор предполагает, что ранее увиденные префиксы остаются в кэше. При высокой параллельности или длительной работе сервисов вытеснение может сделать это представление устаревшим. Выгрузка KV-кэша снижает количество промахов кэша, но маршрутизатор по-прежнему относится ко всем уровням кэша одинаково, игнорируя стоимость перезагрузки выгруженных блоков обратно в память GPU.
Отсутствие учета вытеснения: маршрутизатор предполагает, что ранее увиденные префиксы остаются в кэше. При высокой параллельности или длительной работе сервисов вытеснение может сделать это представление устаревшим. Выгрузка KV-кэша снижает количество промахов кэша, но маршрутизатор по-прежнему относится ко всем уровням кэша одинаково, игнорируя стоимость перезагрузки выгруженных блоков обратно в память GPU.
Аффинити KV-кэша
Маршрутизатор с учетом KV в Ray Serve LLM получает события KV-кэша от движков для построения глобального дерева radix KV-кэш блоков. vLLM генерирует события по мере создания и удаления блоков кэша, предоставляя маршрутизатору актуальное представление о состоянии KV-кэша на всех репликах и позволяя вычислять совпадения на уровне блоков KV-кэша для каждого запроса.
Мы сотрудничаем с командой NVIDIA Dynamo, чтобы предоставить индексатор KV от Dynamo через модульные интерфейсы для внешних библиотек. Плоскости событий, запросов и данных остаются нативными для Ray, в то время как индексатор KV от Dynamo вычисляет совпадения KV-кэша и участвует в оценке запросов. Полученные оценки затем используются плоскостью запросов Ray Serve для выбора целевой реплики.
Рисунок 2: Передача событий KV и построение глобального дерева KV radix. Движки vLLM генерируют события KV при создании или удалении блоков KV-кэша. Ray Serve LLM пересылает эти события в KVAwareRouter, где индексатор KV от NVIDIA Dynamo строит глобальное дерево KV radix и оценивает запросы на основе совпадения KV-кэша.
Помимо совпадения KV-кэша, маршрутизатор аффинити KV-кэша Ray Serve LLM также учитывает токеновую нагрузку при принятии решений о маршрутизации, что мы рассмотрим в следующих разделах.
Почему максимизации повторного использования KV-кэша недостаточно?
Оптимизация повторного использования KV-кэша не обязательно максимизирует производительность обслуживания. Рассмотрим асинхронную многоходовую рабочую нагрузку RL-развертывания с «отстающими» (stragglers), где запросы значительно различаются по длине входных и выходных последовательностей.
- Каждый шаг выполняет восемь развертываний, по 10 ходов на каждое развертывание.
Каждый шаг выполняет восемь развертываний, по 10 ходов на каждое развертывание.
- Каждое развертывание начинается с входных данных объемом 2 тыс. токенов, и каждый ход генерирует 1 тыс. токенов, за исключением двух «отстающих», чей последний ход генерирует 8 тыс. токенов.
Каждое развертывание начинается с входных данных объемом 2 тыс. токенов, и каждый ход генерирует 1 тыс. токенов, за исключением двух «отстающих», чей последний ход генерирует 8 тыс. токенов.
Рисунок 3: Визуализация асинхронных многоходовых RL-развертываний.
Для асинхронных RL-развертываний цель обычно состоит в том, чтобы минимизировать время шага или максимизировать пропускную способность при фиксированной параллельности. Мы используем p99 сквозной задержки развертывания как прокси-показатель времени шага. Мы сравниваем три варианта маршрутизатора при параллельности=16, как показано на рисунке 4:
- PureKVCacheAffinityRouter, который модифицирует KVAwareRouter для оптимизации исключительно ради совпадения KV-кэша.
PureKVCacheAffinityRouter, который модифицирует KVAwareRouter для оптимизации исключительно ради совпадения KV-кэша.
- ConsistentHashRouter, который также максимизирует повторное использование KV-кэша для многоходовых диалогов, обеспечивая при этом балансировку нагрузки на уровне сессий. Мы присваиваем каждому многоходовому развертыванию идентификатор сессии.
ConsistentHashRouter, который также максимизирует повторное использование KV-кэша для многоходовых диалогов, обеспечивая при этом балансировку нагрузки на уровне сессий. Мы присваиваем каждому многоходовому развертыванию ID сессии.
- KVAwareRouter, который учитывает как перекрытие KV-кэша, так и токеновую нагрузку.
KVAwareRouter, который учитывает как перекрытие KV-кэша, так и токеновую нагрузку.
Рисунок 4: Сравнение маршрутизаторов при асинхронных многоходовых RL-развертываниях. KVAwareRouter жертвует частью коэффициента попаданий в KV-кэш ради более сбалансированной токеновой нагрузки, что приводит к снижению p99 сквозной задержки развертывания.
KVAwareRouter демонстрирует лучшую p99 сквозную задержку развертывания, несмотря на более низкий коэффициент попаданий в префиксный кэш. При более детальном рассмотрении коэффициента попаданий в KV-кэш и токеновой нагрузки*, которая оценивает активный объем KV-кэша запросов декодирования на реплике, становится ясно, что KVAwareRouter жертвует коэффициентом попаданий в KV-кэш ради более сбалансированной токеновой нагрузки.
LinkСкопление запросов
Чистая аффинность к KV-кэшу, поддерживаемая PureKVCacheAffinityRouter, может вызывать скопление запросов. Как только реплика кэширует общий префикс, такой как системный промпт, последующие запросы обнаруживают перекрытие кэша и предпочтительно направляются на эту реплику. Запросы из множества различных диалогов сталкиваются на одной и той же реплике, создавая дисбаланс токеновой нагрузки.
LinkБалансировка нагрузки
При согласованном хешировании с достаточным количеством виртуальных узлов балансируется количество сессий между репликами, но они воспринимаются единообразно. В обслуживании LLM это не так: одна сессия может содержать короткие запросы, в то время как другая может стать «отстающей» с длительным временем выполнения. Несколько таких «отстающих» могут попасть на одну и ту же реплику, создавая дисбаланс нагрузки, несмотря на равномерное распределение запросов.
Хотя аффинность сессий отлично справляется с сохранением локальности KV-кэша, балансировка на уровне запросов не гарантирует фактическую балансировку нагрузки.
LinkЗа пределами повторного использования KV-кэша: важность учета токеновой нагрузки
Давайте рассмотрим работу, которую движок фактически выполняет для каждого запроса. Запрос проходит через две фазы:
- Prefill (предварительное заполнение): движок сначала повторно использует любой перекрывающийся KV-кэш, а затем вычисляет KV-кэш для оставшихся некэшированных входных токенов.
Prefill (предварительное заполнение): движок сначала повторно использует любой перекрывающийся KV-кэш, а затем вычисляет KV-кэш для оставшихся некэшированных входных токенов.
- Decode (декодирование): как только Prefill завершается, запрос начинает генерировать выходные токены. На каждом шаге декодирования движок обращается к KV-кэшу для всех сгенерированных к настоящему моменту токенов и выполняет еще один прямой проход.
Decode (декодирование): как только Prefill завершается, запрос начинает генерировать выходные токены. На каждом шаге декодирования движок обращается к KV-кэшу для всех сгенерированных к настоящему моменту токенов и выполняет еще один прямой проход.
Это выявляет ограничение использования перекрытия KV-кэша в качестве цели маршрутизации. Перекрытие KV-кэша показывает, сколько работы по Prefill можно сэкономить, но не сколько работы движку еще предстоит выполнить. Эта оставшаяся нагрузка исходит из двух источников:
- Нагрузка Prefill: активные токены Prefill плюс входящие токены, которые еще не кэшированы.
Нагрузка Prefill: активные токены Prefill плюс входящие токены, которые еще не кэшированы.
- Нагрузка Decode: текущая работа по декодированию от активных запросов.
Нагрузка Decode: текущая работа по декодированию от активных запросов.
Токеновая нагрузка объединяет оба показателя в единую метрику, фиксируя вычислительно-емкую нагрузку Prefill и ограниченную памятью нагрузку Decode на каждой реплике.
Аффинность к KV-кэшу по-прежнему важна, но ее роль меняется: она становится прокси-сервером для оценки нагрузки, а не самой целью маршрутизации. Перекрытие KV-кэша показывает, какая часть входящего Prefill может быть пропущена. Мы используем это для оценки оставшейся нагрузки Prefill, а затем объединяем ее с текущей нагрузкой Decode для оценки общей токеновой нагрузки.
KVAwareRouter — это политика маршрутизации с учетом токеновой нагрузки в Ray Serve LLM. Она также предоставляет некоторые дополнительные преимущества по сравнению с аффинностью сессий при согласованном хешировании:
- Автоматическое повторное использование KV-кэша: аффинность сессий через согласованное хеширование требует от клиентов предоставления ID сессий, поэтому приложения должны явно определять, какие запросы принадлежат к одной сессии. KVAwareRouter вместо этого автоматически обнаруживает и повторно использует доступный KV-кэш без необходимости в ID сессий.
Автоматическое повторное использование KV-кэша: аффинность сессий через согласованное хеширование требует от клиентов предоставления ID сессий, поэтому приложения должны явно определять, какие запросы принадлежат к одной сессии. KVAwareRouter вместо этого автоматически обнаруживает и повторно использует доступный KV-кэш без необходимости в ID сессий.
- Гибкость: в зависимости от ваших рабочих нагрузок вы можете адаптировать из функции оценки.
Гибкость: в зависимости от ваших рабочих нагрузок вы можете адаптировать из функции оценки.
Ray Serve LLM отслеживает каждый запрос на протяжении всего его жизненного цикла: когда он принят, когда сгенерирован первый токен, по мере выполнения декодирования и когда запрос завершается. На каждой фазе соответствующая токеновая нагрузка обновляется на маршрутизаторе, поддерживая актуальное представление о нагрузке на всех репликах движка. Ray Serve LLM предоставляет сквозные плоскости запросов и управления, интегрируя модуль службы выбора Dynamo для оценки запросов.
Рисунок 5: Регистрация токеновой нагрузки. Каждый запрос регистрирует и обновляет свою токеновую нагрузку в KVAwareRouter по мере прохождения этапов приема, завершения Prefill, декодирования и завершения запроса. Маршрутизатор поддерживает актуальное представление о токеновой нагрузке каждого движка, которое используется для оценки и маршрутизации последующих запросов.
С точки зрения масштабируемости, Ray Serve LLM реплицирует маршрутизатор и поддерживает согласованное в конечном счете представление о токеновой нагрузке между репликами маршрутизатора. KVAwareRouter принимает решения о маршрутизации локально, а затем асинхронно обменивается обновлениями нагрузки с другими маршрутизаторами. Это выводит маршрутизацию и потоковую передачу токенов из пути синхронизации, позволяя всем маршрутизаторам прийти к единому представлению о нагрузке.
Тем не менее, аффинность сессий с согласованным хешированием остается сильным выбором для рабочих нагрузок, где:
- Промахи кэша особенно дороги: для длинных многоходовых диалогов удержание всех ходов на одной реплике гарантирует локальность KV-кэша. KVAwareRouter, в зависимости от своих коэффициентов оценки, может жертвовать частью перекрытия кэша ради лучшего баланса токеновой нагрузки и поэтому требует настройки.
Промахи кэша особенно дороги: для длинных многоходовых диалогов удержание всех ходов на одной реплике гарантирует локальность KV-кэша. KVAwareRouter, в зависимости от своих коэффициентов оценки, может жертвовать частью перекрытия кэша ради лучшего баланса токеновой нагрузки и поэтому требует настройки.
- Повторное использование KV-кэша между сессиями ограничено: если диалоги расходятся после общего системного промпта, для KVAwareRouter остается мало возможностей для обнаружения дополнительного повторного использования кэша между сессиями.
Повторное использование KV-кэша между сессиями ограничено: если диалоги расходятся после общего системного промпта, для KVAwareRouter остается мало возможностей для обнаружения дополнительного повторного использования кэша между сессиями.
- Сессии имеют схожие рабочие нагрузки: когда сессии существенно не различаются по длине ввода/вывода или времени выполнения, согласованное хеширование уже обеспечивает разумную балансировку нагрузки на уровне сессий.
Сессии имеют схожие рабочие нагрузки: когда сессии не сильно различаются по объему ввода/вывода или времени выполнения, консистентное хеширование уже обеспечивает разумную балансировку нагрузки на уровне сессий.
Ссылка: Тематическое исследование: воспроизведение трассировок Claude Code
Кодирующие агенты сегодня являются одними из самых популярных рабочих нагрузок для LLM-инференса, поэтому давайте вдохновимся реальными трассировками Claude Code. Мы оцениваем различные стратегии маршрутизации на отфильтрованном подмножестве корпуса трассировок Weka Claude Code, который помещается в контекстное окно gpt-oss-120b.
Рисунок 6: Пример трассировки Claude Code.
Во-первых, давайте взглянем на форму рабочей нагрузки. Выделяются две вещи: сессии крайне неоднородны, и существуют значительные возможности для повторного использования KV-кэша внутри каждой сессии. Отдельные запросы уже сильно различаются по длине ввода и вывода, но на уровне сессий эти различия становятся еще больше. Некоторые сессии состоят всего из одного хода, в то время как другие охватывают десятки ходов и накапливают миллионы входных токенов.
Рисунок 7: Распределение запросов и реконструированных сессий при воспроизведении Weka Claude Code: длинные индивидуальные контексты, сильно варьирующиеся выходные данные, а также «тяжелые хвосты» длительности сессий и количества ходов.
Это создает интересный компромисс при маршрутизации: максимизировать ли повторное использование KV-кэша, сохраняя сессию на одной и той же реплике, или распределять нагрузку по токенам более равномерно между репликами?
KVAwareRouter в Ray Serve LLM учитывает как перекрытие KV-кэша, так и нагрузку по токенам при оценке реплик. Как показывают результаты, он достигает лучших показателей TTFT, TPOT и пропускной способности, чем сессионная аффинность с консистентным хешированием.
Рисунок 8: Учет нагрузки по токенам повышает производительность для неоднородных агентных многоходовых рабочих нагрузок. KVAwareRouter жертвует некоторой долей попаданий в префиксный кэш ради лучшей балансировки нагрузки по токенам, что приводит к улучшению TTFT, TPOT и пропускной способности. В качестве примечания: частота попаданий в префиксный кэш падает по мере роста параллелизма, что подчеркивает влияние вытеснения KV-кэша.
Чтобы понять почему, давайте взглянем на коэффициент вариации (CV) активных блоков декодирования и частоту попаданий в префиксный кэш*. Сессионная аффинность с консистентным хешированием хорошо сохраняет повторное использование KV-кэша, поскольку запросы из одной и той же сессии остаются на одной реплике. Но она балансирует нагрузку только на уровне сессий. Когда одна сессия может быть намного больше или длиннее другой, равномерное распределение сессий не означает равномерное распределение нагрузки по токенам.
Вместо этого KVAwareRouter жертвует некоторой долей попаданий в префиксный кэш ради лучшей балансировки нагрузки по токенам по мере роста параллелизма. Результат показывает, что для неоднородных рабочих нагрузок LLM балансировка повторного использования KV-кэша с нагрузкой по токенам приводит к лучшей общей производительности обслуживания, чем просто максимизация повторного использования KV-кэша.
* Примечание: Частота попаданий в префиксный кэш — это взвешенное по токенам отношение кэшированных токенов промпта (согласно отчету vLLM) к общему количеству токенов промпта, вычисленное для запросов, у которых доступны оба поля использования. Средний CV активных блоков декодирования — это офлайн-реконструкция дисбаланса KV-блоков декодирования на реплику: в 0,5-секундных интервалах каждый запрос вносит вклад в расчетный объем активных KV-блоков с момента первого токена до завершения; более низкий CV означает более сбалансированную нагрузку.
Ссылка: Дальнейшая работа
Возвращаясь к проблемам маршрутизации, обозначенным в начале этого поста, KVAwareRouter решает вопросы выполнения с сохранением состояния и неоднородности рабочей нагрузки, в то время как недетерминированная генерация остается открытой проблемой. Поскольку длина вывода неизвестна на момент приема запроса, запросы, которые изначально ожидались как легкие, могут в итоге иметь длительные фазы декодирования и сталкиваться на одной и той же реплике, создавая неожиданный дисбаланс нагрузки по токенам. Точный учет этой неопределенности остается открытой проблемой во всей отрасли. Один из подходов заключается в том, чтобы агентные системы предоставляли подсказки об ожидаемом поведении генерации.
В ближайшем будущем мы планируем расширить поддержку KVAwareRouter для развертываний с разделением префилла и декодирования, параллельных по данным развертываний и мультимодальных рабочих нагрузок. Маршрутизация — это все еще развивающаяся проблема: выбор того, где и на каком уровне выполнять маршрутизацию, требует глубокого понимания рабочей нагрузки и архитектуры развертывания. Следите за обновлениями!
Ссылка: Заключение
Оптимальная политика маршрутизации зависит от рабочей нагрузки, и Ray Serve LLM предоставляет настраиваемые политики маршрутизации, которые можно подключать и использовать. Сессионная аффинность с консистентным хешированием обеспечивает эффективное повторное использование KV-кэша для многоходовых диалогов, балансируя сессии между репликами. KVAwareRouter идет дальше, балансируя как перекрытие KV-кэша, так и нагрузку по токенам, что позволяет выполнять более тонкую балансировку нагрузки и улучшать TTFT, TPOT и пропускную способность для неоднородных рабочих нагрузок.
Ключевой вывод заключается в том, что перекрытие KV-кэша не должно быть единственной целью маршрутизации. Нагрузка по токенам, которая учитывает как оставшуюся работу по префиллу, так и текущую нагрузку на декодирование, лучше отражает фактическую работу на каждом движке. Балансировка этой работы при одновременном использовании преимуществ повторного использования KV-кэша ведет к более эффективному обслуживанию LLM.
Ссылка: Благодарности
Особая благодарность команде NVIDIA Dynamo за модульную структуру их индексатора KV и модулей оценки запросов, что сделало их доступными для интеграции с Ray Serve LLM.
Ссылка: Примечания по воспроизведению
Бенчмарк code.









