GPU计算能力与CUDA架构:编译框架防翻车指南 编译过深度学习框架的人十有八九都被“计算能力Compute Capability”这个东西卡过。要不就是编译时刷出一排Unsupported gpu architecture要不就是同一个安装包在这块卡上跑得好好的换到另一块卡上直接报no kernel image is available for execution on the device。我第一次在 caffe 上踩这个坑时硬生生折腾到凌晨两点最后才发现问题就出在一个小数字上——GPU 计算能力。这篇就把这个数字的前因后果讲透顺手给出 caffe / pytorch / tensorflow 里设置计算能力的具体写法和翻车应急预案。适合所有需要从源码构建框架的开发者、算法工程师以及被 CUDA 环境逼到怀疑人生的人。1. 先搞懂“计算能力”这个数字它就是 GPU 的架构版本号1.1 数字拆解主版本号是“代”次版本号是“档”计算能力Compute Capability写出来长这样8.6、7.5、9.0。它本质上就是 GPU 硬件架构的版本号前一位代表大代际后一位代表同代际里的细分档位。比如8.6意味着这是 Ampere 大架构下的消费卡档位RTX 3090 就是 8.6。而 A100 同样是 Ampere 架构却是8.0。同一个架构代际内不同芯片的指令集和硬件特性有差异所以才有8.0、8.6、8.9这种细分编号。在编译参数里这个数字还会变成两种写法sm_86SASS 目标架构生成的就是这块卡直接执行的机器码。compute_86PTX 目标架构生成的是中间表示后续可以由驱动现场编译到具体 GPU。我见过不少新手把sm_75和compute_75当成一回事其实它们对应的是两种不同形态的产物这也是后面绝大多数编译坑的根源。1.2 一张表看懂各大架构的计算能力区间我把这些年主流的 GPU 架构按计算能力梳理了一下方便对号入座架构代际计算能力典型 GPU 型号大概年代Fermi2.0 / 2.1GTX 4xx / 5xx2010 左右Kepler3.0 / 3.5 / 3.7GTX 680 / Tesla K402012 左右Maxwell5.0 / 5.2 / 5.3GTX 750 / GTX 9802014 左右Pascal6.0 / 6.1Tesla P100 / GTX 1080 Ti2016 左右Volta7.0Tesla V1002017 左右Turing7.5RTX 2080 Ti / RTX 20602018 左右Ampere8.0 / 8.6A100 / RTX 30902020 左右Ada Lovelace8.9RTX 40902022 左右Hopper9.0H1002023 左右2024 年之后的新架构计算能力已经突破 10.x后续还会有新编号具体以厂商官方规格页为准。记住这张表的核心规律不同计算能力的 GPU它们的 SASS 机器码是不通用的但 PTX 有更强的跨代兼容能力。1.3 计算能力决定了哪些实实在在的事情这个数字不只是一个“型号标签”它背后绑定了一整套硬件特性能力。具体来说指令集版本不同sm_86 能用的某些汇编指令sm_75 是不认的。编译器要针对具体计算能力生成对应指令。Tensor Core 等特性不同8.0 以上的 Ampere 架构对 Tensor Core 的支持方式、WMMAwarp matrix multiply-accumulate指令版本都不一样新框架的某些算子会针对高版本计算能力做专门优化。CUDA Toolkit 版本兼容范围不同老版本的 CUDA Toolkit 不一定认识新计算能力。比如 CUDA 10.x 去编译sm_86的代码基本直接报错因为那个年代还没有 Ampere 的编译器支持。运行时行为不同程序跑起来后驱动需要根据 GPU 的计算能力加载对应内核匹配不上就崩。所以在深度学习框架的编译场景里计算能力直接决定了“你编出来的这个库到底能跑在哪些 GPU 上”。2. 编译框架时为什么要跟这个数字较劲2.1 GPU 代码的“提前编译”与“运行时兜底”很多框架PyTorch、TensorFlow、Caffe在安装时都会把 CUDA 算子源码提前编译成二进制而不是每次运行都现场编译。这个模式叫 AOTAhead Of Time提前编译它和纯 JITJust In Time运行时编译不同。AOT 的问题是你得在编译时告诉编译器“请按哪一代 GPU 的架构来生成代码”。如果你写死了sm_86那生成的就是 RTX 30 系列认识的机器码一旦把编译产物拿到计算能力 7.5 的 RTX 2080 上跑驱动根本没有办法执行这份 SASS 代码因为它压根不认识。现代 CUDA 编译有个兜底机制可以在生成 SASS 机器码的同时也生成一份 PTX 中间代码。PTX 像“跨架构的应急预案”当驱动发现当前 GPU 没有匹配的 SASS 时会尝试把 PTX 现场 JIT 成当前计算能力能跑的代码。但前提是你在编译时确实附上了 PTX很多人恰恰漏了这一手。2.2 参数设高、设低、干脆不设分别是啥后果我把三种常见情况可能引发的后果列一下设得太高例如只写8.9在老 GPU 上直接no kernel image。编译产物像是专门给新卡写的一封信老卡连拆信的机会都没有。设得太低例如只写5.0几乎所有 GPU 都能兼容但新架构的新指令全用不上。Tensor Core 加速等于摆设推理和训练性能会明显偏低。干脆不设置这里的风险最大。不同构建脚本的默认值五花八门有的默认只编当前机器上探测到的架构有的默认编一个非常保守的老架构。你在这台机器上编译时可能一切正常换到另一张卡上就跑不了。我见过一个最经典的场景某人在一台 RTX 4090 机器上编译完 PyTorch看起来很顺利部署到一台 T4 推理机上直接崩。后来我把编译日志翻出来发现TORCH_CUDA_ARCH_LIST是空的构建脚本默认抓到了 4090 的 8.9老卡当然颗粒无收。2.3 谁在读取这个设置CMake、nvcc 与框架脚本的协作从底层逻辑看设置计算能力最终都会转化成 nvcc 的-gencode参数。比如-gencode archcompute_86,codesm_86 -gencode archcompute_86,codecompute_86第一行生成针对 8.6 的 SASS第二行生成 8.6 版本的 PTX。不同框架只是把“如何拼这串参数”包装成了变量PyTorch 用环境变量TORCH_CUDA_ARCH_LISTTensorFlow 用TF_CUDA_ARCHSCaffe 用 CMake 的CUDA_ARCH_BIN和CUDA_ARCH_PTX现代 CMake 工程用CMAKE_CUDA_ARCHITECTURES所以你理解了底层原理后再去看这些变量名就会觉得它们只是同一个东西的“外号”。3. 查自己 GPU 计算能力的三条路线3.1 命令行直接查最省事的方法是用 nvidia-smi 的查询模式。普通nvidia-smi只能看到型号、显存、驱动信息看不到计算能力但只要加两个字段就能直接输出nvidia-smi --query-gpuname,compute_cap --formatcsv输出大概是这样NVIDIA GeForce RTX 3090, 8.6这个办法适合快速确认手头机器到底是什么架构。3.2 官方规格表对号入座如果手头没有机器或者想查一批 GPU 的规格直接去 GPU 厂商的 CUDA 支持矩阵页面。这个页面会按计算能力从小到大列出所有支持 CUDA 的 GPU 型号。比如你想知道 Tesla T4 是多少搜一下就能看到是 7.5A100 是 8.0H100 是 9.0。查表的另一种方式是直接用 CUDA Toolkit 自带的 deviceQuery 示例/usr/local/cuda/samples/1_Utilities/deviceQuery/deviceQuery运行后最后几行会输出CUDA Capability Major/Minor version number: 8.6。3.3 在代码里动态获取如果你要在程序里动态判断 GPU 能力可以直接问 CUDA Runtime。C 语言写法是用cudaGetDevicePropertiesPython 里如果用 PyTorch 则更简洁import torch # 返回一个元组比如 (8, 6) print(torch.cuda.get_device_capability(0))这个我在写需要自适应选 kernel 的代码时经常用先查计算能力再决定走哪个优化分支避免在运行时调用不支持的算子。3.4 常用 GPU 速查参考表个人常备版我把自己平时频繁用到的型号整理成了参考表编译时填参数基本都用得上GPU 型号计算能力备注GTX 1080 Ti6.1老但还能跑Tesla T47.5推理常见RTX 2080 Ti7.5Turing 代表V1007.0Volta适合老 CUDA 环境A1008.0Ampere 数据中心卡RTX 30908.6Ampere 消费卡RTX 40908.9AdaH1009.0Hopper只要把这张表记在心里编译时选架构就有了基本盘。4. PyTorch 源码编译TORCH_CUDA_ARCH_LIST 怎么填4.1 环境变量的填法与格式PyTorch 从源码编译时读取的是环境变量TORCH_CUDA_ARCH_LIST。格式是空格分隔的列表export TORCH_CUDA_ARCH_LIST7.5 8.0 8.6编译时它会逐个生成对应的 SASS 代码。比如包含 7.5 和 8.6就等于给 nvcc 传了两组-gencode参数最终产出的动态库里嵌入了两套机器码。这里有个容易忽略的点如果你是用官方编译好的 PyTorch wheel 包是不需要管这个变量的官方包默认包含了常见计算能力的支持自己搞反而容易出问题。只有当你需要从源码定制编译或者要编译自定义 CUDA 扩展算子时才需要真正关心它。4.2 多架构与“PTX”的组合策略在 PyTorch 的环境变量里单个架构后面可以加PTX例如export TORCH_CUDA_ARCH_LIST7.5 8.0 8.6PTX意思是为 8.6 生成 SASS 机器码同时额外生成一份 compute_86 的 PTX 中间代码。这个 PTX 是给“未来可能出现的更高计算能力 GPU”准备的。比如某台机器是计算能力 8.9 的新卡驱动看到 8.6 的 SASS 没法用但发现有 compute_86 的 PTX就会现场把它 JIT 成 8.9 能跑的代码。虽然性能不一定是最优但至少能跑起来。我建议的策略很直接把你手头最老的目标卡、当前主力卡都写上最后一个档位加PTX兜底。例如开发机器是 RTX 3090但部署机器还有 RTX 2080 Tiexport TORCH_CUDA_ARCH_LIST7.5 8.6PTX这样既照顾了老卡又保留了新卡的优化空间。4.3 一次标准编译实操示例一个比较典型的源码编译流程是这样conda create -n build_env python3.10 conda activate build_env conda install cmake ninja export TORCH_CUDA_ARCH_LIST7.5 8.0 8.6PTX git clone --recursive https://github.com/pytorch/pytorch cd pytorch python setup.py develop编译结束后可以用 CUDA 自带工具检查动态库里到底嵌入了哪些架构cuobjdump --list-elf torch/lib/libtorch_cuda.so | grep sm_正常情况下会看到类似sm_75、sm_80、sm_86的字样。这一步能帮你确认设置是否真的生效非常值得养成习惯。5. TensorFlow 源码编译TF_CUDA_ARCHS 与 bazel 参数5.1 官方 configure 阶段和 bazel action_envTensorFlow 从源码编译用的是 bazel在 configure 阶段会生成一份配置。其中控制计算能力的变量是TF_CUDA_ARCHS。老版本 TensorFlow 的 configure 脚本会让你手动输入架构例如Please specify a list of comma-separated CUDA compute capabilities you want to build with.你也可以在编译时直接通过环境变量覆盖TF_CUDA_ARCHS7.5 8.0 8.6 bazel build --configcuda //tensorflow/tools/pip_package:build_pip_package注意这里同样是空格分隔。最终 bazel 会把架构列表解析成 nvcc 的 gencode 参数。5.2 老参数 -arch / -gencode 其实还在用有些老教程里写的不是TF_CUDA_ARCHS而是往 bazel 编译选项中直接塞 nvcc 参数--copt-archsm_75或者更完整的--copt-gencodearchcompute_75,codesm_75 --copt-gencodearchcompute_75,codecompute_75这种写法在特殊环境下依然有效因为它直接作用于 nvcc。不过维护成本高切到新架构要改一堆地方还是用TF_CUDA_ARCHS更省心。5.3 TF 场景下容易忽略的两件事第一件TensorFlow 官方预编译包的架构范围是固定的。如果你是自己从源码构建定制版别把架构范围设得太窄否则模型在另一种 GPU 上加载时会直接报找不到内核。第二件TensorFlow 对 PTX 的处理在各版本间有变化。新版框架总体上是按架构列表生成 SASS但对“未来架构兜底”的支持不像 PyTorch 那样直白。如果拿不准稳妥做法是把目标机器范围内所有可能用到的计算能力都列进去哪怕稍微增大一点编译产物体积。我遇到过有人图省事只写了8.0结果 A100 上跑得好好的换到 T4 上模型加载直接挂。这就是典型的“只考虑当下机器、没考虑部署环境”的翻车现场。6. Caffe 与通用 CMake 工程CMAKE_CUDA_ARCHITECTURES6.1 Caffe 的老式写法CUDA_ARCH_BIN 和 CUDA_ARCH_PTXCaffe 的构建脚本有一套历史悠久的变量体系CUDA_ARCH_BIN负责写 SASS 要覆盖哪些计算能力CUDA_ARCH_PTX负责写 PTX 兜底档位。编译时这样设cmake .. \ -DCUDA_ARCH_BIN75 86 \ -DCUDA_ARCH_PTX75这条命令的意思是生成 7.5 和 8.6 两份 SASS 代码同时为 7.5 准备一份 PTX。如果你的目标是只对当前 GPU 优化不想管其他型号也可以把CUDA_ARCH_BIN设成你机器对应的计算能力数字。老项目的坑在于不同版本的 Caffe 对这个变量的解析格式不完全一致有的版本要求写成7.5有的版本要求写成75。遇到编译不过时先检查是不是数字格式的问题。6.2 现代 CMake 写法CMAKE_CUDA_ARCHITECTURES如果你用的是较新的 CMake 和自定义 CUDA 工程推荐直接使用标准变量CMAKE_CUDA_ARCHITECTURES。这个变量从 CMake 3.18 开始支持分号分隔多个架构cmake .. -DCMAKE_CUDA_ARCHITECTURES75;80;86注意这里的分号和前面环境变量的空格分隔不一样。忘了带引号的话shell 可能把分号理解成命令分隔符会直接裂开。CMake 3.24 以上版本还支持几个关键字native直接用当前机器探测到的计算能力适合只在本机部署。all支持所有已知架构灵活但编译时间极其感人。all-major覆盖每个大代际里的主力架构比 all 轻量不少。6.3 三个实用档位native、多架构组合、全家族覆盖根据实际用途我一般推荐三档只在本机跑-DCMAKE_CUDA_ARCHITECTURESnative最省事编出来也只认当前卡。要换机器、架构范围明确-DCMAKE_CUDA_ARCHITECTURES75;80;86速度适中兼容性兼顾。要交付给多人/多卡环境、架构不确定保守起见写61;75;80;86;89;90。代价是编译时间和动态库体积都会上升换来的是“装上就能跑”的踏实感。我记得 CMake 官方对这个变量的默认值还闹过一阵子如果不显式设置新版本 CMake 在启用 CUDA 语言时会直接警告甚至报错要求你必须显式指定架构。这不是 CMake 矫情而是它发现太多人因为默认值不明确而在换机器后翻车了。7. 编译和运行阶段的高频翻车现场7.1 编译期Unsupported gpu architecture这个报错通常长这样Unsupported gpu architecture compute_86出现原因一般有两个第一CUDA Toolkit 版本太老编译器根本不知道sm_86/compute_86这个架构。比如老版本 CUDA 10.x 就认不出 8.6。解决办法是升级 CUDA Toolkit或者把目标架构改成当前工具链认识的旧架构。第二CMake 的架构列表里有拼写错误。比如手滑把86写成了6CMake 会一脸懵地表示不认识。查一遍变量值里有没有漏数字、多标点就好。7.2 编译期ptxas fatal error我遇得比较多的是这种ptxas fatal : Unsupported version or architecture这个信息比上一条更底层。ptxas 是 CUDA 工具链里负责把 PTX 编译成 SASS 的程序它对架构的接受范围更敏感。最常见的场景是你设置了很高的计算能力编号例如写了compute_90但实际 CUDA Toolkit 版本还不够新ptxas 不认 9.0于是当场报错。解决办法也很直接查看工具链支持的架构上限把目标架构调低或者升级 CUDA Toolkit两者必有一项。7.3 运行期no kernel image is available for execution on the device这个错误几乎全是在换机器后才出现RuntimeError: CUDA error: no kernel image is available for execution on the device原因是当前 GPU 的计算能力在程序编译的 SASS 列表里找不到对应项而且程序里也没有附带能 JIT 到当前架构的 PTX。比如用 RTX 4090 编译出的库只带了 8.9 的 SASS放到 RTX 30908.6上跑8.9 的机器码它不认又没有 8.6 的 PTX 兜底自然直接崩。碰到这个报错不要急着怀疑代码逻辑先回去看编译时架构列表范围把目标部署机器的计算能力加进去重编一次。7.4 高频问题速查表我把常见情况整理成表方便直接对照现象常见原因处理思路编译报 Unsupported gpu architectureCUDA 版本太老 / 拼写错误升级工具链或修正架构编号ptxas fatal: Unsupported version目标架构超出工具链支持范围降档或升级 CUDA Toolkit运行报 no kernel imageSASS 和 PTX 都与当前卡不匹配把目标架构加入编译范围重编CMake 报 CUDA_ARCHITECTURES 相关错误变量未设置或格式不对显式指定架构列表用引号包裹换机器后部分算子极慢只编了最低兼容架构新卡特性没启用增加新计算能力档位重新编译排查这类问题时我还有个习惯先跑一行nvidia-smi --query-gpuname,compute_cap --formatcsv把当前机器的计算能力确认掉再去看编译日志里实际生成了哪些架构两步就能定位九成问题。8. 折腾这么多次我最后想说的几件事第一件编译框架前一定要先想清楚“这个库到底要在哪些机器上跑”。只服务于本机直接native或者写当前计算能力就行要交付部署就必须把目标机器范围拉出来逐个核对架构列表。第二件多架构列表确实会让编译变慢、包体变大但这是值得的。一个包含75;80;86;89;90的库体积可能多个几百 MB换来的是在所有主流量级 GPU 上“装好即跑”远比在生产环境里重编框架省时间。第三件学会用cuobjdump --list-elf检查编译产物。不管是 PyTorch 的.so还是 Caffe 的动态库编译完以后花十秒看一下里面嵌了哪些 sm 架构就能确认设置到底有没有生效。这一步看着不起眼却帮我避过无数个“以为配好了其实是默认值在撑场面”的坑。最后再分享一个小经验团队多人共用构建机时架构列表最好统一写进构建脚本或 Dockerfile 里别依赖某个人手动 export。否则一个人改环境变量、另一个人不知道编译出的包带回各自的机器上互相甩锅的场景你就等着吧。