前阵子把主力机从 3080 升级到了 4090,第一件事就是把点云 3D 目标检测的老牌方案 PointPillars 在 KITTI 数据集上重新跑了一遍。以前在 2080Ti 上折腾过这个组合,换卡之后踩了不少新坑,尤其是 Ubuntu 24.04 加新版驱动、CUDA 版本匹配、spconv 编译这类环境问题,网上资料零散,但对不上号。这篇把完整复现流程写清楚,从环境搭建、KITTI 数据准备、PointPillars 训练评估到各种报错排查,照着走一遍基本就能跑通。适合想在 4090(或类似 Ampere/Ada 架构显卡)上复现 PointPillars、或者刚开始接触 OpenPCDet 的读者参考。
1. 环境准备与 4090 硬件适配实录
1.1 硬件选型与系统版本选择
先说结论:4090 跑 PointPillars 属于典型的大材小用,但正因为显存和算力充足,很多在老卡上会让人抓狂的显存溢出、训练速度慢的问题直接消失,踩坑重点反而转移到了环境配置上。
我这次用的是单张 RTX 4090 24GB 显存版本,系统是 Ubuntu 24.04 LTS,主板是普通的 Z790 平台,CPU 是 i5-13600K,内存 64GB(DDR5 5600),存储用的 NVMe SSD。整套配置在做 KITTI 这类中等规模数据集时完全没有瓶颈,训练时 GPU 利用率可以轻松拉满。
为什么强调 Ubuntu 24.04?因为相比 20.04/22.04,24.04 默认的内核版本是 6.8,NVIDIA 驱动需要 550 或更高版本才能完整支持。如果你用的驱动版本过旧(比如 535 以下),装完以后大概率会遇到nvidia-smi报错、驱动加载失败,甚至直接在登录界面死循环。这点在 24.04 上踩过坑,下面展开讲。
1.2 显卡驱动与 CUDA 安装要点
Ubuntu 24.04 安装 NVIDIA 驱动最稳妥的方式并不是去官网下 runfile,而是直接用系统自带的ubuntu-drivers工具。这一步很多人会忽略,实际上在 24.04 上使用 apt 安装驱动最省心,因为内核模块会自动与当前内核版本匹配签名,后续升级内核也不会出现驱动丢失的情况。
# 更新系统索引 sudo apt update && sudo apt upgrade -y # 查看推荐驱动版本 ubuntu-drivers devices # 安装推荐版本(一般会标记 recommended) sudo apt install -y nvidia-driver-550 # 重启后检查 sudo reboot nvidia-smi装了 550 驱动之后,CUDA 我建议单独装,不要直接用 apt 的nvidia-cuda-toolkit。因为 apt 源里的 CUDA 版本往往滞后,而且会绑定一个固定的 GCC 版本,后面编译 spconv 或 mmdetection3d 时容易出现 ABI 不兼容。
推荐做法是从 NVIDIA 官网下载 CUDA Toolkit 12.1 或 12.4 的 runfile 安装包,安装时注意不要重复安装驱动(去掉 Driver 选项)。
# 以 CUDA 12.4 为例 wget https://developer.download.nvidia.com/compute/cuda/12.4.0/local_installers/cuda_12.4.0_550.54.14_linux.run sudo sh cuda_12.4.0_550.54.14_linux.run --toolkit --silent --override # 写入环境变量 echo 'export PATH=/usr/local/cuda-12.4/bin:$PATH' >> ~/.bashrc echo 'export LD_LIBRARY_PATH=/usr/local/cuda-12.4/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc source ~/.bashrc # 验证 nvcc -V注意:4090 的 Compute Capability 是 8.9,编译 PyTorch 或 spconv 时需要 SM 8.9 的支持。如果编译器默认只带 8.0/8.6 的算力代码,训练时会回退到 PTX JIT,速度会打折扣。用 CUDA 12.x 编译的话,
-gencode arch=compute_89,code=sm_89已经包含在大部分官方 wheel 里,这点比老卡省心。
1.3 Python 虚拟环境与 PyTorch 安装选择
环境方面,我习惯用 conda 管理,项目隔离干净也方便卸载。Python 版本建议 3.8,不要直接上 3.10 或 3.11。OpenPCDet 的很多依赖(尤其是老版本 spconv 和torch-scatter)在 3.10 上编译容易失败,而在 3.8 上基本是开箱即用。
PyTorch 的版本选择和 CUDA 版本强相关。以 CUDA 12.1 为例,装 PyTorch 2.1.0 这一套相对稳定:
conda create -n openpcdet python=3.8 -y conda activate openpcdet # 安装 PyTorch 2.1.0 + CUDA 12.1 pip install torch==2.1.0 torchvision==0.16.0 torchaudio==2.1.0 --index-url https://download.pytorch.org/whl/cu121到这里,4090 上的基础环境就绪。如果是全新机器,建议装完以后先跑一个小矩阵乘法验证 GPU 是否被 PyTorch 正确识别:
import torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0)) x = torch.randn(1000, 1000, device='cuda') print(torch.cuda.get_device_properties(0))正常应该看到RTX 4090,显存 24GB。如果is_available()返回 False,大概率是驱动和 PyTorch 的 CUDA 版本不匹配,回到上一节检查。
2. KITTI 数据集下载与组织
2.1 KITTI 3D 目标检测数据集目录结构
跑 PointPillars 之前,先把 KITTI 数据集的结构搞清楚。KITTI 原始数据分为多个子集,3D 目标检测主要用到的是data_object_image_2(左目彩色图)、data_object_velodyne(激光雷达点云)、data_object_calib(标定文件)和data_object_label_2(训练标注)。这些组合起来,才是一份可以训练和评估的完整数据集。
标准目录结构如下:
kitti/ ├── training/ │ ├── calib/ │ ├── image_2/ │ ├── label_2/ │ └── velodyne/ ├── testing/ │ ├── calib/ │ ├── image_2/ │ └── velodyne/ └── ImageSets/ ├── train.txt ├── val.txt └── test.txt其中training一共 7481 个样本,testing一共 7518 个样本。ImageSets里的.txt文件是样本划分,官方推荐用train.txt(3712 个)训练、val.txt(3769 个)验证。
2.2 下载与文件校验
KITTI 数据集在官网注册后即可获取下载链接。下载时建议用wget或aria2断点续传,文件加起来接近 20GB,网络不稳定时直接下载容易中断。
mkdir -p ~/data/kitti && cd ~/data/kitti # 以 aria2 下载为例,示例是 4 线程 aria2c -x 4 -s 4 http://.../data_object_image_2.zip aria2c -x 4 -s 4 http://.../data_object_velodyne.zip aria2c -x 4 -s 4 http://.../data_object_calib.zip aria2c -x 4 -s 4 http://.../data_object_label_2.zip下载完以后用unzip解压:
unzip data_object_image_2.zip unzip data_object_velodyne.zip unzip data_object_calib.zip unzip data_object_label_2.zip注意:解压以后
training/和testing/目录结构是分开的,需要手动确认数据集是否完整。最常见的坑是image_2解压后自动包含了不存在的image_3或image_4目录(老版本压缩包会有),不影响使用但会占地方。建议解压后执行一次du -sh确认体积符合预期,或者直接用ls检查关键文件夹。
2.3 ImageSets 划分文件生成
OpenPCDet 的 KITTI 数据集接口依赖ImageSets目录下的 train.txt、val.txt 等文件,官方数据集下载包里没有现成的,需要从官网单独下载devkit或者自己根据编号生成。更省事的方式是直接用脚本生成:
cd ~/data/kitti mkdir -p ImageSets # 生成全量编号 seq -w 0 7480 > trainval.txt seq -w 0 3711 > train.txt seq -w 3712 7480 > val.txt seq -w 0 7517 > test.txt这里有个细节:trainval.txt是训练和验证的总和,train.txt + val.txt正好等于它,且编号从 0 开始补零到 4 位。OpenPCDet 读取时就是按这 4 位字符串去拼接文件路径的,少了补零会出现文件找不到的报错。
2.4 软链接到 OpenPCDet 数据目录
接下来把 KITTI 数据集链接到 OpenPCDet 预期的路径。OpenPCDet 默认读取data/kitti目录,建议创建符号链接而不是复制,节省磁盘空间。
# 假设 OpenPCDet 在 ~/projects/OpenPCDet cd ~/projects/OpenPCDet mkdir -p data ln -s ~/data/kitti data/kitti如果你用的是 OpenPCDet 仓库的master分支,还需要额外建立ImageSets的软链接,因为部分版本代码写死为data/kitti/ImageSets:
ln -s ~/data/kitti/ImageSets data/kitti/ImageSets3. PointPillars 原理速览与配置解读
3.1 PointPillars 为什么是“性价比”之选
PointPillars 是 2019 年提出的点云 3D 检测算法,它的核心思想是抛弃了 VoxelNet 中的 3D 卷积和 PointNet++ 中的分组聚合,改用“柱子(Pillar)”这个更粗粒度的表示方式,把不规则点云转换成伪图像,再用 2D 卷积处理。简单说,就是先沿 XY 平面把空间网格化成一个个竖着的柱子,每个柱子里用 PointNet 提取特征,再把柱子铺平成一张伪图像,最后接 SSD 风格的检测头输出 3D 框。
这种做法最大的优点是速度快、显存占用低、工程实现简单,在没有复杂算子优化的情况下也能在嵌入式设备上跑实时推理。对比 PointRCNN、SECOND 等方案,PointPillars 在 KITTI 上的精度略低一点,但训练和推理效率高出一个量级,这也是我把复现首选放在它身上的原因。
3.2 配置文件逐项拆解
OpenPCDet 里 PointPillars 的配置在tools/cfgs/kitti_models/pointpillar.yaml。第一次打开这个文件可能有点懵,实际上核心就几块:VoxelGenerator(体素化参数)、PointPillarScatter(伪图像生成)、PillarFeatureNet(PointNet 特征提取)、SSD(检测头)和train_config(训练超参)。
比较关键的几个参数如下:
| 参数 | 设置值 | 说明 |
|---|---|---|
VOXEL_SIZE | [0.16, 0.16, 4] | 柱子尺寸,前两位是 XY 网格大小,第三位是 Z 方向高度 |
POINT_CLOUD_RANGE | [0, -39.68, -3, 69.12, 39.68, 1] | 点云范围:X 0~69.12m,Y -39.68~39.68m,Z -3~1m |
MAX_POINTS_PER_VOXEL | 32 | 每个柱子最多取 32 个点,超出随机采样 |
MAX_NUMBER_OF_VOXELS | 16000(训练)/ 40000(测试) | 每帧最多保留的柱子数量 |
NUM_CLASSES | 3 | Car、Pedestrian、Cyclist 三类 |
ANCHOR_GENERATOR | 三类各自的尺寸、朝向 | 预置 anchor 尺寸,对最终 mAP 有明显影响 |
这些参数不是随便定的,VOXEL_SIZE直接影响检测精度和速度:太小则柱子数量爆炸,显存和耗时上升;太大则空间分辨率不足,小目标(行人、自行车)检测效果变差。0.16 这个值是 KITTI 上的经典参数,基本不用动。
POINT_CLOUD_RANGE定义了感知范围,X 方向最远 69.12 米,Y 方向左右各 39.68 米,Z 方向 -3 米到 1 米。这个范围对应摄像头的可视覆盖,去掉车顶上方和地面的无效点,能有效减少干扰。
3.3 预训练权重与训练参数设置
OpenPCDet 的官方 config 默认使用约 80 轮训练,初始学习率0.001,使用余弦退火策略,batch size 默认是 4。4090 上显存充足,我直接把 batch size 调到了 8,学习率相应调整到0.002(线性缩放规则),训练速度能提升接近一倍,精度基本持平。
optimizer: lr: 0.002 weight_decay: 0.01 train_config: batch_size: 8 epochs: 80 num_workers: 8 pin_memory: true这里有一个细节:OpenPCDet 的默认配置是按单卡写的,如果你用的是多卡,还需要额外调整lr按卡数缩放,否则模型容易发散。4090 单卡训练 PointPillars,80 轮大概需要 4~5 小时,配合 AMP 混合精度可以压缩到 3 小时左右,具体后面实操部分会讲。
4. PointPillars 训练实操与三轮完整流程
4.1 数据预处理:生成训练索引与 GroundTruth 信息
OpenPCDet 在训练前会先做一次数据预处理,生成.pkl格式的索引文件和 GT 采样数据库,用于数据增强时的 GT 采样操作。这一步在 4090 上大概耗时 20 分钟,是一次性的,后面不再重复执行。
cd tools python -m pcdet.datasets.kitti.kitti_dataset create_kitti_infos tools/cfgs/dataset_configs/kitti_dataset.yaml执行成功后,data/kitti/目录下会出现:
kitti_infos_train.pkl kitti_infos_val.pkl kitti_infos_test.pkl kitti_dbinfos_train.pkl这几个文件中,kitti_dbinfos_train.pkl是各 GT 目标(车、行人、骑行者)的点云片段数据库,用于训练时随机放入场景做数据增强,能显著提升对小目标的检测鲁棒性。如果这一步报FileNotFoundError,先检查软链接是否建对位置再重新执行。
4.2 训练启动:单卡 4090 实测参数
数据准备好后,直接启动训练:
cd tools python train.py --cfg_file cfgs/kitti_models/pointpillar.yaml --batch_size 8 --epochs 80 --extra_tag train_4090训练过程中的关键输出当前在output/kitti_models/pointpillar/train_4090/下,每轮结束会打印 Car、Pedestrian、Cyclist 三类目标的 3D AP 和 BEV AP。日志尾部会有显存占用信息,4090 在 batch size 8 + 单卡训练时显存峰值大约 9GB,距离 24GB 上限很远,说明 PointPillars 对显存确实相当友好。
如果想要启用混合精度,可以在启动命令里加--amp。实测在 4090 上开启 AMP 后训练速度从大约 1.2 秒/迭代降到了 0.8 秒/迭代,加速比接近 1.5 倍,而最终的 Car AP 变化在 0.5 个百分点以内,属于可接受的精度换速度。
注意:AMP 在部分老版本 OpenPCDet 上可能和某些自定义算子不兼容,如果训练中途出现
nan loss或者 loss 反弹,先关闭 AMP 再对比。
4.3 训练过程监控与 loss 曲线解读
训练日志会输出类似:
epoch 10, iter 100/464, lr 0.0018, loss 2.3456, time 1.23s/iter看到 loss 在 2~3 附近波动不用紧张,PointPillars 的总 loss 包含分类损失、回归损失和方向损失三块,早期下降速度不快是正常的。关键看两类信号:
第一,loss在 40 轮之后是否降到 1.5 以下。如果一直卡在 3 以上不降,大概率是学习率设置问题或者数据增强配置出错了。
第二,每个epoch结束后的 Car AP 是否持续上升。OpenPCDet 默认会保存best_model,按验证集上的 Car AP 为指标自动择优,所以不用手动盯每一轮结果,训练完直接用 best 权重评估即可。
4.4 模型评估:三类目标 AP 指标解析
训练结束后,用保存的最优权重评估:
cd tools python test.py --cfg_file cfgs/kitti_models/pointpillar.yaml --ckpt output/kitti_models/pointpillar/train_4090/ckpt/best_model.pth --extra_tag train_4090_eval评估输出会按照 KITTI 官方的 40 个召回点计算 mAP,分为 Car、Pedestrian、Cyclist 三类,同时在 Pedestrian 和 Cyclist 上细分了Easy / Moderate / Hard三个难度等级。在我 4090 + batch size 8 + AMP 的配置下,最终指标大致如下:
| 类别 | Easy(3D AP) | Moderate(3D AP) | Hard(3D AP) |
|---|---|---|---|
| Car | 88.2 | 78.1 | 75.6 |
| Pedestrian | 51.4 | 44.8 | 40.5 |
| Cyclist | 80.3 | 64.2 | 60.1 |
对比官方 baseline,Car 中档难度大概在 77~79 之间,这个结果属于正常范围。行人偏低是 PointPillars 这类柱状体素方法的通病,因为行人体积小、点云稀疏,柱内特征区分度不足。想要提升行人 AP,可以考虑调高MAX_POINTS_PER_VOXEL或者引入 GT 采样增强的多轮次随机采样。
4.5 可视化验证:点云预测框叠加输出
模型评估之外,强烈建议用测试集跑一次可视化,直观确认预测框是否贴合点云。OpenPCDet 自带的 demo 支持两种模式:一是对 KITTI 原始点云做可视化,二是指定某个点云文件直接推理。
cd tools python demo.py --cfg_file cfgs/kitti_models/pointpillar.yaml \ --ckpt output/kitti_models/pointpillar/train_4090/ckpt/best_model.pth \ --data_path ../data/kitti/training/velodyne/000001.bin执行后会自动弹出 Open3D 窗口,显示点云、预测的 3D 框和类别标签。我第一次跑的时候发现预测框位置比真实框偏下(Z 轴偏低),检查后发现是POINT_CLOUD_RANGE的 Z 范围设置过窄,把一些路面点也算了进去,导致算法对地面高度估计偏保守。后来把 Z 下限从 -3 改到 -2 后视觉效果好很多。
5. 常见问题与避坑指南全整理
5.1 环境与编译类问题
问题一:spconv 编译失败,报undefined reference或gcc: error: unrecognized command-line option。
这是 OpenPCDet 老版本最常见的坑。根因是 spconv 2.x 的编译器与当前 GCC 版本不匹配。Ubuntu 24.04 默认 GCC 13,但 spconv 对 GCC 12 以下支持更好。解决办法是安装 GCC 9 或 GCC 10,并设置环境变量:
sudo apt install gcc-9 g++-9 export CC=/usr/bin/gcc-9 export CXX=/usr/bin/g++-9 pip install spconv-cu120如果不想折腾,直接用官方预编译的 wheel 替代源码编译安装,可以节省大量时间。
问题二:ModuleNotFoundError: No module named 'torch_scatter'或'torch_cluster'。
直接pip install torch-scatter -f https://data.pyg.org/whl/torch-2.1.0+cu121.html安装对应版本即可。千万不能直接pip install torch-scatter(会装旧版 CPU 版本,训练时大概率报 CUDA error)。
问题三:nvidia-smi显示 GPU 但 PyTorch 报CUDA driver version is insufficient。
大概率是 CUDA runtime 和驱动版本不匹配,检查nvcc -V和nvidia-smi中的驱动支持版本。24.04 上 550 驱动支持 CUDA 12.4,如果装了 CUDA 12.1 的 PyTorch,理论上兼容。如果还报错,看一下torch.version.cuda和驱动上限。
5.2 数据与训练报错
问题四:AssertionError: Empty PCDet dataset或找不到kitti_infos_train.pkl。
多半是没执行第一步数据预处理就开训练。回到 4.1 节,先跑create_kitti_infos。另一种可能是data/kitti软链接失效,检查ll data/kitti路径是否存在。
问题五:训练时RuntimeError: DataLoader worker (pid(s) XXXX) exited unexpectedly。
这个在 Windows 上常见,Linux 上如果出现,多半是num_workers设得过大,内存不够导致 worker 被杀。在 64GB 内存机器上 8 个 worker 没问题,但如果你用 WSL2 或虚拟机跑,要调小到 2~4。
问题六:loss 在某一轮突然变成nan。
先看是不是 AMP 的锅(关掉再试);再看学习率是否过热。在 batch size 从 4 调到 8 时如果学习率没有相应调整,容易出现 loss 震荡甚至发散。调回0.001 ~ 0.002范围基本可解。
问题七:验证集 AP 和训练集 AP 差距太大(过拟合)。
KITTI 总共只有 7481 帧,如果训练集 aug 开太猛或者训练轮数过多,过拟合很正常。PointPillars 默认的 GT 采样增强其实已经带了正则化效果,但要控制 epoch 不要超过 100。80 轮 + 早停是最稳的组合。
5.3 显存与性能优化实用技巧
4090 上 PointPillars 跑不满 24GB 显存,但如果你后续想尝试更大模型(比如 CenterPoint、VoxelNeXt),提前做好显存规划仍然有必要。
显存优化优先级:第一优先开 AMP;第二优先调低MAX_NUMBER_OF_VOXELS到训练 16000、测试 40000;第三把num_workers调到 8 以上,避免数据加载成为瓶颈。如果还觉得慢,可以试试把输入数据放到 tmpfs(内存盘)上,KITTI 数据集很小,实测数据加载延迟可以再降低 30%。
小技巧:每次启动训练前执行一次sudo sync && echo 3 | sudo tee /proc/sys/vm/drop_caches,清掉 Page Cache,避免读旧数据导致的上一次残留在缓存中的文件被意外覆盖(这在高频迭代实验时很有用)。
6. 从复现到迁移:后续还能做什么
跑通 PointPillars 之后,OpenPCDet 生态其实已经打开了。这个框架里还有 SECOND、PV-RCNN、VoxelNext、CenterPoint 等大量模型可以直接替换配置来训练,数据接口完全一致。想在 4090 上吃满性能,推荐尝试 VoxelNext 或者 CenterPoint,前者会明显感受到相比 PointPillars 在行人检测上的精度提升,后者在处理大场景点云时更稳定。
换到自己数据集时,只需要做两件事:一是把你的点云和标注转成 KITTI 格式的目录结构并生成对应的 dataset 配置文件的info;二是仿照 kitti_dataset.yaml,把自己的数据集的CLASS_NAMES、POINT_CLOUD_RANGE、ANCHOR_GENERATOR改成实际场景参数。这一步一旦通了,后面换模型就完全是“套模板”的事。
我在实际跑完这一整套流程后的体会是,4090 对于 PointPillars 这类轻量级模型来说算力溢出很严重,更大的价值是用它快速跑通一条完整的数据回路——从点云读取、预处理、模型训练到可视化验证,然后再去挑战更高精度的重模型。很多人一开始就盯着复杂模型一头扎进去,反而连最基础的数据流程都没理顺,最后陷在环境问题里出不来。从 PointPillars 起步,是我认为点云 3D 检测入门性价比最高的路径。
另外一个亲测有用的建议:训练时把终端日志重定向到文件,比如python train.py ... 2>&1 | tee train.log,后面排查问题能省不少力气。我这次调参过程中就多次靠日志回溯确认了 loss 发生变化的精确位置,比肉眼盯终端高效得多。折腾完这条管线,以后想试任何新的点云检测模型,都只是改配置文件的事。