Skip to content

[PyTorch][CP] Add THD format support for AllGather-based Context Parallelism#2829

Merged
sudhakarsingh27 merged 78 commits into
NVIDIA:mainfrom
sudhakarsingh27:cp_thd_swa_with_ag
Jun 25, 2026
Merged

[PyTorch][CP] Add THD format support for AllGather-based Context Parallelism#2829
sudhakarsingh27 merged 78 commits into
NVIDIA:mainfrom
sudhakarsingh27:cp_thd_swa_with_ag

Conversation

@sudhakarsingh27

@sudhakarsingh27 sudhakarsingh27 commented Apr 3, 2026

Copy link
Copy Markdown
Member

Description

Add THD (variable-length sequence) format support to AttnFuncWithCPAndKVAllGather. Previously, AllGather-based CP only supported fixed-length formats (bshd/sbhd). THD format packs variable-length sequences into a single [t, h, d] tensor tracked by cu_seqlens, which is needed for workloads with heterogeneous sequence lengths.

The key challenge is that AllGather CP splits Q across 2 steps (one per local chunk), but THD tensors cannot be naively sliced like fixed-length formats. This PR uses an offset-based approach: the full Q tensor is passed to the attention kernel each step, with per-step cu_seqlens_q_padded values directing the kernel to read the correct chunk. This avoids tensor slicing and follows the padded THD convention used by the backends.

Type of change

  • New feature (non-breaking change which adds functionality)

Changes

  • Offset-based Q chunking: Per-step cu_seqlens_q_padded selects which chunk the kernel reads from the full Q tensor, instead of slicing Q per step.
  • Per-step KV sequence metadata: Computes visible KV token counts, per-step cu_seqlens_kv, and window ranges for causal, full/no-window, and SWA cases.
  • THD AllGather helper kernels: Adds sync-free THD reorder / valid-copy helper kernels for the AllGather CP THD path.
  • FlashAttention v3 AllGather THD support: Enables FlashAttention v3 AG+THD coverage, including padded THD cases through pad_between_seqs and seqused_k handling.
  • max_logit masking fix: Handles non-zero-starting cu_seqlens_q_padded in the valid-token mask without .item() D2H synchronizations.
  • Tests: Adds/extends CP THD coverage for FusedAttention, FlashAttention v3, AllGather, SWA/full/causal masks, padded Flash THD, and helper-kernel unit coverage in test_cp_utils.py.

Checklist:

  • I have read and followed the contributing guidelines
  • The functionality is complete
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Latest validation update (2026-06-11)

CP THD AllGather coverage was validated across FusedAttention and FlashAttention v3.

Backend Coverage Pad Result CI/local test
Flash v3 AG+THD causal False/True PASS CI 54405200 H100 L3 FA3 CP; local focused H100 run
Flash v3 AG+THD full/no-window False/True PASS Local focused H100 run
Flash v3 AG+THD causal SWA (128,0) False/True PASS CI 54405200 H100 L3 FA3 CP; local focused H100 run
Fused AG+THD causal n/a PASS CI 54405200 H100 L1 CP; local focused H100 run
Fused AG+THD full/no-window n/a PASS Local focused H100 run
Fused AG+THD causal SWA (128,0) n/a PASS CI 54405200 H100 L1 CP; local focused H100 run
Fused AG+THD causal SWA (512,512) n/a PASS Local focused H100 run

test_cp_utils.py also passed 14/14 on both H100 and B200 L1 in CI pipeline 54405200. It covers the THD helper kernels, including reorder/copy-valid tests against the legacy Python reference paths.

FusedAttention does not expose the FlashAttention-specific pad_between_seqs axis; Fused THD padding semantics are covered through the THD/padding mask path.

Helper-kernel microbenchmarks, single H100, bf16, batch=16, seqlen=4096, heads=16, dim=64:

Kernel path cp sizes Legacy wall ms New kernel wall ms Speedup
thd_cp_reorder_sequences contiguous->rank 2, 4, 8 11.6829-37.4912 0.0954-0.1090 122.42x-346.08x
thd_cp_reorder_sequences rank->contiguous 2, 4, 8 11.0553-19.2267 0.0966-0.1094 114.43x-177.87x
thd_cp_copy_valid_tokens 2, 4, 8 0.7070-9.4218 0.0911-0.1052 7.76x-89.59x

