Skip to content

p2p: update speculative drafts from trained MTP weights - #3985

Merged
Zhichenzzz merged 39 commits into
yueming/p2p-draft-replicasfrom
yueming/p2p-draft-runners
Oct 12, 2026
Merged

Zhichenzzz merged 39 commits into
yueming/p2p-draft-replicasfrom
yueming/p2p-draft-runners

Conversation

@yueming-yuan

@yueming-yuan yueming-yuan commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Update both target and MTP draft weights through P2P. Previously, the draft replaced the target's weight registration, causing connect failures, and trained MTP weights never reached the draft (sgl-project/sglang#35312).

Create a sender and replica per runner role, using the draft's model config and speculative MoE backends. Route shared embedding/head inputs to the target once. The companion SGLang change registers weights by (role, tp_rank).

Supports the model's own single EAGLE MTP layer with selector all. Separate draft checkpoints, multi-layer EAGLE and a draft with its own MoE runner or a2a backend (sglang reloads a draft under the target's) are rejected; NGRAM/UNO update only the target. Adds GLM-4.7-Flash and Qwen3.5 MTP e2e coverage, including PP-stage writes to multiple engines.

Stacked on #3969; requires sgl-project/sglang#42820 and sgl-project/sglang#43251, which rebuilds Qwen3.5's gemma_weight and runs the GLM-lite NextN post-load after p2p's writes.

Recorded validation

Target/draft equality passed with --check-weight-update-equal:

Hardware / model Startup update Later updates Acceptance
H200 / GLM-4.7-Flash, actor PP2, 2 × TP2 engines, bf16 5.3 s 1.2 s 0.56–0.58
H200 / Qwen3.5-35B-A3B, TP4, bf16 15.6 s 2.9–3.1 s 0.74–0.77

Target-only GB300 Qwen3-30B-A3B TP2 passed in bf16/TRT-LLM and fp8-block/UE8M0. The GB300 equivalence suite passed bf16, fp8 block with UE8M0 and fp32 scales, mxfp8 and nvfp4.

Failure controls reproduced the missing per-role registration at connect, Attempting to copy from device cpu to device meta without #3969's buffer materialization, and UE8M0 loader failures with meta probe inputs.

