Перейти к содержанию

Маршрутизация с использованием политик LB

В этом руководстве на практике рассматриваются политики балансировки нагрузки на уровне маршрутов GPUStack: сохранение устойчивости сеансов при одновременном распределении нагрузки и предоставление службе принятия решений выбора между маленькой и большой моделью для каждого запроса. Справочную информацию по настройке см. в разделе Управление маршрутами модели.

Предварительные условия

  • Запущенная установка GPUStack. Шлюз AI, применяющий эти политики, по умолчанию развертывается с помощью GPUStack, поэтому стандартная установка не требует дополнительной настройки.
  • Две развернутые модели, служащие целями маршрута, например. qwen3-0.6b и qwen3-32b (они используются в примерах ниже).
  • Для части службы принятия решений: служба принятия решений Jev, зарегистрированная на странице Model - Provider в качестве поставщика TypeSafe Decision Service (Jev) (см. Управление модельным поставщиком).

Шаг 1. Создайте маршрут.

  1. Перейдите на страницу Routes.
  2. Нажмите Add Route, назовите его, например. qwen3.
  3. Выберите qwen3-0.6b и qwen3-32b как Route Targets, оставив все веса неустановленными.
  4. Нажмите Save.

Без весов и политики маршрут Route By является циклическим — простым циклическим перебором между двумя моделями.

Шаг 2. Взаимодействие Session Affinity и Least Inflight Request

Когда плагины политик включены, каждый из них оценивает каждую цель-кандидата. Итоговый механизм выбора выбирает цель с наибольшей взвешенной суммой баллов:

total(candidate) = Σ over plugins ( plugin weight × normalized score )

Session Affinity присваивает наивысший балл текущей цели сеанса, а Least Inflight Request — наименее загруженной цели. Сеанс переходит с текущей цели только тогда, когда разница баллов Least Inflight Request превышает разницу баллов Session Affinity.

Когда оба плагина имеют вес по умолчанию (1), следующая таблица показывает результат для случая, когда у текущей цели сеанса накопилось x обрабатываемых запросов, а остальные цели простаивают. Это наиболее благоприятное условие для перехода сеанса на другую цель:

Цели (N) x=0 x=1 x=2 x=3 x=4 Возможен переход?
2 сохраняется ничья ⚠️ ПЕРЕХОД ПЕРЕХОД ПЕРЕХОД да
3 сохраняется сохраняется ничья ⚠️ ПЕРЕХОД ПЕРЕХОД да
4 сохраняется сохраняется сохраняется сохраняется сохраняется только при x ≈ 5.3
≥5 сохраняется сохраняется сохраняется сохраняется сохраняется нет
  • ПЕРЕХОД: сеанс переходит к следующей цели-кандидату.
  • ничья ⚠️: разницы баллов точно равны; итоговый выбор случаен, поэтому запрос может как перейти на другую цель, так и остаться на текущей.
  • N ≥ 5: разница баллов Least Inflight Request ограничена значением 1/(N−1) ≤ 0.25, меньшим, чем разница Session Affinity. Поэтому один лишь дисбаланс нагрузки не может нарушить привязку сеанса.

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

Шаг 3. Включение Session Affinity и Least Inflight Request

  1. Отредактируйте маршрут, начиная с шага 1.
  2. Включить Session Affinity — принять цепочку сеансовых ключей по умолчанию (заголовок session-id, заголовок x-client-request-id, затем поле тела prompt_cache_key) или указать свою собственную.
  3. Включите Least Inflight Request с весом по умолчанию.
  4. Сохраните и убедитесь, что Route By маршрута равно Policy.

Сеансы теперь привязаны: запросы, несущие один и тот же сеансовый ключ, остаются на одной и той же цели — кэши подсказок остаются теплыми — в то время как сильно загруженный владелец в конечном итоге сбрасывает сеансы на простаивающий целевой объект, как указано в таблице выше.

Обратите внимание, какие источники ключей на самом деле применяются на каждом пути: ключи заголовка (например, session-id) работают везде, но ключи тела, такие как prompt_cache_key, вступают в силу только для запросов /responses и /messages — для /chat/completions, у которого нет стандартного поля тела сеанса, учитываются только ключи заголовка цепочки. Предпочитайте ключ заголовка для трафика чата.

