MMDetection性能优化

MMDetection性能优化

使用MMDetection训练时发现速度特别慢,使用nvidia-smi -l 1命令,发现训练时GPU利用率大部分时间是0,说明GPU处于空闲状态在等待数据的到来,内存带宽和数据加载速度已经成为模型训练的瓶颈了,需要想办法提升加载速度。

设置PyTorch可以使用的最大线程数

PyTorch默认使用一半的CPU核心运行,而MMDetection在运行时为防止机器过载可能会限制可利用的核心数为1,导致速度慢,可以在训练的语句前面加上环境变量来修改这一限制。

1
2
3
4
# 原始命令
python tools/train.py configs/tile/faster_rcnn_r50_fpn_100e_tile_coco.py
# 修改后的命令
OMP_NUM_THREADS=2 OPENBLAS_NUM_THREADS=2 MKL_NUM_THREADS=2 VECLIB_MAXIMUM_THREADS=2 NUMEXPR_NUM_THREADS=2 python tools/train.py configs/tile/faster_rcnn_r50_fpn_100e_tile_coco.py

也可以在python程序的起始位置使用os库设置环境变量:

1
2
3
4
5
6
7
8
9
import os

cpu_num = 2 # 这里设置成你想运行的CPU个数
os.environ ['OMP_NUM_THREADS'] = str(cpu_num)
os.environ ['OPENBLAS_NUM_THREADS'] = str(cpu_num)
os.environ ['MKL_NUM_THREADS'] = str(cpu_num)
os.environ ['VECLIB_MAXIMUM_THREADS'] = str(cpu_num)
os.environ ['NUMEXPR_NUM_THREADS'] = str(cpu_num)
torch.set_num_threads(cpu_num)

实验发现并非数值越大越好,我设置为2的时候训练时间最短。

增大workers_per_gpu和samples_per_gpu

workers_per_gpu表示读取数据时每个gpu分配的线程数,samples_per_gpu表示每个GPU的样本数目。增大这两项会使GPU同时处理更多的数据,因此会增大显存占用,对速度的提升效果也并非越大越好。源码显示batch_size=workers_per_gpu * samples_per_gpu,因此设置这两项会改变训练时的batch_size,而测试时batch_size=1

根据线性缩放原则,增大batch_size时也需要对学习率lr按比例缩放,mmdetection默认的学习率缩放策略如下:

1
auto_scale_lr = dict(enable=False, base_batch_size=16)

其中base_batch_size是一个缩放基准,任何情况下都不能修改。如果要进行学习率缩放,可以设置enable=True,或手动按调整优化策略:

1
optimizer = dict(type='SGD', lr=0.025, momentum=0.9, weight_decay=0.0001) # 调整这里的`lr`参数

设置锁页内存

CUDA有分页内存Peageable Memory和锁页内存Pinned Memory的概念,GPU访问分页内存数据时需要首先开辟临时缓冲区(Pinned Memory),然后将数据从分页内存复制到锁页内存上才能读取,而Pytorch框架中DataLoader可以设置pin_memory=True,这会默认打开锁页内存,使硬件外设直接访问CPU内存,避免过多的复制操作,提高数据读取速度。mmdetection默认关闭锁页内存,可以在数据集配置的data环节增加如下设置打开:

1
2
3
data = dict(
train_dataloader=dict(pin_memory=True), # 在这里增加train_dataloader并打开锁页内存
)