Skip to content

fix(inference): make online FP8 weight updates safe and unbiased - #3628

Closed
mikasenghaas wants to merge 10 commits into
fix/muon-uneven-fsdp-paddingfrom
fix/abort-stale-vllm-requests
Closed

mikasenghaas wants to merge 10 commits into
fix/muon-uneven-fsdp-paddingfrom
fix/abort-stale-vllm-requests

Conversation

@mikasenghaas

@mikasenghaas mikasenghaas commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

Summary

  • abort stale vLLM requests at weight-update boundaries so cancelled evaluations cannot retain old-policy KV state or block broadcasts
  • backport Garrett's focused BF16-trainer / FP8-inference path from feat(deepseek_v4): fp8 RL #3627:
    • power-of-two UE8M0 block scales
    • stochastic E4M3 weight rounding for unbiased sub-bin online updates
    • DeepSeek V4 SwiGLU clamp handling on the Triton FP8 fast path
  • retain the existing DeepSeek V4 attention-sink loading fix from the base branch

Why stochastic rounding

Round-to-nearest can pin a weight to its initial E4M3 value until training moves it by half a quantization bin. Stochastic rounding instead selects the adjacent representable value according to the fractional distance, so the served value equals the BF16 trainer value in expectation. Values already on the FP8 grid remain exact.

Validation

  • ruff passes
  • full unit suite: 191 passed
  • dedicated stochastic-rounding tests cover exact on-grid preservation and seeded midpoint unbiasedness
  • 8 trainer + 4 inference node DSV4F smoke test completed all 5 steps on 96 B300 GPUs
  • step-0 BF16-to-FP8 broadcast and reload succeeded; broadcasts/reloads continued through step 5
  • step-0 and terminal online evals completed with 0% rollout errors and 0% truncation
Metric Result
Slurm result COMPLETED (exit 0)
End-to-end runtime 20m 41s
Post-compile throughput 39.9k-50.5k tok/s
Post-compile MFU 18.5%-23.4%
Peak trainer memory 153.3 / 267.7 GiB per B300 (57.3%)

On B300/SM103, DeepGEMM requires packed UE8M0 scales, so the validated config pairs fp8_ue8m0_weight_scales = true with VLLM_USE_DEEP_GEMM_E8M0=1. The older H200-style disabled setting fails during engine startup with Unsupported architecture or scaling factor types.

This PR is stacked on #3625 for the uneven-shard Muon and DSV4 attention-sink fixes. The FP8 commits preserve Garrett's authorship from #3627.


Note

Medium Risk
Changes weight-update pause semantics and online FP8 quantization paths that directly affect trainer–inference policy alignment; incorrect behavior would skew rollouts rather than typical UI-only risk.

Overview
Hardens online RL weight broadcast by pausing inference with mode=abort (orchestrator default) instead of draining in-flight work, and exposes mode / clear_cache on the vLLM /pause admin route so stale rollouts cannot block updates or resume under the old policy’s KV state.

Adds inference.fp8_ue8m0_weight_scales and inference.fp8_stochastic_weight_rounding, wired through worker env vars to vLLM patches that force power-of-two block scales and unbiased stochastic e4m3 rounding on online per_block_cast_to_fp8, so BF16 trainer weights round-trip dequantized checkpoints and track sub-bin updates in expectation. Trainer-side Triton FP8 casts are updated to be bit-identical to vLLM’s CUDA quantizer (RN divide, clamp, scale floor).

Additional vLLM backports: DeepSeek V4 request-level tools attach to the first existing system message (matches reference encoding), and Triton MoE SwiGLU clamp is honored on the fused FP8 fast path. Multi-node SLURM templates set -a when sourcing .env so vars propagate into srun child shells.

Reviewed by Cursor Bugbot for commit a3ffa1a. Bugbot is set up for automated code reviews on this repo. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5748803. Configure here.

logger.debug(f"Pausing inference engines to update weights to policy v{step}")
await asyncio.gather(
*[_admin_post(client, "/pause", params={"mode": "keep", "clear_cache": "false"}) for client in admin_clients]
*[_admin_post(client, "/pause", params={"mode": "abort", "clear_cache": "false"}) for client in admin_clients]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dynamo NCCL pause still uses keep

High Severity

The Dynamo NCCL weight-update path still pauses with mode=keep. Cancelled non-streaming requests can stay alive in vLLM and block that pause indefinitely, leaving KV from the previous policy across the update.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5748803. Configure here.

garrett361 and others added 5 commits September 22, 2026 18:59
…FP8 weight scales

Promotes the PRIME_DIAG_UE8M0_WEIGHTS diagnostic to a config field. For
dequantized FP8 releases the weights already sit on the e4m3 grid under
power-of-two block scales, so quantizing with those scales is exact where
amax / 448 injects full e4m3 rounding error. Measured against a bf16 reference
on 203k SWE tokens, combined with VLLM_USE_DEEP_GEMM_E8M0=1 it gives the lowest
mismatch of every FP8 configuration tried (KL 3.5e-3 vs 5.6e-3 for fp32 scales,
IPO masked fraction 0.0000 vs 0.0002) and removes the decode-step glitch tokens.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ast path

vLLM 0.29.0's TritonExperts.apply fuses SiLU-and-mul with the fp8 block
quantization through ops.silu_and_mul_per_block_quant, which has no clamp
argument, so DeepSeek V4 Flash's swiglu_limit of 10 is dropped for every
batch below 128 tokens when VLLM_USE_DEEP_GEMM_E8M0 is off. Tokens whose
gate or up pre-activation exceeds the limit get a wrong expert output; this
produced the deterministic glitch-token argmaxes seen in the FP8 RL runs.
The patch routes apply to the clamped activation branch whenever a clamp is
configured and leaves models without a clamp on the fast path.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ne FP8 weights

Round-to-nearest pins a weight that starts on the e4m3 grid until the trainer
has moved it half a bin, about 1000 steps at lr 1e-6, so the served policy
stays at step 0 while the trainer drifts. Rounding each weight to its far
neighbour with probability equal to the fractional distance makes the served
weight unbiased and lets it track sub-bin updates in expectation. On-grid
input rounds to nearest, so the checkpoint quantizes to identical bytes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mikasenghaas mikasenghaas changed the title fix(inference): abort stale requests before weight updates fix(inference): make online FP8 weight updates safe and unbiased Sep 22, 2026
mikasenghaas and others added 4 commits September 22, 2026 20:22
…for bit

The trainer's per-token FP8 activation cast now matches vLLM's production CUDA
op, torch.ops._C.per_token_group_fp8_quant, bit for bit. Three kernels floor
amax at 1e-10 and use tl.math.div_rn for both the group scale and the
elementwise quotient. Triton lowers a constexpr `/` to multiply-by-reciprocal,
which sits one fp32 ULP from the correctly rounded value and flipped roughly
0.1% of quantized elements by one e4m3 step; the old 1e-4 floor applied to the
scale rather than to amax also corrupted every group with amax below 0.0448.

On four 4096x4096 bf16 activation tensors, identical group scales went from
41-44% to 100% and identical fp8 bytes from about 99.9% to 100%: zero differing
scales of 131072 groups and zero differing bytes of 16777216 elements. The
weight kernel is deliberately unchanged and stays at 100%, because vLLM's
weight path is itself a multiply-by-reciprocal.

A new GPU test asserts the equality against the CUDA op rather than calling
into vLLM from the trainer, which imports vLLM nowhere.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mikasenghaas

Copy link
Copy Markdown
Member Author

Superseded by the #3692 fork branch, which the DSV4F SFT run pins.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants