首页 / 资讯中心 / 文章详情

PyTorch 2.x升级OpenPCDet踩坑指南:spconv/mmcv版本兼容与算子重编译

PyTorch 2.x升级OpenPCDet踩坑指南:spconv/mmcv版本兼容与算子重编译 ★ FEATURED ARTICLE
上个月我把一台实验机的 PyTorch 从 1.10.1 升到 2.1.0紧接着 OpenPCDet 的训练脚本开场直接崩了。起初我以为是 CUDA 环境变量写错翻来覆去折腾了半天最后发现是 PyTorch 2.x 的 C 扩展 ABI 变化导致之前编译的所有点云算子全部需要重新编译。这个坑我踩得很深所以想把整个升级过程中遇到的所有报错、修复思路和实测可用的版本组合完整记录下来给还在 PyTorch 1.x 上跑 OpenPCDet 的朋友一个可以直接抄作业的路线。OpenPCDet 不是那种装完就能跑通的普通 Python 包它依赖 spconv、mmcv、mmdet还带着 pointnet2、iou3d、roipool3d 一堆 C/CUDA 扩展算子。PyTorch 1.x 到 2.x 这代升级核心变化不在 Python 层的 API而在底层 libtorch 的 ABI、分布式训练实现、AMP 自动混合精度接口、以及一部分 C 头文件的移除。这些变化单独看都不致命但叠加到 OpenPCDet 这种多依赖、多自定义算子的项目上就会变成连环报错。下面把我实际遇到的坑按顺序拆开讲每个问题都会给原因分析和可复现的解决办法。1. 升级前必须弄懂的底层差异1.1 为什么 PyTorch 2.x 会让 OpenPCDet“炸”很多人在升级后第一反应是改 Python 代码但 OpenPCDet 在一开始就挂在加载.so文件上。PyTorch 1.x 下编译出来的扩展包符号表里带着当时的 libtorch 版本标记PyTorch 2.x 里的 libtorch 已经换了一轮内部实现加载旧.so时就会报undefined symbol。这个现象很像两个不同语言版本编译的库硬拼在一起表面上文件都在实际上互相不认。第二个核心变化是 PyTorch 2.x 默认引入torch.compile以及新的 dispatcher 机制导致很多 C 扩展里用到的私有头文件路径发生改动。最典型的是THC/THC.h被移除一大堆老算子源码还在#include THC/THC.h编译时直接死在头文件缺失这一步。第三个变化容易被忽略PyTorch 2.x 的torch.autograd.Function对forward的输入输出类型检查更严格部分 OpenPCDet 老版本写的算子没有显式标set_materialize_grads(False)在反向传播时会报奇怪的梯度错误。这种问题不是必现但一旦出现很难排查。1.2 先看依赖链OpenPCDet 不是单一个 PyTorch 就完事OpenPCDet 的完整链路是PyTorch spconv稀疏卷积核心 mmcv/mmdet用于协同训练和数据处理 点云预处理工具 若干自定义算子。PyTorch 升级后spconv 如果还是 1.2.1 版本基本无法在 PyTorch 2.x 下正常编译运行因为 spconv 1.x 对旧版 Torch 的私有接口依赖非常重。mmcv 也要从 1.x 升到 2.x而 mmcv 1.x 的mmcv-full包在 PyTorch 2.x 下同样有兼容性问题。这里我想强调一个容易被忽略的心态问题升级 PyTorch 不是“单点替换”而是整个依赖链的版本重构。先花二十分钟把版本矩阵列清楚比闷头改代码省一天时间。我见过有人尝试在 PyTorch 2.1 下硬编 spconv 1.2.1折腾了整晚最后还是换成了 spconv 2.3.x。1.3 我的版本对齐建议下面这份版本组合是我在几台不同显卡环境含数据中心级 GPU 和普通消费级显卡下实际跑过 OpenPCDet 训练和测试的稳定性可以接受组件推荐版本说明Python3.93.10 也行但部分点云算子编译时对std::filesystem支持要求更高PyTorch2.0.1 / 2.1.02.0.1 最稳2.1.0 也可以用但别急着上 2.2CUDA11.8配 2.0.1 / 12.1配 2.1.0两个组合我都实测过spconv2.3.6不要用 2.3.6 以下部分算子编译不过mmcv2.0.1不要用 mmcv-full那个项目已停止维护mmdet3.0.0和 mmcv 2.x 配套tensorboardX2.6.1日志可视化用或者直接换成新版 TensorBoard为了减少变量我的建议是固定 Python 3.9 CUDA 11.8 PyTorch 2.0.1 spconv 2.3.6 mmcv 2.0.1 mmdet 3.0.0。这套组合跑通了之后再考虑往 PyTorch 2.1 迁移。2. 环境准备先别急着改代码把环境重装一遍2.1 用 conda 创建干净环境在已经装过 PyTorch 1.x 的机器上直接原地升级依赖大概率会留下各种.so残留文件导致错误信息非常混乱。我强烈建议新建一个 conda 环境让所有旧包与新环境隔离。具体操作如下conda create -n openpcdet2 python3.9 -y conda activate openpcdet2 pip install torch2.0.1 torchvision0.15.2 torchaudio2.0.1 --index-url https://download.pytorch.org/whl/cu118这里用--index-url而不是直接在 pip.conf 里写死是因为 OpenPCDet 的依赖安装还需要拉取默认 PyPI 上的包避免所有包都被引导到一个源去。装完验证一下 GPU 可用性python -c import torch; print(torch.__version__, torch.version.cuda, torch.cuda.is_available())输出应该是类似2.0.1cu118 11.8 True。如果is_available()返回 False别急着继续装先检查驱动是否支持 CUDA 11.8或者是否安装成了 CPU 版本的 PyTorch。2.2 安装 spconv 2.xspconv 是 OpenPCDet 稀疏卷积的基础直接pip install spconv会拉下 2.x 版本但这里有个坑PyPI 上的 spconv 2.3.6 对 PyTorch 2.0.x 的二进制兼容性还行可如果你用的是 PyTorch 2.1.0建议用源码编译因为部分轮子没有针对 2.1 的构建。pip install spconv2.3.6如果后续在 import 时报undefined symbol再用源码编译方式git clone https://github.com/traveller59/spconv.git cd spconv git checkout v2.3.6 pip install -r requirements.txt python setup.py bdist_wheel pip install ./dist/spconv-*.whl源码编译时请确认nvidia-smi显示的驱动版本和 PyTorch 里的 CUDA 版本能对上否则编译出来虽然能装上但运行时一样会崩。2.3 安装 mmcv/mmdet 与 OpenPCDet 的配合mmcv 2.x 的安装方式和 1.x 完全不一样。1.x 时代大家习惯了pip install mmcv-full2.x 里已经没了这个包名直接装mmcv就会带 CUDA 算子前提是必须用--no-cache-dir重新编译。pip install mmcv2.0.1 --no-cache-dir pip install mmdet3.0.0OpenPCDet 依赖 mmcv 的地方主要在数据增强、卷积初始化和检测头比如mmcv.cnn.bricks里的Conv2d、build_norm_layer。mmcv 2.x 把一些mmcv.ops的位置移动到了mmcv.ops包内但 OpenPCDet 新版本已经做了兼容处理。如果你的 OpenPCDet 是几个月前拉的老版本这里可能会遇到cannot import name conv_ws_2d之类的报错最快的方式是更新 OpenPCDet 源码到较新版本或者自己改 import 路径。2.4 编译自定义算子的标准姿势OpenPCDet 的pcdet/ops目录下有 pointnet2、iou3d、roipool3d 等算子。PyTorch 升级后这些算子必须重新编译。我按目录给出一份建议顺序cd pcdet/ops/pointnet2/pointnet2_batch rm -rf build dist *.egg-info python setup.py install cd ../../pointnet2/pointnet2_stack rm -rf build dist *.egg-info python setup.py install cd ../../iou3d rm -rf build dist *.egg-info python setup.py install cd ../../roipool3d rm -rf build dist *.egg-info python setup.py install每次编译前删掉build和*.egg-info是必须的。我自己有次偷懒只跑了python setup.py install结果 pip 检测到旧.so直接跳过了编译后续测试时加载的还是 PyTorch 1.x 时代遗留的二进制文件。为了保险可以在整个ops目录下先跑一个清理find pcdet -name *.so -delete find pcdet -name *.pyc -delete然后再从 pointnet2 开始安装。3. 编译阶段那些undefined symbol与THC/THC.h报错3.1 核心原因解析一旦环境装好接下来的报错基本都集中在编译阶段。最典型的错误长这样undefined symbol: _ZN2at6detail11getCUDAHooksEv这个报错的意思是当前加载的.so文件里引用了一个在 libtorch 中找不到的符号。根本原因是旧.so是 PyTorch 1.x 时代编译的而新环境里的 libtorch 不再导出getCUDAHooks这个函数。解决办法就是重编译但很多人在重编译之后依然遇到这个错因为setup.py install并没有真正触发重建工作目录下还残留着旧.so。另一个高频报错是THC/THC.h: No such file or directoryPyTorch 2.x 把 THC 从公开头文件里移除。老算子源码通常会在pointnet2_batch或iou3d的 CUDA 文件里出现#include THC/THC.h这时候不能简单地把头文件改成ATen/cuda/CUDAContext.h因为原来的代码里可能还用了THCState、THCudaMalloc等结构。但大多数点云算子用 THC 只是拿一个当前 CUDA stream所以替换方法很简单。3.2 针对 pointnet2、iou3d、roipool 的源码补丁以pointnet2_batch的pointnet2_kernel.cu为例原来的写法通常是#include THC/THC.h extern THCState* state; cudaStream_t stream at::cuda::getCurrentCUDAStream();改成#include ATen/cuda/CUDAContext.h cudaStream_t stream at::cuda::getCurrentCUDAStream();同时删掉所有THCState变量声明。因为 PyTorch 2.x 里getCurrentCUDAStream()不再依赖THCState直接用全局 CUDA context 就能获取当前 stream。iou3d和roipool3d也可能遇到AT_CHECK宏失效PyTorch 1.x 里普遍用AT_CHECK(cond, message)到了 2.x 需要改成TORCH_CHECK(cond, message)这两个宏本质上作用一样只是AT_CHECK在新版本里被标记为 deprecated某些情况下会直接编译失败。遇到类似错误时用 vscode 或直接在终端里全局替换grep -rn AT_CHECK pcdet/ops/ --include*.cpp --include*.cu把出现的位置统一替换成TORCH_CHECK即可。3.3 编译前必须修改的 setup.py 写法老版本 OpenPCDet 的setup.py可能会硬编码extra_compile_args比如强制-archsm_75。如果你的显卡是 RTX 30 系或 40 系这个参数会编译出不适合当前 GPU 的算子或者在运行时提示no kernel image is available。我建议把setup.py里的extra_compile_args和extra_ldflags里的 CUDA 架构参数先去掉让 PyTorch 自动检测当前显卡的算力。很多undefined symbol问题其实不是 ABI 不匹配而是编译出来的 cubin 架构不对运行时加载失败后抛出的错误非常误导人看起来像符号缺失实际上是架构不匹配。另外在setup.py中如果发现写死了include_dirs指向某个旧版 PyTorch 的 include 路径也一定要删掉。比如include_dirs [/usr/local/lib/python3.7/site-packages/torch/include]这种写死的方式在新环境里会拉到旧头文件导致各种奇奇怪怪的编译冲突。正确做法是不写 include_dirs或使用torch.utils.cpp_extension.include_paths()动态获取。4. 运行阶段的常见报错与规避技巧4.1 分布式训练脚本的兼容性修改编译通过后训练阶段的坑就轮到torch.distributed.launch。OpenPCDet 旧文档里通常用python -m torch.distributed.launch --nproc_per_node4 tools/train.py --cfg_file cfgs/kitti_models/pointpillar.yaml在 PyTorch 2.x 下这个命令仍然能用但控制台会打印 deprecation 警告。更关键的是OpenPCDet 的init_dist函数如果还在读取环境变量OMPI_COMM_WORLD_RANK或RANK在torchrun下也能工作但如果你使用新版torchrun本地 rank 会从LOCAL_RANK读取部分老版本 OpenPCDet 没做兼容会出现所有进程都使用同一个 GPU 的情况。我推荐的启动方式是torchrun --nnodes1 --nproc_per_node4 tools/train.py --cfg_file cfgs/kitti_models/pointpillar.yaml同时确认源码中pcdet/utils/comm.py的init_dist是否这样写local_rank int(os.environ.get(LOCAL_RANK, 0))如果还是读os.environ[RANK]需要自己加一行兼容逻辑。4.2 AMP 混合精度报错处理OpenPCDet 默认以 FP32 训练但很多同学会在--set参数里开启混合精度。PyTorch 2.x 的torch.cuda.amp.GradScaler接口没变但问题出现在自定义算子本身没做 Half 支持。pointnet2 的ball_query、grouping_operation这些 CUDA 核函数很多只写了float版本一旦你用torch.half输入运行到forward时直接报RuntimeError: expected scalar type Half but found Float这不是因为你代码写错而是算子根本没有 half kernel。最简单的解决办法是不要在自定义算子层面开 AMP把混合精度限定在正常的卷积和全连接层或者直接全 FP32 跑。OpenPCDet 本身的显存占用在 PyTorch 2.x 下比 1.x 稍高一些但整体还在可接受范围。如果显存不够优先减小batch_size而不是贸然开 AMP。4.3 张量属性与 API 变化的“隐形杀手”PyTorch 2.x 移除了一些 1.x 时期经常出现在开源项目里的内部 API其中最常见的是torch._six。你在运行训练脚本时如果看到AttributeError: module torch._six has no attribute PY3说明某个依赖还在用旧写法。搜索所有 Python 文件grep -rn torch._six --include*.py .把这些引用全部移除。PY3这个常量在 Python 3 环境下永远为 True直接删掉相关判断即可。如果是from torch._six import string_classes改成string_classes str。还有一个坑是torchvision的nms接口。新版 PyTorch 2.x 要求 torchvision 与 torch 版本对齐如果 torch 是 2.0.1torchvision 建议 0.15.2。如果版本错位可能在from torchvision.ops import boxes时报undefined symbol。这个错在 import 阶段不会暴露在第一次执行 NMS 时才会触发非常折磨人。4.4 数据加载阶段的 worker 数量与缓存问题OpenPCDet 在 KITTI 数据集上训练时--workers 4是很常规的配置。但 PyTorch 2.x 的 DataLoader 在num_workers0时如果环境中存在旧版openssl或libgomp的冲突会出现DataLoader worker (pid(s) X) exited unexpectedly。我建议先把--workers 0跑通一个小 epoch再逐步加大 worker 数。如果确认是系统级库冲突用 conda 安装libgomp是有效的。5. 实测心得与避坑清单5.1 我的实际升级顺序这次升级我最开始是直接升级 PyTorch结果一地鸡毛。后来我把顺序调整成先建新环境 → 固定 Python 版本 → 安装 PyTorch → 验证 torchvision 匹配 → 安装 spconv → 编译 OpenPCDet 算子 → 最后装 mmcv/mmdet → 运行验证脚本。这个顺序很重要因为 mmcv 的编译依赖于 PyTorch 环境而 spconv 也需要看到 PyTorch 的库路径。如果先装 mmcv 再装 PyTorch很容易导致 mmcv 拿到错误的编译参数。验证脚本我推荐先跑一个最轻量的python tools/train.py --cfg_file cfgs/kitti_models/pointpillar.yaml --batch_size 2 --workers 0 --set DATA_CONFIG.NUM_WORKERS 0用单卡、零 worker、极小 batch size 跑通一个 step确认没有任何算子层面的报错再进入正式训练。5.2 避坑清单速查表现象根本原因解决方式undefined symbol: getCUDAHooks旧.so残留删除 build/egg-info清理*.so后重新编译THC/THC.h not foundPyTorch 2.x 移除 THC 头文件替换为ATen/cuda/CUDAContext.h去掉 THCStateAT_CHECK编译失败宏在新版本中被弃用全局替换为TORCH_CHECKno kernel image is availableCUDA 架构不匹配删除setup.py中硬编码的 arch让 Torch 自动检测module torch._six has no attribute PY3旧内部 API 被移除搜索并删除torch._six引用expected scalar type Half but found Float自定义算子不支持 half关闭 AMP使用 FP32 训练DataLoader worker exited unexpectedly系统 libgomp 冲突先--workers 0验证再排查系统库5.3 最后一个建议版本“向上兼容”不等于“配置兼容”我在升级过程中最大的体会是开源项目文档里的“支持 PyTorch 2.x”往往意味着“最新 commit 支持”而你自己 clone 的某个稳定分支未必支持。所以在动手之前先确认 OpenPCDet 的当前 commit 时间最好把整个项目更新到较新版本再执行升级流程。旧版本配新 PyTorch 所需改动太多不如直接换新代码。另外我强烈建议用一个小技巧把整个环境记录成文件保存下来方便后续复现conda env export environment_2x.yaml pip freeze requirements_2x.txt这样即使过两三个月再搭另一台机器也不用从头回忆版本。如果以后还需要在 PyTorch 1.x 上跑旧实验也不要急着删掉旧 conda 环境。最后再分享一个小技巧在跑正式训练前用torch.autograd.detect_anomaly()包住一小段验证代码可以更快定位到失败算子。升级过程中我遇到过一次反向传播报错错误堆栈完全没有指向 OpenPCDet 的 Python 层反而是出现在一个自定义 CUDA 算子的反向函数里用异常检测之后才发现是forward里的一个中间变量没有requires_grad。这种问题只靠日志很难排查但通过一个小测试脚本能迅速定位比无头绪地试版本快得多。
阅读完成 · 觉得有帮助?
咨询建站