Миграция на vLLM V1: избегаем ошибок logprobs в RL-обучении | AiManual
AiManual Logo Ai / Manual.
10 Май 2026 Гайд

Миграция с vLLM V0 на V1: как избежать ошибок в RL-обучении

Пошаговое руководство по миграции с vLLM V0 на V1 для RL-пайплайнов. Как не сломать PPO/GRPO из-за новых logprobs, clip rate и других подводных камней.

Добро пожаловать в ад logprob-костылей

Если вы когда-нибудь считали, что vLLM выдаёт вам правильные logprobs для RL-обучения, — я вас разочарую. До V1 они были… мягко говоря, не совсем корректными. Но хуже другое: когда разработчики vLLM наконец исправили баг в V1, все наши пайплайны PPO/GRPO/GSPO дружно полетели в тартарары. Потому что алгоритмы, как злые собаки, привыкли к неправильному корму, а правильный — вызвали несварение.

В этой статье я расскажу, как избежать 10-часовых дебаг-сессий, если вы переходите на vLLM V1 (на момент 10.05.2026 — стабильная версия 1.2.0). Заодно покажу, почему rollout logprobs, clip rate и даже ServiceNow (да-да, тот самый кейс из практики) могут свести с ума.

Предупреждение: если вы используете vLLM только для инференса без RL — скорее всего, миграция пройдёт незаметно. Но если вы гоняете rollout-ы для PPO/GRPO — готовьтесь править код.

Что сломалось в V1 (и почему это хорошо)

Главная проблема V0 — logprobs возвращались для токенов с учётом ложного выравнивания. В RL мы используем logprobs политики (policy) для вычисления advantage и KL-регуляризации. Старые V0 могли выдавать logprobs, которые не соответствовали действительной вероятности генерации из-за бага в кэшировании KV-кэша. В V1 это исправили — теперь logprobs корректны. Но цена — все, кто привык к старым значениям, получают резкий скачок clip rate (доля отсечённых clipped surrogate objective). Если после миграции ваш clip rate подскочил с 10% до 90% — не пугайтесь, это нормально. Надо перекалибровать гиперпараметры.

Второе изменение — отказ от устаревшего формата вывода. Раньше вы могли получить logprobs через параметры use_beam_search или top_logprobs. В V1 эти флаги ведут себя по-другому. Теперь нужно явно запрашивать logprobs=True и указывать prompt_logprobs, если нужны логиты промпта. Мелочь? Ага, только из-за этого могут разъехаться весь advantage.

💡
Кстати, если вы используете трюки с Interpretation Drift (а мы разбирали это в статье), то V1 с его корректными logprobs может как помочь, так и усугубить дрифт. Проверяйте!

Пошаговая детективная работа: поиск расхождений

Прежде чем менять вызовы в пайплайне, давайте поймём, где именно расходятся logprobs. Я предлагаю подход: заморозить модель, прогнать 1000 промптов на V0 и V1, сравнить суммы logprobs по последовательности. Если модель не менялась, суммы должны совпадать. В V0 они часто не совпадали из-за бага в top-k выборке.

1 Сравнительный замер на офлайн-датасете

Берём датасет из ваших реальных RL-данных (например, ServiceNow-логи с запросами). Важно: используйте одинаковый seed и температуру. Для чистоты эксперимента — temperature=0.0, чтобы убрать стохастику.

# Неправильно: полагаться на старый API
from vllm import LLM, SamplingParams

llm_v0 = LLM(model="Qwen/Qwen3.5-32B-Instruct", version="v0")  # deprecated в 2026
llm_v1 = LLM(model="Qwen/Qwen3.5-32B-Instruct", version="v1")

params = SamplingParams(temperature=0.0, max_tokens=100, logprobs=True)

# Сравним первые 100 токенов
outputs_v0 = llm_v0.generate(prompts, params)
outputs_v1 = llm_v1.generate(prompts, params)

for o0, o1 in zip(outputs_v0, outputs_v1):
    lp0 = [t.logprob for t in o0.outputs[0].token_ids]
    lp1 = [t.logprob for t in o1.outputs[0].token_ids]
    total_diff = sum(lp0) - sum(lp1)
    print(f"Diff: {total_diff}")  # Если diff > 1e-3 — проблема

В моём эксперименте на Qwen3.5-32B diff достигал 0.3 nats. Этого достаточно, чтобы advantage подскочил на 20%.

2 Настройка param-ов под V1

В V1 изменился способ указания числа logprobs. Раньше top_logprobs=5 подразумевало, что вернутся 5 лучших логпроб для каждого токена. В V1 это поле стало top_logprobs=5 тоже, но теперь оно работает только вместе с logprobs=True. Игнорирование этого флага вернёт пустой список. Ловили баг?

# Правильно для V1
from vllm import SamplingParams

params = SamplingParams(
    temperature=0.0,
    max_tokens=100,
    logprobs=True,         # обязательно
    top_logprobs=5,        # опционально
    prompt_logprobs=False,  # только для промпт-логов
)

Важный нюанс: если вы в RL используете prompt_logprobs=True, будьте готовы к дополнительному расходу памяти. V1 хранит логиты для всех токенов промпта, что может привести к OOM на длинных последовательностях. Рекомендую отключать, если не нужны.

