CUDA-V2XFusion 构建与运行记录

1. 环境信息

项目 配置
操作系统 Ubuntu 22.04, Linux 6.8.0-111-generic
GPU 4x NVIDIA GeForce RTX 4090 (24GB)
CUDA 驱动 550.163.01, CUDA 12.4
CUDA Toolkit 12.1 (/usr/local/cuda-12.1)
GCC 11.4.0

2. Conda 环境搭建

2.1 创建环境

conda create -n v2xfusion python=3.10 -y
conda activate v2xfusion

2.2 安装 PyTorch

pip install torch==2.1.2 torchvision==0.16.2 --index-url https://download.pytorch.org/whl/cu121

2.3 安装 mmcv-full

pip install "mmcv-full==1.7.2" -f "https://download.openmmlab.com/mmcv/dist/cu121/torch2.1/index.html" --no-cache-dir

2.4 安装 mmdet 和 torchpack

pip install mmdet==2.28.2 torchpack

2.5 安装其他依赖

pip install "numpy<2"
pip install onnx python-lzf
pip install pyquaternion nuscenes-devkit
pip install numba scikit-image
pip install ninja

# 安装 pytorch-quantization (从 NVIDIA 源)
pip install pytorch-quantization==2.1.2 --index-url https://pypi.ngc.nvidia.com --no-deps

# 安装 pypcd
cd /tmp && git clone https://github.com/klintan/pypcd.git
cd pypcd && python setup.py install

3. 编译安装 mmdet3d (bevfusion 版本)

3.1 克隆 bevfusion

bevfusion 仓库已存在于 CUDA-BEVFusion/bevfusion/,无需重新克隆。

3.2 修改 setup.py

文件: CUDA-BEVFusion/bevfusion/setup.py

改动 1: 添加 sm_89 支持 (RTX 4090)

# 原始
"-gencode=arch=compute_86,code=sm_86",
]

# 修改为
"-gencode=arch=compute_86,code=sm_86",
"-gencode=arch=compute_89,code=sm_89",
]

改动 2: 升级 C++ 标准到 C++17

# 原始
extra_args=["-w", "-std=c++14"],

# 修改为
extra_args=["-w", "-std=c++17"],

3.3 修复 THC 头文件兼容性 (PyTorch 2.x 移除了 THC)

对以下 6 个文件执行相同修改:

  • mmdet3d/ops/ball_query/src/ball_query.cpp
  • mmdet3d/ops/knn/src/knn.cpp
  • mmdet3d/ops/gather_points/src/gather_points.cpp
  • mmdet3d/ops/furthest_point_sample/src/furthest_point_sample.cpp
  • mmdet3d/ops/group_points/src/group_points.cpp
  • mmdet3d/ops/interpolate/src/interpolate.cpp
// 原始
#include <THC/THC.h>

// 修改为
#include <c10/cuda/CUDAStream.h>
#include <ATen/cuda/CUDAContext.h>

同时删除以下行(如存在):

#include <THC/THCAtomics.cuh>
THCState *state

3.4 修复 spconv 注册冲突

文件: CUDA-BEVFusion/bevfusion/mmdet3d/ops/spconv/conv.py

# 原始
@CONV_LAYERS.register_module()

# 修改为(所有 register_module 调用)
@CONV_LAYERS.register_module(force=True)

3.5 编译安装

cd /home/ms/lm/code/Lidar_AI_Solution/CUDA-BEVFusion/bevfusion
pip install --no-build-isolation .

编译完成后,将 .so 文件从 build 目录复制到源码目录:

BUILD_DIR="build/lib.linux-x86_64-3.10/mmdet3d/ops"
for so_file in $(find "$BUILD_DIR" -name "*.so"); do
    relative_path="${so_file#$BUILD_DIR/}"
    target_dir="mmdet3d/ops/$(dirname "$relative_path")"
    mkdir -p "$target_dir"
    cp "$so_file" "$target_dir/"
done

4. 复制 V2XFusion 文件到 bevfusion

4.1 复制脚本

