智能驾驶感知小模型训练效率跃升:多维度优化策略与实践

智能驾驶感知小模型训练提效指南。本文分享了小模型在多算力卡上的优化实践,覆盖环境依赖、代码适配及CPU/GPU/存储等多层级性能调优,实现训练效率与模型收敛双提升。#智能驾驶 #模型训练

原文标题:150%训练效率提升:感知检测小模型训练优化方法

原文作者:阿里云开发者

冷月清谈:

本文基于智能驾驶场景下的业务实践,总结了在不同算力卡上训练感知检测小模型的优化方法。随着L2向L3/L4自动驾驶演进,模型与数据复杂性对计算平台提出更高要求。文章详细介绍了在PAI DSW实例上进行对比测试的环境配置、所使用的多款感知领域小模型(如MapTR、SparseDrive等),以及训练过程中遇到的具体问题及解决方法。

主要挑战包括:复杂的环境依赖冲突,如MMCV与PyTorch版本不兼容、Flash-Attention与Transformers库引发的“undefined symbol”错误,这些通过选择合适的裸镜像、降级特定库版本得以解决。其次是源代码适配问题,如多卡并行训练中`local_rank`参数的兼容性处理,以及DDP分布式训练时偶尔出现的NCCL P2P通信失败,通过调整参数或禁用P2P来缓解。

文章的重点在于多层级的性能优化:
1. **CPU处理加速**:针对小模型训练时CPU瓶颈,引入进程池(`Process Pool`)和共享内存机制,加速数据预处理。
2. **PyTorch应用加速**:利用`DataLoader`的`pin_memory`提升CPU到GPU数据传输效率,合理配置`num_workers`实现数据加载与GPU计算并行,并尝试使用`torch.compile`进行计算图优化。
3. **外部挂载点CPFS优化**:揭示了存储服务可用区选择对I/O性能的关键影响,例如通过选用支持eRDMA高速网络的CPFS乌兰C区,显著降低了checkpoint写入延迟。

测试结果显示,通过这些优化,训练效率得到显著提升,模型收敛趋势符合预期。文章强调了可以从调度层(CPU)、应用层(PyTorch)和挂载区(CPFS)三个维度全面考虑优化策略,以最大化算力卡的效能。

怜星夜思:

1、搞机器学习的兄弟们,你们平时遇到这种环境依赖冲突(比如某个库版本要求特别龟毛)都是怎么解决的?文里提了换镜像、降级库,还有没有更“一劳永逸”的办法?
2、文章里说禁用了NCCL P2P虽然能解决问题但会降低训练效率。在你们实际工作中,经常会碰到这种“为了解决一个问题,但又会带来另一个负面影响”的权衡抉择吗?你们是怎么判断和取舍的?
3、文章主要聚焦在‘感知检测小模型’的训练优化。相较于现在动辄上亿参数的大模型,优化训练‘小模型’会有哪些独有的坑或者意想不到的甜头?大家有没有相关经验分享?

原文内容

阿里妹导读


本文章基于业务实践,总结有关感知检测小模型在不同算力卡上的训练方法,为有智能驾驶的场景提供可行的借鉴方法。

一、背景

在智能驾驶技术快速发展的背景下,车辆对周围环境的实时感知和决策能力成为系统性能的关键。目标检测、语义分割、多传感器融合等任务构成了智能驾驶系统的核心感知模块,这些算法通常依赖于大规模深度学习模型的训练与部署。随着自动驾驶等级从L2向L3乃至L4演进,模型复杂度和数据量呈指数级增长,这对计算平台提出了更高的要求,尤其是在算力、内存带宽、并行处理能力和能效比等方面。  当前,行业内主流的高性能计算平台包括高速GPU集群,整体提供极高的内存容量和带宽,支持高效的大批量数据处理和分布式训练,可以满足更复杂的模型架构和更大的训练批次需求。因此,在典型智能驾驶场景中,如高精度目标检测、点云感知以及多模态融合感知任务中,对不同的算力卡进行全面的性能对比测试,为客户在选择合适的算力资源时提供有力的数据支撑。

测试环境配置

  • 环境:mmdet3d、mmcv、flash-attn、nuscenes-devkit、torchrun分布式训练框架;

  • 算力:两种不同的算力卡,用机器一和机器二表示;

  • 模型:maptr、sparsedrive、qcnet、Gaussianformer;

  • 数据集:nuScenes、Agoverse。

二、技术架构

整体的对比测试基于PAI DSW实例进行,在dsw机器上进行训练实验,以下是整体的训练步骤:

以maptr模型为例:

1.先选择dsw镜像,要对应合适的py、cuda版本,尽量和模型要求的配置相同,选择autodrive镜像,里面预装了必要mmcv等依赖。

2.然后根据模型的github官方文档安装必要的依赖,如果碰到报错,重新选择不同的版本,这一步可能需要多次实验配置。

3.创建dsw时会指定一个cpfs挂载点,在dsw挂载的cpfs中下载模型文件和数据集做持久化存储。

4.然后执行训练命令,log记录训练时间和吞吐量。

以下是该项目中测试的四个模型,全部是目标检测和感知领域的小模型,适用于智能驾驶场景:

类型

模型

主要依赖

官方地址

感知-地图构造

maptr v1

mmdet3d 0.17.2

mmcv 1.4.0

https://github.com/hustvl/MapTR

感知-端到端

sparsedrive

mmcv 1.7.1

flash-attn 2.3.2

https://github.com/swc-17/SparseDrive

感知-预测

QCNet

mmdet3d 0.17.2

https://github.com/ZikangZhou/QCNet

感知-目标检测

GaussianFormer

mmcv 2.0.1

mmdet3d 1.1.1

https://github.com/huang-yh/GaussianFormer

三、问题&解决方法

3.1 环境依赖冲突

mmengine和torch

在maptr模型测试中,遇到了严重的环境依赖问题,执行pip install mmcv==1.4.0,build wheels时失败。

Collecting mmcv-full==1.4.0
  Using cached mmcv_full-1.4.0.tar.gz (2.8 MB)
  Preparing metadata (setup.py): started
  Preparing metadata (setup.py): finished with status 'done'
Building wheels for collected packages: mmcv-full
  Building wheel for mmcv-full (setup.py): started
  Building wheel for mmcv-full (setup.py): still running...
  error: command '/usr/local/cuda/bin/nvcc' failed with exit code 1
  Complete output from command /usr/bin/python3 -c "import setuptools, tokenize;__file__='.../mmcv_full-1.4.0/setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" bdist_wheel -d :

根据mmcv官方文档排查,发现该1.4.0版本的mmcv是比较旧的依赖,必须要求1.9.1<=torch<=1.10.0,在dsw官方自带的镜像中已经找不到满足要求的低版本torch,重新卸载torch安装低版本会导致大量的镜像自带库产生兼容错误。

mmcv1.4.0又是maptr的必须依赖。

针对maptr模型的解决方法是选择带py38+cu111版本的ubuntu裸镜像,重新安装合适的torch版本,然后再安装mmcv。

flash-attn和transformer

在sparsedrive模型测试时,需要安装flash-attn==2.6.1,setup时失败。

