Skip to content

[PyTorch] Pass canonical layouts to cuDNN for the NVFP4 fused grouped MLP - #3820

Open
hwanseoc wants to merge 3 commits into
NVIDIA:mainfrom
hwanseoc:hwanseoc/canonical-grouped-mlp-nvfp4
Open

hwanseoc wants to merge 3 commits into
NVIDIA:mainfrom
hwanseoc:hwanseoc/canonical-grouped-mlp-nvfp4

Conversation

@hwanseoc

@hwanseoc hwanseoc commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

Description

Reduce host overhead in the NVFP4 fused grouped MLP by passing operands to the cuDNN grouped GEMM wrappers in their natural layouts: the forward FC1 GEMM, and the backward dGLU and wgrad.

#3641 did this for MXFP8 but kept NVFP4 on the legacy layouts. The cuDNN frontend GLU wrapper accepts canonical layouts since 1.31; the Hadamard GLU (NVIDIA/cudnn-frontend#1601), dGLU and wgrad wrappers since 1.32.

Type of change

  • Documentation change (change only to the documentation, either a fix or a new content)
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Infra/Build change
  • Code refactoring

Changes

  • Forward FC1 (GLU / Hadamard GLU): when the wrapper that will be launched advertises supports_canonical_layouts and the NVFP4 input scales are swizzled, TE passes A as (sum(m), k), the probabilities as (sum(m),) and the swizzled scale buffer as is, instead of 3-D and 6-D kernel-facing views. The check reads the flag of the kernel that is actually launched; MXFP8 behaves as before.
  • Backward dGLU (FC2 dgrad + dGLU): the same for the grad output, activation input, probabilities and their gradient, when the dGLU wrapper advertises it and the grad output scales are swizzled. NVFP4 runs the FC1 dgrad on cuBLAS, so it does not need the quant wrapper's support.
  • Backward wgrad (FC1 and FC2): NVFP4 shares the MXFP8 scale handling and passes flat scale buffers when the wgrad wrapper supports them. With cuDNN FE 1.32.x, the wrapper's flat-buffer check costs about 15 µs per FP4 call; Make the grouped wgrad flat scale-buffer check cheap cudnn-frontend#2289 makes it cheap.
  • Unchanged:
    • FC2 fprop and FC1 dgrad, which run on TE's cuBLAS grouped GEMM;
    • the fused GEMM + activation + RHT + quant kernel, which does not advertise canonical layouts;
    • unswizzled scales;
    • the weight layout for single_grouped_weight=True, which NVFP4 does not support yet.
  • Docs: the fused op docstring notes the NVFP4 behavior.

Which path NVFP4 takes, by cuDNN frontend version:

cuDNN FE FC1 GLU (e.g. GeGLU) FC1 Hadamard GLU (e.g. SwiGLU with RHT + amax) dGLU wgrad
1.29.1 legacy legacy legacy legacy
1.31.0 canonical legacy legacy legacy
1.32.x canonical canonical canonical canonical

Measurements

NVFP4 (NVFP4BlockScaling()), ScaledSwiGLU(glu_interleave_size=32), per-expert weights. Host p50 in µs, main (720b252) → this PR, median of three interleaved rounds per cuDNN frontend:

Shape Phase FE 1.32.1 FE develop + NVIDIA/cudnn-frontend#2289
8 experts, hidden 2048, FFN 512, 4096 tokens forward 548 → 527 (−20) 549 → 527 (−21)
forward + backward 2037 → 2020 (−17) 2036 → 1979 (−56)
64 experts, hidden 4096, FFN 1024, 16384 tokens forward 911 → 880 (−31) 905 → 883 (−22)
forward + backward 3447 → 3411 (−36) 3433 → 3380 (−53)

With FE 1.32.1, most of the backward saving goes to the flat wgrad scale check (about 15 µs per FP4 call); NVIDIA/cudnn-frontend#2289 removes it. GPU kernel time is unchanged.

Setup: SM100, cuDNN 9.25.1, CUDA 13.4, torch 2.14.0a0, CuTeDSL 4.7.1, process pinned to an idle CPU core, 300 synchronized samples per process.

Testing

  • tests/pytorch/test_grouped_mlp.py at this PR's head:

    • FE 1.32.1: 1350 passed, 3944 skipped, 0 failed.
    • FE 1.31.0: 1336 passed, 3958 skipped, 0 failed.
    • FE 1.29.1: 1336 passed, 3958 skipped, 0 failed.

    TE CI's container has FE 1.29, so CI exercises only the legacy path.

  • NVFP4 RHT, 4 experts, canonical vs legacy layouts on the same inputs, FE 1.32.1: with canonical layouts on, all four cuDNN calls (FC1, dGLU and both wgrads) receive canonical operands. The forward output, input and probability gradients, and every weight gradient are bitwise identical for SwiGLU (Hadamard GLU) and GeGLU (GLU). Bias gradients use atomics and differ between any two runs, legacy included.

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

@coderabbitai ignore

… MLP forward

When the FC1 GEMM + activation wrapper that will be launched advertises
supports_canonical_layouts and the NVFP4 input scales are swizzled, pass
A as (sum(m), k), prob as (sum(m),) and the swizzled scale buffer as-is
instead of the 3-D and 6-D kernel-facing views. The decision follows the
kernel choice, so the fused GEMM + activation + RHT + quant kernel, which
does not advertise canonical layouts, keeps the legacy layouts, as do
unswizzled scales, FC2 and the backward.

