☰
H100 PyTorch CUDA 环境搭建:sm_90 兼容与版本避坑指南
2026/10/1 16:34:53 网站建设 项目流程

1. 为什么 H100 的 CUDA 版本不能照抄 4090 的作业

上架一台 8 卡 H100 的机器之后,最容易被误解的一件事就是:既然 CUDA 号称向下兼容,那把 4090 上跑得好好的环境原样搬过来不就行了?我见过不止一次这种操作——从另一台 A100 或者 4090 的机器上把 requirements 抄过来,pip install一把梭,torch.cuda.is_available()返回True,随手torch.randn(512, 512).cuda()也不报错,于是所有人以为环境成了。真正的坑在训练脚本跑起来十几分钟之后才露头:loss 掉不下去、精度对不上论文、或者干脆在一个自定义算子那里直接崩掉。

原因在于 H100 用的是 Hopper 架构,计算能力(compute capability)是9.0,而 4090 是 Ada Lovelace 的8.9,A100 是 Ampere 的8.0。这几个数字不是贴在卡上的标签,它直接决定了一个二进制包里有没有塞进对应的 GPU 机器码(kernel image)。CUDA 和 torch 的版本选择,本质上就是在回答一个问题:我装进去的这套东西,里面到底有没有 sm_90 的代码。

1.1 sm_90 这条硬门槛是怎么划出来的

NVIDIA 每一代架构发布,CUDA Toolkit 里就会多一个 target。Hopper 对应的就是sm_90,业界普遍把CUDA 11.8 当作 H100 的起跑线——从这个版本开始,sm_90才被正式纳入支持列表。比它更早的 CUDA 11.x,你可以把 toolkit 装上、nvcc -V也能正常打印,但编译出来的东西里没有 Hopper 的 SASS,跑起来要么退回 JIT 现场编译(慢得让你怀疑人生),要么直接抛no kernel image is available。

这里有个特别容易混淆的点,值得单独拎出来说:能装 ≠ 能跑,能跑 ≠ 跑得快。

  • 能装:pip install torch几乎在任何驱动版本上都可能成功安装,因为 pip 不管你的硬件;
  • 能跑:torch.cuda.is_available()为真,只说明驱动和运行时握上手了;
  • 跑得快:需要 wheel 里真的带sm_90的优化内核,这跟 CUDA 标签强相关。

我见过太多人卡在第二步就以为完事了。判断标准很简单,下面这段代码的输出才是关键:

import torch print("torch:", torch.__version__) print("built with cuda:", torch.version.cuda) print("cudnn:", torch.backends.cudnn.version()) print("device:", torch.cuda.get_device_name(0)) print("capability:", torch.cuda.get_device_capability(0)) # 期望 (9, 0) print("arch list:", torch.cuda.get_arch_list()) # 期望里面出现 sm_90

get_arch_list()这一行是很多教程里压根不写的。它返回的是这个 wheel 编译时打包进去的架构列表。如果里面只有sm_80、sm_86、sm_89,那你在 H100 上就是靠 JIT 兜底,性能损耗在 20% 起步,算子越复杂越明显。

1.2 驱动版本是比 CUDA 版本更靠前的一道闸

新手最容易搞反的一个顺序是:先纠结装哪个 CUDA,装完发现驱动太老,整个白干。正确的思路是看driver 能撑到哪一档 CUDA,然后在那一档里选。

大致门槛如下(Linux 平台,具体以官方 release notes 为准):

CUDA 运行时版本最低 Linux 驱动版本说明
CUDA 11.8≥ 520.61.05H100 的最低可用档
CUDA 12.1≥ 530.30.02很多推理框架的默认基线
CUDA 12.4≥ 550.54.14中期比较稳的一档
CUDA 12.6≥ 560.28.03支持新卡的常见选择
CUDA 12.8≥ 570.26较新的 torch wheel 会用到

注意:nvidia-smi右上角显示的 "CUDA Version" 不是你装了什么 CUDA,而是当前驱动最多能支持的 CUDA 版本上限。它显示 12.4,说明你装 CUDA 12.1、11.8 都没问题,但装 12.6 就会在初始化时报错。

在 H100 的实际部署里,我个人的建议是把驱动刷到550 系列以上。原因不是新,而是 CUDA 12.x 的 minor version compatibility 机制:同一大版本内,新驱动可以跑旧运行时,反过来不行。把驱动顶到高位,等于给后面所有版本的切换都留了余地。

1.3 看到 nvidia-smi 之后的三步判断