Сценарий: бесшовная миграция PPO-пайплайна

Допустим, у вас есть PPO-тренер, который использует vLLM для rollout-ов. Скорее всего, он написан под V0. Вот типичные изменения, которые надо внести.

1 Обновление вызова generate

В V0 llm.generate() возвращал объекты с полем cumulative_logprob. В V1 это поле осталось, но его значение теперь корректно. Если ваш PPO смотрит на cumulative_logprob как на сумму logprobs, а не на advantage — всё ок. Но вот если вы его использовали для computation of reward — пересчитайте.

# Старый код (V0)
output = llm.generate(prompt, params)
logprob_sum = output.outputs[0].cumulative_logprob  # могло быть искажено

# Новый код (V1)
output = llm.generate(prompt, params)
logprobs = [t.logprob for t in output.outputs[0].token_ids]
logprob_sum = sum(logprobs)  # теперь это надёжно

2 Калибровка коэффициентов PPO

Из-за новых значений logprobs, старые гиперпараметры PPO (KL penalty, clip epsilon) перестанут работать. Ожидайте роста clip rate. Мой совет: уменьшите learning-rate в 2 раза и увеличьте clip epsilon с 0.2 до 0.3, пока модель не адаптируется. Потом верните обратно. Это спасёт от взрывного роста policy loss.

💡
Подробнее про то, как не убить модель при RL fine-tuning, читайте в нашем разборе 6 месяцев провальных экспериментов с RL.

3 Особый случай: GSPO и ServiceNow

Недавно столкнулись с кейсом от ServiceNow — они мигрировали свой RLHF-пайплайн с V0 на V1. У них использовался алгоритм GSPO (Group Successive Policy Optimization). Там logprobs нужны для вычисления подъёма группы. Из-за неправильных logprobs в V0 у них был занижен advantage, и модель недоучивалась. После перехода на V1 advantage вырос в 1.5 раза, пришлось увеличивать batch size, чтобы стабилизировать обучение.

Совет: если у вас GSPO, проверьте group_advantage до и после миграции. Скорее всего, придётся переобучить базовую модель.

Грабли, которые сломали мои выходные

Собрал типичные ошибки, которые допустил сам и видел у других. Лучше подсветить сразу, чтобы вы не теряли время.

  • Забыли обновить triton-компилятор. V1 требует triton>=3.2.0. Если версия старая, logprobs могут быть ещё более странными. Проверьте: pip install triton --upgrade.
  • Путаница с logprobs для нескольких гипотез. Если вы используете n=5 для получения нескольких гипотез, в V1 каждая гипотеза теперь имеет независимые logprobs. Раньше они иногда копировались с первой. Теперь алгоритм корректный, но если вы усредняли — разница будет.
  • Не отключили enforce_eager. В V1 режим eager работает медленнее, но даёт более стабильные logprobs. Если заметили аномалии, попробуйте --enforce-eager в запуске движка.
  • Устаревшие модели: Mistral 7B v0.3. Некоторые старые модели (LLaMA 2, Mistral 7B 0.3) перестали корректно работать с V1 из-за несовместимости с новым токенизатором. Решение: используйте trust_remote_code=True, но лучше перейти на актуальные веса (LLaMA 4, Qwen 3.5, DeepSeek v3.5).
  • Clip rate мониторинг забыли. После первого rollouts всегда смотрите на clip rate. Если >50% — у вас проблема. Калибруйте.

Кстати, о катастрофическом забывании: после миграции logprobs могут так изменить преимущества, что модель начнёт затухать на старых задачах. Как бороться - читайте в статье про катастрофическое забывание.

FAQ: вопросы, которые вы постесняетесь задать

Вопрос: Могу ли я сосуществовать V0 и V1 в одном пайплайне?

Технически да, через разные инстансы LLM с разными версиями. Но это UB — они будут по-разному считать KVCache, и logprobs в одном батче разойдутся. Не советую.

Вопрос: Нужно ли переобучать reward model после миграции?

Reward model обычно не зависит от vLLM версии, если вы не передаёте ей logprobs подсказки. Если передаёте — да, переобучить, иначе reward перестанет калиброваться.

Вопрос: Как быстро откатиться обратно на V0 в случае проблем?

Просто pip install vllm==0.8.2. Но учтите, что V0 больше не поддерживается с января 2026. Не попадёте в баги? Возможно нет, но риски.

Прогноз: куда катится vLLM

К 2027 году V0 умрёт окончательно. Разработчики уже обещают в V2.0 поддержку nested KV-cache и интеграцию с Sparse Attention. Но главное — logprobs станут ещё точнее за счёт prefix caching с правильным учётом вероятности. Если сейчас вы перейдёте на V1, то к тому моменту у вас будет стабильный пайплайн. Если застрянете на V0 — переезд будет болезненным.

Мой совет: потратьте неделю на миграцию сейчас, чем потом экстренно фиксить продакшн. И не забывайте про полное руководство по тонкой настройке LLM — там есть много сопутствующих практик.

Бонус: Если у вас высокие требования к Time-to-First-Token при rollout-ах, обратите внимание на динамическую лень через LazyGate — мы писали об этом здесь. В V1 эта фича работает ещё эффективнее.

Удачной миграции. Если что-то пошло не так — пишите в комментариях, разберёмся вместе.

Подписаться на канал