Repository navigation
[CUDA] Fix CUDA architecture gating for SM12x, family/arch-specific targets and -virtual entries - #33373
[CUDA] Fix CUDA architecture gating for SM12x, family/arch-specific targets and -virtual entries#33373Connor Baker (ConnorBaker) wants to merge 7 commits into
Conversation
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
|
@microsoft-github-policy-service agree |
There was a problem hiding this comment.
🔵 Needs a closer look
The current branch remains uncompiled, and newly enabled MoE kernels lack GPU execution validation.
0 open findings
What changed in this PR
Corrects CUDA architecture handling so kernel selection matches requested targets, including SM121, architecture/family suffixes, and PTX-only entries.
Changes:
- Adds shared architecture parsing and capability checks.
- Aligns standard and plugin CUDA provider gates for SM80+, SM90, and SM12x kernels.
- Explicitly applies SM120 XQA constants to SM121.
| File | Description |
|---|---|
| onnxruntime/contrib_ops/cuda/bert/xqa/utils.cuh | Applies the shared-memory limit to SM121. |
| onnxruntime/contrib_ops/cuda/bert/xqa/mha_impl.cuh | Applies SM120 tile constants to SM121. |
| cmake/onnxruntime_providers_cuda.cmake | Corrects standard provider kernel gates. |
| cmake/onnxruntime_providers_cuda_plugin.cmake | Corrects plugin provider kernel gates. |
| cmake/onnxruntime_cuda_source_filters.cmake | Updates source selection and architecture filtering. |
| cmake/external/cuda_configuration.cmake | Adds parsing helpers and corrects normalization and capability gates. |
🧠 Review effort: Balanced
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Failures should be unrelated. Are they flaky? |
|
Bump |
## Description Ports the architecture-table part of NVIDIA/TensorRT-LLM#12170 ("Compile XQA with sm_120f") to ORT's XQA decode kernels: `__CUDA_ARCH__ == 1210` (sm_121 — GB10 / DGX Spark and other Blackwell Arm devices) is now listed next to sm_120 in the two compile-time arch tables. sm_121 has the same 99 KB opt-in shared-memory limit as sm_120 and uses the same K/V tile configuration. This is explicit-parity hardening, not a user-visible bug fix: sm_121 already built and ran through the "unknown architecture" `#else` default, which has the same values as sm_120. See Testing for the evidence that the generated code is identical before and after. ## Summary of Changes ### XQA arch tables | File | Change | |------|--------| | `onnxruntime/contrib_ops/cuda/bert/xqa/mha_impl.cuh` | Add `__CUDA_ARCH__ == 1210` to the `860 / 890 / 1200` branch that selects `preferedKHeadPartBytes = 64` and `cacheVTileSeqLen = 32` (previously reached only via the `#else` default). Mention sm_121 in the related comment and in the shared-memory-limit hint text. | | `onnxruntime/contrib_ops/cuda/bert/xqa/utils.cuh` | Add `__CUDA_ARCH__ == 1210` to the 99 KB `kMAX_SMEM_SIZE` branch. Before, 1210 fell through to the 48 KB default. `kMAX_SMEM_SIZE` is currently only used by a `#if 0` `static_assert`, so this has no runtime effect today; it just keeps the table correct. | ### Not ported from TRT-LLM #12170 (not applicable) The rest of #12170 changes TensorRT-LLM's NVRTC JIT wrapper (`-arch=sm_120f`, dropping the compute_89 PTX to sm_120 two-stage compile through nvPTXCompiler, and the `nvptxcompiler_static` link). ORT compiles XQA ahead of time with nvcc, so there is nothing to port. In TRT-LLM the default arch branch was an `#error` and `kMAX_SMEM_SIZE` was undefined for 1210, which is why sm_121 failed to compile there; ORT's `#else` fallbacks mean it never did. ## Do the shipped libraries include sm_121? Yes, so no packaging or CMake change is needed: - Linux aarch64 CUDA 13 wheel: `87-real;89-real;90-real;100-real;103-real;110-real;120-real;121-real` (`py-cuda13-packaging-pipeline.yml`). - Unified Linux aarch64 wheel (`py-cuda13-unified-packaging-pipeline.yml`): passes no `-a`, so it takes the `build_linux_python_package.sh` default for CUDA 13.0 on aarch64, which is the same list. - CUDA plugin (`plugin-cuda13-pipeline.yml` / `plugin-cuda-packaging-stage.yml`): Linux aarch64 uses the same list; Windows ARM64 uses `120-real;121-real`. - x86_64 lists only carry `120-real` (sm_121 parts are Arm). - CMake normalizes `121-real` to `121a-real`, and XQA sources are compiled for every selected arch >= 80, so XQA gets sm_121a SASS (no PTX). The CUDA plugin's XQA target (`onnxruntime_providers_cuda_plugin_xqa`) uses the same `MIN_SM 80` filter, with no virtual replacement (that option is only used for the plugin's separate LLM target), so it gets the same SASS on Linux and Windows. ## Testing Built the XQA object library of the CUDA provider (`onnxruntime_providers_cuda_xqa`, CUDA 13.0) with `CMAKE_CUDA_ARCHITECTURES='120-real;121-real'`. The configure log shows `CMAKE_CUDA_ARCHITECTURES: 120a-real;121a-real`. - All 44 XQA objects contain both `sm_120a` and `sm_121a` cubins (`cuobjdump -lelf`), with 158 `kernel_mha` kernels each. Per-kernel register / shared / local / constant usage is identical between sm_120a and sm_121a. - A/B against `main` (without this change): SASS is identical for all 44 objects on sm_120a and sm_121a (600,232 instructions compared; all 88 cubins byte-identical), and also identical on sm_90a for 8 representative translation units (loader, paged, spec-dec, fp8/int8/int4, head sizes 64-512). So existing archs are unaffected and sm_121 behaves exactly as before. - The XQA kernels are present in the final `libonnxruntime_providers_cuda.so` (checked on an sm_90a build: 158 `kernel_mha` kernels). - `lintrunner` and `clang-format` are clean. - Not run on real sm_121 hardware (none available), so a smoke test on a DGX Spark would be welcome. To reproduce the cubin check: ``` python tools/ci_build/build.py --build_dir build/xqa_sm121 --config Release --use_cuda \ --cuda_home <cuda13> --cudnn_home <cudnn> --build_shared_lib --skip_tests --update \ --cmake_extra_defines 'CMAKE_CUDA_ARCHITECTURES=120-real;121-real' ninja -C build/xqa_sm121/Release onnxruntime_providers_cuda_xqa cuobjdump -lelf <obj dir>/xqa_loader_fp16_128.cu.o # lists sm_120a and sm_121a cubins ``` ## Motivation and Context Keeps ORT's XQA arch tables in sync with TensorRT-LLM (NVIDIA/TensorRT-LLM#12170) so sm_121 is handled explicitly rather than by the unknown-arch fallback, and so the `kMAX_SMEM_SIZE` table is right if that check is re-enabled. Independent of the XQA race fixes in #33703. Overlap with #33373: its commit "xqa: treat SM121 like SM120 in the per-architecture constants" makes the same two `|| __CUDA_ARCH__ == 1210` edits with identical spelling. This PR carries the XQA part on its own so it does not have to wait for that larger CMake change. The two PRs merge cleanly in either order (checked with `git merge-tree` against the head of #33373), so whichever lands second just has a no-op hunk. The extra edits here (the sm_121 mention in the comment and the shared-memory-limit hint text) are in different hunks. ## Checklist - [ ] Tests added/updated (N/A: arch-table-only change with identical generated code; verified by cubin comparison above) - [x] No breaking changes - [x] Documentation updated (N/A: only an error-message hint and comments changed) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Connor Baker (@ConnorBaker), please rebase upon latest main branch to trigger CI. |
The SM120 kernels were enabled with literal list tests:
"120" IN_LIST CMAKE_CUDA_ARCHITECTURES_ORIG OR "121" IN_LIST ...
setup_cuda_architectures() strips -real (and with it any a/f suffix), but
keeps the a/f suffix on bare entries, so CMAKE_CUDA_ARCHITECTURES_ORIG holds
"121a", "120f", "121f", ... for those spellings. With
CMAKE_CUDA_ARCHITECTURES=121a (DGX Spark / GB10, as Nixpkgs passes it) the
tests fail: COMPILE_BLACKWELL_SM120_TMA_GROUPED_GEMMS and
ORT_ENABLE_BLOCKQUANT_SM120 are not defined, and neither the SM120 TMA
grouped GEMMs nor matmul_block_scaled_fp4_sm120.cu are built, even though
the 121a-real code is otherwise compiled. The same happens for 120a, 120f
and 121f.
The EXCLUDE_SM_<arch> loop has the opposite problem for SM121: it matches
"^120[af]?$", so a build targeting only SM121 (in any spelling, including
121-real) defines EXCLUDE_SM_120. That empties the SM120 MoE GEMM
instantiations (they are wrapped in #ifndef EXCLUDE_SM_120), while for
"121"/"121-real" COMPILE_BLACKWELL_SM120_TMA_GROUPED_GEMMS is still defined
and the dispatcher references them.
Add two helpers to cuda_configuration.cmake:
- onnxruntime_parse_cuda_arch() splits an entry of the form
<cc>[a|f][-real|-virtual] into compute capability, suffix and whether it
is -virtual.
- onnxruntime_cuda_archs_have_sass() reports whether any entry generates
SASS for a compute capability in a given range.
and use them for:
- ORT_HAS_SM12X (SM120 or SM121 in any spelling), which replaces the three
IN_LIST tests in onnxruntime_providers_cuda.cmake,
onnxruntime_providers_cuda_plugin.cmake and
onnxruntime_cuda_source_filters.cmake;
- EXCLUDE_SM_<arch>, where SM121 now keeps the SM120 kernels. They are
compiled for the requested 12.x architectures (121a-real for a 121a
build) by onnxruntime_filter_cuda_archs(... MIN_SM 120), and the runtime
dispatch already accepts SM121 for SM120 configurations (microsoft#33225);
- onnxruntime_filter_cuda_archs(), whose MSVC-only
REPLACE_SM120_REAL_WITH_VIRTUAL / EXCLUDE_SM120_REAL options only matched
"-real$" and so missed family codes such as "120f", which are kept
without -real and also generate SASS.
-virtual entries never count: they only embed PTX, and these gates guard
native kernels that need arch- or family-specific SASS (sm_120a/sm_121a
block-scaled MMA, TMA). This matches the existing behaviour, where -virtual
entries are already left out of CMAKE_CUDA_ARCHITECTURES_ORIG.
Spellings that already worked ("120", "121", "120a-real", ...) and builds
without SM12x are unaffected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The SM90 TMA OBJECT library (the TMA warp-specialized MoE GEMMs, the fpA_intB SM90 launchers and DeepGEMM) is compiled only for 90a-real, but it was built, and COMPILE_HOPPER_TMA_GEMMS, COMPILE_HOPPER_TMA_GROUPED_GEMMS and USE_DEEP_GEMM were defined, whenever any architecture >= 90 was requested (ORT_HAS_SM90_OR_LATER, or any arch >= 90 in onnxruntime_filter_cuda_archs, which includes -virtual entries). Code built for an arch-specific target such as sm_90a runs only on that exact compute capability: it is neither forward nor backward compatible (CUDA C Programming Guide, "Feature Availability"; Blackwell compatibility guide). So for a build targeting only SM100/SM110/SM120/SM121 (e.g. CMAKE_CUDA_ARCHITECTURES=121a for DGX Spark) these 341 heavy CUTLASS and DeepGEMM translation units (324 TMA MoE GEMMs, 16 fpA_intB launchers, deep_gemm_sm90.cu) produce code that can never run; microsoft#33067 removed the last runtime path that tried to use them on SM120. Add ORT_HAS_SM90, set via onnxruntime_cuda_archs_have_sass() when SM90 itself is requested in any spelling (90, 90a, 90a-real, ...; not 90-virtual), and gate on it: - the onnxruntime_providers_cuda[_plugin]_sm90_tma OBJECT libraries; - COMPILE_HOPPER_TMA_GEMMS / COMPILE_HOPPER_TMA_GROUPED_GEMMS; - include(deep_gemm) and USE_DEEP_GEMM. Nothing else references the skipped code: the TMA MoE dispatch throws when COMPILE_HOPPER_TMA_GROUPED_GEMMS is undefined, DeepGEMM callers are behind USE_DEEP_GEMM, and the fpA_intB SM90 launchers are behind EXCLUDE_SM_90, which is defined exactly when ORT_HAS_SM90 is not (both use the same helper). MSVC builds already run without COMPILE_HOPPER_* and DeepGEMM, and builds without any arch >= 90 (e.g. 86-real) already skip the library. Builds that include SM90 are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EXCLUDE_SM_80 was defined unless compute capability 8.0 itself was in
CMAKE_CUDA_ARCHITECTURES, so every build without "80" (121a, 120, 90a,
100a, and also 86;89) compiled the SM80 fused MoE GEMM launchers
(fused_moe_gemm_sm80_*.generated.cu, "#ifndef EXCLUDE_SM_80") down to
nothing. Dispatch still selects them: MoeGemmRunner::dispatchToArch
(moe_gemm_template_dispatch.h) uses the Sm80 grouped GEMM on SM80-SM89
and, on SM90 and later, as the fallback for every non-TMA config, and
genericMoeGemmKernelLauncher routes FP16/BF16 SiLU/SwiGLU/GeLU/GeGLU
epilogues to sm80_generic_fused_moe_gemm_kernelLauncher, which under
EXCLUDE_SM_80 is ORT_THROW("Fused MoE SM80 kernels are not available
...") (added in microsoft#28467, 548ab6e). On SM100 (COMPILE_BLACKWELL_TMA_*
is never defined) and SM12x (isValidSM120MOESpecialisation only accepts
FP4xFP4/FP8xFP4) that fallback is the only path, so unquantized
FP16/BF16 MoE with a gated activation always threw on a 12.x- or
10.x-only build.
The macro conflated "the kernel is tagged Sm80" with "the kernel runs
only on CC 8.0". These kernels are CuTe SM80 atoms (mma.sync
SM80_16x8x16_*, cp.async; fused_moe_kernel_traits.cuh), which CUTLASS
enables for any __CUDA_ARCH__ >= 800 (CUTE_ARCH_MMA_SM80_ENABLED and
CUTE_ARCH_CP_ASYNC_SM80_ENABLED in cute/arch/{mma,copy}_sm80.hpp), and
they live in the llm object library, which is compiled for every
requested architecture >= 7.5. Compiled for sm_121a they are ordinary
sm_121a kernels. There is no separate "SM80 build" that dispatch needs.
Define EXCLUDE_SM_<sm> by what dispatch can reach:
* 80, 86, 89 (CUTLASS 2.x / CuTe SM80-style, no arch-specific
features): keep them if any requested architecture, SASS or PTX,
has at least that compute capability (new helper
onnxruntime_cuda_archs_have_code). PTX counts because compute_<cc>
PTX is JIT-compiled for every GPU >= <cc>.
* 90 (sm_90a only), 100, 110, 120: unchanged, native code for that
compute capability (or 12.0/12.1 for 120).
The gates now read the normalized CMAKE_CUDA_ARCHITECTURES instead of
CMAKE_CUDA_ARCHITECTURES_ORIG. The normalized list states what nvcc
generates for every entry ("-real", "-virtual", bare "<cc>f") and also
holds the -virtual entry, which _ORIG drops. That also keeps
ORT_HAS_SM12X/ORT_HAS_SM90 correct if _ORIG ever includes virtual
architectures without their suffix, as microsoft#29601
proposes. The gates moved below the normalization for that reason;
their logic is otherwise unchanged.
TensorRT-LLM, where these kernels come from, also defines
EXCLUDE_SM_<sm> only by exact match against the requested
architectures (cpp/cmake/modules/cuda_configuration.cmake), but its
dispatcher calls the SM80 fused MoE launcher unconditionally, with no
EXCLUDE_SM_80 branch. So the throwing branch exists only in
onnxruntime.
Checked with a cmake -P harness that runs setup_cuda_architectures()
on 30 architecture lists and compares the result against an
independent model of which GPUs can run the generated code. Before:
28 lists (MSVC off) had "SM80 fused MoE reachable but EXCLUDE_SM_80
throws", including 121a, 120f, 90a, 100a and 86;89. After: none. A
75-only build still excludes SM80, and "75;121-virtual" now keeps it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EXCLUDE_SM_100 was cleared only by native code for compute capability 10.0 itself, so a 103a (B300) or 107a build defined it, although both belong to the SM100 family. The CUDA C++ Programming Guide's "Family-Specific Compatibility" table (Compute Capabilities appendix) lists compute_100f as compatible with 10.0, 10.3 and 10.7, compute_110f with 11.0, and compute_120f with 12.0 and 12.1. EXCLUDE_SM_120 already uses the family (ORT_HAS_SM12X, 12.0-12.1). Use the same CC-range helper for 100 (10.0-10.7). 110 stays 11.0 only, which is its whole family. No C++ tests EXCLUDE_SM_100 or EXCLUDE_SM_110 today. This makes the macros mean the same thing as EXCLUDE_SM_120 before anything starts relying on them; upstream's SM100 kernels in TensorRT-LLM are generated for "any(has_arch(sm) for sm in range(100, 110))" (cpp/tensorrt_llm/kernels/cutlass_kernels/ python/generate_kernels.py). cmake -P harness: "103a", "103a-real" and "107a-real" defined EXCLUDE_SM_100 before and do not after. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
setup_cuda_architectures() dropped the suffix of every "-real" entry
("^(([1-9])([0-9])+)[af]?-real$" keeps only the number) and then
re-added "a" for the numbers in ARCHITECTURES_WITH_ACCEL. That has
two effects:
* "120f-real" became "120a-real", and "100f-real" became "100a-real".
Per the CUDA C++ Programming Guide (Compute Capabilities appendix),
compute_120a/sm_120a code runs only on compute capability 12.0,
while the family target compute_120f covers 12.0 and 12.1, and
compute_100f covers 10.0, 10.3 and 10.7. So a build asking for
portable 12.x SASS got code that does not run on 12.1 (GB10/DGX
Spark), and a 10.x one got code that does not run on 10.3 (B300).
Bare "120f" was already kept verbatim (family codes were added in
microsoft#27278, 6625856, which extended the -real regex to [af]? but
still discarded the match).
* "107a-real" (any "a" entry whose number is not in
ARCHITECTURES_WITH_ACCEL) silently became "107-real".
Keep the suffix: "<cc>f-real" is kept verbatim (the "-real" tells it
apart from "<cc>f", which also embeds PTX), and "<cc>a-real" keeps its
"a". The normalization loop passes "<cc>f-real" through like "<cc>f".
_ORIG now holds "120f-real" for such an entry. Its consumers
(HAS_SM*_OR_LATER, which matches the leading number, and the
onnxruntime_parse_cuda_arch-based gates) handle that. Since "121" and
"121a-real" now normalize to the same entry, the normalized list is
de-duplicated.
Coordination with the open microsoft#29601 ("Follow
standard CMake semantics for CUDA architecture suffixes"): that PR
splits the parsing into real and virtual lists, keeps every -virtual
entry, and gives suffix-less entries PTX as well. It uses the same
"-real" regex as the second branch here, "^(([1-9])([0-9])+[af]?)-real$"
(suffix captured), and adds the same
list(REMOVE_DUPLICATES CMAKE_CUDA_ARCHITECTURES_NORMALIZED), so the two
changes overlap textually without contradicting each other. One
difference: microsoft#29601 still emits family entries without a kind suffix
("120f"), so "120f-real" gains PTX and "120f-virtual" gains SASS. On
top of microsoft#29601 the equivalent fix is to emit "<cc>f-real" for real-list
and "<cc>f-virtual" for virtual-list family entries.
cmake -P harness (independent model of which GPUs each entry
generates SASS for, plus "an explicit a/f suffix survives"): before, 7
violations on 5 of 30 lists ("120f-real", "121f-real", "100f-real",
"107a-real", "120f-real;121a-real"). After, none. Only lists that
contain a "-real" entry normalize differently.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ORT_HAS_SM{80,90,100,120}_OR_LATER (and the HAS_SM*_OR_LATER
definitions) were computed from CMAKE_CUDA_ARCHITECTURES_ORIG, which
leaves out the -virtual entry that setup_cuda_architectures() keeps in
CMAKE_CUDA_ARCHITECTURES_LAST_VIRTUAL and appends to the normalized
list. For "75;121-virtual" or "90-virtual" the CUDA sources are still
compiled for compute_121/compute_90 PTX, but HAS_SM80_OR_LATER was not
defined. xqa_impl_gen.cuh and xqa_paged_impl_gen.cuh rely on it in the
host pass ("If any SM80+ arch is enabled, the host stub must be
emitted"), so their Launch() returned "XQA is only supported on Ampere
(SM80) or newer GPUs" on the 12.1 GPU that the embedded PTX targets.
Iterate over _ORIG plus _LAST_VIRTUAL. The "^([0-9]+)" match already
handles a "-virtual" entry. microsoft#29601 would get the
same result by making _ORIG the union of real and virtual
architectures. On top of it this line is a no-op (it removes
_LAST_VIRTUAL).
This was the only other gate that a sweep of cmake/ and the CUDA
sources found to mishandle an architecture spelling. The literal
__CUDA_ARCH__ comparisons outside XQA (fpA_intB_gemm.h "== 890",
XQA's "== 900" occupancy) and the runtime "sm == 90" checks select
sm_90a-only or SM89-only code paths and are correct as written.
cmake -P harness: "121-virtual", "90-virtual" and "75;121-virtual" no
longer violate "an architecture >= N is built but HAS_SMN_OR_LATER is
not defined" (10 violations before, 0 after); the rest of the harness
output is identical. In those builds ORT_HAS_SM90_OR_LATER now also
adds -DCUTLASS_ENABLE_GDC_FOR_SM90=1 and moves the SM90 TMA sources out
of the main list. The SM90 library itself still requires ORT_HAS_SM90
(sm_90a SASS).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
d44dea2 to
7082d93
Compare
Tianlei Wu (tianleiwu)
left a comment
There was a problem hiding this comment.
The shared parsing/gating direction looks good. I exercised the checked-out CMake functions: nine normalization/native-gating cases passed, but the Windows plugin probes expose a missing native fallback for family spellings, detailed inline.
This is configuration-level validation, not a full CUDA build/runtime test. The only current-head reported check is CLA. No approval submitted.
…ibrary
Under MSVC, onnxruntime_filter_cuda_archs(... REPLACE_SM120_REAL_WITH_VIRTUAL)
turns every native SM120+ entry of the plugin's LLM OBJECT library into
<cc>-virtual, and the plugin then adds non-"a" native SASS
(-gencode=arch=compute_120,code=sm_120) so the ordinary LLM kernels do not
depend on the driver JIT-compiling the toolkit's PTX. That fallback only
checked for the literal entries "120" and "120a" in
CMAKE_CUDA_ARCHITECTURES_ORIG. Since the filter now also converts family
codes, "120f" and "120f-real" became PTX-only, and every SM121 spelling
("121", "121a", "121f", "121a-real", ...) never had the fallback.
Have the filter report the compute capabilities it converted
(REPLACED_SM120_CCS) and add -gencode=arch=compute_<cc>,code=sm_<cc> for
each of them, so the fallback always matches the conversion. sm_120 SASS
runs on 12.0 and 12.1, which covers what compute_120f covers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tianlei Wu (tianleiwu)
left a comment
There was a problem hiding this comment.
Re-reviewed 3afda6615b01172a502c798d1ff717893601b896. The previous Windows plugin native-SASS concern is addressed; no additional actionable code findings.
Fresh local configuration validation executed the real architecture normalization/filter functions and the unchanged MSVC plugin branch: 22 architecture-list cases and 136 assertions passed. Coverage includes SM120/SM121 native spellings, a/f and -real variants, virtual-only entries, mixed lists, Hopper gating, and duplicate fallback removal.
Validation limit: these are CMake configuration checks, not CUDA compilation or a Windows build. The current head reports only license/cla; please obtain Linux and Windows CUDA/plugin CI before merging. No approval submitted.
|
Connor Baker (@ConnorBaker), Thanks for the contribution. |
Description
cmake/external/cuda_configuration.cmakeand the CUDA provider cmake files decide which CUDA kernels to build bystring-matching
CMAKE_CUDA_ARCHITECTURESentries. Several of those tests mishandle architecture spellings that CMakeand nvcc accept:
a/fsuffixes,-real/-virtual, and compute capabilities other than the exact one tested. Theresults range from kernels silently left out, to kernels built that can never run, to dispatch throwing at run time.
This adds two helpers to
cuda_configuration.cmake:onnxruntime_parse_cuda_arch()splits an entry<cc>[a|f][-real|-virtual]into compute capability, suffix andwhether it is
-virtual.onnxruntime_cuda_archs_have_sass()/onnxruntime_cuda_archs_have_code()report whether any entry produces SASS(or SASS or PTX) for a range of compute capabilities.
It then uses them consistently. One commit per change:
121a/120f/121f/120a(cmake: match CUDA architectures with a/f/-real suffixes).matmul_block_scaled_fp4_sm120.cu,COMPILE_BLACKWELL_SM120_TMA_GROUPED_GEMMSandORT_ENABLE_BLOCKQUANT_SM120were gated on"120" IN_LIST ... OR "121" IN_LIST CMAKE_CUDA_ARCHITECTURES_ORIG. Bare entries keep their suffix there, so121a(DGX Spark/GB10),120fand121fsilently built none of it.EXCLUDE_SM_*loop matched^120[af]?$, so any SM121-only build definedEXCLUDE_SM_120. That empties the SM120 MoE GEMM instantiations, which the dispatcher still references for121/121-real.ORT_HAS_SM12X(12.0–12.1, any spelling) replaces the threeIN_LISTtests, andEXCLUDE_SM_120follows it. SM121 then uses the SM120 kernels compiled for the requested 12.x targets; [CUDA] Fix QMoE SM120 configuration dispatch on SM121 devices #33225 lets the runtime
dispatch accept SM121 for SM120 configurations.
cmake: only build SM90 TMA kernels when SM90 is targeted).is compiled only for
90a-real, but it was built for any architecture ≥ 90. Code built forsm_90aruns only oncompute capability 9.0 (CUDA C++ Programming Guide, compute capabilities appendix; Blackwell compatibility guide),
so for a build without SM90 these 341 translation units produce code that can never run. [CUDA] Fix QMoE MXFP4 W4A16 on SM120/SM121 and speed up FP4 decode #33067 removed the last
runtime path that used them on SM120.
ORT_HAS_SM90gates the library,COMPILE_HOPPER_TMA_GEMMS/COMPILE_HOPPER_TMA_GROUPED_GEMMS, andDeepGEMM/
USE_DEEP_GEMM. That's the configuration MSVC builds already use.EXCLUDE_SM_90is defined exactlywhen
ORT_HAS_SM90is not.cmake: keep SM80-generation kernels whenever an SM80+ GPU is targeted).EXCLUDE_SM_80was defined unless80itself was listed. So121a,120,90a,100a, andeven
86;89compiled thefused_moe_gemm_sm80_*launchers to nothing. Dispatch still selects them, and they hitORT_THROW("Fused MoE SM80 kernels are not available ...")(from QMoE CUDA EP — FP4/FP8/WFP4AFP8 Quantized Mixture-of-Experts + MoE GEMM Refactor #28467). On SM100 and SM12x that path is the onlyone for unquantized FP16/BF16 MoE with a gated activation.
mma.sync,cp.async), which CUTLASSenables for any
__CUDA_ARCH__ >= 800. They are compiled in the llm library for every requested architecture≥ 7.5. TensorRT-LLM, where they come from, has no such throwing branch.
90/100/110/120 still require native code for that family. The gates now read the normalized architecture list,
which also keeps them correct with Follow standard CMake semantics for CUDA architecture suffixes #29601.
cmake: clear EXCLUDE_SM_100 for every SM100-family architecture).103a(B300) and107abuilds definedEXCLUDE_SM_100, althoughcompute_100fcovers 10.0,10.3 and 10.7 (Programming Guide, "Family-Specific Compatibility").
EXCLUDE_SM_100/EXCLUDE_SM_110today; this makes them mean thesame thing as
EXCLUDE_SM_120.a/fon-realentries (cmake: keep explicit a/f suffixes of -real CUDA architectures).setup_cuda_architectures()dropped the suffix of every-realentry (Add support for CUDA architecture family codes #27278 widened the regexto accept
fbut still discarded it).120f-realbecame120a-real, which doesn't run on 12.1, and100f-realbecame
100a-real, which doesn't run on 10.3/10.7.107a-realbecame107-real.<cc>f-realis kept verbatim, an explicitais kept, and the normalized list is de-duplicated. Theregex and the de-duplication line match Follow standard CMake semantics for CUDA architecture suffixes #29601's text, to keep the two changes compatible. The commit message
describes how they combine.
mainon 2026-11-03. [CUDA] Add sm_121 to XQA arch-specific branches #33762 made the same|| __CUDA_ARCH__ == 1210change toutils.cuhandmha_impl.cuh, so the commit became empty.-virtualinHAS_SM*_OR_LATER(cmake: count the -virtual architecture in HAS_SM*_OR_LATER).CMAKE_CUDA_ARCHITECTURES_ORIG, which leaves out the-virtualentry.For
75;121-virtual,HAS_SM80_OR_LATERwas undefined, so XQA'sLaunch()returned "XQA is only supported onAmpere (SM80) or newer GPUs" on the 12.1 GPU the PTX targets.
_LAST_VIRTUAL. This becomes a no-op on top of Follow standard CMake semantics for CUDA architecture suffixes #29601.cmake: add native SASS for every SM12x entry in the MSVC plugin LLM library, from review).<cc>-virtualand adds-gencode=arch=compute_120,code=sm_120so the ordinary LLM kernels don't depend on JIT. That fallback onlychecked for the literal
120/120a. With change 1 the filter also converts120f/120f-real, which becamePTX-only, and no SM121 spelling ever had the fallback.
onnxruntime_filter_cuda_archs()reports the compute capabilities it converted (REPLACED_SM120_CCS),and the plugin adds
-gencode=arch=compute_<cc>,code=sm_<cc>for each.Builds that spell their architectures the way the gates already expected (e.g.
80;86;89;90;100;120) are unchanged,apart from the SM80 change (3), the
-virtualchange (7) and the MSVC plugin fallback (8), which only add kernelsthat were previously missing. The
literal
__CUDA_ARCH__andsm ==checks elsewhere (fpA_intB== 890, XQA== 900, runtimesm == 90) selectSM89- or sm_90a-only code and are left as they are.
Motivation and Context
Building ONNX Runtime for DGX Spark / GB10 (compute capability 12.1) with
CMAKE_CUDA_ARCHITECTURES=121a, as Nixpkgsdoes, produced a CUDA provider that:
onnxruntime_providers_cuda_sm120_tmatarget in the build;
sm_90acubins next to 286sm_121aones inlibonnxruntime_providers_cuda.so(cuobjdump --list-elf,v1.30.0). The
sm_90aones can't run on the target GPU and are among the most expensive translation units tocompile;
EXCLUDE_SM_80.Testing
A
cmake -Pharness runssetup_cuda_architectures()and the gates from all four changed cmake files on 30architecture lists, with MSVC on and off. It checks each result against an independent model of which GPUs can run
the generated code: CUDA binary/PTX compatibility plus the family table.
121a,121a-real,121,120,120f,121f,120a-real,121-virtual,90a;121a,90,90a-real,90-virtual,100a,103a,107a-real,100f;120f,86,86;89,75;121-virtualand80;86;89;90;100;120.For change 8, a second
cmake -Pharness runssetup_cuda_architectures()and the plugin's MSVC LLM block (readfrom
onnxruntime_providers_cuda_plugin.cmake) on 22 lists, and checks that every GPU covered by a requested nativeSM12x entry gets plain (non-
a/f) native SASS. Before: 12 of 22 fail (every120fand121spelling,90a;121a,100f;120f, ...). After: 0. This is configuration-level only; there is no Windows build.Same changes backported to v1.30.0, with [CUDA] Fix QMoE MXFP4 W4A16 on SM120/SM121 and speed up FP4 decode #33067, [CUDA] Fix QMoE SM120 configuration dispatch on SM121 devices #33225 and [CUDA] Fix SM120 QMoE MoE GEMM TMA build failure with GCC #32584 (ConnorBaker/onnxruntime:v1.30.0-sm12x-fixes),
built with
CMAKE_CUDA_ARCHITECTURES=121aon a GB10 (aarch64, CUDA 13.4, GCC 16) and compared with unpatchedv1.30.0 built the same way:
onnxruntime_providers_cuda_sm120_tma/_sm90_tmabuiltlibonnxruntime_providers_cuda.so(cuobjdump --list-elf)sm_90a, 286sm_121asm_121acuobjdump --dump-elf-symbols)fused_moe(SM80 fused MoE) kernel symbolslibonnxruntime_providers_cuda.sosizelibonnxruntime_providers_cuda.sohas no undefinedMoeGemmRunnersymbols.5.5e-3, the same as unpatched v1.30.0).
(
moe_gemm_tma_ws_launcher.inl:82: error: expression list treated as compound expression in functional cast).That fix is already on
main.This branch itself (on
main) has not been compiled.Related: #29601 (CUDA architecture suffix semantics; these changes are written to stack with it), #33067 and #33225
(SM120/SM121 MoE dispatch), #28467 (where the
EXCLUDE_SM_80throw came from), #27278 (family codes).AI disclosure: this change was developed with AI assistance (Claude Code): tracing the gating logic, writing the
changes and the
cmake -Pharness, and the backport build. I reviewed the changes and the results.