说到BEVFusion,很多刚转入自动驾驶方向的读者应该都不陌生:它以LiDAR和Camera两种异构传感器的特征融合为核心,在鸟瞰视图(BEV)下做统一的3D目标检测,一度是学术榜和工业落地都绕不开的模型。不过真正劝退很多人的不是算法本身,而是环境搭建这一步——PyTorch版本、mmcv全家桶、spconv编译、nuscenes-devkit配套,任何一个环节错位都能让你卡上两三天。这篇文章就基于我自己从头到尾踩过的那些坑,把BEVFusion的LiDAR-Camera融合3D目标检测环境搭建过程完整拆开,从硬件评估、CUDA选型、源码编译到数据准备、多卡训练,全程记录,给出一份可以直接照着抄的避坑清单。
这里涉及的核心关键词也很集中:BEVFusion、LiDAR-Camera融合、3D目标检测、环境搭建。适合的读者包括刚入门自动驾驶感知方向的研究生、准备复现论文效果的算法工程师,以及对BEV融合方法感兴趣但总被环境问题劝退的开发者。
1. 项目背景与整体设计
1.1 BEVFusion是什么,为什么要做LiDAR-Camera融合
3D目标检测领域长期存在两条技术路线:纯LiDAR方案和纯Camera方案。LiDAR点云能提供精确的深度和几何信息,在KITTI等数据集上精度很高,但缺点是点云稀疏、成本高,单靠点云做远距离小目标识别并不稳定;摄像头图像拥有密集的纹理和颜色信息,分辨率高,却天生缺乏深度,一旦光照变化或目标遮挡,单目测距误差就会显著放大。BEVFusion的核心思路就是不再把两种模态在输入阶段或结果层面做简单的“拼接”,而是分别提取点云特征和图像特征,转换到统一的BEV网格空间里做融合,再送入检测头完成3D框回归和分类。
这个设计带来的好处很直接:一方面,神经网络学习到了跨模态互补的信息;另一方面,BEV表示天然适合后续的规划、控制模块,前后处理链路更简洁。论文中还有针对性设计,比如图像分支通过深度估计将2D特征投影到3D空间,再通过体素池化操作形成BEV特征,点云分支则采用常见的体素化加稀疏卷积来生成BEV特征。这些细节对环境的要求其实并不高,真正的难点在于把各类CUDA算子、开源库版本配上套。
1.2 环境搭建的整体思路与版本选型考虑
在动手装环境之前,最省时间的做法是先明确“版本版本选型”这个大前提。BEVFusion官方仓库(MIT-HAN-LAB/BEVFusion,以及后续的BEVFusion_final)基于PyTorch 1.9.x、mmcv-full 1.6.0、mmdet 2.25.0、mmsegmentation 0.14.1、spconv 2.x开发。很多人在跑通之后擅自升级到新版本,结果不是算子接口对不上,就是模型加载时状态字典缺失一大片。我的建议是:如果你只是想复现实验,严格锁定官方requirements;如果你想切到更新的mmcv版本,那就要做好修改源码的准备,时间成本并不低。
另外要强调一个容易忽略的坑:BEVFusion仓库里没有统一的requirements.txt能够一键装完所有依赖,spconv、mmcv系列、nuscenes-devkit、timm都需要按特定顺序安装。而且BEVFusion作者在代码中使用了比较底层的CUDA算子,例如Voxelization、scatter操作、depthnet中的BEVPool等,都需要本地编译,所以必须提前备好与CUDA版本匹配的gcc、ninja。综合下来的推荐组合是Ubuntu 18.04或20.04 + CUDA 11.3 + Python 3.8 + PyTorch 1.9.1,兼容性和性能都最稳。
2. 基础环境准备
2.1 硬件评估与驱动/CUDA安装
在动手之前,先评估硬件。BEVFusion是双模态模型,训练时同时跑图像backbone(ResNet或Swin)和点云backbone(稀疏卷积),显存占用明显高于纯点云方案。以我在RTX 3090(24GB显存)上的实测为例,batch_size为1、输入分辨率接近论文默认设置时,单卡显存吃到16GB左右;如果开了梯度累积或大batch,24GB也会被吃得很紧张。所以建议最低配置是一块显存不小于16GB的卡,训练默认配置至少要有两块24GB显存,否则只能把batch_size调小,但调的太小收敛效果会受影响。
NVIDIA驱动和CUDA的安装其实没有太多玄学,关键是先确认显卡驱动版本支持哪个CUDA版本。用nvidia-smi看到右上角显示的CUDA Version是驱动支持的最高版本,比如驱动535.x通常支持到CUDA 12.2,向下兼容CUDA 11.x,所以即使你装CUDA 11.3也没问题。
nvidia-smi # 查看驱动版本与最高CUDA版本CUDA 11.3的安装建议直接使用runfile方式,不要用deb包,因为runfile可以自定义安装路径并保留系统原有的软链接。安装完以后务必配置环境变量:
export PATH=/usr/local/cuda-11.3/bin:$PATH export LD_LIBRARY_PATH=/usr/local/cuda-11.3/lib64:$LD_LIBRARY_PATH然后通过nvcc -V验证。这里有一个我踩过好几轮的坑:很多仓库内部编译时调用的是CUDA_HOME这个环境变量,如果你装了CUDA但没有设置,后面mmcv或spconv编译会出现“找不到cuda_runtime.h”。所以不管有没有把cuda目录软链到/usr/local/cuda,都建议在~/.bashrc里加上export CUDA_HOME=/usr/local/cuda。
2.2 conda环境与Python版本
Python版本方面,3.8是最稳妥的选择。BEVFusion大量使用mmcv 1.x系列,而这个系列在Python 3.10以上会有很多隐性问题,比如C++扩展编译时对Py_SSIZE_T_CLEAN的处理方式不同,时不时冒出一堆诡异的编译警告和错误。3.9还能勉强跑,但为了避免无谓的排查成本,直接用3.8就好。
创建环境的命令没什么特殊的,但建议把conda的channel优先级设好,以免装依赖时被源的问题卡住。
conda create -n bevfusion python=3.8 -y conda activate bevfusion pip install torch==1.9.1+cu111 torchvision==0.10.1+cu111 -f https://download.pytorch.org/whl/torch_stable.html这里有个细节:官方推荐用torch==1.9.1+cu111,不要自己去pip装最新的torch,否则后面编译的扩展可能因为ABI不兼容而加载失败。强烈建议在激活环境后先pip install ninja,别等编译到一半才意识到缺少ninja,那会耽误很多时间。
2.3 源码获取与目录结构
获取BEVFusion源码建议直接clone官方仓库,这里以BEVFusion_final为例,它的代码组织比早期版本更清晰。
git clone https://github.com/mit-han-lab/bevfusion.git # 如果网络不稳定,可以使用镜像加速地址clone完成后,建议别再git pull最新更新,因为仓库后续可能有新的依赖调整,但论文复现结果未必能对得上。写项目时建议固定commit号,并记录下当前commit,方便以后回溯。
git log --oneline -1源码的目录结构大致如下:
mmdet3d/是核心代码,包含模型定义、数据增强、检测头、NMS等;mmdet/、mmseg/是mmcv生态中的配套库;tools/存放训练、测试、转换数据的入口脚本;configs/里面是各个数据集的实验配置。
很重要的一点是:BEVFusion的代码修改了部分mmdet和mmdet3d的内部源码,这也就是为什么必须用源码方式安装mmdet和mmdet3d,而不是直接pip装一个现成版本。如果直接pip install mmdet3d,很可能与BEVFusion的结构定义版本不一致,进而导致checkpoint加载时出现unexpected key。
3. 核心依赖安装与源码编译
3.1 编译工具链与前置依赖
编译这类CUDA项目,最怕的就是gcc版本过新与CUDA不兼容。CUDA 11.3官方支持的最大gcc版本是gcc-10,而Ubuntu 20.04默认的gcc-9和gcc-10都能用,Ubuntu 22.04默认gcc-11则与CUDA 11.3不完全兼容,所以建议提前用gcc --version检查一下,超了就临时装一个gcc-10:
sudo apt install gcc-10 g++-10 -y sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-10 100另外无论Ubuntu哪个版本,都建议先装好这些基础库,否则在编译mmcv-full的过程中会冒出各种缺失头文件的报错:
sudo apt update sudo apt install build-essential ninja-build libglib2.0-0 libsm6 libxext6 libxrender-dev libgomp1 -y还有一个常见问题:opencv-python和opencv-python-headless不可混装。BEVFusion在数据预处理时要读图片,在可视化时代码又会调用cv2.imshow,这时候如果只装了headless版本会直接报错。纯训练环境建议装常规版本,但如果你在无显示器的服务器上跑可视化,则需要把headless版本也备好,并且在调用可视化前用环境变量切换。
3.2 mmcv系列组件安装
mmcv环境是BEVFusion最大的坑来源。很多人直接把mmcv-full装成最新版,然后发现代码里调用的函数名已经变了。BEVFusion要求的是mmcv-full 1.6.0,这个版本目前不能直接用pip install一下完事,建议用源码编译,因为官方构建的wheel不一定包含所有自定义算子。
pip install cython pip install mmcv-full==1.6.0 -f https://download.openmmlab.com/mmcv/dist/cu111/torch1.9.0/index.html如果这一步编译太慢,可以选择MMCV_WITH_OPS=1 FORCE_CUDA=1 pip install mmcv-full==1.6.0,但要留意repo里有些函数修改过,建议直接用源码方式安装更保险。
mmdet和mmseg同理:
pip install mmdet==2.25.0 pip install mmsegmentation==0.14.1这三个库的版本号必须锁定,尤其是mmdet,BEVFusion在配置文件中直接依赖FCOS3D等检测头,新版mmdet把这些结构拆到了mmdet3d中,接口变化巨大,版本一旦不对,各种KeyError、ModuleNotFoundError都会冒出来。
3.3 spconv与扩展算子编译
spconv是点云稀疏卷积的核心库。BEVFusion要求spconv 2.x,这个库的编译需要注意的点非常集中:它依赖cumm,而cumm要求与CUDA版本严格匹配,否则在import时就会提示找不到libcudnn或算子未注册。安装命令:
pip install spconv-cu113spconv-cu113表示针对CUDA 11.3的预编译版本,如果你的CUDA版本是11.3,直接pip即可,不用源码编译,极大节省时间。但如果你换了不同的CUDA小版本,还是老老实实从源码编译吧。安装完成后,进入Python环境验证一下:
import spconv print(spconv.__version__)如果能正常输出版本号,基本说明环境没问题。接下来是编译BEVFusion扩展的部分,主要是mmdet3d和mmdet中包含的一些自定义算子,比如voxelization、points_op、iou3d等。在项目根目录执行:
python setup.py develop此时会调用setuptools编译所有CUDA扩展。这里有几个高频报错,我挨个说一下。
如果出现fatal error: cusparse.h: No such file or directory,通常是CUDA_HOME没设置好,回到2.1节检查环境变量。如果出现undefined symbol: _ZN2at...这类符号找不到,基本都是PyTorch版本和编译时的ABI不匹配,重新按照1.9.1+cu111安装即可。如果ninja编译到一半卡死或OOM,可以在编译命令前加MAX_JOBS=4限制编译并行度:
MAX_JOBS=4 python setup.py develop这条命令非常实用,服务器内存不大时能避免编译进程被打断。
4. 数据准备与预处理
4.1 nuScenes数据集结构
BEVFusion默认使用nuScenes数据集,这是目前自动驾驶领域最常用的多模态数据集之一。你要先到官网注册账号,申请下载权限,然后把数据下载下来。官方数据包结构大致如下:
nuscenes/ ├── maps/ ├── samples/ ├── sweeps/ ├── v1.0-trainval/ │ ├── sample_annotation.json │ ├── scene.json │ ├── sample_data.json │ └── ... └── can_bus/需要注意,下载的时候还需要下载can_bus包,因为BEVFusion在数据处理和可视化时可能会用到IMU/GPS信息。很多人只下载了完整训练集和地图包,漏掉can_bus,结果在生成pkl时提示找不到相关文件。
为了降低磁盘占用和验证时间,建议先在v1.0-mini上跑通全流程。mini版只有10个场景,训练和验证各约1500帧左右,完全可以在半小时内完成一次训练验证全流程。等代码和参数都调通了,再下载完整的trainval数据也不迟。
4.2 生成数据pkl文件
BEVFusion官方的数据准备脚本在tools/create_data.py。执行时,需要先建立一个软链接或把数据路径直接通过参数传过去。下面是一个实际跑通的命令:
python tools/create_data.py nuscenes --root-path ./data/nuscenes --out-dir ./data/nuscenes --extra-tag nuscenes --version v1.0-mini --canbus ./data/can_bus这个过程需要一点耐心,尤其是首次运行会重建infos,包括逐帧读取每辆车的标定参数、点云token、图像token、标注的3D框等。生成的nuscenes_infos_train.pkl和nuscenes_infos_val.pkl就是后续训练和评测直接使用的索引文件。
有一个坑必须提醒:生成pkl时的nuscenes-devkit版本和推理时需要加载nuscenes数据集的版本最好保持一致。如果你在其他项目里升级了nuscenes-devkit,重新回到BEVFusion时建议在虚拟环境里重新安装一个特定版本,例如:
pip install nuscenes-devkit==1.1.10因为新版nuscenes-devkit对标注类别顺序和属性字段的解析有变化,可能导致类别id错位,最终评测时的mAP与官方模型完全对不上。
4.3 目录建议与软链接
数据放在哪个路径很重要。BEVFusion的configs默认从./data/nuscenes读取数据,因此建议在项目根目录下建一个软链接:
mkdir -p data ln -s /path/to/your/nuscenes ./data/nuscenes ln -s /path/to/your/can_bus ./data/can_bus这样无论是训练还是评估,都不需要每次手动修改config里的路径。另一个容易被忽略的点是,nuscenes的mini版本和完整版本在目录名字上有差异,如果你只用mini,生成pkl时--version v1.0-mini必须写对;训练时config中data字段里的version也要同步改,否则程序会去加载v1.0-trainval的json文件,然后报一堆KeyError。
5. 训练与测试实操
5.1 单卡训练与参数说明
环境、数据都准备到位后,先跑一个简单的单卡训练。BEVFusion官方提供的训练配置叫做bevfusion_lidar_voxel0075_rcnn.py,对应的是LiDAR-only的单模态基线。全模态融合的配置是bevfusion_lidar_voxel0075_rcnn.py这个文件里通过modality=dict(use_camera=True)等字段控制的,具体可以直接看config内容。
启动训练的命令很固定:
python tools/train.py configs/bevfusion_lidar_voxel0075_rcnn.py --work-dir work_dirs/test_run训练开始后,你会发现BEVFusion的日志打印信息比mmdet3d默认版本多很多,主要原因是它加了多模态时间对齐和各类loss的统计指标。如果发现loss一直不降,首先检查数据pkl是否生成正确,其次看是否用了随机初始化权重:BEVFusion的图像分支默认会加载预训练ResNet权重,如果你在--pretrained参数里没有指定下载好的权重,第一次训练时它的backbone会随机初始化,收敛会慢很多。
下载ImageNet预训练权重的环节值得多说一句。mmdet框架在加载预训练模型时会自动到官网链接拉取权重,但在国内环境下多数情况会超时。更稳的做法是手动下载resnet50,放到~/.cache/torch/hub/checkpoints/目录下,或者放到自定义路径后在config里通过load_from参数指定。
5.2 多卡训练与常见训练问题
BEVFusion支持多卡训练,使用官方脚本:
CUDA_VISIBLE_DEVICES=0,1,2,3 bash tools/dist_train.sh configs/bevfusion_lidar_voxel0075_rcnn.py 4 --work-dir work_dirs/dist_run多卡模式能显著缩短训练时间,但也暴露了一些单卡不易出现的问题。最常见的是NCCL通信失败,报错往往带有timeout或connection reset字样。这一问题有相当概率出在NCCL的P2P通信上,特别是使用3090、4090等不带NVLink互联的卡时。推荐在训练脚本前加上:
export NCCL_P2P_DISABLE=1 export NCCL_IB_DISABLE=1这会让NCCL走共享内存或TCP通道替代GPU直接内存访问,虽然通信速度会慢一些,但稳定性大幅提升。
另一个多卡运行的坑是参数广播不一致。如果你的数据集分布在多台机器上,或者使用了海量小文件,而文件读取顺序不一致,会导致每张卡看到的数据顺序不同,进而引发梯度同步时不收敛。这种情况一般表现为训练loss波动大、valid mAP极差,而在单卡训练时一切正常。解决方法是设置固定的worker_seed,并开启shuffle的随机种子,让每张卡的dataloader保持一致。
5.3 模型评估与可视化
训练完成后,评估命令也很简洁:
python tools/test.py configs/bevfusion_lidar_voxel0075_rcnn.py work_dirs/dist_run/epoch_20.pth --eval bbox跑完会输出各类别在BEV视角和3D视角下的AP,以及Nuscense官方标准中的NDS指标。如果你用的模型是官方提供的预训练权重,正常环境配置能复现论文中接近mAP 65%以上的结果;如果你从零开始训练mini集,指标会明显偏低,不用慌张,这只是验证训练流程贯通。
可视化对排查问题很有用,BEVFusion支持在测试模式下保存预测结果的3D框和点云。如果你想快速看看融合效果,可以用官方提供的可视化脚本,将BEV图像、图像检测结果、点云检测结果保存成图片序列。这一步往往还能发现一个另类问题:某些batch的数据里图像尺寸或标定矩阵异常,导致特征投影错位,前端训练loss看似正常,但3D框和图像框完全对不上。遇到这种情况优先检查数据pkl的是否用了不同版本的nuscenes-devkit生成,这一条我前面已经强调过。
6. 常见问题与避坑指南
6.1 环境坑速查表
为了便于排查,我把整个搭建过程中最常遇到的错误和对应解决办法整理成了一张表。建议把它收藏起来,复现时遇到问题先对照一遍。
| 问题现象 | 可能原因 | 解决办法 |
|---|---|---|
ModuleNotFoundError: mmcv | 没有安装mmcv,或安装了mmcv而不是mmcv-full | pip install mmcv-full==1.6.0 |
AttributeError: module 'mmcv' has no attribute 'ops' | mmcv安装的是纯Python版,没有编译CUDA算子 | 源码安装mmcv-full,或以预编译wheel方式安装 |
ImportError: libcudnn.so.8: cannot open shared object file | CUDA与cuDNN版本不匹配,或LD_LIBRARY_PATH没包含cuda库 | 安装对应的cuDNN 8.x并检查环境变量 |
No module named 'spconv' | 未安装spconv | pip install spconv-cu113 |
KeyError: 'voxel_rcnn' | mmdet3d版本不一致,BEVFusion修改被覆盖 | 在BEVFusion源码根目录重新执行python setup.py develop |
训练时CUDA error: out of memory | 显存不足 | 降低batch_size、降低图像输入尺寸或开启cudnn.benchmark与混合精度 |
| 多卡训练时通信卡住 | NCCL P2P通路异常 | 添加NCCL_P2P_DISABLE=1、NCCL_IB_DISABLE=1 |
加载checkpoint时unexpected key in source state_dict | 模型结构定义不匹配,例如mmdet版本太新导致检测头命名不同 | 对照config和checkpoint的键名,确认mmdet、mmcv版本 |
生成pkl后训练报KeyError: 'LIDAR_TOP' | 数据token命名异常或软链接路径错误 | 检查数据目录结构,确认samples路径下存在LIDAR_TOP子目录 |
6.2 训练运行时的整体经验
除了上面表格里这些显式报错,还有一些不会报错但严重影响体验的问题。比如数据加载非常慢,训练一个epoch要几个小时,很可能是dataloader的num_workers没有调起来。BEVFusion默认配置里workers_per_gpu可能只有2,建议在卡数不多时手动调到4或8,但也不要调得过大,否则数据读取会成为瓶颈并拖慢整个训练进程。
另外,BEVFusion在使用图像分支时,需要把多视角图像统一resize到固定分辨率,而不同视角图像的宽高比不同,会造成一些黑边和变形。如果发现图像特征与点云特征没有对齐,不妨在config里调整img_norm_cfg和resize策略,而不是怀疑模型代码出了问题。
还有一点非常值得注意:BEVFusion数据增强策略中附带了一些随机翻转操作,这类操作会将BEV空间中的3D框倒置,如果你在推理阶段需要在原图视角显示检测框,必须把翻转过程也加到测试时的数据流水线里,否则框会错位。许多人在训练后评估指标正常,但可视化时发现框全乱,就是因为测试时没有同步增强参数的设置。
我在实际搭建中最大的体会是,BEVFusion环境最难的部分不是某个单独组件的安装,而是版本间的隐性耦合。PyTorch、mmcv、spconv、nuscenes-devkit这四者的版本只要有一个不匹配,后面就会引发一串连锁问题,很多报错并不会直接告诉你“版本不对”,而是以一个更隐蔽的API变更形式出现,排查起来极其难受。
最后再说一个小技巧:把一套正在正常运行的环境做成镜像或conda环境导出文件备份下来,等以后再想复现实验时直接恢复,能省下海量时间。我自己的习惯是把整个conda虚拟环境和源码目录打成一个tar包存档,同时在服务器上保留一份环境依赖清单,用pip freeze > requirements.txt记录版本,这样即使环境崩了也能快速重建。BEVFusion这个模型本身非常值得复现,只要你跨过了环境这道坎,后续做消融实验、换backbone、改融合策略都会顺利不少。