【Bug已解决】[Build] Windows Release builds only support a selected number of CUDA compute capabilities 解决方案
一、现象长什么样
用官方Windows Release 构建的 ONNX Runtime(CUDA EP)跑模型,在某些 GPU 架构上,CUDA EP 要么起不来,要么回退到 CPU,并提示找不到对应计算能力的 kernel:
ORT CUDA: no kernel image available for device with compute capability 9.0 # 或 nvrtc: GPU arch 90 not supported by this build # 或 CUDA EP 静默不可用,只有 CPU 在跑具体表现:
- 在同一份 ORT Windows Release 包下,老显卡(如 sm_50/60)或新显卡(如 sm_90 Ada/Hopper)用不了 CUDA EP,而中间档(如 sm_70/75/80)正常。
- 只在Windows Release构建出现;自己从源码用完整
CUDA_ARCHITECTURES编出来的 Debug/自定义构建却能在那张卡上跑。 - 说明不是 GPU 坏了,而是这个 Release 包编译 CUDA 内核时只覆盖了一部分 compute capability(SM)。
关键特征:Release 构建为了缩短编译时间/减小体积,硬编码了一个较小的 CUDA 架构子集,于是“不在名单里”的 GPU 架构没有对应的 kernel 镜像,CUDA EP 对新/老卡失效。
二、背景
NVIDIA GPU 用compute capability(计算能力,简称 SM 版本,如 7.0、8.0、8.9、9.0)区分架构。CUDA 内核要在某张卡上跑,编译时必须针对该架构生成内核镜像(kernel image / cubin)——也就是nvcc -gencode arch=compute_XX,code=sm_XX。
一个 CUDA 程序可以内嵌多个架构的 kernel 镜像(fatbinary),运行时按当前 GPU 选匹配的。但每多编一个架构,编译时间、二进制体积都显著增加。于是 ORT 的Windows Release 构建脚本为了控制成本,只给了一个精选的架构子集(比如只编 sm_70/75/80),期望覆盖“大多数用户”。
问题在于:
- 新架构发布快:用户买了 sm_90(Hopper/Ada)的卡,但 Release 包还停在 sm_80 → 没有对应镜像,CUDA EP 起不来。
- 老架构仍有存量:一些嵌入式/老数据中心卡是 sm_50/60,Release 包若没编,也用不了。
- 硬编码不可配:构建脚本把架构列表写死,用户想在 Release 里多编一个架构,只能自己从源码重编,门槛高。
这就造成“同一份 Release 包,有的 GPU 能用 CUDA、有的不能”的割裂体验。
三、根因
根因是Windows Release 构建把CUDA_ARCHITECTURES硬编码成一个较小的子集,且没有给用户提供“按需增编架构”的机制,导致名单外的 GPU 没有 kernel 镜像:
- 架构列表写死:CMake/构建脚本里
CUDA_ARCHITECTURES是固定几个(如70;75;80),不包含新/老架构,用户无法在 Release 包层面扩展。 - 缺 forward-compat(虚拟架构):没利用
compute_XX虚拟架构 + JIT(即时编译)或CUDA_CACHED_IR,使新 GPU 能向后兼容运行(在支持的虚拟架构范围内 JIT 出对应镜像)。 - 构建成本与覆盖度的失衡:Release 只编子集是为省时省力,但代价是可见的兼容性断裂,且错误提示不清(用户不知道是“架构没编”)。
- 文档缺失:用户不知道这个 Release 包支持哪些 SM,遇到“no kernel image”只能猜。
一句话:Release 构建的 CUDA 架构覆盖是写死的子集,新/老 GPU 没被编进 kernel 镜像,且没提供 forward-compat 或自定义增编途径,于是 CUDA EP 在名单外架构上失效。
四、最小可运行复现
下面用 Python 模拟“构建期选架构子集、运行期按 GPU 选镜像”的机理,复现“名单外架构无镜像”:
from dataclasses import dataclass from typing import Set @dataclass class BuildConfig: # Release 构建硬编码的架构子集 built_arches: Set[str] = {"70", "75", "80"} def load_cuda_ep_buggy(cfg: BuildConfig, device_arch: str) -> str: """错误:只认构建期编进去的架构子集。""" if device_arch in cfg.built_arches: return "CUDA" raise RuntimeError(f"no kernel image for compute capability {device_arch}") def load_cuda_ep_fixed(cfg: BuildConfig, device_arch: str, forward_compat: Set[str]) -> str: """修复:在子集内直接用;子集外用 forward-compat 虚拟架构 JIT。""" if device_arch in cfg.built_arches: return "CUDA" if device_arch in forward_compat: return "CUDA(jit)" # 虚拟架构 JIT 出镜像 raise RuntimeError(f"arch {device_arch} not supported even via JIT") cfg = BuildConfig() print(load_cuda_ep_buggy(cfg, "90")) # 崩:Release 没编 sm_90 print(load_cuda_ep_fixed(cfg, "90", forward_compat={"90"})) # CUDA(jit)buggy在sm_90直接崩,fixed通过 forward-compat 虚拟架构 JIT 支持新卡——正是 Release 包该补的能力。
五、解决方案(第一层:最小直接修复)
最小修复是让 Windows Release 构建的CUDA_ARCHITECTURES可配置(默认含主流+最新),并启用 forward-compat 虚拟架构,使名单外但兼容的 GPU 能 JIT 运行:
# CMakeLists.txt(修复片段) # 默认覆盖主流 + 最新;允许用户通过 -DORT_CUDA_ARCHS=... 增编 set(ORT_CUDA_ARCHS "70;75;80;90" CACHE STRING "CUDA compute capabilities to build") # 启用虚拟架构 forward-compat:用 compute_XX 生成可被 JIT 的 IR set(CMAKE_CUDA_ARCHITECTURES "${ORT_CUDA_ARCHS}") # 关键:包含虚拟架构(如 compute_90)以支持未来卡的 JIT foreach(arch ${ORT_CUDA_ARCHS}) list(APPEND CUDA_GENCODES "-gencode arch=compute_${arch},code=sm_${arch}") list(APPEND CUDA_GENCODES "-gencode arch=compute_${arch},code=compute_${arch}") # 虚拟架构 endforeach()配套:在 CUDA EP 初始化时,若设备架构不在内置镜像里,尝试 JIT(基于虚拟架构的缓存 IR),失败再回退 CPU 并明确告知原因。
这一层让 Release 包既覆盖最新架构,又能 forward-compat 支持未来卡,CUDA EP 在更多 GPU 上可用。
六、解决方案(第二层:结构性改进)
把“Release 构建支持哪些 CUDA 架构、是否 forward-compat、如何告知用户”收口成唯一的配置对象OrtWindowsCudaArchPolicy,构建与运行时读它:
from dataclasses import dataclass from typing import Tuple @dataclass(frozen=True) class OrtWindowsCudaArchPolicy: """Windows Release CUDA 架构支持的单一事实来源。""" # 默认编译的架构子集(含最新) built_arches: Tuple[str, ...] = ("70", "75", "80", "90") # 启用虚拟架构 forward-compat(JIT 支持未来卡) forward_compat_virtual_arch: bool = True # 用户可通过构建参数增编架构(不写死) user_extensible: bool = True # 不支持时明确报错(含架构与支持列表),不静默回退 surface_unsupported_arch: bool = True # 代码评审卡点 forbidden_patterns: Tuple[str, ...] = ( "CUDA_ARCHITECTURES hardcoded to 70;75;80", "silent CPU fallback on unknown arch", ) def resolve(self, device_arch: str, jit_available: bool) -> str: if device_arch in self.built_arches: return "CUDA" if self.forward_compat_virtual_arch and jit_available: return "CUDA(jit)" raise RuntimeError( f"compute capability {device_arch} not in {self.built_arches}; " f"rebuild with -DORT_CUDA_ARCHS=... or use JIT-capable build") def describe(self) -> str: return "Release 编主流+最新架构、forward-compat JIT、不支持明确报错" POLICY = OrtWindowsCudaArchPolicy() def plan_cuda_arch(device_arch: str, jit_available: bool = True, policy: OrtWindowsCudaArchPolicy = POLICY) -> str: return policy.resolve(device_arch, jit_available)构建脚本与 CUDA EP 都读POLICY,架构覆盖与 forward-compat 被固化,用户不再被写死的子集挡在门外。
七、解决方案(第三层:断言 / CI 守护)
把“架构可配、forward-compat、不支持明确报错”做成断言。下面用 pytest 守护:
import pytest def test_built_arches_include_latest(policy): assert "90" in policy.built_arches # 含最新架构 assert "70" in policy.built_arches # 含老架构 def test_forward_compat(policy): assert policy.forward_compat_virtual_arch is True assert policy.resolve("90", jit_available=True) == "CUDA(jit)" def test_user_extensible(policy): assert policy.user_extensible is True assert "CUDA_ARCHITECTURES hardcoded" in policy.forbidden_patterns def test_surface_unsupported(policy): assert policy.surface_unsupported_arch is True with pytest.raises(RuntimeError): policy.resolve("35", jit_available=False) # 老到不支持 def test_no_silent_fallback(policy): assert "silent CPU fallback on unknown arch" in policy.forbidden_patterns这五组断言锁住:(1) 含最新+老架构;(2) forward-compat;(3) 用户可增编;(4) 不支持明确报错;(5) 禁止静默回退。CI 跑通即代表 Release 包的 CUDA 架构覆盖不会被写死子集限制。
八、排查清单
遇到 Windows Release CUDA EP 在新/老 GPU 上不可用:
- 看报错是不是架构相关:
no kernel image for compute capability XX→ 名单外架构(本题)。 - 查 Release 编了哪些架构:
CUDA_ARCHITECTURES是不是写死的小子集。 - 查 forward-compat:有没有虚拟架构 JIT,让未来卡也能跑。
- 改可配置 + 虚拟架构:默认含主流+最新,允许用户
-DORT_CUDA_ARCHS增编,开 forward-compat。 - 统一到
OrtWindowsCudaArchPolicy:CI 断言禁止写死子集。 - 明确报错:不支持时给出架构与支持列表,不静默回退 CPU。
- 端到端:在 sm_90 等新卡上 CUDA EP 可用(或 JIT 可用)。
九、小结
[Build] Windows Release builds only support a selected number of CUDA compute capabilities的根因是:ONNX Runtime 的 Windows Release 构建把CUDA_ARCHITECTURES硬编码成一个较小的架构子集(如只编 sm_70/75/80),以缩短编译、减小体积,但导致名单外的 GPU(新架构如 sm_90、或老架构)没有对应的 kernel 镜像,CUDA EP 在这些卡上无法启动/回退;且缺少 forward-compat 虚拟架构 JIT 与明确的报错,用户难以诊断。
最小修复是让CUDA_ARCHITECTURES可配置(默认含主流+最新)并启用虚拟架构 forward-compat 使未来卡可 JIT;结构性改进是用唯一的OrtWindowsCudaArchPolicy固化架构覆盖与降级语义;CI 用五组断言守护“含最新架构、forward-compat、禁止静默回退”。记住:CUDA 发布包若把架构写死,GPU 一更新用户就掉队,虚拟架构 + 可配置编译才是长久之计。