Добро пожаловать в ад 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.
Пошаговая детективная работа: поиск расхождений
Прежде чем менять вызовы в пайплайне, давайте поймём, где именно расходятся 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.
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 эта фича работает ещё эффективнее.
Удачной миграции. Если что-то пошло не так — пишите в комментариях, разберёмся вместе.