机器是上周到的,4090插上去第一件事就是想把官方SegFormer复现出来,结果栽在环境上一个下午。你去看NVIDIA/SegFormer仓库里的environment.yaml,里面还写着pytorch 1.8、cuda 10.2,这在4090上根本起不来。如果你也是刚换了4090、又想在官方代码基础上跑分割实验,这篇就是把你从“装完驱动不知道下一步”带到“单图验证通过、测试脚本能跑”的完整过程,包含预训练权重下载、conda环境组合、代码改动点,以及几个我实际踩到、搜索引擎也救不了的坑。
1. 为什么在4090上跑官方SegFormer,第一件事是忘掉官方environment.yaml
1.1 4090的sm_89与老版本PyTorch/CUDA的错配
RTX 4090用的是Ada Lovelace架构,计算能力是sm_89。这里有个非常实际的问题:CUDA扩展(比如mmcv里的自定义算子)在编译时,会针对特定算力生成机器码。PyTorch 1.8时代默认只编译到sm_50~sm_80附近,根本没有sm_89的SASS(机器码)。显卡加载老内核时,只能尝试用PTX JIT即时编译,一旦算子复杂一点就极其容易崩,或者干脆直接报“no kernel image available”。
这还不是全部。老版本CUDA Toolkit(官方环境里写的10.2)在Linux下搭配新版NVIDIA驱动,经常出现驱动版本过高、与旧CUDA runtime不兼容的告警,有些场景直接起不来。所以一句话:在4090上,凡是“pytorch 1.8 + cuda 10.2 + mmcv-full 1.3.x”这种组合,理论上有机会,实操就是血压升高。
1.2 官方仓库的版本依赖到底卡在哪
SegFormer官方仓库的核心依赖其实不多,真正麻烦的是三个包的版本联动:
| 组件 | 官方环境写的版本 | 4090上建议版本 | 原因 |
|---|---|---|---|
| Python | 3.7 | 3.8 | 3.7太老,很多新版编译链不再提供wheel |
| PyTorch | 1.8.0 | 1.13.1(cu117) | 1.13是mmcv-full 1.7.2官方预编译wheel覆盖到位的版本 |
| CUDA Toolkit | 10.2 | 11.7 或 11.8 | 驱动层和编译层必须支持sm_89 |
| mmcv-full | 1.3.9 | 1.7.2 | 1.7.2对CUDA 11.x适配成熟,且提供官方预编译包 |
| mmsegmentation | 0.13.0 | 0.20.2 | 与mmcv-full 1.7.2搭配稳妥,能直接读官方repo的config |
为什么不是PyTorch 2.0?因为mmcv-full 1.7.2没有针对torch 2.0的官方预编译包,需要自己从源码编,而编译的变数远大于预编译。如果你硬要torch 2.0,就得走mmcv 2.0.x + mmsegmentation 1.0.x,但SegFormer仓库里那一堆老式config格式在mmseg 1.0里要做迁移改动,不划算。老老实实1.13.1,所有精力留在模型上。
2. 从NVIDIA驱动到conda环境:一套在4090上实测通过的配置组合
2.1 显卡驱动与CUDA Toolkit的版本对应关系
先装驱动。4090刚上市那会儿装驱动是个坎,现在驱动版本已经很成熟,直接用535或545系列就行。确认驱动装好,用nvidia-smi看输出,注意CUDA Version那一栏显示的是“驱动支持的最高CUDA版本”,它和你在conda里装的CUDA Toolkit是两码事。很多新手在这里被绕晕:驱动显示的CUDA 12.2不代表你的PyTorch就能用CUDA 12.2,PyTorch通过自己的CUDA runtime运行,你只需要保证驱动版本足够新就行。
我的建议是驱动装完,直接用conda装PyTorch时自带的CUDA 11.7 runtime,完全不用单独装CUDA Toolkit。cuDNN也一样,PyTorch官方wheel里已经带了,不再需要系统级安装。
2.2 conda创建虚拟环境与PyTorch安装
创建环境这一步,命名随意,但Python版本一定锁3.8:
conda create -n segformer python=3.8 -y conda activate segformer接着装PyTorch。网上很多教程让你pip install torch torchvision,这在4090上有风险,因为默认源的包可能不带CUDA算子,必须指定cu117的index-url:
pip install torch==1.13.1 torchvision==0.14.1 --index-url https://download.pytorch.org/whl/cu117如果下载慢,可以带上--proxy之类的手段,或者挑网络空闲时段。装完务必验证一下CUDA是否可用:
python -c "import torch; print(torch.__version__, torch.cuda.is_available(), torch.cuda.get_device_name(0))"看到1.13.1+cu117 True NVIDIA GeForce RTX 4090,这一步才算过。
2.3 mmcv-full安装:编译还是预编译包,这里最容易翻车
mmcv-full是整个环境里最容易翻车的组件,因为它包含大量CUDA自定义算子,比如deformable attention、carafe、sync_bn等。安装前建议先把ninja和gcc装好,避免中途失败后无从下手:
pip install ninja sudo apt-get install gcc g++ # Ubuntu/Debian,其他系统用对应包管理器有两条路可以走:
路径A:用openmim装预编译包(推荐优先试)
pip install openmim mim install mmcv-full==1.7.2openmim会根据你当前环境里的PyTorch和CUDA版本自动选择合适的wheel。如果你的PyTorch是1.13.1+cu117,它大概率能拉到预编译版本,直接几分钟装完。
路径B:源码编译(预编译拉不到时走这条)
git clone https://github.com/open-mmlab/mmcv.git cd mmcv git checkout v1.7.2 export FORCE_CUDA=1 export TORCH_CUDA_ARCH_LIST="8.9" export MMCV_WITH_OPS=1 pip install -e .这里的TORCH_CUDA_ARCH_LIST="8.9"是4090的算力编号。不设这个变量的话,编译时会尝试把能想到的所有算力全部编一遍,耗时翻好几倍,而且容易在老架构编译环节报错。只锁定8.9,干净利落。
装完mmcv-full,接着装mmsegmentation:
pip install mmsegmentation==0.20.2最后回到SegFormer官方仓库,把仓库clone下来:
git clone https://github.com/NVIDIA/SegFormer.git cd SegFormer到这里环境部分基本齐了。建议现在就把环境整体保存一下,比如记录一份requirements.txt,因为接下来装权重、跑模型的过程中,你随时可能因为某个操作把环境搞乱,有个记录能快速恢复。
3. 预训练权重:下载、命名规律与本地化路径修正
3.1 官方权重放哪、文件名与config的对应关系
SegFormer的权重分为两段:一是mix_transformer的backbone预训练权重,二是整个模型在ADE20K等数据集上finetune后的完整权重。你实际测试时直接用完整权重,不用管backbone那一段。
官方仓库的README.md里,每个模型都给了Google Drive和Hugging Face的下载链接。文件名规律很直观:
segformer.b0.512x512.ade.160k.pth:B0模型,输入512x512,ADE20K训练160k iterationsegformer.b1.512x512.ade.160k.pth- 一直到
segformer.b5.640x640.ade.160k.pth
下载后统一放到一个目录,比如SegFormer/pretrained/,后续测试脚本会用到。
这里特别说一句:如果你的网络环境对海外下载不友好,Hugging Face镜像站和Google Drive的第三方下载器都是替补方案。重点是下载完确认文件大小。ADE20K完整权重大概是几百MB级别,B5会更大一些,如果一个文件只有几十MB,那基本是下载中断或下载了个错误页面,别往下跑,直接重新下载。
3.2 权重路径写死导致“找不到文件”的解决办法
官方config文件里,model.backbone.init_cfg.checkpoint默认指向一个http地址,也就是ImageNet预训练权重的路径。你本地跑测试时,这个地址能不能访问都是问题,而且也没必要再下那一段权重。
测试的时候,完整模型权重是通过命令行参数--checkpoint传进去的,用的是mmseg里load_checkpoint的逻辑,它不会去看config里那个init_cfg.checkpoint。所以如果你只是跑官方测试命令,下载完整权重就够了,不需要改config里的预训练路径。
但如果你后续要finetune自己的数据集,那就要改两个地方:一是把init_cfg.checkpoint改成你本地backbone权重路径,二是把model.decode_head.num_classes改成自己的类别数。很多人在这一步把pretrained和checkpoint搞混,花几个小时排查“为什么没加载权重”——其实一个是训练初始化用,一个是你加载进来评估/推理用,属于两套机制。
3.3 权重文件损坏的快速判断
下载下来的pth文件如果损坏了,跑测试时不会马上报“文件损坏”,而是会报类似“size mismatch”或者unexpected key,因为有些层的形状对不上,或者加载到一半就EOFError。碰到这种错误,第一反应不是去改代码,而是先比对文件大小、重新下载。
我习惯下载完先做个加载自检:
python -c "import torch; ckpt=torch.load('pretrained/segformer.b0.512x512.ade.160k.pth', map_location='cpu'); print(type(ckpt), len(ckpt) if isinstance(ckpt, dict) else '')"能正常打印出dict结构和长度,说明文件基本完整,可以进入下一步。
4. 代码适配:让官方Config和测试脚本在新显卡上跑起来
4.1 config里必须调整的采样与同步批量配置
SegFormer官方repo的config文件放在local_configs/segformer/下,以B0为例,核心配置在segformer.b0.512x512.ade.160k.py里。对单卡4090测试来说,默认的samples_per_gpu是1,用不着改,但有两个点需要留意。
第一,data.test的img_scale默认是(2048, 512)之类的大尺寸。测试时mmseg会用这个尺度做resize,对单图推理来说没毛病,但如果你拿的是自己随手拍的遥感图或街景图,分辨率比例跟默认不一致,输出结果会跟你预期差很多。建议先保持默认跑通,再根据自己的图片尺寸调整。
第二,model.decode_head.loss_decode里的loss_weight和num_classes在ADE20K权重下是150类,不需要动。如果换到自己数据集,num_classes必须改成自己的类别数,否则size mismatch立刻找上门。
4.2 单图验证脚本:不依赖数据集,一张图看出流程是否通了
官方repo的tools/test.py主要面向数据集评估,对“我就想拿一张图试试”的场景并不友好。所以我自己写了一个极简推理脚本,逻辑清晰,方便你逐步验证环境、权重、预处理流水线是否全通。
import torch import mmcv import numpy as np from mmseg.models import build_segmentor from mmseg.apis import inference_segmentor, init_segmentor config_file = 'local_configs/segformer/B0/segformer.b0.512x512.ade.160k.py' checkpoint_file = 'pretrained/segformer.b0.512x512.ade.160k.pth' # 模型初始化,显式指定GPU model = init_segmentor(config_file, checkpoint_file, device='cuda:0') # 读取任意图片,输出是list,里面是HxW的预测标签图 img_path = 'test.jpg' result = inference_segmentor(model, img_path) # 保存可视化结果,附件会叠加在原图上 model.show_result(img_path, result, out_file='test_seg.jpg', opacity=0.6) # 如果想看各类别像素分布,可以用np.bincount统计 labels, counts = np.unique(result[0], return_counts=True) for lab, cnt in zip(labels, counts): print(f'class {lab}: {cnt} pixels')这个脚本里init_segmentor会自己读取config、加载checkpoint、构建模型并加载权重,不用手动load_state_dict,避免了很多人踩的“权重键名对不上”问题。第一次跑如果没报错,并且能在test_seg.jpg里看到清晰的语义分割色块,就说明环境、权重、推理链路全部打通了。
4.3 官方test.py跑数据集评估时,需要注意的路径与参数格式
想复现论文里的mIoU,就要用官方数据集评估。以ADE20K为例,先把数据集下载好,目录结构按mmseg要求组织:
data/ ade/ ADEChallengeData2016/ annotations/ training/ validation/ images/ training/ validation/然后修改config中的data_root。官方config里写的是data_root = 'data/ade/ADEChallengeData2016',如果你的数据集在别的路径,改这个地方,别去改一堆data.train、data.val里的具体路径,因为它们都基于data_root拼接。
跑评估的命令:
python tools/test.py local_configs/segformer/B0/segformer.b0.512x512.ade.160k.py \ pretrained/segformer.b0.512x512.ade.160k.pth \ --eval mIoU如果你的显存或显存带宽足够,可以把--eval后面加上mDice、mFscore一起评估。4090的24G显存跑B0验证集完全没压力。评估结果会在终端里逐类打印IoU,最后汇总mIoU。我这个环境里B0在ADE20K val上复现到37%左右,和论文报告的37.32%对得上,说明环境和权重属实没问题。
5. 运行与测试:用一张图和验证集分别验证流程是否真的通了
5.1 单图推理的完整输出与预期结果
用第4节的脚本跑一张ADE20K里的室內图,你会看到这样的输出:
class 0: 12345 pixels class 10: 4567 pixels class 88: 1024 pixels ...预测标签的数值范围是0到149(ADE20K共150类),这些值对应CLASSES元组里的具体类别名,model.CLASSES可以直接查看。可视化输出test_seg.jpg里,同一类别的区域会统一成一个色块,边界轮廓清晰,说明decoder部分工作正常。
如果输出图是一团乱麻、色彩完全不对,常见原因有两个:一是预处理时图片被错误resize,导致模型输入比例失衡;二是权重加载不完整,模型实际是随机初始化状态。排查顺序是先看init_segmentor有没有输出“load checkpoint from ...”的日志,再检查图片路径的读取是否正常。
5.2 验证集评估的耗时与显存参考
用官方tools/test.py跑ADE20K val(共2000张图),在4090上B0模型大概需要2到3分钟,B5模型在640x640输入下大约10分钟左右,具体取决于显卡功耗限制和驱动版本。显存方面,B0在batch size 1下大概占2.5GB,B5占5GB左右,24G显存远远够用。
| 模型 | 输入尺寸 | 推理显存占用(batch=1) | val 2000张耗时参考 | ADE20K val mIoU |
|---|---|---|---|---|
| SegFormer-B0 | 512x512 | 约2.5GB | 约2-3分钟 | 约37.3% |
| SegFormer-B2 | 512x512 | 约3.8GB | 约4分钟 | 约45.6% |
| SegFormer-B5 | 640x640 | 约5GB | 约10分钟 | 约49.5% |
(注:这些耗时是在我的机器上的量级参考,不同驱动版本和散热策略会有波动,但有一个趋势是确定的:SegFormer的高效性在4090上体现得非常明显,B5也就这个量级。)
5.3 4090上特别值得关注的GPU利用率问题
跑测试时盯着nvidia-smi看,你可能会发现GPU利用率只有30%到60%,显存也没吃满。这不是代码有问题,而是SegFormer的解码器本身轻量,batch size又只有1,主力瓶颈在数据加载和预处理上,GPU根本没有被喂饱。想榨干性能可以:
- 在
DataLoader参数里调高num_workers,减少CPU预处理拖后腿 - 把测试的
samples_per_gpu提到2或4,提升GPU利用率 - 打开
cudnn.benchmark = True,让cuDNN针对当前输入尺寸做算法选择
不过这些都是后期优化,第一轮验证老老实实用batch=1跑通,别一上来就开优化,出了问题会更难排查。
6. 踩坑实录:这几天我遇到过的报错和排查链路
6.1 “No kernel image available”的完整追溯过程
这个报错是我接手4090后遇到的第一道坎,信息量很少,就一句话。当时我的排查链路是:
nvidia-smi看驱动,显示正常,CUDA版本12.x。- 用
torch.cuda.get_device_name(0)验证PyTorch能识别显卡,识别到了。 - 随便跑个
torch.randn(100, 100).cuda() @ torch.randn(100, 100).cuda(),正常。 - 一旦跑SegFormer的decode head,立刻崩,报No kernel image available。
到这里才意识到,PyTorch基础张量操作能跑,不等于所有CUDA算子都能跑。mmcv的自定义算子里有些编译目标是旧架构,在4090上缺少对应SASS。解决方式就是把mmcv版本升级到我在第2节写的那套组合,让所有算子都带上sm_89的机器码。
这个报错给的经验是:遇到CUDA相关崩溃,先分清楚是PyTorch基础库的问题,还是第三方扩展库的问题,排查范围能缩小一半。
6.2 mmcv版本兼容性引发的SegFormer head报错
另一个高频报错是:
AttributeError: 'ConfigDict' object has no attribute 'xxx'或者:
TypeError: format() argument 'indent' must be an int, not None这类报错十有八九是mmcv版本和mmsegmentation版本不匹配。mmcv 1.7.x的ConfigDict行为和旧版默认值有差异,而mmseg 0.13.0的开发时间线更早,两者接口对接时就容易出现奇葩报错。
我当时的解决路径是:
- 先用
pip list | grep mm检查版本组合,发现装的是mmcv-full 1.7.2 + mmsegmentation 0.13.0。 - 查阅open-mmlab的版本对应表,发现1.7.2对应的mmsegmentation应该在0.20.0以上。
- 升级到mmsegmentation==0.20.2,报错消失。
这就是为什么我整个“环境搭建”章节里反复强调版本组合。你单独看每一个包都没问题,但它们凑在一起可能就有问题,最好直接按经过验证的组合来。
6.3 印象最深的两个“小时级”坑:apex与triton
这两个坑都会在编译或启动时冒出来,但本质完全不同。
第一个是apex。很多老教程会在装SegFormer时让你顺手装一下NVIDIA的apex优化库,说能加速。但这个库在4090上遇到新版PyTorch,经常在编译阶段卡死,而且SegFormer官方代码根本没有强制依赖apex。我的建议是:不装,完全不影响官方推理和评估。
第二个是triton。新版mmcv在编译时如果检测到triton,会尝试启用一些高版本特性算子。这些算子在老config里不一定被触发,但一旦被触发,容易变成“能load但推理结果全错”的隐蔽bug。保险做法是编译mmcv时不要额外安装triton,或者在环境里pip uninstall triton,确保mmcv跑回经典的自定义算子路径。
这两个坑都是“没有报错但浪费你时间”的类型,前者是编译期卡死,后者是推理结果诡异,写出来希望你直接避过。
一路走下来,我最大的感触是:在4090这种新架构显卡上复现老仓库,真正卡你的通常不是模型原理,而是“老版本代码与新版编译链之间的错配”。拿到一个老仓库,第一件事不要急着读模型代码,而是先把它的依赖版本和环境声明全部列出来,然后逐一对照当前显卡算力重新组合出一套可行的版本矩阵。这套思路跑通了SegFormer,换到其他老仓库同样适用。