Signed-off-by: Hwanseo Choi <hwanseoc@nvidia.com>
@greptile-apps

greptile-apps Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; the remaining test suggestion is non-blocking.

Findings

  1. P2 Layout choice lacks a test ▶

Summary

The PR lets supported cuDNN wrappers receive NVFP4 operands in their natural layouts.

  • NVFP4 FC1 forward uses canonical layouts with supported cuDNN wrappers.
  • NVFP4 dGLU backward uses canonical layouts with supported cuDNN wrappers.
  • NVFP4 wgrad uses packed scales with wrappers that support canonical layouts.

hwanseoc acknowledges the known flat-scale check cost in cuDNN FE 1.32.1 and keeps this path because the measured overall speedup remains positive.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Select FC1 or dGLU wrapper] --> B{Wrapper supports canonical layouts?}
    B -->|No| L[Use legacy views]
    B -->|Yes| C{NVFP4 scales are swizzled?}
    C -->|No| L
    C -->|Yes| N[Pass natural operand layouts]
    W[Weight-gradient wrapper] --> S{Supports canonical layouts?}
    S -->|Yes| F[Pass scale buffers as-is]
    S -->|No| V[Build two-dimensional scale views]
Loading

Reviews (5) · Last reviewed commit: "Merge branch 'main' into hwanseoc/canoni..." · Reviewed by Greptile

@hwanseoc hwanseoc changed the title [PyTorch] Pass canonical layouts to cuDNN for the NVFP4 fused grouped MLP forward [PyTorch] Pass canonical layouts to cuDNN for the NVFP4 fused grouped MLP Nov 5, 2026
@hwanseoc
hwanseoc force-pushed the hwanseoc/canonical-grouped-mlp-nvfp4 branch from 50d94c2 to f16bfe8 Compare November 5, 2026 16:41
@greptile-apps

greptile-apps Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Want your agent to iterate on Greptile's feedback? Try greploops.

@hwanseoc

hwanseoc commented Nov 5, 2026

Copy link
Copy Markdown
Member Author

Tested locally at f16bfe8 with tests/pytorch/test_grouped_mlp.py:

  • cuDNN FE 1.32.1: 1350 passed, 3944 skipped, 0 failed
  • cuDNN FE 1.31.0: 1336 passed, 3958 skipped, 0 failed
  • cuDNN FE 1.29.1: 1336 passed, 3958 skipped, 0 failed

TE CI's container has cuDNN FE 1.29, so CI only exercises the legacy layout path. The canonical paths need FE 1.31 (GLU) or 1.32 (Hadamard GLU and dGLU).

… MLP backward

When the dGLU wrapper advertises supports_canonical_layouts and the NVFP4
grad output scales are swizzled, pass the grad output as (sum(m), k), the
probabilities as (sum(m),) and the swizzled scale buffer as-is. NVFP4 runs
the FC1 dgrad on cuBLAS, so it does not need the quant wrapper's support.
A single grouped NVFP4 weight keeps its legacy layout.

The NVFP4 wgrad now shares the MXFP8 scale handling and passes flat scale
buffers when the wgrad wrapper supports them. With cuDNN frontend 1.32.x,
checking flat FP4 scale buffers costs about 15 us per wgrad call;
NVIDIA/cudnn-frontend#2289 makes that check cheap.

Signed-off-by: Hwanseo Choi <hwanseoc@nvidia.com>
@hwanseoc
hwanseoc force-pushed the hwanseoc/canonical-grouped-mlp-nvfp4 branch from f16bfe8 to 4ee7c8f Compare November 5, 2026 21:28
@hwanseoc

hwanseoc commented Nov 5, 2026

Copy link
Copy Markdown
Member Author

Update: the head is now 4ee7c8f, which also passes flat wgrad scales for NVFP4. tests/pytorch/test_grouped_mlp.py on that code: FE 1.32.1 1350 passed, FE 1.31.0 1336 passed, FE 1.29.1 1336 passed, 0 failed on each. The flat wgrad scale check is cheap once NVIDIA/cudnn-frontend#2289 lands; numbers for both frontends are in the description.

@timmoon10

Copy link
Copy Markdown
Member

/te-ci pytorch

@timmoon10 timmoon10 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Overall LGTM

and getattr(dactivation_kernel, "supports_canonical_layouts", False)
and getattr(self.grouped_gemm_quant_kernel(), "supports_canonical_layouts", False)
# NVFP4 runs the FC1 dgrad on cuBLAS, so only the dGLU kernel needs
# canonical support, with swizzled scales.

@timmoon10 timmoon10 Nov 7, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: This is invalidating the previous "Pack data tensors" comment. Previously that one comment was enough to describe this entire section, but if we need this additional comment then that's a sign that we should restructure. I'd suggest having a separate section to figure out whether or not to use a canonical layout, and leave this code section dedicated to the non-canonical layout reshapes.

As a general pattern, I observe that agents prefer minimal line changes even if it makes the code quality worse.

Comment on lines +2524 to +2525
# NVFP4 runs the FC1 dgrad on cuBLAS, so only the dGLU kernel needs
# canonical support, with swizzled scales.

@timmoon10 timmoon10 Nov 7, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: This comment is awkward if you're not aware of the edit history. Compare "NVFP4 doesn't use the cuDNN kernel, so we don't need to check" vs "MXFP8 uses the cuDNN kernel, so we need to check". Agents will overemphasize the last thing they worked on and don't consider how it affects the final code.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants