Nobody should have to debug a reinforcement learning pipeline by staring at a clip-rate chart that looks like a heart attack. But that’s exactly what happened when we migrated from vLLM 0.8.5 to the V1 engine in 0.18.1. The initial run was a mess — trainer-side logprobs and reward curves veered wildly from the known-good reference, and the clamping indicator lit up like a Christmas tree. The instinct was to blame the RL objective. That instinct was wrong.

The real culprits were four backend mismatches hiding in plain sight. First, V1’s default logprob mode returns raw model outputs, skipping temperature scaling and top-k filtering that the trainer expects. Switching to logprobs-mode=processed_logprobs fixed the obvious mean offset, centering the policy ratio near 1.0. But the training curves still gaped.

Second, V1 runtime defaults — prefix caching and async scheduling — introduced execution-path differences that V0 never had. In an online RL setup where weights update mid-stream, a prefix-cache hit can reuse state computed before the update, silently corrupting the optimization. We disabled both. Third, the inflight weight-update path needed to mirror V0’s behavior precisely: block, load new weights, resume without trashing cached state. The mode="keep", clear_cache=False incantation proved critical.

The lesson is simple and humbling. We spent too long squinting at objective-level staleness when the problem was semantic and mechanical. Don’t change the loss function until you’ve verified the bytes flowing through the engine actually mean what you think they mean. The green run in Figure 1 is boring — it hugs the reference exactly — and that’s the point.