Repository navigation
[megatron] feat: delta_sharded on Megatron-Bridge param mappings (TP+EP, hybrid-Mamba) - #7181
Conversation
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
Worker default log level swallows info, so the FULL-SEED line never reached any console; the seed duration is the number people ask for when sizing a run. Also stamps elapsed time and wire GB on the line. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The mcore backend joins the HF-delta contract: - delta_export.py: build_export_index enumerates parameters through AutoBridge.get_conversion_tasks and declares each one's geometry as a ShardSpec (TP partition dims -> BlockPlacement over the TP group, expert tensors -> virtual [ep, *etp] blocks over the etp x ep group, replicated -> the engine's rank-0 direct path). mcore_hf_delta_entry probes the bridge's own megatron_to_hf with NaN sentinels: make_probe copies each mapping and installs degenerate single-rank process groups (recursively -- composite mappings like QKVMapping delegate their TP gather to an inner _tp_mapping that does not receive the outer injection), so the converter short-circuits every collective and degrades into a pure permutation the sentinels survive. - transformer_impl: get_per_tensor_param_shard / get_per_tensor_param_delta_ shard / _hf_delta_entry wired on the index; seed and prime come free (bridge full export + BaseEngine). Scope: TP+EP, PP=1/VPP=1, no LoRA, Megatron-Bridge flavor only (the deprecated vanilla mbridge is rejected). Validated on a tiny Qwen2 through the real bridge (1 GPU, container): probe entries match the bitwise diff of the bridge's own full HF export on all 22 touched HF tensors (0 mismatched, 0 extra); CPU suite 24/24. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
QKVMapping._tp_mapping is an AutoMapping whose concrete Column/Row/Replicated delegate is created lazily on first use, snapshotting whatever process groups exist at that moment -- when that first use happened inside the probe, the delegate was born with the REAL groups and gathered for real (TP2 smoke: qkv-bias buffer arrived at split_qkv_biases doubled, 9216 vs 4608). Warm the lazy delegates from the task's module before copying, and inject the degenerate groups recursively into every nested mapping. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The cupy pool holds its blocks; after the full seed streams up to 2x bucket_size through it, that memory is invisible to the torch allocator and raw cudaMalloc (NCCL workspaces, optimizer buffers) OOMs on tight mcore shapes. Free the pool at the end of the seed and of every steady send. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pinning a whole shard set per rank (cudaHostAlloc) competes with everything else that pins on the node, and its failure surfaces as a CUDA out-of-memory during unrelated allocations. VERL_DELTA_PIN=0 keeps the delta snapshots in pageable host memory (slower diff read-back, cannot OOM the device) -- also the isolation knob for the mcore 30B OOM triage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mcore expert weights do not always carry the standard tensor_model_parallel attribute (PR#2 hit the same); without it the ETP-full shape never got its etp factor and the expert slot shapes/coordinates were declared at 1/etp size -- the rollout's fused-MoE loader then narrowed out of bounds (start 192 + length 192 exceeds 192). Fall back to the mcore layout rule (fc1 column dim 0, fc2 row dim 1) and treat '.mlp.experts.' names as expert params regardless of the mapping flag. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Log (warning level) how much the cupy staging pool held at the end of each send and the device-free delta across the release -- the e2e evidence for the pool-holding mechanism, and a permanent observability line for the staging footprint. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The bridge's MegatronParamMapping.is_expert is a base-class property and is itself a name test broader than our ".mlp.experts." check, so the or-clause could never fire; keep the mapping property as the single source. Reword the expert pdim comment to state what the mcore source shows: tensor_model_parallel is deliberately not stamped on expert weights, so for experts the layout rule is the primary source rather than a fallback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
No caller passes it: the engine builds its bridge from_hf_pretrained, which carries the HF config, so get_conversion_tasks never needs the path. Leftover from an early probe draft. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
โฆty boundary _inject already warms every child right before copying it, so each node of the mapping tree is warmed exactly once -- the children scan inside _warm_lazy_mappings was redundant (and used a different filter than _inject's isinstance). The ordering invariant that matters (delegate must exist before the child is copied) is now stated at the call site. Module docstring gains the degenerate-PG trick's safety boundary: group state must stay communication-only inside megatron_to_hf; the D1 bitwise probe is the regression oracle on Megatron-Bridge upgrades. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A group carries two separable meanings -- SIZE/RANK (value math such as Mamba's per-rank de-interleave dims consumes it) and COMMUNICATION. The old degenerate single-rank groups collapsed both, which made mappings whose transform depends on tp_size (MambaInProj, GDN, Chunked) silently wrong. Probe copies now keep sizes faithful via _ProbeGroup (real size/rank; any actual communication through the group raises) and stub the bridge's comm helpers with local synthesis: gather_from_tp_ranks returns this rank's shard at its true rank index with NaN placeholders elsewhere, the ep fan-out reduces to the task-bound global name. megatron_to_hf now runs its REAL tp>1 code paths on the LOCAL shard buffer, so the entry no longer builds full-shape buffers or translates indices, and unknown communication patterns fail loud instead of skewing silently. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
make_probe's module=None path was unreachable (every caller passes the task's module) -- module is required now. spec.py's to_hf_chunk comment described the pre-backend-export receiver-side segment rebuild; it documents the sender-side NaN row probe now. New tests/special_distributed/test_mcore_probe_differential.py: torchrun TP>1 oracle asserting real megatron_to_hf == merged comm-stubbed probe outputs bitwise per conversion task (MODEL_KIND=qwen2|qwen3_moe|nemotron_h), the regression gate for Megatron-Bridge upgrades and the Mamba acceptance. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With the comm-stubbed probe emitting final HF coordinates straight from the local shard, the engine consumes only each parameter's wire merge group and the replicated-from-rank-0 rule -- the hand-built BlockPlacements (TP slice offsets, the virtual all-experts tensor, the expert partition-dim layout rule) had no reader left. Expert entries merge over the joint etp x ep group; the former ep_size==1-with-TP expert gap is covered by the same branch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
โฆs, versioned tiny-model cache) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
NemotronH-family hybrids carry no rope at all; the provider/bridge owns rotary setup, so a missing rope_theta is not an error on the bridge path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The stub minted (tp_size-1) fresh all-NaN chunks per parameter per sync; they are read-only constants, so one pooled block per (shape, dtype, device) serves the run's lifetime. Recovers most of the comm-stub probe's 235B steady-sync regression (the per-param cudaMalloc+fill bandwidth). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
VERL_DELTA_VERIFY_EVERY=K appends the trainer's FULL export to every K-th steady sync inside the same receive session (the steady bucket withholds is_last; the sweep's final flush carries it). The receiver bit-compares each copy_ destination -- the exact internal slice sglang's own load_weights name-mapping resolves -- against what the server already holds before overwriting, and raises on any mismatch. Zero mismatched elements proves the delta-accumulated server state is bit-identical to the trainer's, end to end through sglang's fusion/sharding, independent of generation or log-probs and therefore usable on any model. Loader unit tests cover the pass and the fail-loud paths. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Snapshot pinning is the ENGINE's memory-profile call, not an environment variable: prime_delta_snapshots takes an explicit pin flag, BaseEngine resolves it from a class-level delta_pin_snapshots default, and MegatronEngine overrides it to False (pinning the whole shard set on top of mcore's resident params and per-step host traffic starves the node's cudaHostAlloc pool -- observed at 30B/235B). The verification sweep cadence likewise becomes the delta engine's verify_every constructor kwarg (engine_kwargs.delta_sharded.verify_every) instead of an env var. The full seed/verify export now fails loud on duplicate HF tensor names -- two source params mapping to the same output silently desyncs the diff base (observed: NemotronH A_log). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
โฆality sglang loads some params through multi-stage loaders (mamba's composed loader writes raw A_log, then overwrites with -exp in place); comparing at every copy_ call flags raw-vs-transformed at each stage -- a guaranteed false positive (the Nano9 A_log 2x128-per-layer signature). The sweep now snapshots every copy_ destination before the load, runs the real load path untouched, and bit-compares post-load state against the snapshot: replaying the trainer's full state onto an in-sync server must be a no-op, whatever the loader does in between. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
perturb -> sparse delta flush -> engine.update_weights_from_tensor (production load_format path) -> idempotence verify flush asserting zero mismatches, on a tiny qwen2 / nemotron_h against a REAL sglang engine. On-demand special_distributed script for sglang/bridge upgrades, not wired into per-PR CI; the equivalent standing check for e2e jobs is the delta engine's verify_every knob. Validated logic-wise via the loader CPU tests; full runs in our enroot environment still fight infra quirks (kernel dims, offline caches). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The slot table freezes in slot_cache after each param's first probe, so a zero-delta steady entry (frequent at small lr, late training, or with frozen modules) needs only the cached slots and zero counts to stay in lockstep -- not a NaN buffer, a transform run and a full-output scan. First sync always probes (cache miss), so the table is guaranteed before any short-circuit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
e0f1484 to
4358b94
Compare
|
hi @ChangyiYang, do we have a protocol file defining the format of delta, I prefer a training backend agnostic protocol (like HF safetensor for full parameter) and difference backends have their impl |
hi @ISEEKYAN Yes, that's exactly how it works today: each training backend implements get_per_tensor_param_delta_shard, whose output format is the protocol โ final HF-coordinate sparse entries, defined in verl/workers/engine/spec.py (wire encoding: delta_checkpoint_engine.py header). Everything past that boundary โ gather, bucketing, the receiver โ is backend-agnostic, and each impl fully controls how it produces its delta (snapshot diff, converters, or even diffing against a stored checkpoint). |
|
@ChangyiYang Does pipeline parallelism supported? |
|
@wuxibin89 PP is left as unsupported and will raise immediately. @gxlvera will support it |
What does this PR do?
The mcore backend joins the sharded-delta contract (#7144). #7085 (veomni EP) is merged; this branch is rebased onto latest main, so the diff is the megatron/engine increment only. Everything rides Megatron-Bridge's own per-param machinery โ no legacy converters, no hand-written index math:
AutoBridge.get_conversion_tasksenumerates parameters; each becomes aShardSpec(TP partition dims โBlockPlacementover the TP group; expert tensors โ virtual[ep, *etp]blocks over the etpรep group; replicated โ the engine's rank-0 direct path). No hand-derived geometry: the engine only consumesgather_group+ the replicated-rank0 rule.megatron_to_hfruns on a probe copy whose process groups are_ProbeGroups (realsize()/rank(), any collective raises) and whose gather helpers are stubbed to pure local semantics โ NaN sentinels through the REAL converter code give final HF coordinates with zero collectives and zero converter forks. This keeps converters with real value math (Mamba's TP de-interleave, GQA fusions) bit-exact, which the earlier "degenerate world" probe could not.verify_every=Kengine kwarg): every K-th sync appends a full re-export in the same receive session; the rollout loader snapshots everycopy_destination before its real load path, re-applies, and fails loud on any bitwise divergence โ a production-grade end-to-end oracle that tolerates multi-stage loaders (e.g. sglang's composed A_log loader).delta_pin_snapshots = Falseclass attr โ pinning whole shard sets surfaces as CUDA OOM at 30B/235B scale).Scope: TP+EP+ETP, PP=1/VPP=1, no LoRA, Megatron-Bridge flavor only.
Test
tests/special_distributed/test_mcore_probe_differential.py)megatron_to_hfvs merged probe outputs, bitwise, on qwen2 / qwen3_moe / nemotron_h (Mamba) / falcon_h1Additional Info
๐ค Generated with Claude Code