Skip to content

[CUDA] Fix SM120 QMoE MoE GEMM TMA build failure with GCC - #32584

Merged
Tianlei Wu (tianleiwu) merged 1 commit into
microsoft:mainfrom
bheu:fix-sm120-qmoe-moe-gemm-tma-build
Sep 15, 2026
Merged

Tianlei Wu (tianleiwu) merged 1 commit into
microsoft:mainfrom
bheu:fix-sm120-qmoe-moe-gemm-tma-build

Conversation

@bheu

@bheu bheu commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

📌 Summary

Fix a GCC build failure in the CUDA EP for SM120 (Blackwell consumer) FP4 QMoE MoE GEMM TMA warp-specialized kernels. All 11 moe_gemm_tma_ws_sm120_*.generated.cu files fail to compile on Linux with:

moe_gemm_tma_ws_launcher.inl:82:8: error: expression list treated as compound
expression in functional cast [-fpermissive]
   82 |         return ReturnType(std::forward<Args>(args)...);

(repeated per std::forward argument, then cc1plus: all warnings being treated as errors → build of onnxruntime_providers_cuda_sm120_tma fails).

🔍 Root cause

moe_gemm_tma_ws_launcher.inl:535:

using EpilogueScalars = decltype(EpilogueArguments{}.thread);

EpilogueArguments{} is a prvalue, so the member access .thread yields an xvalue, and decltype therefore gives an rvalue reference. For the SM120 epilogue this resolves to

cutlass::epilogue::fusion::FusionCallbacks<
    cutlass::epilogue::Sm90PtrArrayTmaWarpSpecialized<3,2,4,true,false,2>,
    cutlass::epilogue::fusion::LinearCombination<half_t,float,half_t,float,round_to_nearest>,
    cute::tuple<...>, cute::tuple<...>>::Arguments&&

(the instantiated ReturnType = ...::Arguments&& is visible in the compiler
diagnostic above).

The 8-argument call site then expands construct_if_true<IsBlackwell, EpilogueScalars>(...)
into Arguments&&(alpha, beta, nullptr, nullptr, alpha_ptr_array, nullptr, dAlpha, dBeta) — a
multi-expression functional cast to a reference type, which is ill-formed in C++. GCC rejects
it under -fpermissive; with the project's -Werror the build fails.

The call was written as C++20 parenthesized aggregate initialization of the value type
FusionCallbacks::Arguments (whose 8 members are exactly alpha, beta, alpha_ptr, beta_ptr, alpha_ptr_array, beta_ptr_array, dAlpha, dBeta — see
cutlass/epilogue/fusion/sm90_callbacks_tma_warpspecialized.hpp), i.e. the reference qualifier is
unintended.

For SM90 the thread member is already a plain value type
(cutlass::epilogue::thread::LinearCombination<...>::Params), which is why the bug only surfaces
for the SM120 epilogue.

🛠️ Fix

One line — strip the reference so the intended value type is used:

using EpilogueScalars = std::remove_reference_t<decltype(EpilogueArguments{}.thread)>;
  • SM120: FusionCallbacks<...>::Arguments&& → FusionCallbacks<...>::Arguments (value type,
    8-member aggregate — the parenthesized init is well-formed and maps member-by-member).
  • SM90: already a value type, so remove_reference_t is an identity no-op (no behavior change).

No new includes needed; no functional change to the constructed values.

✅ Verification

Environment: Linux x86_64, GCC 13, CUDA 13.3, CUTLASS 4.7.0, CMAKE_CUDA_ARCHITECTURES=86-real;120-real.

Check Before fix After fix
11 × moe_gemm_tma_ws_sm120_*.generated.cu (sm_120a) ❌ expression list treated as compound expression in functional cast [-fpermissive] (all files) ✅ all compile, 0 errors
moe_gemm_tma_ws_sm90_*.generated.cu (sm_90a, from onnxruntime_providers_cuda_sm90_tma) ✅ ✅ unchanged (identity transform)
Full ./build.sh shared lib + wheel ❌ fails at onnxruntime_providers_cuda_sm120_tma ✅ built and installed successfully