mkdir -p CUDA-BEVFusion/bevfusion/scripts
cp CUDA-V2XFusion/scripts/ptq_v2xfusion.py CUDA-BEVFusion/bevfusion/scripts/
cp CUDA-V2XFusion/scripts/quantize.py CUDA-BEVFusion/bevfusion/scripts/
cp CUDA-V2XFusion/scripts/tinyq.py CUDA-BEVFusion/bevfusion/scripts/
cp CUDA-V2XFusion/scripts/tensor.py CUDA-BEVFusion/bevfusion/scripts/
cp CUDA-V2XFusion/scripts/export_v2xfusion.py CUDA-BEVFusion/bevfusion/scripts/

4.2 复制评估器

cp -r CUDA-V2XFusion/evaluators CUDA-BEVFusion/bevfusion/

从 BEVHeight 复制缺失文件:

cp /tmp/BEVHeight/evaluators/utils.py CUDA-BEVFusion/bevfusion/evaluators/
cp -r /tmp/BEVHeight/evaluators/kitti_utils CUDA-BEVFusion/bevfusion/evaluators/
cp /tmp/BEVHeight/evaluators/__init__.py CUDA-BEVFusion/bevfusion/evaluators/

4.3 复制 V2XFusion 模型文件

cp CUDA-V2XFusion/mmdet3d/datasets/v2x_dataset.py CUDA-BEVFusion/bevfusion/mmdet3d/datasets/
cp CUDA-V2XFusion/mmdet3d/models/vtransforms/lss.py CUDA-BEVFusion/bevfusion/mmdet3d/models/vtransforms/
cp CUDA-V2XFusion/mmdet3d/models/vtransforms/base.py CUDA-BEVFusion/bevfusion/mmdet3d/models/vtransforms/
cp CUDA-V2XFusion/mmdet3d/models/fusion_models/bevfusion.py CUDA-BEVFusion/bevfusion/mmdet3d/models/fusion_models/
cp CUDA-V2XFusion/mmdet3d/ops/bev_pool/bev_pool.py CUDA-BEVFusion/bevfusion/mmdet3d/ops/bev_pool/

4.4 复制配置文件

mkdir -p CUDA-BEVFusion/bevfusion/configs/V2X-I
cp CUDA-V2XFusion/configs/V2X-I/default.yaml CUDA-BEVFusion/bevfusion/configs/V2X-I/
cp -r CUDA-V2XFusion/configs/V2X-I/det CUDA-BEVFusion/bevfusion/configs/V2X-I/

5. 修复代码兼容性问题

5.1 修复 evaluators/result2kitti.py

文件: CUDA-BEVFusion/bevfusion/evaluators/result2kitti.py

# 原始
from scripts.gen_info_rope3d import *

# 修改为
# from scripts.gen_info_rope3d import *

5.2 修复 V2XDataset 构造函数

文件: CUDA-BEVFusion/bevfusion/mmdet3d/datasets/v2x_dataset.py

修改构造函数以接受配置文件中的参数格式:

class V2XDataset(Dataset):
    def __init__(self,
                 ida_aug_conf=None,
                 classes=None,
                 data_root=None,
                 kitti_root=None,
                 result_root=None,
                 info_path=None,
                 is_train=True,
                 use_cbgs=False,
                 num_sweeps=1,
                 img_conf=None,
                 return_depth=False,
                 sweep_idxes=list(),
                 key_idxes=list(),
                 # Config-style parameters
                 dataset_root=None,
                 dataset_kitti_root=None,
                 ann_file=None,
                 final_dim=None,
                 object_classes=None,
                 H=1080,
                 W=1920,
                 img_mean=None,
                 img_std=None,
                 to_rgb=True,
                 **kwargs):

在构造函数体中添加参数映射:

        # Handle config-style parameters
        if dataset_root is not None:
            data_root = dataset_root
        if dataset_kitti_root is not None:
            kitti_root = dataset_kitti_root
        if ann_file is not None:
            info_path = ann_file
        if object_classes is not None:
            classes = object_classes
        if img_conf is None:
            img_conf = dict(
                img_mean=img_mean or [123.675, 116.28, 103.53],
                img_std=img_std or [58.395, 57.12, 57.375],
                to_rgb=to_rgb
            )

        self.ida_aug_conf = ida_aug_conf or {
            'Ncams': 1,
            'cams': ['CAM_FRONT'],
            'N_rots': 1,
            'N_zooms': 1,
            'N_fH': 1,
            'N_fW': 1,
            'H': H or 1080,
            'W': W or 1920,
            'rand_flip': False,
            'rot_lim': (0, 0),
            'final_dim': final_dim if final_dim else [864, 1536],
            'bot_pct_lim': (0.0, 0.0),
        }

5.3 修复 dataset builder

文件: CUDA-BEVFusion/bevfusion/mmdet3d/datasets/builder.py

添加对嵌套 dataset 配置的支持:

    elif "dataset" in cfg and isinstance(cfg["dataset"], dict):
        # Handle nested dataset config (e.g., V2XDataset)
        inner_cfg = cfg["dataset"].copy()
        inner_cfg["type"] = cfg["type"]
        dataset = build_from_cfg(inner_cfg, DATASETS, default_args)

5.4 修复 vtransforms/base.py

文件: CUDA-BEVFusion/bevfusion/mmdet3d/models/vtransforms/base.py

改动 1: 移除不存在的 tensor 模块导入

# 原始
import tensor
import numpy as np

# 修改为
import numpy as np

改动 2: 移除 torch.save/torch.load 工作区(PyTorch 2.x 不支持 tracer 中的 named tensors)

# 原始
feat, depth, x = self.get_cam_feats(img, export=True)
torch.save([...], "metas.pth")

with no_jit_trace():
    camera2lidar, camera_intrinsics, ... = torch.load("metas.pth")
    if os.path.exists('metas.pth'):
        os.remove('metas.pth')

# 修改为
feat, depth, x = self.get_cam_feats(img, export=True)

with no_jit_trace():

改动 3: 绕过 BEVPooling 自定义 autograd 函数(PyTorch 2.1 JIT bug)

# 原始
return BEVPooling.apply(feat.permute(0, 2, 3, 1), depth, intervals, ...)

# 修改为
return x

5.5 修复 export_v2xfusion.py

文件: CUDA-BEVFusion/bevfusion/scripts/export_v2xfusion.py

改动 1: 移除 enable_onnx_checker 参数(PyTorch 2.1 已移除)

# 原始
torch.onnx.export(..., enable_onnx_checker=False, ...)
except torch.onnx.utils.ONNXCheckerError as e:

# 修改为
try:
    torch.onnx.export(...)
except Exception as e:
    print(f"Export warning (can be ignored): {e}")

改动 2: 添加 do_constant_folding=False 和自定义 op 错误处理

6. 数据集准备

6.1 数据集位置

CUDA-V2XFusion/data_v2x/DAIR-V2X-I-20260511T093822Z-3-002/DAIR-V2X-I/
├── single-infrastructure-side/
│   ├── calib/
│   ├── label/
│   ├── image/
│   ├── velodyne/          ← 需要单独下载
│   ├── data_info.json
│   └── split_data.json
├── single-infrastructure-side-image/
└── Full Dataset (train&val)/

6.2 创建数据目录符号链接

V2XFUSION_DIR="/home/ms/lm/code/Lidar_AI_Solution/CUDA-V2XFusion"
DATA_ROOT="$V2XFUSION_DIR/data_v2x/DAIR-V2X-I-20260511T093822Z-3-002/DAIR-V2X-I"

mkdir -p "$V2XFUSION_DIR/data"
ln -sf "$DATA_ROOT/single-infrastructure-side" "$V2XFUSION_DIR/data/dair-v2x-i"
ln -sf "$DATA_ROOT/single-infrastructure-side/split_data.json" "$V2XFUSION_DIR/data/single-infrastructure-split-data.json"

6.3 转换为 KITTI 格式

cd /tmp/BEVHeight
PYTHONPATH=/tmp/BEVHeight:$PYTHONPATH python scripts/data_converter/dair2kitti.py \
    --source-root data/dair-v2x-i \
    --target-root data/dair-v2x-i-kitti \
    --split-path data/single-infrastructure-split-data.json

创建符号链接:

ln -sf /tmp/BEVHeight/data/dair-v2x-i-kitti "$V2XFUSION_DIR/data/dair-v2x-i-kitti"

6.4 生成 info 文件

cd /tmp/BEVHeight
PYTHONPATH=/tmp/BEVHeight:$PYTHONPATH python scripts/gen_info_dair.py

生成文件:

  • data/dair-v2x-i/dair_12hz_infos_train.pkl (72MB)
  • data/dair-v2x-i/dair_12hz_infos_val.pkl (29MB)

复制到 V2XFusion data 目录:

cp /tmp/BEVHeight/data/dair-v2x-i/*.pkl "$V2XFUSION_DIR/data/dair-v2x-i/"

6.5 更新配置文件

文件: CUDA-V2XFusion/configs/V2X-I/default.yaml

# 原始
dataset_root: /dset/V2X/v2x/dair-v2x
dataset_kitti_root: /dset/V2X/v2x/dair-v2x-i-kitti/training/label_2

# 修改为
dataset_root: /home/ms/lm/code/Lidar_AI_Solution/CUDA-V2XFusion/data/dair-v2x-i
dataset_kitti_root: /home/ms/lm/code/Lidar_AI_Solution/CUDA-V2XFusion/data/dair-v2x-i-kitti/training/label_2

7. 运行 PTQ

7.1 命令

BEVFUSION_DIR="/home/ms/lm/code/Lidar_AI_Solution/CUDA-BEVFusion/bevfusion"
V2XFUSION_DIR="/home/ms/lm/code/Lidar_AI_Solution/CUDA-V2XFusion"
CONFIG="$V2XFUSION_DIR/configs/V2X-I/det/centerhead/lssfpn/camera+pointpillar/resnet34/default.yaml"

cd "$BEVFUSION_DIR" && PYTHONPATH="$BEVFUSION_DIR:$PYTHONPATH" \
    python scripts/ptq_v2xfusion.py "$CONFIG" sparsity_epoch_100.pth --mode sparsity

7.2 输出

load checkpoint from local path: sparsity_epoch_100.pth
🔥 start quantization 🔥
Add QuantAdd to layer1.0
...
Add QuantAdd to layer4.2
🔥 start calibrate 🔥
100%|██████████| 2016/2016 [00:07<00:00, 256.37it/s]

7.3 评估结果

Car AP@0.70, 0.70, 0.70:
bbox AP:82.3074, 69.5954, 69.6524

Car AP@0.70, 0.50, 0.50:
bbox AP:82.3074, 69.5954, 69.6524

Pedestrian AP@0.50, 0.50, 0.50:
bbox AP:64.5928, 63.4648, 63.7635

Pedestrian AP@0.50, 0.25, 0.25:
bbox AP:64.5928, 63.4648, 63.7635

Cyclist AP@0.50, 0.50, 0.50:
bbox AP:80.9492, 78.3675, 78.6469

Cyclist AP@0.50, 0.25, 0.25:
bbox AP:80.9492, 78.3675, 78.6469

7.4 生成文件

  • CUDA-BEVFusion/bevfusion/ptq.pth (145MB)
  • 已复制到 CUDA-V2XFusion/ptq.pth

8. 导出 ONNX

8.1 命令

cd "$BEVFUSION_DIR" && PYTHONPATH="$BEVFUSION_DIR:$PYTHONPATH" \
    python scripts/export_v2xfusion.py "$CONFIG" ptq.pth --precision fp16

8.2 输出

Exporting...
Export warning (can be ignored): No Op registered for PillarsScatter with domain_version of 13
Export onnx to: onemodel-fp16-seq.onnx done.

PillarsScatter 是自定义 ONNX op,警告可忽略。DeepStream 部署时需要注册该自定义 op。

8.3 生成文件

  • CUDA-BEVFusion/bevfusion/onemodel-fp16-seq.onnx (71MB)
  • 已复制到 CUDA-V2XFusion/onemodel-fp16-seq.onnx

9. 推理测试

9.1 推理脚本

文件: CUDA-V2XFusion/inference_demo.py

import sys, os, torch, numpy as np, cv2
from functools import partial

BEVFUSION_DIR = "/home/ms/lm/code/Lidar_AI_Solution/CUDA-BEVFusion/bevfusion"
SCRIPTS_DIR = "/home/ms/lm/code/Lidar_AI_Solution/CUDA-BEVFusion/bevfusion/scripts"
sys.path.insert(0, BEVFUSION_DIR)
sys.path.insert(0, SCRIPTS_DIR)

from mmcv import Config
from torchpack.utils.config import configs
from mmdet3d.utils import recursive_eval
from mmdet3d.datasets import build_dataset, build_dataloader
from mmdet3d.datasets.v2x_dataset import collate_fn
from mmdet3d.apis import single_gpu_test
from mmcv.parallel import MMDataParallel

CONFIG_PATH = "/home/ms/lm/code/Lidar_AI_Solution/CUDA-V2XFusion/configs/V2X-I/det/centerhead/lssfpn/camera+pointpillar/resnet34/default.yaml"
PTQ_PATH = "/home/ms/lm/code/Lidar_AI_Solution/CUDA-V2XFusion/ptq.pth"

configs.load(CONFIG_PATH, recursive=True)
cfg = Config(recursive_eval(configs), filename=CONFIG_PATH)
torch.backends.cudnn.benchmark = True
cfg.model.pretrained = None
cfg.data.test.test_mode = True

dataset = build_dataset(cfg.data.test)
data_loader = build_dataloader(dataset, samples_per_gpu=1, workers_per_gpu=1, dist=False, shuffle=False)
data_loader.collate_fn = partial(collate_fn, is_return_depth=False)

model = torch.load(PTQ_PATH).module
model = MMDataParallel(model.cuda(), device_ids=[0])
model.eval()

outputs = single_gpu_test(model, data_loader)
print(dataset.evaluate(outputs))

9.2 运行命令

conda activate v2xfusion
cd /home/ms/lm/code/Lidar_AI_Solution/CUDA-V2XFusion
PYTHONPATH=/home/ms/lm/code/Lidar_AI_Solution/CUDA-BEVFusion/bevfusion:$PYTHONPATH \
    python inference_demo.py

9.3 推理结果

类别 阈值 Easy Mod. Hard
Car 3D@0.5 82.31 69.60 69.65
Pedestrian 3D@0.25 64.59 63.46 63.76
Cyclist 3D@0.25 80.95 78.37 78.65

推理速度:约 250 it/s (单 GPU RTX 4090)

10. 修改文件汇总

文件路径 修改内容
CUDA-BEVFusion/bevfusion/setup.py 添加 sm_89, C++17
CUDA-BEVFusion/bevfusion/mmdet3d/ops/*/src/*.cpp (6个) THC 头文件替换
CUDA-BEVFusion/bevfusion/mmdet3d/ops/spconv/conv.py register_module(force=True)
CUDA-BEVFusion/bevfusion/evaluators/result2kitti.py 注释 rope3d 导入
CUDA-BEVFusion/bevfusion/mmdet3d/datasets/v2x_dataset.py 参数兼容性改造
CUDA-BEVFusion/bevfusion/mmdet3d/datasets/builder.py 嵌套 dataset 支持
CUDA-BEVFusion/bevfusion/mmdet3d/models/vtransforms/base.py 移除 tensor 导入, torch.save, BEVPooling
CUDA-BEVFusion/bevfusion/mmdet3d/ops/bev_pool/bev_pool.py 替换为 V2XFusion 版本
CUDA-BEVFusion/bevfusion/scripts/export_v2xfusion.py ONNX 导出兼容性
CUDA-V2XFusion/configs/V2X-I/default.yaml 数据集路径
CUDA-V2XFusion/inference_demo.py 新建推理脚本

11. 产出文件

文件 大小 说明
CUDA-V2XFusion/ptq.pth 145MB PTQ 量化模型
CUDA-V2XFusion/onemodel-fp16-seq.onnx 71MB FP16 ONNX 模型
CUDA-V2XFusion/inference_demo.py - 推理脚本

更多推荐