拿到一台陌生的 H100 机器,别急着 pip,先按这个顺序走一遍:

  1. 确认卡型和数量:nvidia-smi -L,确认是 H100 SXM 还是 PCIe,8 卡还是 4 卡。SXM 版本走 NVLink,PCIe 版本走 PCIe Switch,后续多卡通信的参数完全不同。
  2. 记录驱动版本和 CUDA 上限:nvidia-smi | head -n 15,把 Driver Version 抄下来,对照上表确定可用区间。
  3. 确认有没有已存在的 CUDA Toolkit:ls /usr/local | grep cuda,看看/usr/local/cuda指向哪一版。这一步是为了避免后面编译扩展时,nvcc和 torch 打架。

这三步走完,你才真正知道自己手里有什么牌。跳过它们直接 pip,等于闭着眼睛换轮胎。

2. torch wheel 的命名逻辑:cu118、cu121、cu128 到底差在哪

搞清楚硬门槛之后,下一件事是把 PyTorch 的版本体系吃透。这套命名规则说实话有点绕,但只要理解了一次,后面所有平台都能套用。

2.1 pip 装的 torch 为什么自带一整套 CUDA 运行时

这是最反直觉、也是最重要的一点:用 pip 从官方源装的 torch,会把 CUDA 运行时库一起装进来。

你可以自己验证一下:

pip list | grep nvidia

在 cu121 的 torch 环境下,你会看到一长串nvidia-cuda-runtime-cu12、nvidia-cublas-cu12、nvidia-cudnn-cu12、nvidia-nccl-cu12之类的包。它们就是被塞进 site-packages 里的一整套运行时。

这意味着什么?意味着:

  • 跑 PyTorch 本身,你系统里装不装 CUDA Toolkit 完全无所谓,只要驱动够新就行;
  • nvcc -V显示的版本和torch.version.cuda不一致,是正常的,不是 bug;
  • 只有当你需要编译自定义 CUDA 算子时,系统里的 CUDA Toolkit 才会真正参与进来,那时候版本对齐才成为必须。

我见过太多人在 H100 上折腾一整天,就因为发现nvcc是 11.8 而torch.version.cuda是 12.1,然后开始重装系统级 CUDA,最后把本来好好的环境搞崩。真的没必要,这两个数字本来就允许不同。

2.2 index-url 和三件套版本号的换算规律

PyTorch 官方源按 CUDA 版本分了多个 channel,安装时通过--index-url指定:

# CUDA 12.1 版本 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 # CUDA 12.4 版本 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124 # CPU 版本(H100 上当然不用,但别装错) pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu

torch、torchvision、torchaudio三者的版本号有严格的对应关系,不能随便配。规律其实很朴素:torchvision 的次版本号 = torch 的次版本号 + 15。

  • torch 2.0.x → torchvision 0.15.x
  • torch 2.1.x → torchvision 0.16.x
  • torch 2.4.x → torchvision 0.19.x
  • torch 2.7.x → torchvision 0.22.x

torchaudio 则基本和 torch 保持同号,2.x 对 2.x。

网上流传着大量"拍脑袋写出来"的安装命令,比如把版本号随手写成一个看起来很长很新的组合,然后--index-url指向某个 cu 目录。这类命令几乎百分百会报Could not find a version that satisfies the requirement。遇到这个报错,先别怀疑网络,先怀疑自己抄的版本号是不是压根不存在,或者三件套没配平。

提示:只装torch一个包是完全可行的。torchvision 和 torchaudio 只在你要用视觉/音频模型时才需要。单独指定三件套版本,反而更容易因为某一个次版本不存在而整条命令失败。

2.3 一份可以直接对着抄的对应关系表

下面这张表是我在几台 H100 机器上按实际安装结果整理的,可以作为起点。装之前建议还是用pip index versions torch复核一次,避免拿到过期的信息。

torch 版本官方 wheel 的 CUDA 标签torchvision对 H100 的适配情况
2.0.xcu1180.15.x可用,sm_90 支持有限
2.1.xcu118 / cu1210.16.x可用,cu121 更推荐
2.2.x – 2.3.xcu118 / cu1210.17.x / 0.18.x生产上比较常见
2.4.x – 2.5.xcu118 / cu121 / cu1240.19.x / 0.20.xcu124 是甜点档
2.6.xcu118 / cu124 / cu1260.21.x开始逐步淘汰 cu118
2.7.xcu118 / cu126 / cu1280.22.x新驱动环境首选
2.8.x 及以上cu126 / cu128 / cu1290.23.xCUDA 11.8 已不再提供

