feat(distributed): block-diagonal varlen CP for packed sequences - #2989
Merged
Conversation
…packed sequences Add a self-contained CP implementation for packed (multi-document) sequences where masking must stay block-causal per document, which the load-balanced DTensor context_parallel path cannot express: - batch: contiguous sequence sharding + per-step train context, pluggable via the model-owned _cp_make_batch_fn hook in cp_utils.make_cp_batch_and_ctx - runtime: drop-in SDPA that exchanges K/V across the CP group and runs per-document causal attention on local queries, with collective-safe path selection and fallback consensus across ranks - kernels: FlashAttention/TransformerEngine varlen kernels driven by per-step precomputed cu_seqlens (zero per-layer host syncs), a dense masked-SDPA fallback, host-side validation of all kernel indices, and a long-left-prefix guard for boundary documents - exchange: differentiable K/V collectives -- fused [K;V] all-gather with reduce-scatter backward (default), plus opt-in needed-only delivery via left-neighbor halo p2p or all-to-all-v for documents spanning >2 ranks - packed: the cp_size==1 degenerate path that routes stock SDPA calls of a packed sequence through the same varlen kernel - state: knob normalization and activation-checkpointing-safe step state (readable from the autograd recompute worker thread) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
- CPU unit tests: block-causal mask construction, CP-vs-full attention parity (simulated ranks, incl. GQA), all-gather forward/backward, all-padding-rank gradient attachment, varlen metadata validation, halo/a2a exchange-plan geometry, KV path-selection downgrades, the flash long-prefix guard, and the cp1 packed hook contract - 1-GPU parity tests (skipped without CUDA): flash and TE varlen outputs vs the dense-mask SDPA reference across multi-doc packs with padding tails, GQA layouts, rank-straddling documents, single documents spanning all ranks, and head_dim 256; bit-identity of the per-step precomputed metadata against inline segmentation; the cp_size==1 packed-sequence SDPA hook Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
…ions The pre-embedded-input contract check and the defense-in-depth CP-group world-size guard vanish under python -O as asserts. Raise ValueError / RuntimeError instead, matching the adjacent local_batch_size guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
…on hooks enable_cp1_packed_varlen permanently monkeypatched torch.nn.functional.scaled_dot_product_attention process-wide, leaving a shape-heuristic dispatch latched for any coincidentally-shaped SDPA call. Rescope it with the repo's bounded pattern (cp_utils.attach_cp_sdpa_hooks): attach_cp1_packed_varlen_hooks installs/restores the patch via forward pre/post hooks (always_call=True) on the checkpoint-wrapped inner self_attn module, which also covers activation-checkpointing recompute in backward. enable/disable now only arm the per-forward doc_ids/backend state; the latching _PACKED_SDPA_INSTALLED global is gone. Also shrink the package __all__ to the genuine integration entry points (make_cp_blockdiag_batch_and_ctx, cp_blockdiag_sdpa, configure_cp_varlen, and the cp1 packed hook API); knob normalization, the varlen metadata precompute, and the fire counters stay module-internal (tests import them directly). Model wiring is a follow-up PR. Tests: new CPU unit test pins the scoping contract (process-wide SDPA untouched, patch live inside hooked forwards and during AC recompute, restored after); the GPU cp1 parity test now runs the full hook chain through a hooked module instead of the global patch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
… helpers Replace the bare try/except import probes for flash-attn and TransformerEngine with module-level safe_import_from / safe_import_te from nemo_automodel.shared.import_utils, per the repo's optional-dependency convention. The kernel symbols themselves are still imported at the call sites so per-call stubs (tests) and lazy TE extension loading keep working. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
Add the first real-process-group coverage for the differentiable K/V collectives (_AllGatherSeqDiff, _LeftHaloExchange, _NeededKVExchange) and the halo/a2a attention paths: a 2-rank NCCL torchrun runner (run_blockdiag_cp_2rank.py, launched from pytest via L2_CP_BlockDiag_Varlen_Test.sh) drives the full production path (configure_cp_varlen -> make_cp_blockdiag_batch_and_ctx on a real DeviceMesh -> cp_blockdiag_sdpa) forward + backward and compares local outputs, the input-embedding grad slice, and cross-rank-summed per-parameter grads of a tiny q/k/v/o attention module against a single-process dense block-causal reference. Matrix: kv_exchange allgather / halo / a2a (both needed-only paths selectable at cp=2 via the knob; the selector's decision is pinned so a silent downgrade to all-gather cannot fake coverage) x backend dense (fp32), flash, TE (bf16), over three doc layouts: a document straddling the rank boundary, an entire rank chunk of padding (collective no-hang), and a single document spanning both ranks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
Contributor
Author
|
/ok to test abb90e2 |
…diagnostics Add a single-GPU shape-stress sweep for the block-diagonal CP FlashAttention boundary guard. The sweep drives production-sized query/key segment lengths that bracket FlashAttention tile boundaries plus maximally packed layouts with non-tile-aligned tails, each through a non-reentrant activation checkpoint that replays the attention forward during backward, and asserts finite forward and gradient outputs. Explicit synchronizations pin any delayed illegal-address report to the exact case that launched the offending kernel. Carry compact host-only forensic context so an asynchronous kernel fault named at the post-exchange consensus can report the exact shape without adding a hot-path synchronization: the varlen validator retains a validation snapshot of its shape metadata, the needed-only halo/all-to-all exchanges record a diagnostic describing the chosen path, and the collective success check prints that diagnostic to stderr before re-raising an async CUDA error. Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
Contributor
Author
|
/ok to test dff34c2 |
Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
Contributor
Author
|
/ok to test 1e3b7c9 |
akoumpa
approved these changes
Jul 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
nemo_automodel/components/distributed/blockdiag_cp/, a self-contained context-parallelimplementation for packed (multi-document) sequences, where correct masking is
block-causal per document. Softmax attention runs as a FlashAttention/TransformerEngine
varlen kernel driven by per-step precomputed
cu_seqlens(zero per-layer host syncs),with a dense masked-SDPA fallback that is numerically identical. K/V delivery across the CP
group is a fused differentiable
[K;V]all-gather by default, with opt-in needed-onlyexchanges (left-neighbor halo p2p, or all-to-all-v when a document spans more than two
ranks) that cut per-rank K/V from
O(S)toO(S/cp) + straddle.Part of #2985.
Scope and follow-up
This PR intentionally lands the standalone block-diagonal CP primitive and its correctness
tests. It does not modify any model, recipe, or example config. A follow-up Amber upstream
PR will wire it into the Qwen3.5 VLM path using a real configuration with context
parallelism and packed sequences, building on this PR and #2990. That integration will
attach
make_cp_blockdiag_batch_and_ctxas the model's_cp_make_batch_fnand route itsattention through
cp_blockdiag_sdpa.Motivation
Sequence packing is the standard way to train VLMs/LLMs on mixed-length documents without
padding waste, and long-context packed training needs CP. The stock DTensor
context_parallelpath (ring attention over load-balanced shards) assumes one causaldocument per sequence; a packed sequence attended that way leaks attention across document
boundaries. TE's THD path covers
input_ids-based LLM batches but not pre-embedded VLMbatches (
inputs_embeds, deepstack visual embeds, mRoPE position ids).This PR provides CP that:
inputs_embedsslice, so gradients flowback to the trainable vision tower),
rank whose whole chunk is padding must still participate in every collective — see the
gradient-attachment tests),
infrastructure (
_cp_make_batch_fnincp_utils.make_cp_batch_and_ctx), so no changesto
cp_utilsare needed.Design overview
Key correctness properties, each pinned by a test:
attention under the block-causal mask (fp32 CPU, and bf16 flash/TE vs dense on GPU).
(
K > Q) first varlen segment with bottom-right causal alignment; a guard peels itinto a fixed-shape Flash call to avoid a known varlen failure mode on long prefixes.
K/V (0-weighted touch) so backward reduce-scatter fires symmetrically; needed-only
paths reach an all-reduce consensus before and after the kernel so every rank raises
together instead of forking the collective order.
cu_seqlens/offset consumed by a varlen kernel isvalidated on the host once per step (FlashAttention trusts these; a bad terminal
offset is an async illegal memory access, not a Python exception).
Proof
tests/unit_tests/distributed/test_blockdiag_cp.py(CPU): mask construction,CP-vs-full parity for world 2/4 incl. GQA, all-gather fwd/bwd, all-pad-rank gradient
attachment, metadata validation accept/reject, halo/a2a plan geometry, KV path
downgrade reasons, long-prefix guard, dropout forwarding, 1-D document ids, and the
cp1 packed hook contract.
tests/unit_tests/distributed/test_blockdiag_cp_varlen_gpu.py(1 GPU, skippedwithout CUDA): flash and TE varlen vs dense-mask SDPA across multi-doc packs with
padding tails, GQA (incl. 8:1, head_dim 256), rank-straddling docs, single doc
spanning all ranks, heavy padding; bit-identity of precomputed metadata vs inline
segmentation; the cp_size==1 packed hook; native Flash dropout and TE's correct
dense block-diagonal dropout fallback.
tests/unit_tests/distributed/test_blockdiag_cp_flash_boundary_gpu.py(1 GPU,skipped without CUDA): production-sized FlashAttention boundary shapes under
checkpointed forward replay and backward.
tests/functional_tests/context_parallel/test_blockdiag_cp_parity.py(2 GPUs,scheduled L2): real NCCL forward/backward parity for dense, FlashAttention, and TE
across all-gather, halo, and all-to-all-v exchange. It checks local outputs, input
gradients, and every Q/K/V/output-projection parameter gradient, including a document
straddling the rank boundary and an all-padding rank.
The implementation has been validated in internal large-scale runs (up to 128 GPUs),
including long-context (32k+) packed VLM training with FSDP2 and multi-node CP, where
loss curves match the cp_size=1 baseline.
Notes
kv_exchange="allgather") andauto-downgrade with a logged reason (non-varlen backend, missing metadata, or a CP
group spanning nodes;
NEMO_CP_ALLOW_XNODE=1overrides for validation).🤖 Generated with Claude Code