Collecting flash-attn
  Using cached flash_attn-2.6.1.tar.gz (49 kB)
  Preparing metadata(setup.py): started
  Preparing metadata(setup.py): finished with status 'error'

  ERROR: Command errored out with exit status 1:
   command: /usr/bin/python3 -c ‘import io, os, sys, setuptools, tokenize; …’ bdist_wheel -d …
       cwd: /tmp/pip-install-xxxx/flash-attn/

  Complete output from command:

    Running from flash-attn source directory.
    TORCH_CUDA_ARCH_LIST=8.0+PTX
    Traceback (most recent call last):
      File “<string>”, line 1, in <module>
      File “/tmp/pip-build-env-xxxx/overlay/lib/python3.8/site-packages/setuptools/init.py”, line 16, in <module>
        import ez_setup
      File “/tmp/pip-build-env-xxxx/overlay/lib/python3.8/site-packages/ez_setup.py”, line 139, in <module>
        raise RuntimeError(“ez_setup cannot run as a namespace package”)
    RuntimeError: ez_setup cannot run as a namespace package

    During handling of the above exception, another exception occurred:

    Traceback (most recent call last):
      File “<string>”, line 2, in <module>
      File “/tmp/pip-install-xxxx/flash-attn/setup.py”, line 10, in <module>
        import torch
      File “/home/user/.local/lib/python3.8/site-packages/torch/init.py”, line 197, in <module>
        _load_global_library(‘libtorch_cpu.so’)
      File “/home/user/.local/lib/python3.8/site-packages/torch/_utils_internal.py”, line 85, in _load_global_library
        ctypes.CDLL(path)
    OSError: /home/user/.local/lib/python3.8/site-packages/torch/lib/libtorch_cpu.so: undefined symbol: _ZN3c106detail15unchecked_inplace_False_aten_14_GLOBALS_INITIALIZED_for___nv_isnan

这个错误很复杂,看起来是cuda版本的错误,但查看flash的官方文档,又显示和torch123兼容,很难找到根因,后面查看了py3.8的torch/_utils_internal.py的源代码,里面调用了transformers库的一个函数,这个函数在高版本的transformers已经被废弃,只有该模型指定的transformers4.30.1在使用,因为缺少这个函数,导致这个undefined symbol错误。

解决方法是transformers库降级,pip install transformers==4.30.1。

3.2 源代码适配

由于不同算力卡底层编译环境的差异,在进行模型测试一般需要对源代码做处理。

local rank处理

local_rank是指定GPU序号的参数变量,在多卡并行训练时需要传入local_rank默认值,目前大部分的模型,包括本项目测试的四个模型,默认以Nvidia算力作为模型训练和推理的环境,因此会在启动脚本中硬编码传入参数--local_rank,但不同的算力卡调度逻辑不一样,可能会采用不用的GPU序号变量。

比如sparsedrive在机器一执行启动命令会出现变量错误:

train.py: error: unrecognized arguments: --local_rank=0
E0522 17:29:16.787000139811472776256 torch/distributed/elastic/multiprocessing/api.py:826] failed (exitcode: 2) local_rank: 0 (pid: 100906) of binary: /usr/local/bin/python3

该问题的解决方法是在训练脚本中修改代码parser.add_argument('--local-rank',type=int,default=0)

NCCL P2P处理

DDP多卡并行训练时,会默认使用多卡的点对点通信,多张卡之间共享模型权重和数据集,实现并行训练;点对点通信直接使用卡之间的内部通信网络,不经过主机内存中转。

在测试实验中会偶发出现以下的错误,rank0和rank1的参数不一样:

Traceback (most recent call last):
  File "train_ddp.py", line 42, in <module>
    model = DDP(model)
  File "/usr/local/lib/python3.10/site-packages/torch/nn/parallel/distributed.py", line 610, in __init__
    _check_same_numel(self.module.parameters(), self.device_ids[0])
  File "/usr/local/lib/python3.10/site-packages/torch/nn/parallel/distributed.py", line 587, in _check_same_numel
    assert torch.distributed.is_initialized(), "Distributed process group is not initialized"
RuntimeError: DDP expects same model across all ranks, but Rank 0 has 1012 params, while rank 1 has inconsistent 0 params.

这个错误原因在rank1没有读取到rank0的状态,多卡通信失败;这个时候禁用点对点通信,可以解决这个错误,解决方法是在训练脚本加入EXPORT NCCL_P2P_DISABLE=1该参数可以禁用点对点通信。

但P2P被禁用,让多卡状态共享不再通过高速网络,会让训练效率略微下降。

3.3 性能优化

实际在对比测试中,可以采用一些优化性能的方法,让算力卡发挥尽可能多的算力优势;以下介绍从cpu、应用、外部挂载三个方面的优化点。

3.3.1 cpu处理加速

在maptr模型测试中,发现实际算力卡的显存使用率和显存占用量都比较低,从以下监控中显示训练进程大量在CPU核心上执行,GPU上的运算很短时间就结束,然后等待CPU执行完毕再执行。

图片

由于该模型是小模型,本身的模型的计算节点数远小于大模型,根据其源码显示,GPU用于模型本身的梯度计算和参数更新,CPU用于读取图片数据做预处理tensor,而进程监控的结果是CPU的处理时间很长,GPU在等待CPU处理数据完毕后再进行计算,因此算力卡肯定无法全部发挥算力,需要对CPU的计算做优化来提升效率。

进程池

进程池(Process Pool) 是一种用于并行执行任务的机制,主要用于在多核 CPU 环境下提高程序的运行效率。它通过预先创建一组工作进程,将多个任务分配给这些进程并发地执行,从而充分利用计算机的硬件资源。使用进程池可以避免频繁创建和销毁进程所带来的性能开销,因为进程池中的进程是复用的。

当主程序向进程池提交任务时,进程池会根据当前可用的工作进程数量自动调度任务,可以在提升执行速度的同时,避免资源竞争和系统过载。

对cpu计算要求高的小模型可以使用进程池来加速计算,进程数一般设置成cpu核心数:

from multiprocessing import Pool

def image_process(image):
    …
    return tensor

if name == ‘main’:
    with Pool(processes=n) as pool:  # 创建一个包含n个进程的池
        results = pool.map(image_process, image_list)  # 进程池并行计算
        print(results)

共享内存

共享内存是一种进程间通信机制,它允许多个进程访问同一块内存区域,从而实现数据的高效交换和同步。相比于其他 IPC 机制,共享内存具有极低的通信开销,因为不需要在内核与用户空间之间频繁复制数据;多个进程可以通过标识符或名称映射到同一块物理内存地址空间上。一旦某个进程将数据写入该共享内存区域,其他进程可以立即读取这些数据,而无需通过复杂的序列化或网络传输过程。

这种机制特别适用于多进程之间的频繁数据交换和并发操作。

对于小模型在处理数据时,需要大量使用cpu对内存的读取写入,前面启用了进程池,共享内存可以配合多进程使用,加快cpu的处理速度,对于共享内存的大小,一般设置成实例的分配内存;当数据占用量很大时,需要增加共享内存。

3.3.2 torch应用加速

用torch做模型训练时,有一些torch内置的方法,能够对batch数据做高效处理,对模型本身做图处理,加速训练过程,以下介绍三种不同的方法:

pin_memory

PyTorch 会将数据加载到 page-locked 内存,这种内存不会被操作系统交换到磁盘,因此可以更高效地通过 PCIe 总线传输到 GPU,直接由 GPU 异步访问,从而加快从 CPU 到 GPU 的数据拷贝速度。

CUDA 支持从 pinned memory 到 GPU 的异步数据传输,这可以与计算重叠,提升整体训练效率:

dataloader = DataLoader(
    dataset,
    batch_size=32,
    shuffle=True,
    num_workers=4,
    pin_memory=True  # 启用 pinned memory
)
num_workers

num_workers指定了用于数据预处理和加载的子进程数量,每个子进程负责从磁盘读取数据、进行预处理,并将处理好的 batch 数据传递给主进程。使用多个 workers 并行读取和处理数据,可以显著提升训练效率,在 GPU 进行模型训练的同时,workers 可以提前准备好下一个 batch 的数据,实现“GPU计算 + 数据加载”并行。

num_workers的原理和上文中提到的cpu进程池原理类似,都是通过多进程并发来提升速度,这个是torch自带的加速数据加载和处理的方法,进程池是cpu层面所有任务的加速方法。

num_workers的值一般设置成cpu核心数。

dataloader = DataLoader(
    dataset,
    batch_size=32,
    shuffle=True,
    num_workers=4, #cpu核心数
    pin_memory=True  
)

torch compile

torch compile使用torchdynamo对模型进行追踪,将其转换为Graph。这个过程会记录模型的计算路径,忽略掉非必要的控制流,并构建一个可优化的计算图。

然后对生成的计算图进行各种优化,例如:算子融合:将多个连续的操作合并成一个,减少内核启动次数。常量折叠:提前计算静态值。内存布局优化:减少数据拷贝,提高缓存命中率。

将优化后的图转换为目标平台的高效代码。PyTorch 默认使用 Inductor 编译器在后续的前向/反向传播中直接调用,绕过 Python 解释器,实现高速执行。

注意:compile无法编译非pytorch的指令,自定义 C/CUDA 扩展的模型可能会遇到兼容性问题。

model=torch.compile(
    model,
    backend='inductor',
    dynamic=False,
    fullgraph=False,
)

3.3.3 cpfs优化加速

在maptr模型测试中,发现模型在写入checkpoint文件到cpfs时的时间存在很大差异,在同一地域乌兰察布下的cpfsA区的机器一时延比较长在2分钟左右,但机器二秒级就能写入完成,理论上同一套cpfs不应该有这么大的读写性能差异,根据后台的监控显示两台不同机器上的cpfs时延差了3倍。

机器一

机器二

后面分析cpfs的实例情况,发现挂载的cpfs都在乌兰A区,但根据智算CPFS的官方文档显示,cpfs使用高速eRDMA网络通信,目前只有乌兰C区支持,初步判断是因为可用区导致无法使用网速网络读写,使机器一读写效率很低。

重新新建一个C区的cpfs,重新训练后发现ppu的cpfs写入延迟大大降低,时延问题解决。

要启用cpfs的高速eRDMA网络读写,cpfs的可用区需要选择乌兰C区。

四、测试结果

从loss结果来看,机器一在100000 step时基本收敛,机器二在180000 step时基本收敛,基本符合预期的加速比,收敛趋势一致。

总结

如果在实际业务场景中需要训练感知检测类的小模型,可以按照如下的操作方法来优化性能,总体的性能优化可以从调度层、应用层、挂载区三个方面来考虑。

模型训练过程中的tensor计算会先进入底层cpu做数据预处理,然后处理完的tensor会用cuda gpu算力来做模型梯度计算,训练过程中产生的checkpoint会写入到挂载区cpfs,从三个阶段可以分别做三方面的优化:

1.调度层:创建进程池,提高cpu并行处理速度,增加共享内存,加快cpu读取内存的速度;

2.应用层:torch dataloader启用pin_memory,加快数据传输速度,增加num_workers,提高并行计算速度,用compile加速模型计算;

3.挂载区:使用有高速读写网络的CPFS存储,并选择合适的可用区。

基于 RAGFlow 构建私有知识问答应用


传统 RAG 应用因文档解析能力不足,导致相关问题的回答失准。RAGFlow 凭借创新的深度文档理解技术,能精准解析各类复杂格式的原始数据,提升回答准确性。本方案介绍如何一键部署 RAGFlow 并构建私有知识问答应用,无需编码,最快 10 分钟、最低 2 元即可实现。


点击阅读原文查看详情。

这不就是常说的‘没有银弹’嘛!技术方案往往不是完美的,总会有代价。禁用P2P这个操作,我觉得是典型的‘先求稳,再求快’。很多时候,稳定性是第一位的,尤其是在生产环境。性能固然重要,但如果崩了,performance再好也白搭。所以,我的原则是,先让它跑起来、跑稳定,再一点点优化性能。毕竟,稳定压倒一切啊。

小模型简直是‘智能驾驶’场景的‘救星’啊!车载端芯片算力有限,如果都用大模型根本跑不起来。所以优化小模型最大的甜头就是——能上车!而且训练周期也短,迭代快。至于坑嘛,感觉就是模型容量有限,有时候效果很难再往上抠一点点,可能大模型的架构在某些极限场景下天生就有优势。所以小模型更多是‘够用就行’,而非‘追求极限’。

对‘感知检测小模型’的优化,确实与大模型有所不同。小模型参数量少,计算瓶颈往往不在GPU的纯算力,而更多体现在CPU的数据预处理、I/O速度以及访存带宽。文中提到的进程池、共享内存、pin_memory这些,对小模型的效率提升尤其显著,因为GPU可能频繁等待CPU提供数据。而大模型则更关注分布式训练的通信效率、显存优化(如混合精度训练、梯度累积、Checkpointing等)以及如何利用多卡。小模型的‘甜头’是训练和部署成本更低,更适合边缘设备;‘坑’可能是更难达到高精度(需要更精妙的网络结构或数据增强),且相对鲁棒性较差。

每次遇到依赖冲突,都感觉自己在玩‘俄罗斯套娃’,一个套一个,永远不知道下一个爆出来的是啥!我一般是先谷歌报错信息,实在不行就祭出大招:重装系统(开玩笑的,哈哈),或者找一个大佬的Docker镜像直接用。有时候,玄学操作比任何技术文档都有效!

针对讨论中提到的环境依赖冲突,除了文中策略,最推荐的还是采用容器化技术(如Docker)。它能将应用及其所有依赖项打包成一个可移植的独立单元,确保在任何环境中运行一致。配合版本管理工具(如Conda或Poetry),可以为每个项目创建隔离的虚拟环境,精准锁定依赖版本,极大减少’works on my machine’的问题。对于生产环境,还会考虑Mamba之类的快速包管理器,进一步提升构建效率。

关于训练优化中的权衡取舍,这是一个普适性问题,不仅限于ML。核心在于理解优化目标和约束条件。禁用NCCL P2P虽然损失了部分通信效率,但解决了模型参数不一致的稳定性问题,这是在’可用性’与’极致性能’之间的权衡。在实际项目中,我们常在模型复杂度(性能与资源)、数据增广(鲁棒性与训练时间)、精度(过拟合风险与泛化能力)和部署成本(推理速度与硬件)之间做平衡。决策时,需要清晰定义项目的优先级,例如稳定性是否高于微小的性能损失,或更快的迭代速度是否值得短期内的额外资源消耗。

哎,这依赖冲突简直是ML工程师的噩梦!我个人的经验是:1. 早期就固定一个基准镜像,不要轻易换。2. 用requirements.txt把所有依赖写死,然后用pip freeze定期更新。3. 遇到实在解决不了的,就上Docker,直接搭一个纯净环境,把所有东西都锁死在里面,省心很多。但Docker也有学习成本,小项目不一定划算。

可太有了!每次模型上线前,都得做各种取舍。比如,为了推理速度,我们可能得牺牲一点模型精度,去做量化或者模型蒸馏。还有,为了节省标注成本,可能会用弱监督或者半监督学习,但又担心数据质量问题。感觉就是在走钢丝,每次优化不是提升了这个,就是影响了那个。最后都是看老板的需求(狗头),以及哪种方案风险最低、收益最大,能赶紧上线才是王道。

小模型?那不就是ML界的‘麻雀虽小五脏俱全’吗?优化小模型,感觉就像给一个‘小而美’的手机做性能优化,关注的是怎么让它用起来更丝滑、更省电。大模型嘛,那是给‘航母’做性能优化,考虑的是怎么让它跑得更快、能装更多武器。坑点可能就是小模型特别挑数据,一点点脏数据都可能让它‘拉胯’。但甜头就是不用等那么久训练,发家致富更快!哈哈。