304 weight-update/API-client tests passed on H200 with sglang (296 on #3969 alone). Cleanup passed pre-commit and 64 before/after context-scope cases. The GLM timing row was measured on cleanup revision ddcfab372a; other table/equivalence results predate that cleanup.

H200 CI run: GLM test step 758 s, Qwen3.5 1132 s; registered estimates 1000/1500 s.

ci-sglang-pr: #43251

With a draft model, each rollout engine runner registered its weights under
the rank alone, so a p2p sender read the draft's weight table for the
target, and the draft itself was never written: the trained MTP layer never
reached it.

sglang now publishes each runner under its role. When the update covers the
draft (the trainer holds MTP layers) and the engines draft with the target
model's own MTP layer, a sender keeps replicas, a stager and write targets
per runner; the draft replica is built from the draft model config under
the draft's speculative MoE backends, as EAGLE v2 builds its draft. A
bucket's HF tensors go to the first runner whose replica loads them, the
target before the draft, so the weights a draft shares with its target are
written once. Multi-layer EAGLE, drafts from their own checkpoint and
engines that run different runners are rejected at connect.
Four training GPUs (TP1 DP4 EP4) write the target and its MTP draft into
two TP2 EAGLE engines over Mooncake; the equality check covers both.
The loader probe maps the Qwen3.5 target, which ParameterMapper never did,
and its Gemma norms' derived buffers. Four training GPUs write the target
and its MTP draft into one TP4 EAGLE engine; the equality check covers
both, vision weights skipped.
@yueming-yuan yueming-yuan added run-ci-weight-update Run weight update tests bypass-fastfail Disable CI fast-fail: run all stages and all tests to surface every failure run-ci labels Oct 8, 2026
@yueming-yuan yueming-yuan added run-on-hopper Dispatch: run the selected tests on Hopper (H100/H200) only run-on-blackwell Dispatch: move every selected test that supports Blackwell onto B200 labels Oct 8, 2026

@claude claude 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.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

@yueming-yuan yueming-yuan removed the run-on-hopper Dispatch: run the selected tests on Hopper (H100/H200) only label Oct 9, 2026

@claude claude 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.

I reviewed this PR and didn't find any bugs. Because it's a large change to the core P2P weight-transfer path spanning speculative-decoding draft runners, Mooncake transport, and write-failure handling, a human look is still worthwhile.

What was reviewed (new since the last run, commits 5496f76/43a9bd2/04a7485): the double-load zero-vs-0xFF probe in model_replica.py that rejects a loader leaving param bytes unwritten — the byte-diff and unwritten-byte-count logic checks out against its tests; the move of write-failure/timeout handling into TransferBuffers.acquire()/wait_for_all_writes so a broken write fails the update before more writes queue behind it; the previously-flagged candidate that a draft's shared embed/head weights could stay stale if its registered address differs from the target's remains open and worth human attention.

Extended reasoning...

This review's scope diff (b056fc6..HEAD) reduces, net of what I already reviewed twice before, to the delta between commit 088fc53 (my last review) and HEAD: a refactor moving write-failure/timeout handling into TransferBuffers and a new double-load probe in model_replica.py that detects a loader leaving param bytes unwritten. The surface touched is purely internal weight-transfer plumbing (no auth/crypto/injection exposure); the new probe logic is subtle (compares results of loading over zero-filled vs 0xFF-filled buffers to detect untouched bytes) but is exercised by matching unit tests whose expected byte counts I verified against the test fixtures. Given the overall PR remains large and touches a critical correctness path (RL rollout weight synchronization) with prior non-author review comments addressed via follow-up commits, and the previously surfaced candidate about stale shared draft weights is still outstanding, a human review is still warranted even though this run found no new bugs.

This review covers commit 04a7485, which is no longer the latest commit on this pull request; later commits are not covered by it.

@claude claude 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.

Code review found no issues

No high-confidence issues detected in this change.

# Conflicts:
#	miles/backends/training_utils/weight_update/protocols/p2p.py
send_bucket writes each HF tensor through the first runner that loads
it, the target, which is right only while the draft loads such a tensor
into params it shares with the target, as drafts share its embed and
head. A draft holding its own copy would never be written, and its
acceptance would fall as training moved the target. begin_sync now
checks that each such draft param is published at an address the target
publishes on the same engine rank.
Routing each HF tensor to the first runner that loads it never updated a
draft's own copy of a target weight. The engine's own weight updates run
every runner's loader over the whole stream; p2p now does the same, so
the check rejecting such drafts goes. A weight the draft shares with the
target is written by both with the same bytes.
… sends

Its weight would keep the value the engine started with. Each sender
loads only its own engine ranks (EP splits the experts), so every rank
ORs its view of its PP stage's tensors in one all_reduce, once per
connect. Exempt: towers (resent unchanged; a language-only engine has
none), lm_head on a tied engine, and MTP layers when no draft runner is
written. Replaces the info log of skipped tensors.

@claude claude 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.

No new blocking issues. 1 optional suggestion (a nit or a note on pre-existing code) was found and not posted. Nothing in this review needs a push before merging.

@claude claude 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.

Code review found no issues

No high-confidence issues detected in this change.

… trainer sends"

This reverts commit ee00ab4. Coverage rests on
--check-weight-update-equal, as on the non-p2p path. Telling at runtime
which tensors the rollout engines accept needs a maintained list of
exceptions (towers, tied heads, MTP layers without a draft runner),
which does not hold up as models and formats change.

@claude claude 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.

Code review found no issues

No high-confidence issues detected in this change.

@guapisolo

Copy link
Copy Markdown
Collaborator

Known issue, to be fixed in a follow-up PR: weight_update_selector is meant to say whether the trainer sends draft weights, but only the Megatron iterator narrows it (to "target" when speculative decoding is on and the trainer has no MTP, megatron_utils/update_weight/hf_weight_iterator.py:38-40). FSDP and torchtitan never train MTP, yet they keep the default "all". With this PR that matters for p2p:

  • EAGLE3, STANDALONE, a separate draft checkpoint or multi-layer EAGLE: _select_runner_roles treats "all" as needing the draft, so connect raises NotImplementedError; on p2p: derive HF-to-parameter mappings from the model loader #3969 the same setups updated only the target.
  • Same-model EAGLE: no error, but each sender builds and probes a draft replica that never receives MTP weights.

Pre-existing and not p2p-specific: the same "all" reaches begin_weight_update/end_weight_update in every session-based mode, so sglang restores and postprocesses a draft that receives no weights. Restores that zero params (MXFP4 / MXFP4 Marlin MoE) would then leave that draft's experts zeroed (inferred from the code, not reproduced). --check-weight-update-selector also defaults to "all" independently of the iterator.

Planned fix: move the trainer-has-MTP rule into HfWeightIteratorBase so every backend derives the selector the same way, and default the checker's selector to the iterator's.

@Zhichenzzz
Zhichenzzz merged commit 8afeee2 into main Oct 12, 2026
56 checks passed
@Zhichenzzz
Zhichenzzz deleted the yueming/p2p-draft-runners branch October 12, 2026 23:55
guapisolo added a commit that referenced this pull request Oct 14, 2026
train/grad_norm jitters between runs and is exactly 0 on steps where every
GRPO group's rewards are uniform, so the old default band (0.5x up, 0.8x
down, 0.1 floor) failed healthy runs. Twenty tests had opted out with
enforce=False, but a failing shadow check still untrusts the run: 480 of
their 490 untrusted stored runs carry such a failure, so their baselines
for every metric starved. The P2P MTP tests (#3985) still enforced it.

The gate only needs to catch an order-of-magnitude jump. The default now
allows up to 10x the trusted mean (1.0 when that mean is near zero) and any
value down to 0, and every grad_norm declaration is enforced again, which
also reverts the previous commit's per-test opt-out. Replaying all stored
grad_norm history with every run as a baseline, the old band fails 262 of
3347 comparisons with at least ten baselines and the new band fails none.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bypass-fastfail Disable CI fast-fail: run all stages and all tests to surface every failure run-ci-weight-update Run weight update tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants