Skip to content

[PyTorch] DotProductAttention: declarative packed qkv/kv inputs - #18

Open
pggPL wants to merge 37 commits into
mainfrom
dpa_packed_qkv_api
Open

[PyTorch] DotProductAttention: declarative packed qkv/kv inputs#18
pggPL wants to merge 37 commits into
mainfrom
dpa_packed_qkv_api

Conversation

@pggPL

@pggPL pggPL commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Motivation

A fused QKV projection produces one packed buffer, but callers must slice it into query_layer/key_layer/value_layer views, which DotProductAttention then reverse-engineers via pointer-based layout detection (get_qkv_layout inspecting data pointers, strides and offsets on every forward). That detection graph-breaks under torch.compile and re-derives information the caller already knows.

This PR lets callers declare the packing instead. The declared layout is truthful by construction, so no detection runs on this path — including for thd and FP8 DPA.

API

DotProductAttention.forward gains three optional arguments (existing call sites unchanged):

  • qkv_layer — fully packed QKV, e.g. [b,s,3,h,d] or [s,b,h,3,d] (thd: [t,3,h,d]).
  • kv_layer — packed KV (e.g. [b,s,2,hg,d]), used with query_layer; supports GQA.
  • qkv_interleave_dim (default -3) — where the 3/2 interleave sits (-3 or -2); explicit since shapes can be ambiguous.

Handling lives in a single helper (_unpack_packed_qkv): q/k/v come out as zero-copy select() views, the exact layout string (bs3hd, sbhd_sb2hd, th3d, ...) is built from the declaration, and inputs are strictly validated (mutual exclusion, rank/size at the interleave dim, stride(-1) == 1 so the declared layout cannot lie about memory, no inference_params).

get_qkv_layout now emits a DeprecationWarning when it detects a packed layout purely from pointers, pointing callers at the new arguments. Separate q/k/v never warn.

FP8: combine_and_quantize

For packed layouts, FP8 attention used to rebuild the packed buffer from the q/k/v views via combine_tensors (a raw set_ with a silent adjacency assumption). The original packed buffer is now threaded down as packed_qkv/packed_kv and combine_and_quantize(combined_qkv=..., combined_kv=...) quantizes it directly. Legacy call sites are unchanged (None defaults); backward combine paths untouched.

MultiheadAttention adoption

MHA hands its fused projection output straight to DPA: self-attention passes packed QKV as qkv_layer, cross-attention passes packed KV as kv_layer. The legacy sliced-views path is kept for RoPE, QK norm, KV caching, CPU offloading, GQA and FP8 projection outputs.

Tests

No new test file. _run_dot_product_attention gains a declarative_packed mode (packed buffer passed via qkv_layer/kv_layer, input grads read off the packed buffer), exercised by two small additions to the existing suite:

  • test_dpa_qkv_layout_declarative — all 8 packed dense layouts x {self causal+bias, cross padding} configs, compared cross-backend like test_dpa_qkv_layout.
  • test_dpa_qkv_layout_thd_declarative — the 4 packed thd layouts (Hopper+).

Verified on sm89 (bf16, fused+flash+unfused): declarative tests green, test_dpa_qkv_layout matrix unchanged and green, lint 10/10.

🤖 Generated with Claude Code

Fused QKV projections naturally produce one packed buffer, but
DotProductAttention forces callers to slice it into q/k/v views that TE
then reverse-engineers with pointer-based layout detection
(get_qkv_layout inspects data_ptr/storage_offset on every forward,
which graph-breaks under torch.compile and adds CPU overhead).

Let callers declare the packing instead (JAX-style):

* DotProductAttention.forward gains optional qkv_layer (fully packed
  QKV: [b,s,3,h,d]/[s,b,3,h,d]/[b,s,h,3,d]/[s,b,h,3,d] dense, [t,3,h,d]/
  [t,h,3,d] thd), kv_layer (packed KV used with query_layer), and
  qkv_interleave_dim (-3 or -2; explicit knob rather than shape
  inference since h==3 or hg==2 would be ambiguous).
* Q/K/V are derived as zero-copy select() views and the exact layout
  enum (bs3hd, bsh3d, sb3hd, bshd_bs2hd, t3hd, ...) is constructed
  declaratively -- it is truthful by construction, so get_qkv_layout is
  never called on this path, including for thd and FP8 DPA.
* combine_and_quantize no longer re-combines what is already combined:
  a new optional combined= argument carries the caller's original
  packed buffer, which is quantized directly instead of rebuilding the
  packed buffer from q/k/v views via combine_tensors (a raw set_ with
  a silent adjacency/interleave assumption). The packed original is
  threaded from DPA.forward through FusedAttention to
  FusedAttnFunc.forward; all legacy call sites are untouched
  (combined=None preserves exact behavior), and backward combine calls
  are unchanged (gradients have no pre-packed original).

Tests: dense fwd+grad bit-exactness vs separate contiguous q/k/v for
bs3hd/bsh3d/sb3hd/kv-packed/GQA (fused + flash), validation errors,
torch.compile (no data_ptr/UntypedStorage graph breaks), FP8
combined-vs-views bit equivalence, and detection-free declared t3hd.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
@pggPL
pggPL requested a review from cyanguwa as a code owner July 7, 2026 15:36
pggPL and others added 28 commits July 8, 2026 12:55
…claratively

Adopt the new DotProductAttention packed API inside MultiheadAttention:

* self-attention (np == ng): the fused QKV projection output, already
  viewed as [.., h, 3, d] (qkv_weight_interleaved) or [.., 3, h, d], is
  handed to DPA directly as qkv_layer with the matching
  qkv_interleave_dim (-2 / -3) -- no SplitAlongDim slicing in MHA and
  no pointer-based layout detection in DPA.
* cross-attention: the packed KV projection output is exposed as
  [.., hg, 2, d] / [.., 2, hg, d] and passed as kv_layer.
* The pass-through only engages when no per-tensor operation needs the
  individual q/k/v slices: it is skipped for RoPE, QK normalization,
  KV caching (inference_params), CPU offloading, GQA (np != ng, not a
  uniform 3-interleave) and quantized (FP8) projection outputs; those
  keep the legacy sliced-views path unchanged.

Tests: MHA self (interleaved + non-interleaved) and cross (both
interleaves) are bit-exact vs the same MHA with packed inputs converted
back to separate contiguous q/k/v (output, input grad, weight grads);
spy asserts the packed argument and interleave dim actually reach DPA;
GQA and RoPE fall back to the views path. TransformerLayer regression
suite unchanged; test_kv_cache failures on this device are pre-existing
on origin/main (verified).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
…ack_packed_qkv

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
… arguments

Rename the single layout-dependent packed_qkv plumbing argument (which held
the full QKV buffer for *3* layouts but the KV buffer for *_2* layouts) into
explicit packed_qkv/packed_kv, mirroring the public qkv_layer/kv_layer API.
combine_and_quantize's combined= is split into combined_qkv=/combined_kv=
accordingly, and _unpack_packed_qkv no longer returns the packed tensor since
the callers already hold qkv_layer/kv_layer.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Fold the tests from the new test_dpa_packed_inputs.py file into the existing
attention test suite as a dedicated section, reusing its imports. No new test
file; test logic unchanged apart from renaming the module-level constants
(_B/_S/_H/_D/_DTYPE -> _PACKED_*) to avoid collisions.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
get_qkv_layout now emits a DeprecationWarning when it recognizes q/k/v as
views of a packed buffer purely from data pointers/strides/offsets (detected
*3*/*_2* layouts), pointing callers at the declarative qkv_layer/kv_layer
API. Separate q/k/v tensors (hd_hd_hd layouts) never warn since there is
nothing to declare.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Drop the API-validation, torch.compile graph-break, combine_and_quantize
equivalence and thd no-detection spy tests; keep the dense/flash bit-exact
equivalence tests and the MHA packed pass-through/fallback tests.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
…arative param

Parametrize test_dpa_qkv_layout and test_dpa_qkv_layout_thd with
declarative={views,declarative}: the declarative mode passes the packed buffer
to DotProductAttention via qkv_layer/kv_layer (declared layout, gradients read
off the packed buffer) instead of slicing it into q/k/v views for pointer-based
detection. This reuses the whole existing config matrix (masks, bias, SWA,
cross-attention, thd, all backends) for the declarative API, replacing the
dedicated packed-input test section.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
…matrix

Revert the declarative parametrization of test_dpa_qkv_layout(_thd) (which
doubled their whole config x layout product) and instead add
test_dpa_qkv_layout(_thd)_declarative covering all packed layouts on a trimmed
config dimension: one self-attention and one cross-attention config (kv_layer
path) for dense, one config for thd. Past the input handling the backend code
is identical to the views mode, so the full config matrix added no coverage.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Replace the .grad-holder objects substituted for q/k/v in declarative packed
mode with q_grad/k_grad/v_grad variables computed right after backward, used
uniformly by all return paths.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
…to dpa_packed_qkv_api

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>

# Conflicts:
#	tests/pytorch/attention/test_attention.py
…plicit k_norm

- get_qkv_layout deprecation warning: add stacklevel=2 and skip it while CPU
  offloading is enabled (offloading forces MultiheadAttention onto the
  sliced-views fallback, so the caller has no migration option there).
- MultiheadAttention: gate the packed pass-through on k_norm explicitly
  instead of relying on q_norm/k_norm being created together.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
* [JAX] Resync onto upstream PR NVIDIA#3036, restore TE-EP-only MoE block

Reset 33 local commits onto phuong/ep-3-jax @ c34771d (her latest with
EpConfig + EpLayerConfig API, NCCL bumped to 808d2433) and re-applied
the three deltas uniquely ours:

  * transformer_engine/jax/moe.py: replaces upstream's multi-backend
    MoE block with our TE-EP-only single-custom-vjp rewrite. Adapted
    to her new API surface: tex.EpLayerConfig replaces tex.ep_make_handle
    (no more EpHandle pool/cache); 5 EP callsites rewired (cfg passed
    in place of handle, ep_prepare arg order swapped, top_k= dropped
    from ep_dispatch_bwd since it's now in cfg.
  * tests/jax/test_te_ep_moe.py: TE-EP MoE test (kept), with
    ep_bootstrap kwargs ep_size= and allow_handle_mem_reloc= dropped
    (no longer supported; ep_size is derived from mesh axes and the
    handle_mem reloc gating is gone).
  * tests/jax/run_te_ep_moe.sh: multi-process launcher (kept).

Pre-sync state preserved at branch
teddy/te_ep_integration.backup-pre-phuong-sync.
EOF
)

Signed-off-by: Teddy Do <tdophung@nvidia.com>

* tests/jax: trim TE-EP MoE suite (drop bootstrap, flax-wrapper, bias-zero)

* drop ``TestZZZTeEpMoeBootstrap``: the re-bootstrap mismatch is a
  one-line guard in ``ep_bootstrap`` and not the MoE block's concern;
  exercising it from this suite also taints the per-process NCCL
  bootstrap cache for the rest of the file with no real upside.
* drop ``TestTeEpMoEBlockFlax::test_init_apply_parity``: every config
  in ``_CONFIGS`` already runs ``MoEBlock`` (the Flax wrapper)
  end-to-end via ``test_forward`` / ``test_backward``, so this was a
  duplicate of ``softmax`` parity in another wrapper -- leave wrapper
  refactors to devs without paying for an extra CI run each time.
* drop ``sigmoid-bias-zero``: with a zero-init bias buffer the routing
  math collapses to the no-bias case, so ``sigmoid`` already covers
  that numerical path. The bias-aware codepath is still exercised by
  ``sigmoid-bias-strong`` (non-zero bias).
* refresh the module-level docstring to list intentional
  non-coverage so future readers don't re-add these tests.

Signed-off-by: Teddy Do <tdophung@nvidia.com>

* jax/router: fix two bwd custom_partitioning bugs (aux-loss rank, topk closure)

Two unrelated one-line bugs in the bwd custom_partitioning machinery
that only surface once the MoE block's aux-loss path is lifted out of
shard_map (the custom_partitioning_sharding_rule check is skipped under
shard_map, which is why these never tripped before).

1. FusedMoEAuxLossBwdPrimitive.shardy_sharding_rule:
   ``grad_aux_loss`` is the cotangent of a scalar loss and is rank-0;
   declaring it with a spurious ``grad_one`` factor gave it rank-1 and
   tripped JAX's custom_partitioning_sharding_rule rank check at global
   view. Change the rule's third operand entry to empty:

     "const_buf_one, num_experts, grad_one -> i num_experts"
   ->
     "const_buf_one, num_experts, -> i num_experts"

2. FusedTopkWithScoreFunctionBwdPrimitive.partition:
   ``del result_infos, routing_map_format`` removed
   ``routing_map_format`` from the enclosing scope before the nested
   ``sharded_impl`` closure was invoked. Python closures resolve names
   at call time, not definition time, so when XLA finally invoked
   ``sharded_impl`` for the bwd partitioned impl it raised
   ``NameError: cannot access free variable 'routing_map_format'``.
   Drop ``routing_map_format`` from the ``del`` and leave a NOTE so
   future cleanups don't reintroduce the bug. Sibling partition
   methods (fwd topk, both aux-loss directions) already only
   ``del result_infos`` and need no change.

Signed-off-by: Teddy Do <tdophung@nvidia.com>

* jax/ep: skip size-1 dp/fsdp axis in _ep_outer_axis

A dp_resource or fsdp_resource that exists in the active mesh resource
config but is sized 1 in the actual mesh would still be returned by
``_ep_outer_axis()``, pinning EP-output PartitionSpecs to a degenerate
axis. JAX collapses size-1 mesh axes during lowering, which made the
EP-output specs reference an axis that no longer exists at runtime --
breaking shard_map output stitching on configs where DP or FSDP is
optional.

Treat a size-1 axis as absent: prefer dp -> fsdp, but only when the
candidate axis is actually sized > 1 in the current mesh. Falls back
to the previous behaviour when no axis is configured at all.

Signed-off-by: Teddy Do <tdophung@nvidia.com>

* jax/flax: realign _MoEBlock with post-resync moe() signature

After the upstream PR NVIDIA#3036 resync the moe() API surface lost
PermutationBackend (TE-EP is the only backend now), gate_inside_vjp
(always True), and the per-call quantizer_sets knob (quantization
flows through the standard TE autocast / with_quantizer_set context).
It also gained apply_topk_weights_early and renamed the wrapper's
private _align_size to the public align_size the test suite already
uses. The Flax _MoEBlock wrapper was still passing the old kwargs,
which broke every test that touched the wrapper.

Wrapper changes:
  * drop "from ..moe import PermutationBackend" plus the dataclass
    field, the isinstance(..., PermutationBackend) validation in
    __post_init__, and the pass-through to moe().
  * drop "from ..quantize import noop_quantizer_set" and the
    quantizer_sets=(noop, noop, noop) pass-through.
  * drop gate_inside_vjp=True.
  * rename _align_size: int = 0 -> align_size: int = 0 (matches
    what tests/jax/test_te_ep_moe.py already passes).
  * add apply_topk_weights_early: bool = False and pass it through
    to moe().
  * refresh class docstring: drop permutation_backend / _align_size
    / quantizer_sets descriptions, add apply_topk_weights_early /
    align_size, note that quantization currently flows only through
    fp8_autocast.

Signed-off-by: Teddy Do <tdophung@nvidia.com>

* jax/moe: plumb token_counts to grouped_gemm and zero 0-token wgrad slices

Two correctness fixes for the TE-EP MoE custom_vjp that together let
the bwd parity tests pass on 0-token-globally experts, and drop a
workaround that is no longer needed.

(1) Plumb per-expert padded token_counts into grouped_gemm group_sizes.

NCCL EP HT dispatch lays out recv_tokens expert-major as
  [expert_0_padded | expert_1_padded | ... | overalloc_tail]
where each per-expert block already includes the
dispatch_output_per_expert_alignment zero-padding and only the trailing
overalloc tail (slack between sum(token_counts) and the worst-case
recv_pr) is unused. Previously _ffn_fwd_per_shard built a static
local_group_sizes = jnp.full((num_local_experts,), slots_per_expert),
which over-counted by the overalloc tail and forced cuBLAS to run the
GEMM for every group including 0-token-routed experts.

Pipe the real per-shard token_counts (1, num_local_experts) from
ep_prepare through _moe_fwd_rule (added to ffn_in_specs/ffn_in_args
with ep2_spec), into _ffn_fwd_per_shard as token_counts_local, and
reshape into local_group_sizes for both grouped_quantize and
grouped_gemm. cuBLAS now skips both 0-token experts and the trailing
overalloc tail. Mirror the residual spec change on the bwd
(local_group_sizes residual moves from P() to ep2_spec).

(2) Per-group jnp.where zero-fill on wgrad outputs.

cuBLAS grouped_gemm skips groups with size_g == 0 without zero-filling
the corresponding out[g, :, :] slice (cublaslt_grouped_gemm.cu lines
2086/2096). For a shard hosting an expert that received zero tokens
globally, d_wo / d_wi_combined for that expert is left uninit, which
propagates NaN straight into the user's optimizer state.

Add wgrad_group_active = (local_group_sizes > 0)[:, None, None] in
_ffn_bwd_per_shard and apply via jnp.where on d_wo (right after the wo
wgrad) and d_wi_combined (right after the fused wi_0+wi_1 wgrad).
Mask shape is (num_local_experts, 1, 1) so cost is negligible.

(3) Drop the lax.cond zero-init guard on r_tok in _moe_fwd_rule._body.

Previously a jax.lax.cond(jnp.any(r_w != 0), identity, zeros_like)
wrapper around recv_tokens worked around tex.ep_dispatch_fwd leaving
the recv buffer uninit on fully-empty-receiver ranks. With (1) in
place, cuBLAS skips experts whose group_sizes == 0 and the per-row
trailing tail of dispatched recv_tokens is unread by every downstream
consumer (subsequent grouped_gemms read only sum(group_sizes) rows;
ep_combine and ep_dispatch_bwd are handle_mem-aware). The only
per-row consumer that would propagate the tail is grouped_dbias
(per-row segment_sum), which only runs when has_bias=True, and that
FFN bias path is currently gated upstream (cuBLAS grouped_gemm has
no fused bias on Hopper yet; PR 3083 adds the pure-JAX bias add).
With (2) handling the user-visible wgrad-NaN risk on 0-token experts,
the lax.cond is now redundant. Replace with a NOTE pointing at the
two follow-ups that would force its reintroduction:
  - a future caller that reads the full recv tile non-group-aware
    (e.g. an inspect probe), or
  - the FFN bias path landing, which would resurrect grouped_dbias.

Also rewrite the _ffn_fwd_per_shard and _ffn_bwd_per_shard docstrings
to spell out the per-row vs per-group uninit semantics so the next
person debugging a NaN here has the invariants written down.

Signed-off-by: Teddy Do <tdophung@nvidia.com>

* jax/flax,tests: rename use_bias/use_expert_bias for symmetry (PR NVIDIA#3116)

Address jberchtold-nvidia's PR NVIDIA#3116 nit "rename use_bias ->
use_ffn_bias and use_expert_bias -> use_expert_routing_bias". The
two flags are siblings (they enable two different bias buffers) but
the old names suggested ``use_bias`` was the general fallback, which
wasn't the intent. The new names make the FFN-vs-routing distinction
obvious from the call site.

* transformer_engine/jax/flax/moe.py
    use_bias -> use_ffn_bias  (dataclass field + branch in __call__
    + docstring entry)
    use_expert_bias -> use_expert_routing_bias  (same)
* tests/jax/test_te_ep_moe.py
    _make_block(use_expert_bias=...) -> use_expert_routing_bias
    sigmoid-bias-strong config key updated
    _reference_kwargs_from_config now reads use_expert_routing_bias

``_MoEBlock`` is still the experimental underscore-prefixed alias
(no public ``MoEBlock`` export yet), so the rename is API-safe.

The pre-resync legacy tests (``test_moe_vjp.py``,
``test_multiprocess_moe_vjp.py``) are intentionally not updated --
they already reference removed APIs like ``PermutationBackend`` and
need a separate post-resync cleanup pass.

Signed-off-by: Teddy Do <tdophung@nvidia.com>

* jax/moe: address PR NVIDIA#3116 review feedback (hardcode align + expand inline justifications)

Responds to jberchtold-nvidia's PR NVIDIA#3116 review threads on
``transformer_engine/jax/moe.py``. All changes are confined to a
single file because each review thread targets a localized region
and splitting mid-file would risk reordering bugs.

Per review thread:

1. "Why do we need _with_sharding_constraint_cast_bwd? I haven't
    seen something like this required for our other VJPs."
   -- Expand the helper's docstring to spell out exactly why MoE
   needs it: unlike LN+MLP, the MoE bwd composes a bf16 cotangent
   from ep_dispatch_bwd with an fp32 cotangent from
   fused_topk_with_score_function_bwd (which the fwd's
   logits_2d -> fp32 promotion forces). Without the cast, ``d_x``
   surfaces at fp32 even when ``x`` is bf16, doubling activation
   grad bandwidth and breaking any downstream LN bwd that pins a
   bf16 layout. (Review thread "Why do we need this utility
   function?".)

2. "Why is this dtype casting required? I don't recall us needing
    it for the non-MoE LNMLP block."
   -- Expand the comment above the bwd activation fp32 promotion
   to explain the MoE-specific math: LN+MLP's silu sits behind a
   downstream LN that absorbs the bf16 rounding error, while
   MoE's silu sits on the *expert* side of routing -- the bf16
   rounding rides directly into expert_outputs and is summed
   across topk experts by ep_combine. Bf16 silu alone drifts ~1%
   vs fp32 silu and compounds through wo->combine into the ~1.4%
   per-element parity gap we measured against the pure-JAX
   softmax reference. Mirroring the fwd's fp32 promotion in the
   bwd keeps silu' in lock-step with silu. (Review thread on
   "# Activation bwd. Mirror the fwd's fp32 promotion of
   silu+multiply".)

3. "Do we have a use-case for user-specified alignments beyond
    128 currently? ... it'd make sense to instead hardcode
    _ALIGN_SIZE = 128 as a constant at the top of the file for
    now to simplify this MoEBlock API. We can always expand the
    API to support a user-specified align size in the future."
   -- Implement the suggestion. Drop ``align_size`` from
   ``_moe_fwd_rule`` / ``_moe_bwd_rule`` / ``_moe`` / public
   ``moe()``; shift the ``custom_vjp`` ``nondiff_argnums`` from
   ``range(9, 27)`` -> ``range(9, 26)``; replace ``effective_align
   = max(int(align_size), 128)`` with the new module-level
   ``_ALIGN_SIZE = 128`` constant. Trim the ``moe()`` docstring
   accordingly. (Review thread on
   "natural_spe = num_ep * max_tokens_per_rank".)

4. "Which axis name inputs are physical mesh axes and why can be
    logical axes? ... No need to make any changes for now, I just
    want to assess which are which and then we can discuss if it
    makes sense to support logical on some/all or if some are
    required to be physical axes."
   -- Add an "Axis-name parameters" section to ``moe()``'s
   docstring listing which kwargs are physical mesh axes
   (``ep_axis``, ``data_parallelism_axes`` -- they index
   ``Mesh.shape`` directly to compute ``num_ep`` / ``dp_size``
   and to construct the ``P((dp..., ep), None, None)`` for
   ``jax.lax.with_sharding_constraint``) vs logical axes
   (``input_axes``, ``gate_kernel_axes``, ``wi_kernel_axes``,
   ``wo_kernel_axes`` -- resolved via the Flax logical-axis
   rules). Also document why ``ep_axis`` / ``data_parallelism_axes``
   are intentionally non-logical: the EP comm-group construction
   (``dp_color = rank // ep_size``) and the bootstrap signature
   check both require concrete integer sizes. (Review thread on
   "batch_pspec_axis = (*data_parallelism_axes, ep_axis)".)

5. "Is this NaN filtering a debugging artifact or something we
    need in the final version?"
   -- Strengthen the inline comment above
   ``sparse_probs = jnp.where(jnp.isnan(sparse_probs), 0, ...)``
   to explicitly call this out as a CORRECTNESS REQUIREMENT, not
   a debugging artifact: it covers the sigmoid+K>1 underflow
   path where top-K sigmoid scores all round to zero and the
   ``weights / (weights.sum + 1e-20)`` normalisation emits NaN.
   Observationally the filter is a no-op on the dense unit-test
   distributions, but it must stay in for sparse / production
   routing. (Review thread on
   "sparse_probs = jnp.where(jnp.isnan(sparse_probs), ...).")

Not addressed in this commit (intentional):

* Review thread on the ``align_size: int = 0`` placeholder in
  ``flax/moe.py`` ("Placeholder comment for me to fix this so
  align_size is inferred automatically based on the recipe and
  doesn't need to be specified by the user"). That's
  jberchtold's own follow-up.
* Review thread on the explicit ``tree_flatten`` /
  ``tree_unflatten`` on ``_Ctx`` ("better to use the
  ``@flax_struct.dataclass``"). Deferred to a separate, testable
  commit because changing a ``custom_vjp`` residual's pytree
  registration touches subtle ordering / None-handling semantics
  that warrant their own bisect surface.
* Review thread on ``use_bias`` / ``use_expert_bias`` renames --
  handled in the immediately preceding commit
  ``jax/flax,tests: rename use_bias/use_expert_bias for symmetry``.
* Review thread on the ``expert_bias`` fp32 init -- already
  resolved during the Phuong PR NVIDIA#3036 resync (the redundant
  ``jnp.float32`` second-dtype argument on ``self.param`` was
  dropped; ``expert_bias`` now lives at ``self.dtype``).

Signed-off-by: Teddy Do <tdophung@nvidia.com>

* jax/moe: strip PR-response framing from comments; drop sparse_probs NaN sanitizer

* Rewrite the inline justifications added in 078a7d80 so each one
  reads as standalone code documentation, not as a reply to a
  reviewer: drop "per PR NVIDIA#3116 review", "review feedback",
  "Renamed from ... per PR ..." and similar PR/thread references
  from moe.py, flax/moe.py, and tests/jax/test_te_ep_moe.py.
  Technical content (why the fp32 promotion is needed for the MoE
  silu+multiply, why _with_sharding_constraint_cast_bwd exists,
  physical-vs-logical axis split in moe() docstring, the 128
  alignment rationale) is preserved and reframed to be useful to
  a reader who has no PR context.

* Drop the jnp.where(jnp.isnan(sparse_probs), 0, sparse_probs)
  guard. Tracing fused_topk_with_score_function.cu shows the
  kernel divides by sum_scores + 1e-20, so finite non-negative
  sigmoid scores cannot produce NaN here; the filter was only
  defense against upstream NaNs, which would mask a real
  regression if anything ever did start producing them.

Signed-off-by: Teddy Do <tdophung@nvidia.com>

* jax/moe: drop fp32 island around silu+multiply (fwd, bwd, reference)

The SwiGLU intermediate (activation inputs gate_proj_out/up_proj_out,
silu+multiply, and activation output) was previously promoted to fp32
in _ffn_fwd_per_shard and again in _ffn_bwd_per_shard, then cast back
to the wi/wo GEMM dtype. The promotion bought nothing: the activation
inputs come out of the wi grouped_gemm in bf16, the activation output
is consumed by the wo GEMM (or wo's quantizer for FP8/FP4) in the same
dtype, and storing higher precision than either consumer is wasted
bandwidth.

* _ffn_fwd_per_shard: drop the .astype(jnp.float32) on gate_proj_out
  and up_proj_out and the trailing .astype(sorted_x.dtype). The
  multiply now stays in the wi GEMM output dtype end-to-end.
* _ffn_bwd_per_shard: symmetric simplification. jax.vjp(act_fn, ...)
  runs at bf16, both d_intermediate * silu' and d_intermediate * up
  stay at bf16, no casts. silu' is now consistent with silu (both
  bf16) so the chain rule composes cleanly without the prior fp32
  detour.
* tests/jax/test_te_ep_moe.py::_pure_jax_moe_reference: drop the
  matching fp32 silu in the parity reference so the test compares
  bf16-vs-bf16. Parity tolerance was not loosened; expect the
  comparison to tighten now that both sides round silu identically.

Also fix an inaccurate inline comment at the apply_topk_weights_early
fwd branch: the bf16 requirement on expert_outputs is enforced by
ep_bootstrap (which rejects max_token_dtype != bf16 and sizes the
NCCL EP HT mega-buffer for 2-byte slots accordingly), not by a
runtime assert in the combine FFI.

Signed-off-by: Teddy Do <tdophung@nvidia.com>

* remove useless comments

Signed-off-by: Teddy Do <tdophung@nvidia.com>

* tests/jax: remove legacy MoE VJP tests + launcher; point CI at TE-EP successor

test_moe_vjp.py and test_multiprocess_moe_vjp.py both import
PermutationBackend from transformer_engine.jax.moe -- an API that
was removed during the Phuong PR NVIDIA#3036 resync. Both files have
been dead-on-import ever since; the multiprocess launcher
run_multiprocess_moe_vjp.sh only points at the dead test.

test_te_ep_moe.py (the TE-EP-only custom_vjp suite) already covers
everything the legacy files exercised that is still meaningful:
fwd, bwd parity vs the pure-JAX reference, aux loss, both score
functions, multi-process. The legacy parametrize axis
(PermutationBackend.PURE_JAX vs TRITON) no longer exists.

* Delete tests/jax/test_moe_vjp.py
* Delete tests/jax/test_multiprocess_moe_vjp.py
* Delete tests/jax/run_multiprocess_moe_vjp.sh
* qa/L0_jax_distributed_unittest/test.sh: switch the MoE VJP
  distributed suite invocation from run_multiprocess_moe_vjp.sh /
  test_multiprocess_moe_vjp.py to run_te_ep_moe.sh /
  test_te_ep_moe.py.
* tests/jax/conftest.py: docstring reference updated.
* tests/jax/test_te_ep_moe.py: drop stale "successor to ..." aside
  and the "mirroring run_multiprocess_moe_vjp.sh" parenthetical.

Net: -981 / +9.

Signed-off-by: Teddy Do <tdophung@nvidia.com>

* jax/moe: swap _Ctx to @flax.struct.dataclass, drop manual pytree boilerplate

Per reviewer feedback (Jaberchtold on PR NVIDIA#3036): the manual
tree_flatten / tree_unflatten on _Ctx duplicate exactly what
@flax.struct.dataclass auto-generates, and the permutation
dataclasses elsewhere in this module already use flax.struct.

Switching to @flax.struct.dataclass:
* Removes ~75 lines of mechanical tree_flatten / tree_unflatten
  that have to be kept in sync with the field list by hand.
* Keeps cfg as the single static field via
  flax.struct.field(pytree_node=False), so the fwd -> bwd boundary
  behavior under jax.custom_vjp is unchanged.
* Drops two now-unused imports (dataclasses.dataclass,
  jax.tree_util.register_pytree_node_class) and adds flax.struct.

Field order and the (children, aux_data) split are byte-equivalent
to the previous manual implementation, so the pytree treedef seen
by jax.custom_vjp is identical.

Signed-off-by: Teddy Do <tdophung@nvidia.com>

* jax/moe: drop bwd recv_topk_weights NaN sanitizer; trust the dispatch contract

Mirrors the sparse_probs NaN-sanitizer removal in fe44697: we trust
ep_dispatch_fwd's contract that recv_topk_weights does not contain
NaN, and would rather see NaN propagate (catching a contract
violation immediately) than silently sanitize it.

The mask_bool dance itself stays: ctx.expert_outputs and
grad_pre_combine still carry NaN at padded slots (ep_dispatch_fwd
leaves uninit memory in recv_tokens, FFN and combine_bwd propagate
it), and IEEE NaN * 0 = NaN means jnp.where is structurally needed
to overwrite padded positions with literal zeros before the sum
reduction.

What changed:
* Drop `recv_w_clean = jnp.where(jnp.isnan(...), 0, ...)` and
  thread ctx.recv_topk_weights directly into w / mask_bool.
* Replace the NaN-defensive comment block with a shorter note that
  explains the structural reason the mask is still needed (NaN in
  expert_outputs / grad_pre_combine at padded slots), without
  claiming anything about recv_topk_weights.

Addresses Greptile P1 by removing the asymmetry (fwd had no
sanitizer, bwd did) -- chosen direction is "remove the bwd
sanitizer", matching the project-wide stance of trusting kernel
contracts rather than papering over violations.

Signed-off-by: Teddy Do <tdophung@nvidia.com>

* jax/moe: assert output dtype; tests cover d_x parity (dtype + values)

Two related dtype-contract changes:

1. moe.py: one-line assert at the moe() return path that
   output.dtype == x.dtype. Cheap structural guard against any
   future bug that lets the public output drift wider than the
   user-supplied input dtype.

2. test_te_ep_moe.py: extend test_backward to also check d_x, the
   gradient propagated back to the previous layer in backprop.
   _grad_step now uses jax.grad(loss_fn, argnums=(0, 1)) and
   returns (grads_variables, grad_x); the reference path does the
   same so we can compare. d_x is checked for:
   * shape == x.shape
   * dtype == x.dtype (protects the
     _with_sharding_constraint_cast_bwd wrapper that casts the
     fp32-promoted gate path back to the primal dtype on bwd; a
     regression in that wrapper would silently double activation
     gradient bandwidth)
   * finiteness + non-zero
   * numerical parity vs the pure-JAX reference d_x

Addresses jberchtold review comment on test_te_ep_moe.py:650
("we also need to check the final propagated gradient that will
be passed onto the next layer in backprop").

test_combined_loss_grads is adjusted to ``grads, _`` unpacking;
it doesn't need d_x for its main+aux finiteness check.

Signed-off-by: Teddy Do <tdophung@nvidia.com>

* tests/jax/test_te_ep_moe: strip docstring to just "what this suite covers"

Drops two paragraphs whose content was agent-flavoured PR-review
notes rather than user-facing test docs:

* The final "FP8 / MXFP8 deferred" paragraph that referenced an
  internal review artifact (``.pr3036-review/INTEGRATION_DESIGN.md``)
  not in the repo.
* The "Intentional non-coverage" section that explained which
  tests deliberately do not exist (no Flax-wrapper smoke, no
  re-bootstrap-mismatch test) and why -- exactly the kind of
  defensive / forward-looking justification prose CLAUDE.md says
  to keep out of the codebase.

The remaining docstring covers what readers actually need: how
to launch the suite, what each test class exercises, and a short
note on the parametrize-vs-class layout.

Addresses jberchtold review comment on test_te_ep_moe.py:54.

Signed-off-by: Teddy Do <tdophung@nvidia.com>

* jax/moe: address TE EP alignment review feedback

Signed-off-by: Teddy Do <tdophung@nvidia.com>

* jax/moe: fix early topk weighting padded-slot masking

Signed-off-by: Teddy Do <tdophung@nvidia.com>

* jax/moe: remove unused EP mesh size

Signed-off-by: Teddy Do <tdophung@nvidia.com>

* jax/moe: tighten TE EP recv capacity bound

Signed-off-by: Teddy Do <tdophung@nvidia.com>

* jax/moe: simplify late TE EP weighting

Signed-off-by: Teddy Do <tdophung@nvidia.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* jax/moe: reduce padded-slot recv weight masking

Signed-off-by: Teddy Do <tdophung@nvidia.com>

---------

Signed-off-by: Teddy Do <tdophung@nvidia.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
* [Common] NVRTC for fused softmax and normalization (Phase 0)

Move the fused-softmax and LayerNorm/RMSNorm kernels from build-time template
instantiation to runtime NVRTC compilation, with full coverage of the existing
kernel set so the NVRTC path is the default.

Fused softmax:
- RTC compile/launch path for scaled / scaled-masked / scaled-upper-triangular /
  scaled-aligned-causal softmax, keyed by dtype, shape and mask/causal mode.
- NVTE_BUILD_LEGACY_STATIC_FUSED_SOFTMAX (default OFF) restores the static
  template dispatch.

Normalization (LayerNorm + RMSNorm, forward + backward):
- Replace the static REGISTER_NORM_LAUNCHER template fanout with an NVRTC
  registry that compiles the selected (norm type, direction, dtypes, hidden size,
  CTA config) kernel on first use and caches it.
- NVTE_BUILD_LEGACY_STATIC_NORM (default OFF) restores the static launchers.
- NVRTC-safe kernel sources: kernel sources/headers avoid common.h under
  __CUDACC_RTC__; add the dtype aliases and a minimal std::is_same/conditional_t
  in the RTC build, and replace a zero-length padding array (a GNU extension nvcc
  accepts but NVRTC rejects) with a no-padding union specialization.

KernelManager (util/rtc.{h,cpp}) gains occupancy / function-attribute /
cooperative-launch helpers needed by the norm launchers.

Validated on sm_89 (RTX 6000 Ada): full normalization operator suite 192/192,
softmax + NVRTC unit tests pass; libtransformer_engine.so shrinks ~72 MB -> ~65 MB.
On sm_100a the NVRTC norm forward kernel builds where the static instantiation
crashed the compiler.

Signed-off-by: CarlosGomes98 <carlosmiguel.gomes@live.com.pt>

* Add fully qualified name to softmax kernels

Signed-off-by: CarlosGomes98 <carlosmiguel.gomes@live.com.pt>

* Add static fallback option, fix softmax acc_t dtype

Signed-off-by: CarlosGomes98 <carlosmiguel.gomes@live.com.pt>

* add missing license

Signed-off-by: CarlosGomes98 <carlosmiguel.gomes@live.com.pt>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* greptile changes

Signed-off-by: CarlosGomes98 <carlosmiguel.gomes@live.com.pt>

* fix formatting, .clang-format

Signed-off-by: CarlosGomes98 <carlosmiguel.gomes@live.com.pt>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* import cleanup

Signed-off-by: CarlosGomes98 <carlosmiguel.gomes@live.com.pt>

* Test more columns for softmax, mr changes

Signed-off-by: CarlosGomes98 <carlosmiguel.gomes@live.com.pt>

* Fix tests

Signed-off-by: Carlos Gomes <cgomes@nvidia.com>

---------

Signed-off-by: CarlosGomes98 <carlosmiguel.gomes@live.com.pt>
Signed-off-by: Carlos Gomes <cgomes@nvidia.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Tim Moon <4406448+timmoon10@users.noreply.github.com>
* support scaled swiglu, scaled srelu and scaled clamp swiglu

Signed-off-by: zhongboz <zhongboz@nvidia.com>

* vectorized loading improvement

Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>

* fix bug for backward kernel

Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>

* optimize

Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>

* fix unit test failure

Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update tests/cpp/operator/test_scaled_activation.cu

Signed-off-by: vthumbe1503 <vthumbe@nvidia.com>

* resolve comments

Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>

* refactor, resolve comments

Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>

* address review comment

Signed-off-by: Varun Thumbe <vthumbe@nvidia.com>

* adaptive cta to fix slow block reduce for scale grads

Signed-off-by: Varun Thumbe <vthumbe@nvidia.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* refactor to have gated and unary activation in activation infra

Signed-off-by: Varun Thumbe <vthumbe@nvidia.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* reuse scale grad kernel for non scale grad since it is faster anyway

Signed-off-by: Varun Thumbe <vthumbe@nvidia.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Signed-off-by: zhongboz <zhongboz@nvidia.com>
Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>
Signed-off-by: vthumbe1503 <vthumbe@nvidia.com>
Signed-off-by: Varun Thumbe <vthumbe@nvidia.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: vthumbe1503 <vthumbe@nvidia.com>
…DNN SDPA fprop (NVIDIA#3186)

* [Common] Pass cu_seqlens and token-unit ragged offsets directly to cuDNN SDPA fprop

cuDNN >= 9.24 SDPA (unified engine) accepts cumulative sequence lengths
directly (cu_seq_len_q/kv) and can scale ragged offsets stored in coarser
units back to elements via a per-tensor ragged offset multiplier. Use both
in the f16/bf16 forward to skip the two conversion kernels
(cu_seqlens_to_actual_seqlens and cu_seqlens_padded_to_offsets) that
previously ran before every varlen fprop:

- Bind the user's int32 cu_seqlens buffers as CU_SEQ_LEN_Q/KV for the
  padding mask, and the token-unit cu_seqlens_padded buffers as ragged
  offsets for Q/K/V/O/Stats with elements-per-token multipliers.
- Gate on cudnn >= 9.24 and !dropout (the FE rejects dropout together with
  generated stats on the unified engine; TE always generates stats).
  CU_SEQ_LEN inputs pin implementation selection to the unified engine.
- Keep the true batch size on the direct path: cuDNN reads the user's
  [actual_b+1] buffers, so the quantized max_b graph batch would read out
  of bounds. Token-dim bucketing (max_t) is unaffected.
- No conversion workspace is needed on the direct path.
- Factor the layout-group -> multiplier mapping into RaggedOffsetMultipliers
  (utils.h), shared by the graph builder and the legacy conversion kernel so
  the two cannot drift. The kernel rewrite also removes a cross-thread read
  (offsets_v[tid] = offsets_k[cu_seqlens_id]) that raced for quantized-batch
  tail entries with interleaved layouts.
- Backward is unchanged (no backend support yet).

NVTE_FUSED_ATTN_DIRECT_SEQLENS=0 disables the new path (testing aid, to be
removed before merging).

Validated on H100 and Blackwell against cuDNN 9.25: test_dpa_softmax_thd
15/15 in both modes, and direct-vs-legacy fused outputs/grads match for all
THD layouts (thd_thd_thd, t3hd, th3d, thd_t2hd, thd_th2d) x MHA/GQA x
padding/padding_causal x pad_between_seqs {false,true}.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Emil Gilliam <egilliam@nvidia.com>

* [Common] Pass cu_seqlens directly to cuDNN SDPA FP8/MXFP8 fprop

Extend the direct-seqlens path to the FP8/MXFP8 forward: bind the user's
int32 cu_seqlens buffers as CU_SEQ_LEN_Q/KV for the padding mask instead of
converting them to per-batch lengths with the cu_seqlens_to_actual_seqlens
kernel before every call. (Unlike the F16 path, the FP8 path has no
THD/ragged support, so this is the only conversion kernel there.)

FP8/MXFP8 on the unified engine requires cuDNN >= 9.25 and cuDNN frontend
>= 1.26. The frontend is header-only, so its version is a compile-time
property; the gate uses a constant-folded CUDNN_FRONTEND_VERSION check (all
referenced symbols exist in 1.25, so no preprocessor guards are needed).
Dropout with generated stats stays on the legacy path, same as F16.

Backward is unchanged (no backend support yet).

Validated against cuDNN 9.25 + frontend 1.26 (test_dpa_fp8_vs_f16, padding
configs, direct path on with no fallback): 56 passed on H100 (delayed +
current scaling), 168 passed on Blackwell (adds MXFP8); zero failures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Emil Gilliam <egilliam@nvidia.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* [Common] Harden direct-seqlens version gates

Address review feedback and a version-mix bug found in testing:

- Remove the NVTE_FUSED_ATTN_DIRECT_SEQLENS env override (unnecessary; the
  version gates fully determine the path).
- Check the compile-time CUDNN_VERSION in addition to the runtime version.
  The cuDNN frontend gates cu_seq_len support on min(compile-time, runtime)
  version, so e.g. a binary built against 9.24 headers running on a 9.25
  library must take the legacy path; a runtime-only check let it attempt
  the direct fp8 graph, which the frontend rejects ("No suitable
  implementation") with no fallback.
- Add a (currently redundant) CUDNN_FRONTEND_VERSION >= 1.25 check to the
  f16 gate for symmetry with the fp8 gate.
- Raise the fp8 frontend floor from 1.26 to 1.27: 1.26 suffices for this
  C++ API use, but 1.27 is the floor for the python FE API's fp8 cu_seq_len
  support (exposed post-1.26-cut), and a single version story per feature
  avoids a silent gap when TE moves to the python FE API.

Smoke-tested on H100: f16 THD 15/15 (direct path, cuDNN 9.24), fp8 padding
subset 56 passed via legacy on 9.24, and 56 passed via legacy on the
9.24-compile/9.25-runtime mix that previously failed 56/56.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Emil Gilliam <egilliam@nvidia.com>

* [Common] Lower fp8 direct-seqlens frontend floor to 1.26

Per TE team discussion: 1.26 is all the C++ FE API needs for fp8 +
cu_seqlens (the support surface made the 1.26 cut; SDPA_fp8_attributes has
had the setters since 1.25). Keep a comment noting that the python FE API
requires 1.27 (its sdpa_fp8 binding gained cu_seq_len_q/kv post-1.26-cut),
so a future migration to the python FE API knows to raise the floor.

Smoke-tested on H100: f16 THD 15/15 (direct, cuDNN 9.24), fp8 padding
subset 56 passed via legacy on 9.24 and on the 9.24-compile/9.25-runtime
mix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Emil Gilliam <egilliam@nvidia.com>

* [Common] Fix sm120 THD softmax-stats layout and allocation

use_ragged_stats excludes sm120, but the forward Stats declaration used
the weaker condition (is_ragged_q && cudnn >= 9.6). On sm120 with THD,
fwd therefore declared the ragged-style [b][s][h] stats stride with a
null ragged offset (i.e. dense token-major), while bwd read the stats
tensor as dense [b][h][s] -- a fwd/bwd layout mismatch. It would also
have let the direct-seqlens path set a ragged-offset multiplier on a
null ragged offset, a frontend validation error.

Use use_ragged_stats for the fwd declaration so fwd and bwd agree, and
give the stats allocation the same sm120 exception Max already has:
without it the buffer is [num_tokens_q, h, 1], undersized for the dense
[b, h, s_q, 1] graph whenever num_tokens_q < b * s_q.

Pre-existing issue, independent of the direct-seqlens work.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Emil Gilliam <egilliam@nvidia.com>

* [Common] Rename use_direct_seqlens to use_cu_seqlens_directly

Clearer name for the flag controlling whether cu_seqlens buffers are
passed straight to cuDNN SDPA; comment wording updated to match. No
functional change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Emil Gilliam <egilliam@nvidia.com>

* [Common] Suppress fn_size lint on fused_attn_arbitrary_seqlen_fwd_impl

The direct-seqlens additions push the function to 508 non-comment lines,
over cpplint's 500 limit. Per TE team, refactoring this long-standing
function is beyond the scope of this PR, so suppress with NOLINT for now.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Emil Gilliam <egilliam@nvidia.com>

* [Common] Pin the UNIFIED implementation on the direct cu_seqlens path

cu_seq_len (and the ragged offset multiplier) are unified-engine-only, so
with those inputs attached AUTO can only ever resolve to UNIFIED anyway.
Pinning changes only the failure mode: an unsupported config fails with the
unified engine's specific error instead of auto-selection's generic "no
suitable implementation". Ordinary graphs (no cu_seq_len attached) keep
AUTO. Matches the cudnn-frontend cu_seq_len sample, which pins for the
same reason.

Smoke-tested on H100: f16 THD 15/15 via the pinned direct path (cuDNN
9.24); fp8 padding subsets 56 passed via legacy on 9.24 and on the
9.24-compile/9.25-runtime mix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Emil Gilliam <egilliam@nvidia.com>

---------

Signed-off-by: Emil Gilliam <egilliam@nvidia.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Sudhakar Singh <sudhakars@nvidia.com>
NVIDIA#3204)

[PyTorch] Add per-version FlashAttention env vars (NVTE_FLASH_ATTN_V2/V3/V4)

NVTE_FLASH_ATTN enables or disables the whole FlashAttention family, but
the choice between FlashAttention 2, 3, and 4 is automatic (package
presence and compute capability) with no user override. Some workloads
need to pin the FlashAttention generation, e.g. RL training that must
produce bitwise-identical logprobs to an inference engine running a
specific FlashAttention version: different generations use different tile
sizes and online-softmax accumulation orders, so mixed versions between
training and inference break batch-invariant / train-inference parity
guarantees.

Add NVTE_FLASH_ATTN_V2, NVTE_FLASH_ATTN_V3, and NVTE_FLASH_ATTN_V4
(default 1) that disable a specific FlashAttention version even when it
is installed, following the existing NVTE_FLASH_ATTN filter pattern.
Behavior is unchanged when the variables are unset.

Signed-off-by: wdykas <wdykas@nvidia.com>
Signed-off-by: Tim Moon <tmoon@nvidia.com>
* fix grouped linear hang

Signed-off-by: Varun Thumbe <vthumbe@nvidia.com>

* make the same change in grouped mlp as well

Signed-off-by: Varun Thumbe <vthumbe@nvidia.com>

---------

Signed-off-by: Varun Thumbe <vthumbe@nvidia.com>
…GroupedLinear and fused grouped MLP (NVIDIA#3161)

* Add optional caller-provided output/grad-input buffers to GroupedLinear module and fusible ops

Signed-off-by: Phuong Nguyen <phuonguyen@nvidia.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Route per-op kwargs through Sequential via module-keyed op_kwargs mapping

Signed-off-by: Phuong Nguyen <phuonguyen@nvidia.com>

* Write fused grouped MLP MXFP8 output and dgrad directly into caller buffers, eliminating the D2D copy + cleanup

Signed-off-by: Phuong Nguyen <phuonguyen@nvidia.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Use 256-aligned splits in caller-buffer grouped MLP test

Signed-off-by: Phuong Nguyen <phuonguyen@nvidia.com>

* use basic_ops to track op kwargs

Signed-off-by: YangFei1990 <feiw@nvidia.com>

* add doc and resolve comments

Signed-off-by: YangFei1990 <feiw@nvidia.com>

* move out/dgrad_out out from the non_tensor_args

Signed-off-by: YangFei1990 <feiw@nvidia.com>

---------

Signed-off-by: Phuong Nguyen <phuonguyen@nvidia.com>
Signed-off-by: YangFei1990 <feiw@nvidia.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: YangFei1990 <feiw@nvidia.com>
Co-authored-by: Fei Wu <33940270+YangFei1990@users.noreply.github.com>
* Fix FusedAdam empty tensor handling

Signed-off-by: Jingyue Wu <wujingyue@gmail.com>

* Move empty tensor filtering into MultiTensorApply

Signed-off-by: Jingyue Wu <wujingyue@gmail.com>

---------

Signed-off-by: Jingyue Wu <wujingyue@gmail.com>
Co-authored-by: vthumbe1503 <vthumbe@nvidia.com>
Signed-off-by: Kshitij Lakhani <klakhani@nvidia.com>
…opk_weight tensor (NVIDIA#3187)

* expose user-provided weights

* adding pool based symm allocation; remove the persistent buffer in EpBuffer

* add zero copy tests

Signed-off-by: YangFei1990 <feiw@nvidia.com>

---------

Signed-off-by: YangFei1990 <feiw@nvidia.com>
Co-authored-by: Phuong Nguyen <phuonguyen@nvidia.com>
…3222)

* Migrate NCCL EP submodule to NVIDIA/nccl-extensions

* Drop PYTHONPATH override from EP test, example, and bench launchers

* Drop cross-mode recv comparison in EP zero-copy IdentityAllSymm test

Signed-off-by: Phuong Nguyen <phuonguyen@nvidia.com>

* [Common] Rename 3rdparty/nccl submodule directory to nccl-extensions

Signed-off-by: Phuong Nguyen <phuonguyen@nvidia.com>

---------

Signed-off-by: Phuong Nguyen <phuonguyen@nvidia.com>
denera and others added 8 commits July 22, 2026 15:34
…NVIDIA#3171)

* [Common/PyTorch] Support power-of-2 scales in grouped FP8 block-scaling quantize

The default Float8BlockScaling recipe constrains scales to powers of 2,
so the fused grouped path must honor the flag to stay numerically
consistent with the unfused path. Thread a runtime pow_2_scales argument
through the grouped quantize kernels (the shared scale helper already
implements the rounding) and drop the force_pow_2_scales rejections.

Also add a quantization-config parameter to nvte_group_quantize_dbias,
which previously had no way to receive force_pow_2_scales or
amax_epsilon on the bgrad path.

Signed-off-by: Alp Dener <adener@nvidia.com>

* [PyTorch] Enable fused grouped FP8 block-scaling path in GroupedLinear module

Admit Float8BlockQuantizer in the fused GroupedTensor path on Hopper.
The existing usage flags already match the Hopper TN-only mapping and
the grouped GEMM selects transposed columnwise storage for NN/NT
layouts, so only the path predicate changes.

The fused path is an explicit opt-in via
NVTE_GROUPED_LINEAR_USE_FUSED_GROUPED_GEMM, so raise on Blackwell
(SM100/SM110) instead of silently falling back; the fused path has no
MXFP8-broadcast emulation.

Extend the fused dbias path (tex.bgrad_group_quantize) to FP8 block
scaling when dgrad is required (dbias is computed in the rowwise pass).

Add fp8_block_scaling to the fused-path tests with a Hopper-only gate,
assert the fused path engages via a group_quantize spy, and add a
Blackwell error-path test.

Signed-off-by: Alp Dener <adener@nvidia.com>

* [PyTorch] Enable FP8 block-scaling in GroupedLinear fusible op

Replace the blanket FP8 block-scaling rejection in
BasicOperation.reset_recipe_state with a per-op
supports_float8_block_scaling flag and opt in the GroupedLinear op.
Mirror the module-path predicate and fused-bgrad changes; since the
graph-safe flow is default-on here (no env-var opt-in), other
architectures fall back to the split-quantize flow instead of raising.

Force use_split_accumulator=True for FP8 block-scaling operands in
general_grouped_gemm_for_grouped_tensor, matching non-grouped
general_gemm: cuBLAS has no fast-accum FP8 block-scaling algorithm, so
the ops-layer forward failed algo selection without it.

Add fp8_block_scaling coverage to the ops GroupedLinear tests. The
CUDA-graph-safe test skips it for now: the replayed wgrad for the last
expert diverges between replays depending on process allocation
history; under investigation. Graph capture remains covered by the
module-path test.

Signed-off-by: Alp Dener <adener@nvidia.com>

* [PyTorch] Use persistent workspaces in grouped-tensor GEMM

general_grouped_gemm_for_grouped_tensor allocated its setup workspace
(the cuBLAS per-group pointer/dimension arrays) and its cuBLAS
workspace with per-call torch.empty. Under make_graphed_callables the
forward and backward graphs share one capture memory pool, and a
per-call allocation's block returns to that pool as soon as the Python
reference dies, so blocks alias across the two graphs and captured
kernels from one graph overwrite the GEMM metadata the other graph
reads at replay. Observed as allocation-history-dependent failures in
the ops-layer GroupedLinear cuda-graph test: capture-time
cublasLtMatmulAlgoGetHeuristic NOT_SUPPORTED errors and corrupted
wgrad outputs. This is also the likely mechanism behind the FP8
block-scaling wgrad corruption under CUDA graphs previously observed
on Hopper and attributed to cuBLAS.

Cache the setup workspace per (device, group size) and reuse the
cached per-device cuBLAS workspace from the non-grouped path;
consecutive GEMMs reusing one workspace are ordered by the stream.

Signed-off-by: Alp Dener <adener@nvidia.com>

* [PyTorch] Fix grouped FP8 block-scaling CUDA-graph deadlock via per-role cuBLAS workspaces

The grouped-tensor GEMM path shared one persistent cuBLAS workspace across all
grouped matmuls. cuBLAS's grouped GEMM keeps a grid-synchronization flag in the
first bytes of that workspace and zeros it (via a captured memset) before each
matmul. When the dgrad and wgrad grouped matmuls of a GroupedLinear backward share
one workspace inside a replayed CUDA graph, that flag is aliased between the two
matmuls; on the second graph replay the second matmul's cooperative kernel
deadlocks with cuBLAS 13.6 (and corrupts the last expert's wgrad on cuBLAS < 13.6).
The two matmuls are strictly stream-ordered (single stream, all-DEFAULT graph
edges, no programmatic dependent launch), so this is shared-workspace reuse, not
concurrent co-scheduling.

Give dgrad/forward (slot 0) and wgrad (slot 1) distinct persistent cuBLAS
workspaces, dedicated to the grouped path. Each slot remains a single persistent
allocation, so CUDA-graph capture safety is preserved.

Also drop the cuBLAS-version gate that skipped the FP8 block-scaling GroupedLinear
CUDA-graph test, so it now exercises the fix on all supported cuBLAS versions.

Signed-off-by: Alp Dener <adener@nvidia.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* [PyTorch] Address review: document split-accumulator override, fix stale dbias comment

- general_grouped_gemm_for_grouped_tensor: expand the comment to state that the fused
  grouped FP8 block-scaling GEMM forces use_split_accumulator=True and intentionally
  overrides the caller-supplied value, consistent with the Float8BlockScaling recipe
  (which fixes it True for fprop/dgrad/wgrad).
- Float8BlockScaling recipe docstring: document that FP8 block scaling always uses
  split accumulation and that the fused grouped GEMM path ignores any caller- or
  recipe-supplied use_split_accumulator value.
- GroupedLinear ops backward: correct the stale "BF16/FP16 path" comment; that branch
  also handles quantized paths where bgrad fusion did not apply (e.g. FP8 block
  scaling without a dgrad pass).

Signed-off-by: Alp Dener <adener@nvidia.com>

* [PyTorch] Revert fusible-ops FP8 block-scaling; scope PR to GroupedLinear module

Restrict this PR to the GroupedLinear module fused-quantize path. Revert the fusible-ops FP8 block-scaling enablement -- the BasicOperation opt-in gate, the GroupedLinear op support, and the fusible-ops test coverage -- back to main. Enabling fusible-ops FP8 block-scaling for both grouped and non-grouped paths is deferred to a separate PR.

The blanket FP8 block-scaling rejection in BasicOperation.reset_recipe_state is restored. The split-accumulator guard in general_grouped_gemm_for_grouped_tensor is retained: it is correct for the module's FP8 block-scaling grouped GEMM.

Signed-off-by: Alp Dener <adener@nvidia.com>

* [PyTorch] Isolate grouped wgrad cuBLAS workspace by NT layout, not out-discreteness

_get_grouped_cublas_workspace slots were keyed on is_discrete_out as a proxy for "this is the wgrad GEMM", which only holds when wgrad writes a list of per-expert grads. With single_grouped_weight=True, wgrad writes a single grouped weight-grad (GroupedTensor out, not a list), so is_discrete_out is False and it collided with dgrad on slot 0 -- reintroducing the FP8 block-scaling grid-sync-flag aliasing deadlock/corruption under CUDA-graph replay. Key the slot on the wgrad layout (NT / transb) instead: fprop (TN) and dgrad (NN) share slot 0, wgrad (NT) is always isolated on slot 1.

Signed-off-by: Alp Dener <adener@nvidia.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* [PyTorch] Address review: isolate grouped cuBLAS workspace per layout; drop redundant test spy

- _get_grouped_cublas_workspace now keys the persistent workspace on the grouped
  GEMM layout, so fprop (TN), dgrad (NN), and wgrad (NT) each get a distinct
  workspace. The previous NT-vs-rest scheme left fprop and dgrad sharing one
  workspace; those have also been reported to conflict under CUDA-graph replay.
  Documents that the deadlock is deterministic and present through cuBLAS 13.7.
- Drop the group_quantize call-counting spy in
  test_grouped_linear_grouped_tensor_path_matches_legacy; fused-path engagement is
  covered by the graph-safe test.

Signed-off-by: Alp Dener <adener@nvidia.com>

* updated grouped GEMM workspace comment on stale TMA descriptor related deadlocks

Signed-off-by: Alp Dener <adener@nvidia.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Signed-off-by: Alp Dener <adener@nvidia.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
update nccl-ext submodule name

Signed-off-by: Phuong Nguyen <phuonguyen@nvidia.com>
)

* [JAX] Schedule EP dispatch/combine on XLA collective stream

* [JAX] Gate EP collective-stream annotation on JAX/XLA version

Signed-off-by: Phuong Nguyen <phuonguyen@nvidia.com>

---------

Signed-off-by: Phuong Nguyen <phuonguyen@nvidia.com>
* Generalized Tensor Parallelism (GTP) init commit

Co-authored-by: Jieming Zhang <jiemingz@nvidia.com>
Signed-off-by: Shiqing Fan <shiqingf@nvidia.com>

* GTP + gmm fusion

Signed-off-by: Shiqing Fan <shiqingf@nvidia.com>

* [fix] Respect per-op activation-offload markers in fused grouped MLP

Signed-off-by: Shiqing Fan <shiqingf@nvidia.com>

* Code clean: rename GTP weight-sharding axis to gtp_remat

Signed-off-by: Shiqing Fan <shiqingf@nvidia.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Revert "[fix] Respect per-op activation-offload markers in fused grouped MLP"

This reverts commit 8bb26f0.

Signed-off-by: Shiqing Fan <shiqingf@nvidia.com>

* Make TE GTP-agnostic at construction

Signed-off-by: Shiqing Fan <shiqingf@nvidia.com>

* GTP+nvfp4: fix GTP backward GEMM scaling-mode mismatch for bf16-gathered weights

Signed-off-by: Shiqing Fan <shiqingf@nvidia.com>

* Make TE runtime GTP-agnostic via a DistributedWeight protocol

Signed-off-by: Shiqing Fan <shiqingf@nvidia.com>

* Code clean

- Take a single leader weight in the DistributedWeight dispatchers
- Gather the FC2 grouped weight late in the fused grouped MLP

Signed-off-by: Shiqing Fan <shiqingf@nvidia.com>

* Simplify the NVFP4 gather post-process; Materialize the EGTP FC1 weight before the NVFP4 dgrad dispatch

Signed-off-by: Shiqing Fan <shiqingf@nvidia.com>

* Code clean

- Rename gather coalescing flag grouped -> external_coalescing;
- Clean up DistributedWeight wiring in TE modules
- Restructure _all_gather_nvfp4

Signed-off-by: Shiqing Fan <shiqingf@nvidia.com>

* fix comments

Signed-off-by: Shiqing Fan <shiqingf@nvidia.com>

* Support DistributedWeight in the fusible grouped-linear ops path

- Add a self-contained dispatch test with a fake DistributedWeight implementer

Signed-off-by: Shiqing Fan <shiqingf@nvidia.com>

* Unify distributed-weight wgrad finalize to return a graph-safe dummy

- `finalize_weight_grads` now accepts a weight list or a bare leader, mirroring
  materialize_weight_for_backward;
- Centralize the in-place / dummy / async-None finalize contract in
  DistributedWeight.finalize_group_grads and delegate the dispatcher docstring to it.

Signed-off-by: Shiqing Fan <shiqingf@nvidia.com>

---------

Signed-off-by: Shiqing Fan <shiqingf@nvidia.com>
Co-authored-by: Jieming Zhang <jiemingz@nvidia.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Signed-off-by: Tim Moon <tmoon@nvidia.com>
fix nproc

Signed-off-by: Phuong Nguyen <phuonguyen@nvidia.com>
- DotProductAttention.forward: mark query/key/value_layer as Optional and
  note they are required only when no packed input (qkv_layer/kv_layer) is
  given (Charlene).
- combine_and_quantize: describe combined_qkv/combined_kv in terms of
  qkv_group=1 / qkv_group=2 layouts instead of '3'/'2' layouts (Charlene).

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
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.