Шаг 4. Добавьте маршрутизацию службы принятия решений.

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

  1. Отредактируйте маршрут и включите Decision Service Routing:

    • Decision Service — выберите зарегистрированного провайдера.
    • Decision Model — e.g. jev-latest.
    • Instructions — «Выберите наименьшую модель, которая сможет хорошо обработать запрос. Простые, короткие или фактические запросы переходят к быстрой модели; сложные запросы рассуждений, математических вычислений или кода переходят к большой модели».
    • Model Criteria — одна запись для каждой цели (или нажмите Generate from targets и уточните):
      • qwen3-0.6b — «Очень быстрая и недорогая модель 0.6B. Обрабатывает приветствия, простые вопросы по фактам, форматирование и короткую болтовню. Не справляется с многоэтапными рассуждениями, математикой и программированием».
      • qwen3-32b — «Модель рассуждения 32B с высокими возможностями. Обрабатывает сложные рассуждения, математические вычисления, генерацию кода и длительный контекстный анализ. Медленнее и дороже за запрос».
  2. Сохраните, затем отправьте два аутентифицированных запроса по имени маршрута qwen3 (замените $GPUSTACK_API_KEY ключом API, созданным на странице API Keys):

1
2
3
4
5
curl http://my-gpustack/v1/chat/completions \
  -H "Authorization: Bearer $GPUSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -H "session-id: demo-session" \
  -d '{"model": "qwen3", "messages": [{"role": "user", "content": "Write a one-line greeting card."}]}'
1
2
3
4
5
curl http://my-gpustack/v1/chat/completions \
  -H "Authorization: Bearer $GPUSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -H "session-id: demo-session" \
  -d '{"model": "qwen3", "messages": [{"role": "user", "content": "Prove that there are infinitely many primes, then implement the proof sketch in Python."}]}'

Первый запрос направляется на qwen3-0.6b, второй на qwen3-32b.

Как работают веса

Веса появляются в двух разных местах и имеют разное значение:

Целевой вес (доля трафика)

В режиме Target Weight вес цели равен общей доле трафика этой цели — независимо от того, сколько экземпляров она запускает. GPUStack масштабирует веса для каждого экземпляра, поэтому разделение всегда соответствует настроенным целевым весам:

  • Цель A (вес 100, 1 экземпляр) и Цель B (вес 100, 2 экземпляра) делят трафик 50/50 между двумя целями, а не 1/3 на каждый экземпляр.
  • Увеличение масштаба цели с 1 до 2 экземпляров не меняет ее долю; каждый экземпляр затем получает половину этого количества.

Пример с qwen3-0.6b и qwen3-32b: вес 100 на qwen3-0.6b и вес 300 на qwen3-32b отправляет 25 % запросов на маленькую модель и 75 % на большую, независимо от количества их реплик.

Вес политики (влияние в сумме)

Параметр Weight каждого плагина определяет его вклад во взвешенную сумму в режиме Policy. Это положительное число; дробные значения тоже допустимы. Если Weight не задан, используется встроенный вес плагина: 1 для Session Affinity и Least Inflight Request, 10 для Decision Service Routing.

Правила расчёта баланса Session Affinity × Least Inflight Request при x обрабатываемых запросах у текущей цели сеанса и простое всех остальных целей:

  • разница баллов Session Affinity: affDiff = 0.5 / (2·(1 − 2⁻ᴺ))
  • разница баллов Least Inflight Request: loadDiff = x / ((1+x)·(N−1) + 1)
  • сеанс переходит к другой цели, когда w_leastInflight × loadDiff > w_affinity × affDiff

Изменение соотношения весов сдвигает порог перехода. В следующей таблице указано минимальное число x обрабатываемых запросов у текущей цели, при котором сеанс переходит на другую цель, для каждого соотношения весов (Least Inflight Request : Session Affinity) и числа целей N:

N 1:1 (по умолчанию) 1.5:1 2:1
2 2 1 1
3 3 1 1
4 6 2 1
5 никогда 3 2
6 никогда 7 3
7 никогда никогда 4
8 никогда никогда 9

Читаем таблицу:

  • При соотношении по умолчанию 1:1 на маршрутах с пятью и более целями сеанс никогда не переходит на другую цель: разница баллов Least Inflight Request ограничена значением ниже разницы Session Affinity.
  • Уменьшение Weight Session Affinity вдвое (соотношение 2:1) позволяет нагрузке влиять на выбор уже при x = 2–4 на маршрутах с количеством целей до 8. Удвоение Weight Least Inflight Request даёт тот же эффект: важно только соотношение весов.
  • Точные ничьи (например, 1:1 при N=2/x=1) определяются финишером случайным образом.

Как политика разделяет работу

Все включённые политики вносят вклад в одну общую взвешенную сумму, выполняя каждая свою задачу:

  • Маршрутизация службы принятия решений решает, какая модель соответствует запросу (ее значение по умолчанию Weight, равное 10, означает, что обычно это доминирующий голос);
  • Привязка сеанса сохраняет текущую модель сеанса, сохраняя локальность кэша подсказок;
  • Least Inflight Request распределяет запросы между экземплярами выбранной модели.

Если модель развернута в нескольких экземплярах, Decision Service Routing сужает выбор до нужной модели, а Least Inflight Request выбирает экземпляр этой модели.