Note: the SM90 files are compiled by a separate object library with hardcoded 90a-real
architecture, so any build with ≥ one SM90+ architecture exercises both epilogue paths; both are
verified above.

📝 Note (not fixed here, follow-up candidate)

moe_gemm_tma_ws_mixed_input_launcher.inl:201 uses the same
decltype(EpilogueArguments{}.thread) pattern and then declares EpilogueScalars scalars; and
mutates its fields directly. It is currently safe because that launcher only instantiates SM90
epilogues (value type), but it would break the same way if an SM120 epilogue were ever routed
there. Kept out of this PR to keep it minimal.

Copilot AI balanced review requested due to automatic review settings September 14, 2026 07:13
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The one-line change is a well-understood, behavior-preserving reference-stripping transform that fixes the SM120 build without affecting the SM90 path, and both were verified to compile.

Pull request overview

This PR fixes a GCC -Werror build failure for the CUDA EP's SM120 (Blackwell consumer) FP4 QMoE MoE GEMM TMA warp-specialized kernels. In moe_gemm_tma_ws_launcher.inl, the alias EpilogueScalars was derived via decltype(EpilogueArguments{}.thread), which for the SM120 epilogue resolves to a reference type (...::Arguments&&). Feeding that reference type into the C++20 parenthesized aggregate-init helper construct_if_true (return ReturnType(args...)) produces a multi-argument functional cast to a reference — ill-formed C++ that GCC rejects, failing the build of onnxruntime_providers_cuda_sm120_tma. Wrapping the decltype in std::remove_reference_t restores the intended value type. The change is a no-op for the SM90 epilogue, whose thread member is already a value type.

Changes:

  • Strip the reference qualifier from the EpilogueScalars alias so the value aggregate type is used for parenthesized aggregate initialization.
File summaries
File Description
onnxruntime/contrib_ops/cuda/llm/moe_gemm/launchers/moe_gemm_tma_ws_launcher.inl Wraps decltype(EpilogueArguments{}.thread) in std::remove_reference_t to yield the epilogue's value type, fixing the SM120 functional-cast build error while leaving SM90 unchanged.

I found no actionable issues. The fix is minimal and safe: std::remove_reference_t strips the offending reference for SM120 and acts as an identity transform for SM90's already-value type, so no constructed values change. std::remove_reference_t is available transitively (the file already relies on std::forward), and the author verified both epilogue paths build. The analogous pattern in moe_gemm_tma_ws_mixed_input_launcher.inl:201 is outside this diff and documented as intentionally deferred (currently safe since it only instantiates SM90 value-type epilogues).

Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@tianleiwu
Tianlei Wu (tianleiwu) merged commit 5f44b5b into microsoft:main Sep 15, 2026
90 checks passed
@bheu
bheu deleted the fix-sm120-qmoe-moe-gemm-tma-build branch September 15, 2026 20:15
Mark Harfouche (hmaarrfk) added a commit to hmaarrfk/onnxruntime-feedstock that referenced this pull request Sep 18, 2026
<details><summary>Claude's draft</summary>

conda-forge-pinning moved cuda_compiler_version from 13.0 to 13.4 and the
three CUDA 13.4 configs failed in two different ways.

linux-64 and linux-aarch64: every moe_gemm_tma_ws_sm120_*.generated.cu
failed with

  moe_gemm_tma_ws_launcher.inl:82:8: error: expression list treated as
  compound expression in functional cast

Backport patch 0012 from upstream microsoft/onnxruntime#32584 (5f44b5b,
merged two days after the 1.30.0 release): EpilogueArguments{}.thread is
an xvalue, so decltype yields an rvalue reference and construct_if_true is
instantiated with ReturnType = T&&. std::remove_reference_t fixes it. This
is not specific to the unvendoring; main hits it once it moves to 13.4.

win-64: the CUDA translation units failed compiling abseil's headers

  absl/hash/internal/hash.h(1428): error C3856: 'HashStateBase': symbol is
  not a class template

conda-forge's abseil defaults ABSL_DLL to __declspec(dllimport) (a
conda-forge addition to absl/base/config.h), so MixingHashState is a
dllimport class and the host code nvcc 13.4 generates makes MSVC reject
"friend class MixingHashState::HashStateBase;". nvcc 13.0 accepted it and
onnxruntime's vendored abseil is not dllimport, so only this build is
affected. Rewrite the declaration as the equivalent
"friend class HashStateBase<MixingHashState>;" in the host environment's
copy of the header before building. This belongs in the libabseil package;
the script is temporary and the workaround is confirmed by CI first.

Resume this Claude session:
```
cd /home/mark/git/feedstock
claude --resume 3cf41599-db98-4c6f-a67a-ee7dcacda8b4
```
</details>

Claude-Session: https://claude.ai/code/session_01Jxvsn2W6MfjGKXcabWVHNe
Mark Harfouche (hmaarrfk) added a commit to hmaarrfk/onnxruntime-feedstock that referenced this pull request Sep 19, 2026
<details><summary>Claude's draft</summary>

conda-forge-pinning moved cuda_compiler_version from 13.0 to 13.4 and the
three CUDA 13.4 configs failed in two different ways.

linux-64 and linux-aarch64: every moe_gemm_tma_ws_sm120_*.generated.cu
failed with

  moe_gemm_tma_ws_launcher.inl:82:8: error: expression list treated as
  compound expression in functional cast

Backport patch 0012 from upstream microsoft/onnxruntime#32584 (5f44b5b,
merged two days after the 1.30.0 release): EpilogueArguments{}.thread is
an xvalue, so decltype yields an rvalue reference and construct_if_true is
instantiated with ReturnType = T&&. std::remove_reference_t fixes it. This
is not specific to the unvendoring; main hits it once it moves to 13.4.

win-64: the CUDA translation units failed compiling abseil's headers

  absl/hash/internal/hash.h(1428): error C3856: 'HashStateBase': symbol is
  not a class template

conda-forge's abseil defaults ABSL_DLL to __declspec(dllimport) (a
conda-forge addition to absl/base/config.h), so MixingHashState is a
dllimport class and the host code nvcc 13.4 generates makes MSVC reject
"friend class MixingHashState::HashStateBase;". nvcc 13.0 accepted it and
onnxruntime's vendored abseil is not dllimport, so only this build is
affected. Rewrite the declaration as the equivalent
"friend class HashStateBase<MixingHashState>;" in the host environment's
copy of the header before building. This belongs in the libabseil package;
the script is temporary and the workaround is confirmed by CI first.

Resume this Claude session:
```
cd /home/mark/git/feedstock
claude --resume 3cf41599-db98-4c6f-a67a-ee7dcacda8b4
```
</details>

Claude-Session: https://claude.ai/code/session_01Jxvsn2W6MfjGKXcabWVHNe
automatic conda-forge administrator (conda-forge-admin) pushed a commit to conda-forge/onnxruntime-feedstock that referenced this pull request Sep 20, 2026
<details><summary>Claude's draft</summary>

With cuda_compiler_version 13.4, every moe_gemm_tma_ws_sm120_*.generated.cu
fails on linux:

  moe_gemm_tma_ws_launcher.inl:82:8: error: expression list treated as
  compound expression in functional cast

Backport microsoft/onnxruntime#32584 (5f44b5b), merged two days after the
1.30.0 release: EpilogueArguments{}.thread is an xvalue, so decltype gives
an rvalue reference and construct_if_true is instantiated with
ReturnType = T&&. std::remove_reference_t fixes it.