Note: the helper-kernel numbers are fixed-shape microbenchmarks, not a sweep over sequence length or number of sequences.

… cu_seqlens

- Use per-step cu_seqlens_q_padded to select Q chunks instead of tensor slicing
- Use padded cu_seqlens_kv for K/V reordering (ensures divisibility)
- Add cu_seqlens_kv and cu_seqlens_kv_padded to AllGather function signature
- Compute per-step Q and KV cu_seqlens correctly from actual seqlens
- Support non-causal attention (all KV visible)
- Zero-initialize out/dq for THD to avoid garbage in padding regions
- Save per-step cu_seqlens in ctx for backward (avoid recomputation)

Signed-off-by: Sudhakar Singh <sudhakars@nvidia.com>
Remove skip gates that blocked THD format with all_gather CP comm type.

Signed-off-by: Sudhakar Singh <sudhakars@nvidia.com>
…seqlens_q_padded

The interleaved valid mask computation assumed cu_seqlens_q_padded starts
at 0. With the CP offset-based approach, cu_seqlens_q_padded can start at
a non-zero offset, causing a size mismatch. Use absolute positions from
cu_seqlens_q_padded to build the valid mask instead.

Signed-off-by: Sudhakar Singh <sudhakars@nvidia.com>
if qkv_format == "thd":
# [cp*t, h, d] -> reorder to contiguous per-sequence order -> [t_full, h, d]
chunk_ids_for_kv_ag = get_seq_chunk_ids_for_reordering_before_attn(cp_size, k.device)
k_ag = reorder_seq_chunks_after_a2a_before_attn_thd(

@sudhakarsingh27 sudhakarsingh27 Apr 3, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This reorder_seq_chunks_after_a2a_before_attn_thd and the other releated method are not "a2a" specific now, rename them to something like dualchunk_to_contiguous_order_thd and the other one contiguous_to_dualchunk_order_thd

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Resolved on the current branch, with final cleanup in 0e926c42. The THD reorder entry points are now reorder_thd_sequences_to_rank_sharded and reorder_thd_sequences_to_contiguous, and the stale Python permutation helpers were removed. Both wrappers call the fused tex.thd_reorder path, so this logic is no longer A2A-named or A2A-specific.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Still resolved at PR head af2bd1c3. The Python wrappers are not A2A-specific (reorder_thd_sequences_to_contiguous / reorder_thd_sequences_to_rank_sharded) and now call the renamed fused binding tex.thd_cp_reorder_sequences.

Comment thread transformer_engine/pytorch/attention/dot_product_attention/context_parallel.py Outdated
Comment thread transformer_engine/pytorch/attention/dot_product_attention/context_parallel.py Outdated
@sudhakarsingh27 sudhakarsingh27 changed the title Cp thd swa with ag [PyTorch][CP] Add THD format support for AllGather-based Context Parallelism Apr 13, 2026
@sudhakarsingh27 sudhakarsingh27 marked this pull request as ready for review April 13, 2026 21:53
Comment thread transformer_engine/pytorch/attention/dot_product_attention/context_parallel.py Outdated
Comment thread transformer_engine/pytorch/attention/dot_product_attention/context_parallel.py Outdated
@greptile-apps

greptile-apps Bot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds THD (variable-length sequence) format support to AttnFuncWithCPAndKVAllGather, enabling AllGather-based Context Parallelism to work with heterogeneous sequence lengths. The implementation uses an offset-based approach where the full Q tensor is passed to the attention kernel each step, with per-step cu_seqlens_q_padded values directing the kernel to read the correct chunk, avoiding tensor slicing.

  • Offset-based Q chunking: Per-step cu_seqlens_q_padded selects which chunk of the full Q tensor the kernel reads, with FusedAttention using cu_seqlens_padded offsets and FlashAttention v3 using seqused_q/seqused_k; the docstring correctly notes that FlashAttention v2 cannot represent both per-step offsets and visibility limits simultaneously.
  • New CUDA helper kernels: Adds thd_reorder_between_sequence_and_cp_rank_order_kernel and thd_copy_valid_tokens_from_per_split_to_rank_local_kernel as fused GPU replacements for legacy Python reorder/copy loops, with reported 100–350× speedups; also exposes them as Python-callable ops via pybind11.
  • Stream synchronization: Adds the missing cp_stream.wait_stream(torch.cuda.current_stream()) after K/V preparation in the THD forward path, and pre-loop stream waits before default-stream max_logit merging for step-1 outputs.

Confidence Score: 4/5

Safe to merge for environments with FusedAttention or FlashAttention v3; a user on FA2-only hardware who reaches the AllGather+THD forward path receives silently wrong attention output with no error.

The AllGather+THD forward and backward use per-step cu_seqlens_q_padded offsets that FA2 varlen cannot represent. Both steps read from Q position 0, producing identical partial outputs. The test file skips this combination with pytest.skip, but production code has no corresponding assert — so any direct caller without FA3 or cuDNN reaches the broken path silently. All other changes (stream sync, max-logit masking, CUDA helper kernels, pybind wrappers) look correct and well-tested.

transformer_engine/pytorch/attention/dot_product_attention/context_parallel.py — specifically the not use_fused_attention + not use_flash_attn_3 + qkv_format == 'thd' path in AttnFuncWithCPAndKVAllGather.forward (and the symmetric backward path).

Important Files Changed

Filename Overview
transformer_engine/pytorch/attention/dot_product_attention/context_parallel.py Adds THD format support to AttnFuncWithCPAndKVAllGather with offset-based Q chunking, per-step KV metadata, and stream synchronization; missing runtime assertion for FA2 + THD combination allows silently wrong output.
transformer_engine/common/fused_attn/context_parallel.cu Adds three new CUDA kernels: a fused THD reorder kernel, a copy-valid-tokens kernel, and their C API wrappers; shared binary_search function is reused via thd_partition_src_index helper.
transformer_engine/pytorch/cpp_extensions/fused_attn.py Replaces per-batch .item() loop for max_logit masking with a vectorized GPU scatter_add_ approach that handles non-zero cu_seqlens_q_padded offsets without D2H synchronization.
transformer_engine/common/include/transformer_engine/fused_attn.h Adds C API declarations for the three new THD reorder/copy kernels with comprehensive doc-comments.
transformer_engine/pytorch/csrc/extensions/attention.cpp Implements PyTorch C++ bindings for the three new THD helper kernels with appropriate tensor validation and contiguity checks.
tests/pytorch/attention/test_attention_with_cp.py Adds pytest.skip guards for THD+AllGather on FA2 and for pad_between_seqs without FA3; converts test_essential to environment-variable-controlled flag.
tests/pytorch/attention/test_cp_utils.py Adds unit tests for the new THD reorder and copy-valid-tokens kernels with legacy-Python-reference cross-checks.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Fwd as AttnFuncWithCPAndKVAllGather.forward
    participant AG as AllGather (cp_group)
    participant Reorder as thd_cp_rank_order_to_sequence_order
    participant Stream0 as current_stream (step 0)
    participant Stream1 as cp_stream (step 1)
    participant Copy as thd_copy_valid_tokens

    Fwd->>AG: gather_along_first_dim(k, v)
    AG-->>Fwd: "k_ag, v_ag [cp*t, h, d]"
    Fwd->>Reorder: reorder k_ag, v_ag (THD path)
    Reorder-->>Fwd: k_ag, v_ag in sequence order
    Fwd->>Fwd: cp_stream.wait_stream(current_stream)
    Fwd->>Stream0: step 0 attention (q_full, cu_seqlens_q_padded_step0)
    Fwd->>Stream1: step 1 attention (q_full, cu_seqlens_q_padded_step1)
    Stream0-->>Fwd: out_per_step[0]
    Stream1-->>Fwd: out_per_step[1]
    Fwd->>Copy: copy valid tokens from out_per_step[0] to out
    Fwd->>Copy: copy valid tokens from out_per_step[1] to out
    Copy-->>Fwd: out (accumulated rank-local result)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Fwd as AttnFuncWithCPAndKVAllGather.forward
    participant AG as AllGather (cp_group)
    participant Reorder as thd_cp_rank_order_to_sequence_order
    participant Stream0 as current_stream (step 0)
    participant Stream1 as cp_stream (step 1)
    participant Copy as thd_copy_valid_tokens

    Fwd->>AG: gather_along_first_dim(k, v)
    AG-->>Fwd: "k_ag, v_ag [cp*t, h, d]"
    Fwd->>Reorder: reorder k_ag, v_ag (THD path)
    Reorder-->>Fwd: k_ag, v_ag in sequence order
    Fwd->>Fwd: cp_stream.wait_stream(current_stream)
    Fwd->>Stream0: step 0 attention (q_full, cu_seqlens_q_padded_step0)
    Fwd->>Stream1: step 1 attention (q_full, cu_seqlens_q_padded_step1)
    Stream0-->>Fwd: out_per_step[0]
    Stream1-->>Fwd: out_per_step[1]
    Fwd->>Copy: copy valid tokens from out_per_step[0] to out
    Fwd->>Copy: copy valid tokens from out_per_step[1] to out
    Copy-->>Fwd: out (accumulated rank-local result)
Loading

Comments Outside Diff (1)

  1. transformer_engine/pytorch/attention/dot_product_attention/context_parallel.py, line 3064-3085 (link)

    P1 Missing runtime guard: FA2 + THD + AllGather silently produces wrong output

    The docstring (lines 2988–2989) explicitly states that "FlashAttention v2 cannot represent both values, so THD all-gather is restricted to FusedAttention or FlashAttention v3." However, when not use_fused_attention and not use_flash_attn_3 and qkv_format == "thd", this block sets flash_attn_fwd = _flash_attn_varlen_fwd and execution continues without any error.

    At step 1 (i=1) the valid Q tokens live at padded mid-point offsets (thd_cu_seqlens_q_padded_per_step[1][:-1] != 0), but FA2 varlen always reads from position 0 for each batch element — meaning both steps attend to the same first chunk. The same offset mismatch applies in the backward. The test file adds a pytest.skip for this combination, but a direct caller on an FA2-only environment (e.g., older GPU without cuDNN or FA3) hits this silently.

    A runtime assertion placed immediately after the qkv_format == "thd" check in the if not use_fused_attention: block would prevent the silent wrong-output path.

Reviews (21): Last reviewed commit: "Update comment to clarify padding exclus..." | Re-trigger Greptile

Comment thread transformer_engine/pytorch/attention/dot_product_attention/context_parallel.py Outdated
Comment thread transformer_engine/pytorch/attention/dot_product_attention/context_parallel.py Outdated
Comment thread transformer_engine/pytorch/attention/dot_product_attention/context_parallel.py Outdated
sudhakarsingh27 and others added 2 commits April 16, 2026 11:28
…ific helpers

The AllGather THD path was not extending KV visibility beyond the causal
boundary when window_size had a right component > 0, meaning tokens right
of the diagonal were invisible to the kernel. Fix by adding window_size[1]
to visible_padded (clamped at actual seqlen) and max_seqlen_kv_.

Also rename reorder helpers to backend-neutral names since AllGather now
uses them too, and add a clarifying comment for non-causal KV cu_seqlens.

Signed-off-by: Sudhakar Singh <sudhakars@nvidia.com>
Comment thread tests/pytorch/attention/test_attention_with_cp.py Outdated
Comment thread transformer_engine/pytorch/attention/dot_product_attention/context_parallel.py Outdated
Comment thread transformer_engine/pytorch/attention/dot_product_attention/context_parallel.py Outdated
Comment thread tests/pytorch/attention/test_attention_with_cp.py Outdated
Comment thread transformer_engine/pytorch/attention/dot_product_attention/context_parallel.py Outdated
Signed-off-by: Sudhakar Singh <sudhakars@nvidia.com>
@ptrendx ptrendx added this to the 2.15 milestone Apr 23, 2026
Comment thread transformer_engine/pytorch/cpp_extensions/fused_attn.py
return out;
}