从这张表能看出一个趋势:CUDA 11.8 的 wheel 正在被逐步淘汰。如果你在 H100 上接手了一个老项目,锁死在 torch 1.13 加 cu117,那就只能考虑从源码编译,或者把项目整体升级。这件事越早决策越好,拖到上线前再动,代价会翻好几倍。

3. 从裸机到可跑通:H100 上搭一套 PyTorch 环境的完整动作

前面两章讲的是"怎么选",这一章讲"怎么做"。我按实际落地顺序写一遍,中间会标注哪些地方容易出错。

3.1 拿到机器后的验货清单

第一步永远是确认硬件状态,尤其是二手机或者刚上架的机器。

# 卡型号和数量 nvidia-smi -L # 详情:驱动版本、显存、功耗上限 nvidia-smi -q | grep -E "Product Name|Driver Version|Total|Power Limit|Persistence" # 错误计数(H100 上这一项很重要,XID 错误会导致莫名其妙的崩) nvidia-smi -q -d ECC,PAGE_RETIREMENT # NVLink 拓扑(SXM 版本必看) nvidia-smi topo -m

nvidia-smi topo -m的输出里,如果卡间是NV18之类的标记,说明走的是 NVLink;如果显示PHB或者SYS,说明走 PCIe,多卡训练的通信瓶颈会很明显。这个信息直接影响你后面选并行策略和 batch size。

特别提一句 Persistence Mode。在 H100 这种多卡大显存的机器上,不开持久化模式的话,每次进程结束驱动都要重新初始化,多卡加载模型能慢到让你以为卡死了:

sudo nvidia-smi -pm 1

3.2 conda 环境怎么建才不污染系统

我不建议用系统 Python,也不建议用sudo pip install。H100 的机器一般是多人共用,环境隔离是刚需。

conda create -n h100-torch python=3.10 -y conda activate h100-torch # 换国内镜像加速基础包(可选) pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

Python 版本我推荐3.10 或 3.11。这两个版本在 H100 生态里的轮子最全,尤其是 FlashAttention、vLLM、DeepSpeed 这类需要编译的库。3.12 也能用,但偶尔会遇到某个包还没出对应 wheel,然后被逼着从源码编译,那是纯粹的浪费时间。

环境建好之后,记得把镜像源改回来再装 torch:

pip install torch torchvision torchaudio \ --index-url https://download.pytorch.org/whl/cu124

顺序很重要。先用镜像装基础依赖,再用官方源装 torch,这样既快又不会因为镜像不同步而装到裁剪过的包。

3.3 安装命令与自检脚本

装完之后,跑一遍完整自检。下面这段脚本我基本每个环境都会跑一次:

import torch, time assert torch.cuda.is_available(), "CUDA 不可用,先查驱动" n = torch.cuda.device_count() print(f"GPU 数量: {n}") for i in range(n): p = torch.cuda.get_device_properties(i) print(f"[{i}] {p.name} CC={p.major}.{p.minor} 显存={p.total_memory/1024**3:.1f}GB") print("torch:", torch.__version__, "| cuda:", torch.version.cuda) print("arch list:", torch.cuda.get_arch_list()) # 实测一次矩阵乘,顺便看看有没有明显异常 a = torch.randn(8192, 8192, device="cuda", dtype=torch.float16) b = torch.randn(8192, 8192, device="cuda", dtype=torch.float16) torch.cuda.synchronize(); t0 = time.time() for _ in range(20): c = a @ b torch.cuda.synchronize() dt = (time.time() - t0) / 20 print(f"fp16 8192^3 matmul: {dt*1000:.2f} ms") # 多卡通信自检 if n > 1: import torch.distributed as dist dist.init_process_group("nccl")

这段脚本一次性覆盖了四件事:卡能不能用、算力是否正常、编译架构对不对、多卡通信是否通畅。8192³的 fp16 矩阵乘在单张 H100 SXM 上大概在 25–45 毫秒这个量级,如果你测出来是几百毫秒,大概率是没走 Tensor Core 或者退回了 JIT。

4. 装完跑不通的几类高频报错,按这个顺序排查

环境装完只是开始,真正消耗时间的是各种运行期报错。这一章我按"最常见 → 最难缠"的顺序排,每一条都给出定位路径。

4.1 no kernel image is available:内核缺失的定位方法

报错长这样:

RuntimeError: CUDA error: no kernel image is available for execution on the device

这个报错基本可以直接锁定为"二进制里没有 sm_90"。排查路径非常清晰:

  1. 打印torch.cuda.get_arch_list(),看里面有没有sm_90;
  2. 如果是从源码编译的 torch 或某个扩展,检查编译时有没有设置TORCH_CUDA_ARCH_LIST;
  3. 检查这个报错是出现在 torch 原生操作上,还是出现在某个第三方 CUDA 扩展上。前者说明 torch 本身装错了,后者只需要重编那个扩展。

重编扩展时,正确的环境变量写法是:

export TORCH_CUDA_ARCH_LIST="9.0" # 如果算子用到了 Hopper 专属指令(wgmma 等),需要带 a 后缀 export TORCH_CUDA_ARCH_LIST="9.0a"

9.0和9.0a的区别很多人搞不清。简单说,9.0a是"架构特定"(architecture-specific)的 target,只有它才能用 Hopper 新引入的那些指令。用9.0编出来的代码跑在 H100 上没问题,但拿不到那些新特性带来的加速。所以你看到某个库的安装文档强调"必须用 9.0a",不要以为是笔误。

4.2 编译自定义算子时的 CUDA_HOME 版本错配

另一个高频报错:

The detected CUDA version (11.8) mismatches the version that was used to compile PyTorch (12.4).

注意,这个报错只在编译扩展时出现,纯跑推理是不会有这个问题的。原因是扩展编译需要真实的nvcc,而nvcc来自系统或 conda 里的 CUDA Toolkit。

解决办法有两条路:

  • 路线 A(推荐):用 conda 装一套和 torch 完全对齐的 toolkit,不碰系统环境:
conda install -c nvidia cuda-toolkit=12.4 -y export CUDA_HOME=$CONDA_PREFIX
  • 路线 B:直接指定CUDA_HOME指向某个已装好的 toolkit 目录,然后在PATH里把它的bin放前面。

路线 A 的好处是干净、可复制、跟着环境走。路线 B 适合机器上已经有多套 CUDA 的情况,但PATH的顺序很容易被别的脚本打乱,出问题时很难查。

注意:如果扩展编译卡在ninja那一步很久没动静,八成是在为多个架构编译。把TORCH_CUDA_ARCH_LIST显式设成9.0,编译时间能从十几分钟降到两三分钟。

4.3 显存、NCCL 和多卡启动参数

H100 单卡 80GB 显存听起来很大,但实际用起来很快见底,尤其是跑长上下文的大模型。常见的三类问题:

现象大概率原因处理方向
显存 OOM 但不报具体位置缓存碎片 + 激活值峰值开PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
多卡启动卡在 init_process_groupNCCL 走了错误网卡设NCCL_SOCKET_IFNAME指定网卡
训练中途 NCCL timeout卡间通信被其他任务抢占检查是否开了 MPS,必要时隔离 GPU

第一项值得展开说。H100 80GB 显存下,PyTorch 默认的分配器在反复分配释放之后会产生碎片,明明nvidia-smi显示还有 20GB,却报 OOM。打开可扩展内存段之后,这个问题基本消失:

export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True

NCCL 那一项,H100 机器通常有多张网卡(业务网 + 高速互联网),NCCL 默认可能会挑错。多机训练前,先把NCCL_SOCKET_IFNAME和NCCL_IB_HCA确认一遍,能省下大量"训练启动了三分钟然后 timeout"的时间。

5. 一台机器多个项目:CUDA 多版本共存与容器化落地

H100 的机器通常不归一个人用,也不只跑一个项目。怎么让不同项目各用各的 CUDA,是个绕不开的问题。

5.1 别去动 /usr/local/cuda 这个软链接

很多教程会教你ln -sf /usr/local/cuda-12.4 /usr/local/cuda去切换版本。这个操作在单项目机器上没问题,多项目机器上就是灾难——你切一次,别人正在跑的训练进程可能就崩了。

我的做法是:系统级的/usr/local/cuda保持不动,交给运维统一管。所有版本差异都通过环境变量在进程内解决。

# 需要哪个版本,就在当前 shell 里改,不影响别人 export CUDA_HOME=/usr/local/cuda-12.4 export PATH=$CUDA_HOME/bin:$PATH export LD_LIBRARY_PATH=$CUDA_HOME/lib64:$LD_LIBRARY_PATH

这三个变量的关系值得记一下:CUDA_HOME是给编译系统找 toolkit 用的,PATH是给nvcc找执行文件用的,LD_LIBRARY_PATH是给运行期找动态库用的。三者缺一个都可能出问题,尤其是第三个,很多"明明 nvcc 对了但运行报找不到 libcudart"的问题都出在这里。

5.2 用 conda 装一套完整的 CUDA Toolkit 用来编译

如果要在一个 conda 环境里从源码编译带 CUDA 的包,最省事的方案是让这个环境自带 toolkit:

conda activate h100-torch conda install -c nvidia cuda-toolkit=12.4 -y export CUDA_HOME=$CONDA_PREFIX

这样这套 toolkit 就跟着环境走了。删掉环境就等于删掉 toolkit,不留任何系统残留。对于需要频繁试错、装了一堆编译依赖的项目,这种方式几乎是唯一解。

成本是要多占几个 GB 磁盘。在 H100 这种机器上,磁盘一般不是瓶颈,这笔账很划算。

5.3 验证过的组合要固化下来

环境折腾成功的那一刻,是整件事里最容易被浪费的时刻——很多人就这样让它跑着,过了三个月再想复现,完全想不起来当时装了哪些包。

我的习惯是装完立刻冻结两份清单:

pip freeze > requirements.lock.txt # 只保留直接依赖,方便别人阅读 pip install pipdeptree pipdeptree --warn silence | grep -v "^ " > deps.txt

pip freeze出来的清单可以直接用于重建,但可读性差。pipdeptree出来的顶层依赖列表则方便别人快速看懂环境构成。两份都留着,前者用于复现,后者用于沟通。

再进一步,把验证过的组合打成容器镜像,从根上解决"在我机器上是好的"这类问题。镜像里显式写死驱动兼容的上限,比在文档里写一段说明可靠得多。

6. H100 上值得顺手打开的几项设置

环境跑通之后,还有几个开关值得花十分钟调一下,收益往往是两位数百分比的性能提升。

6.1 TF32 相关的精度开关

PyTorch 从 2.x 开始在矩阵乘上默认使用 TF32(在 Ampere 及之后架构上)。TF32 用 19 位有效位做乘法累加,速度比 FP32 快不少,代价是精度下降。

在 H100 上,如果你做的是科学计算、数值模拟这类对精度敏感的任务,需要关掉:

import torch torch.backends.cuda.matmul.allow_tf32 = False torch.backends.cudnn.allow_tf32 = False

如果是常规的深度学习训练,开着基本没坏处,甚至能顺带起到一点正则化效果。这个决定没有标准答案,取决于任务本身对数值误差的容忍度。我的做法是先开着跑,如果发现 loss 曲线和论文对不上,再关掉对比一次。

需要注意的是,新版 PyTorch 里这类开关有过调整,不同小版本之间的默认值可能有差异。写训练脚本时最好显式声明,而不是依赖默认行为。

6.2 FlashAttention 与 torch 版本绑定

FlashAttention 在 H100 上的收益非常明显,但它对 torch 和 CUDA 版本相当挑。

几个实际经验:

  • FlashAttention 2.x 需要较新的 torch,且对 CUDA 版本有最低要求;
  • 版本不匹配时,通常不是报错,而是静默退回普通注意力实现,你以为开了加速其实是白开;
  • 一定要在装完之后实测一次显存占用和吞吐,确认真的生效了。

最简单有效的验证方式是跑一个固定规模的注意力计算,对比开与关的显存峰值。如果两者一样,那说明根本没走 FA 的路径。

6.3 TORCH_CUDA_ARCH_LIST 的正确写法

最后回到一个看起来很小但影响很大的点。任何时候你在 H100 上从源码编译东西,都把这一行加上:

export TORCH_CUDA_ARCH_LIST="9.0"

不设这个变量,编译系统默认会为一大堆历史架构都编一遍(sm_50到sm_90全来),编译时间翻好几倍,编出来的包还特别大。在确定只用 H100 的场景下,锁定9.0是纯粹的收益。

唯一的例外是,你编出来的 wheel 要分发给不同架构的机器使用。这时候老老实实列出全部目标架构,别偷这个懒。

我自己在 H100 上踩过的最大一个坑,其实跟技术细节关系不大:曾经为了图快,直接复制了别人博客里的安装命令,连版本号都没核对,结果那次排查花了大半天,最后发现问题出在 torchvision 的次版本号少了一位。从那之后,无论多简单的安装,我都会先跑一次pip index versions确认包真实存在,再动手。这个习惯看起来很笨,但它省下来的时间远比它花掉的多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询