Resume this Claude session:
```
cd /home/mark/git/feedstock
claude --resume 3cf41599-db98-4c6f-a67a-ee7dcacda8b4
```
</details>

Claude-Session: https://claude.ai/code/session_01Jxvsn2W6MfjGKXcabWVHNe
Mark Harfouche (hmaarrfk) added a commit to hmaarrfk/onnxruntime-feedstock that referenced this pull request Sep 20, 2026
<details><summary>Claude's draft</summary>

conda-forge-pinning moved cuda_compiler_version from 13.0 to 13.4 and the
three CUDA 13.4 configs failed in two different ways.

linux-64 and linux-aarch64: every moe_gemm_tma_ws_sm120_*.generated.cu
failed with

  moe_gemm_tma_ws_launcher.inl:82:8: error: expression list treated as
  compound expression in functional cast

Backport patch 0012 from upstream microsoft/onnxruntime#32584 (5f44b5b,
merged two days after the 1.30.0 release): EpilogueArguments{}.thread is
an xvalue, so decltype yields an rvalue reference and construct_if_true is
instantiated with ReturnType = T&&. std::remove_reference_t fixes it. This
is not specific to the unvendoring; main hits it once it moves to 13.4.

win-64: the CUDA translation units failed compiling abseil's headers

  absl/hash/internal/hash.h(1428): error C3856: 'HashStateBase': symbol is
  not a class template

conda-forge's abseil defaults ABSL_DLL to __declspec(dllimport) (a
conda-forge addition to absl/base/config.h), so MixingHashState is a
dllimport class and the host code nvcc 13.4 generates makes MSVC reject
"friend class MixingHashState::HashStateBase;". nvcc 13.0 accepted it and
onnxruntime's vendored abseil is not dllimport, so only this build is
affected. Rewrite the declaration as the equivalent
"friend class HashStateBase<MixingHashState>;" in the host environment's
copy of the header before building. This belongs in the libabseil package;
the script is temporary and the workaround is confirmed by CI first.

Resume this Claude session:
```
cd /home/mark/git/feedstock
claude --resume 3cf41599-db98-4c6f-a67a-ee7dcacda8b4
```
</details>

Claude-Session: https://claude.ai/code/session_01Jxvsn2W6MfjGKXcabWVHNe
Mark Harfouche (hmaarrfk) added a commit to regro-cf-autotick-bot/onnxruntime-feedstock that referenced this pull request Oct 10, 2026
<details><summary>Claude's draft</summary>

Patch 0009 backported microsoft/onnxruntime#32584, which was merged after
1.30.0. It is already part of the 1.31.0 sources, so the patch is detected
as reversed and `patch --dry-run` exits 1, failing source preparation on
every CI job. Patches 0001-0008 still apply cleanly to the 1.31.0 tarball.

Resume this Claude session:
```
cd /home/mark/git/f
claude --resume 277f2e03-c720-448a-a7e3-27bb8900dbc3
```
</details>
Connor Baker (ConnorBaker) pushed a commit to ConnorBaker/onnxruntime that referenced this pull request Oct 14, 2026
…32584)

## 📌 Summary

Fix a GCC build failure in the CUDA EP for SM120 (Blackwell consumer)
FP4 QMoE MoE GEMM TMA warp-specialized kernels. All 11
`moe_gemm_tma_ws_sm120_*.generated.cu` files fail to compile on Linux
with:

```
moe_gemm_tma_ws_launcher.inl:82:8: error: expression list treated as compound
expression in functional cast [-fpermissive]
   82 |         return ReturnType(std::forward<Args>(args)...);
```

(repeated per `std::forward` argument, then `cc1plus: all warnings being
treated as errors` → build of `onnxruntime_providers_cuda_sm120_tma`
fails).

## 🔍 Root cause

`moe_gemm_tma_ws_launcher.inl:535`:

```cpp
using EpilogueScalars = decltype(EpilogueArguments{}.thread);
```

`EpilogueArguments{}` is a **prvalue**, so the member access `.thread`
yields an **xvalue**, and `decltype` therefore gives an rvalue
reference. For the SM120 epilogue this resolves to

```
cutlass::epilogue::fusion::FusionCallbacks<
    cutlass::epilogue::Sm90PtrArrayTmaWarpSpecialized<3,2,4,true,false,2>,
    cutlass::epilogue::fusion::LinearCombination<half_t,float,half_t,float,round_to_nearest>,
    cute::tuple<...>, cute::tuple<...>>::Arguments&&
```

(the instantiated `ReturnType = ...::Arguments&&` is visible in the
compiler
diagnostic above).

The 8-argument call site then expands `construct_if_true<IsBlackwell,
EpilogueScalars>(...)`
into `Arguments&&(alpha, beta, nullptr, nullptr, alpha_ptr_array,
nullptr, dAlpha, dBeta)` — a
**multi-expression functional cast to a reference type**, which is
ill-formed in C++. GCC rejects
it under `-fpermissive`; with the project's `-Werror` the build fails.

The call was written as C++20 parenthesized aggregate initialization of
the *value* type
`FusionCallbacks::Arguments` (whose 8 members are exactly `alpha, beta,
alpha_ptr, beta_ptr,
alpha_ptr_array, beta_ptr_array, dAlpha, dBeta` — see
`cutlass/epilogue/fusion/sm90_callbacks_tma_warpspecialized.hpp`), i.e.
the reference qualifier is
unintended.

For SM90 the `thread` member is already a plain value type
(`cutlass::epilogue::thread::LinearCombination<...>::Params`), which is
why the bug only surfaces
for the SM120 epilogue.

## 🛠️ Fix

One line — strip the reference so the intended value type is used:

```cpp
using EpilogueScalars = std::remove_reference_t<decltype(EpilogueArguments{}.thread)>;
```

- SM120: `FusionCallbacks<...>::Arguments&&` →
`FusionCallbacks<...>::Arguments` (value type,
8-member aggregate — the parenthesized init is well-formed and maps
member-by-member).
- SM90: already a value type, so `remove_reference_t` is an identity
no-op (no behavior change).

No new includes needed; no functional change to the constructed values.

## ✅ Verification

Environment: Linux x86_64, GCC 13, CUDA 13.3, CUTLASS 4.7.0,
`CMAKE_CUDA_ARCHITECTURES=86-real;120-real`.

| Check | Before fix | After fix |
|---|---|---|
| 11 × `moe_gemm_tma_ws_sm120_*.generated.cu` (sm_120a) | ❌ `expression
list treated as compound expression in functional cast [-fpermissive]`
(all files) | ✅ all compile, 0 errors |
| `moe_gemm_tma_ws_sm90_*.generated.cu` (sm_90a, from
`onnxruntime_providers_cuda_sm90_tma`) | ✅ | ✅ unchanged (identity
transform) |
| Full `./build.sh` shared lib + wheel | ❌ fails at
`onnxruntime_providers_cuda_sm120_tma` | ✅ built and installed
successfully |

Note: the SM90 files are compiled by a separate object library with
hardcoded `90a-real`
architecture, so any build with ≥ one SM90+ architecture exercises both
epilogue paths; both are
verified above.

## 📝 Note (not fixed here, follow-up candidate)

`moe_gemm_tma_ws_mixed_input_launcher.inl:201` uses the same
`decltype(EpilogueArguments{}.thread)` pattern and then declares
`EpilogueScalars scalars;` and
mutates its fields directly. It is currently safe because that launcher
only instantiates SM90
epilogues (value type), but it would break the same way if an SM120
epilogue were ever routed
there. Kept out of this PR to keep it minimal.

(cherry picked from commit 5f44b5b)
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