void thd_valid_copy(at::Tensor out, const at::Tensor &inp, const at::Tensor &cu_seqlens_padded,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should cp_ be in the names as well? i.e. thd_cp_reorder_sequences/cp_thd_reorder_sequences or thd_cp_copy_valid_tokens/cp_thd_copy_valid_tokens? (Just a suggestion)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Pushed in 27d2b84a, with pre-commit follow-up af2bd1c3: the Python-visible/C++ wrapper names are now thd_cp_reorder_sequences and thd_cp_copy_valid_tokens. The underlying C API names remain nvte_cp_thd_*.

"Fused dual-chunk THD reorder for context parallel (gather/scatter), inline index",
py::call_guard<py::gil_scoped_release>());
m.def("thd_valid_copy", &transformer_engine::pytorch::thd_valid_copy,
"Sync-free copy of valid THD token rows into an accumulator (CP AllGather fwd/bwd)",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What's "token rows" here? Sequences? Ranks?

Also, what's "inline index" in thd_reorder? :)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Pushed in 27d2b84a, with pre-commit follow-up af2bd1c3: cleaned up the pybind docstrings. They no longer use “token rows” or “inline index”; they now describe reordering between CP rank-sharded dual-chunk order and contiguous per-sequence order, and copying valid THD sequence entries from a padded tensor.

Comment thread transformer_engine/pytorch/csrc/extensions/pybind.cpp Outdated
@cyanguwa

cyanguwa commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

I feel the code changes are complex enough that I have to rely on the tests. Please make sure the tests cover both Fused and Flash v3 backends, SWA/full attention/causal mask, pad_between_seqs=T/F combinations, and post the performance numbers if there's any (for the new reorder/valid_copy kernels vs the old PyTorch-based functions). Thanks!

@cyanguwa

cyanguwa commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Also, please fix the DCO, and make the comments a bit more succinct if you can (some comments are quite thorough but also very long :) ). Thanks!

sudhakarsingh27 and others added 3 commits June 10, 2026 13:36
Resolve review comments for PR 2829 by tightening the THD all_gather output shape, renaming the new THD CP helper bindings, removing the unrelated pybind helper extraction, and aligning the FP8 t3hd aux handling with the post-FP8DS code path.

Also clean up the THD CP test skip logic and remove an unnecessary dtype conversion from the THD max-logit mask construction.

Signed-off-by: Sudhakar Singh <sudhakars@nvidia.com>
The AllGather CP path already has a support assert that rejects padding masks for non-THD inputs. Keep the earlier THD-specific padding requirement, and rely on the later AllGather support assert for the non-THD padding case so the check is not duplicated.

Signed-off-by: Sudhakar Singh <sudhakars@nvidia.com>
]
# Adjust chunks for each step
thd_cu_seqlens_kv_per_step[0][1:] = visible_actual[0].cumsum(0)
thd_cu_seqlens_kv_per_step[1][1:] = visible_actual[1].cumsum(0)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

L3286-L3370 could be cudafied if this becomes a performance bottleneck.

@sudhakarsingh27

Copy link
Copy Markdown
Member Author

/te-ci pytorch L3

sudhakarsingh27 and others added 7 commits June 11, 2026 12:42
Signed-off-by: Sudhakar Singh <sudhakars@nvidia.com>
…leanup

Signed-off-by: Sudhakar Singh <sudhakars@nvidia.com>
…codex/pr2829-review-cleanup

Signed-off-by: Sudhakar Singh <sudhakars@nvidia.com>
Rename the new THD CP reorder helpers so the source and destination layouts are encoded in the API names instead of a direction boolean. Also rename the valid-token copy helper to describe its per-split to rank-local accumulator role.

Guard copy-valid tokens that precede the first padded THD offset before indexing shared cu_seqlens arrays; later split offsets can legitimately leave those token positions outside any valid sequence range.

Signed-off-by: Sudhakar Singh <sudhakars@nvidia.com>
(cherry picked from commit 55f9f18747510d92791ef2650c65d93d9d90c27c)
…view-cleanup

Signed-off-by: Sudhakar Singh <sudhakars@nvidia.com>
@sudhakarsingh27

Copy link
Copy Markdown
Member Author

/te-ci pytorch L3

Bring PR 2829 up to date with latest main (77054fa): picks up the EP
common C API + NCCL EP backend, dense router output, VERSION bump to
2.18.0.dev0, and the multi_tensor_apply int32-overflow fix. Merge is
conflict-free; the only files touched by both sides are header/binding
decls that auto-merge. Local validation to follow.

Signed-off-by: Sudhakar Singh <sudhakars@nvidia.com>
@sudhakarsingh27

Copy link
Copy Markdown
Member Author

/te-ci pytorch L3

cyanguwa
cyanguwa previously approved these changes Jun 24, 2026
remove confusing info

Signed-off-by: Sudhakar Singh <sudhakars@nvidia.com>
@sudhakarsingh27 sudhakarsingh27 merged commit 7010118 into NVIDIA:main Jun 25, 2026
9 of 13 checks passed
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.

3 participants