柯依力YOLO训练调优
一:NPU环境准备+训练
步骤1 进入OBS管理控制台创建桶
- 点击进入“OBS”服务页面,选择“桶列表”,然后点击右上角“创建桶”;
- 创建桶的相关配置:主要关注项:区域(选 贵阳一)、桶名称、数据冗余存储策略,其他保持默认即可。
步骤2 进入ModelArts管理控制台
ModelArts上做AI应用开发依赖OBS、SWR等服务,需获取依赖服务的授权后,才能正常使用。因此第一次在某区域使用ModelArts时,需要先进行访问授权。点此进入ModelArts控制台,按照以下步骤操作即可。
- 选择左侧导航栏中的权限管理,在右侧界面中点击添加授权:
- 在弹出的添加授权界面中,按照下图所示依次操作:
步骤3 创建NPU-Notebook
1.在ModelArts管理控制台左侧导航栏中选择“开发空间 > Notebook”,进入“Notebook”管理页面。
点击进入ModelArts的开发环境,右上角创建Notebook。
2.在创建notebook页面,按照如下参数进行配置:
名称:notebook-yolov11
自动停止:开启,1小时
镜像:“公共镜像”,选择“pytorch_2.1.0-cann_8.0.rc2-py_3.9-euler_2.10.7-aarch64-snt9b”
资源类型:公共资源池
类型:Ascend
规格:Ascend: 1ascend-snt9b1|ARM: 24核 192GB,或者 Ascend: 1ascend-snt9b2|ARM: 24核 192GB
存储配置:云硬盘EVS,50G
SSH远程开发:不开启
3.点击“立即创建”,确认产品规格后点击“提交”,并返回。此时,notebook正在创建中,创建时长大约1分钟左右。
4.待notebook状态变为“运行中”,点击该notebook实例右侧“打开”,即可进入到jupyterlab环境中。
步骤4 准备训练环境
- 新建一个jupyter代码环境(PyTorch-2.1.0),在notebook中执行如下命令,将从OBS下载YOLO预训练模型权重和PCB工业质检数据集
输入:
import moxing as mox
mox.file.copy_parallel("obs://dtse-model-guiyangyi/course/yolov8/pcb_data", "yolov11/pcb_data")
mox.file.copy_parallel("obs://dtse-model-guiyangyi/course/yolov8/code", "yolov11")
!wget https://github.com/ultralytics/assets/releases/download/v8.3.0/yolo11n.pt -P yolov11
双击 yolov11文件夹,进入代码目录,目录结构如下:
/home/ma-user/work/yolov11
├── pcb_data # PCB缺陷检测数据集
| ├── images
| ├── labels
├── 0041204.jpg
├── Arial.ttf
└── pcb-detect.yaml # 配置文件
└── yolo11n.pt # 预训练权重
- 修改配置文件 pcb-detect.yaml中的数据集路径:/home/ma-user/work/yolov11/pcb_data
- 执行如下代码,将为YOLOv11的训练安装依赖环境:
输入:
!mkdir -p /home/ma-user/.config/Ultralytics
!cp /home/ma-user/work/yolov11/Arial.ttf /home/ma-user/.config/Ultralytics/Arial.ttf
!pip install ultralytics==8.3.80
输出:
Looking in indexes: http://pip.modelarts.private.com:8888/repository/pypi/simple
Collecting ultralytics==8.3.80
Downloading http://pip.modelarts.private.com:8888/repository/pypi/packages/ultralytics/8.3.80/ultralytics-8.3.80-py3-none-any.whl (921 kB)
|████████████████████████████████| 921 kB 56.0 MB/s eta 0:00:01
... # 省略pip安装日志
Successfully installed numpy-1.26.4 seaborn-0.13.2 tqdm-4.67.1 ultralytics-8.3.80 ultralytics-thop-2.0.14
步骤5 开启训练
- 为了便于修改适配文件,在 yolov11目录下,为 ultralytics训练有关的核心文件,创建软链接:
%cd /home/ma-user/work/yolov11
!ln -s /home/ma-user/anaconda/lib/python3.9/site-packages/ultralytics/utils/dist.py dist.py
!ln -s /home/ma-user/anaconda/lib/python3.9/site-packages/ultralytics/engine/trainer.py trainer.py
- 其中,dist.py是单机多卡训练的启动文件,trainer.py是训练流程的核心文件。
1.训练脚本
- ultralytics封装的训练接口,用户只需要创建 YOLO的模型类,并调用 model.train即可开启训练。ultralytics会自动解析权重文件并加载配置,会自动选择单卡/多卡启动代码,会自动创建 Trainer实例开始训练。
- 与GPU训练脚本的区别在于,插入自动迁移代码,其训练脚本如下:
%%writefile main.py
device = "0"
import os
os.environ["NPU_VISIBLE_DEVICES"] = device
os.environ["ASCEND_RT_VISIBLE_DEVICES"] = device
# ============== GPU->NPU 关键代码 ============== #
import torch
import torch_npu
from torch_npu.contrib import transfer_to_npu
# ============== GPU->NPU 关键代码 ============== #
from ultralytics import YOLO
model = YOLO("yolo11n.pt")
model.train(
data="pcb-detect.yaml", epochs=10, imgsz=640,
device=device, batch=32, plots=False, seed=1024, workers=4
)
- 主要关注如下参数:
- epochs即总训练周期
- batch即batch-size
- plots控制是否可视化训练样本,绘制验证结果等
- seed即随机种子,精度对齐前必须设置随机种子
2.单机多卡原生适配(可跳过)
- 注意:本案例以单机单卡训练为例,若没有单机多卡的训练需求,可不适配此步
- ultralytics开启单机多卡训练非常容易,只需在 model.train函数中,指定 device参数为多个整数即可。例如,device=2,3表示使用GPU2和GPU3开启DDP训练。
- 原生适配是指,NPU环境也可以通过指定 device=2,3来开启单机多卡训练。通过修改 dist.py文件的 generate_ddp_file函数完成:
def generate_ddp_file(trainer):
"""Generates a DDP file and returns its file name."""
module, name = f"{trainer.__class__.__module__}.{trainer.__class__.__name__}".rsplit(".", 1)
device = trainer.args.device
content = f"""
# Ultralytics Multi-GPU training temp file (should be automatically deleted after use)
overrides = {vars(trainer.args)}
if __name__ == "__main__":
import os
os.environ["NPU_VISIBLE_DEVICES"] = "{device}"
os.environ["ASCEND_RT_VISIBLE_DEVICES"] = "{device}"
# ============== GPU->NPU 关键代码 ============== #
import torch
import torch_npu
from torch_npu.contrib import transfer_to_npu
# ============== GPU->NPU 关键代码 ============== #
from {module} import {name}
from ultralytics.utils import DEFAULT_CFG_DICT
cfg = DEFAULT_CFG_DICT.copy()
cfg.update(save_dir='') # handle the extra key 'save_dir'
trainer = {name}(cfg=cfg, overrides=overrides)
trainer.args.model = "{getattr(trainer.hub_session, 'model_url', trainer.args.model)}"
results = trainer.train()
"""
(USER_CONFIG_DIR / "DDP").mkdir(exist_ok=True)
with tempfile.NamedTemporaryFile(
prefix="_temp_",
suffix=f"{id(trainer)}.py",
mode="w+",
encoding="utf-8",
dir=USER_CONFIG_DIR / "DDP",
delete=False,
) as file:
file.write(content)
return file.name
3.训练日志
- ultralytics提供的训练日志,不方便解析训练损失和训练吞吐率(steps-per-second)。为了精度对齐以及性能调优,需要在 trainer.py文件中,输出格式化的训练日志。
- 在 _do_train函数中创建 logger,并在训练迭代中写入loss和steps-per-second等,修改示例如下:
①创建 logger示例,342行左右位置:
...
epoch = self.start_epoch
self.optimizer.zero_grad() # zero any resumed gradients to ensure stability on train start
if RANK in {-1, 0}:
import logging
logging.basicConfig(
level=logging.DEBUG,
format='[%(asctime)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
handlers=[logging.FileHandler(self.save_dir / "log.txt")]
)
logger = logging.getLogger(name="loss_recorder")
start_time = time.time()
...
②写入日志示例,433行左右位置:
for i, batch in pbar:
self.run_callbacks("on_train_batch_start")
# Warmup
ni = i + nb * epoch
# Forward
# Backward
# Optimize
# Log
if RANK in {-1, 0}:
pbar.set_description(
("%11s" * 2 + "%11.4g" * (2 + loss_len))
% (f"{epoch + 1}/{self.epochs}", mem, *losses, batch["cls"].shape[0], batch["img"].shape[-1])
)
self.run_callbacks("on_batch_end")
if self.args.plots and ni in self.plot_idx:
self.plot_training_samples(batch, ni)
train_steps = ni
steps_per_sec = 1.0 / (time.time() - start_time)
logger.info("(step=%07d) BoxLoss: %.4f, ClsLoss: %.4f, DFLLoss: %.4f, Train Steps/Sec: %.2f",
train_steps, *self.tloss, steps_per_sec)
start_time = time.time()
self.run_callbacks("on_train_batch_end")
...
③写入单个epoch耗时,471行左右位置:
...
self.run_callbacks("on_fit_epoch_end")
self._clear_memory()
logger.info("Epoch Consume Time ------- %.2f", self.epoch_time)
# Early Stopping
...
4.执行训练
- 在notebook中,执行如下命令,将启动单机单卡并训练10个epoch
输入:
%cd /home/ma-user/work/yolov11
!python main.py
输出:
/home/ma-user/work/yolov11
Warning : ASCEND_HOME_PATH environment variable is not set.
... # torch_npu导入日志
Ultralytics 8.3.80 🚀 Python-3.9.10 torch-2.1.0 CUDA:0 (Ascend910B4, 30208MiB)
engine/trainer: task=detect, mode=train, model=yolo11n.pt, data=pcb-detect.yaml, epochs=10, time=None, patience=100, batch=32, imgsz=640, save=True, save_period=-1, cache=False, device=0, workers=4, project=None, name=train, exist_ok=False, pretrained=True, optimizer=auto, verbose=True, seed=1024, deterministic=True, single_cls=False, rect=False, cos_lr=False, close_mosaic=10, resume=False, amp=True, fraction=1.0, profile=False, freeze=None, multi_scale=False, overlap_mask=True, mask_ratio=4, dropout=0.0, val=True, split=val, save_json=False, save_hybrid=False, conf=None, iou=0.7, max_det=300, half=False, dnn=False, plots=False, source=None, vid_stride=1, stream_buffer=False, visualize=False, augment=False, agnostic_nms=False, classes=None, retina_masks=False, embed=None, show=False, save_frames=False, save_txt=False, save_conf=False, save_crop=False, show_labels=True, show_conf=True, show_boxes=True, line_width=None, format=torchscript, keras=False, optimize=False, int8=False, dynamic=False, simplify=True, opset=None, workspace=None, nms=False, lr0=0.01, lrf=0.01, momentum=0.937, weight_decay=0.0005, warmup_epochs=3.0, warmup_momentum=0.8, warmup_bias_lr=0.1, box=7.5, cls=0.5, dfl=1.5, pose=12.0, kobj=1.0, nbs=64, hsv_h=0.015, hsv_s=0.7, hsv_v=0.4, degrees=0.0, translate=0.1, scale=0.5, shear=0.0, perspective=0.0, flipud=0.0, fliplr=0.5, bgr=0.0, mosaic=1.0, mixup=0.0, copy_paste=0.0, copy_paste_mode=flip, auto_augment=randaugment, erasing=0.4, crop_fraction=1.0, cfg=None, tracker=botsort.yaml, save_dir=runs/detect/train
Overriding model.yaml nc=80 with nc=6
from n params module arguments
0 -1 1 464 ultralytics.nn.modules.conv.Conv [3, 16, 3, 2]
1 -1 1 4672 ultralytics.nn.modules.conv.Conv [16, 32, 3, 2]
2 -1 1 6640 ultralytics.nn.modules.block.C3k2 [32, 64, 1, False, 0.25]
3 -1 1 36992 ultralytics.nn.modules.conv.Conv [64, 64, 3, 2]
4 -1 1 26080 ultralytics.nn.modules.block.C3k2 [64, 128, 1, False, 0.25]
5 -1 1 147712 ultralytics.nn.modules.conv.Conv [128, 128, 3, 2]
6 -1 1 87040 ultralytics.nn.modules.block.C3k2 [128, 128, 1, True]
7 -1 1 295424 ultralytics.nn.modules.conv.Conv [128, 256, 3, 2]
8 -1 1 346112 ultralytics.nn.modules.block.C3k2 [256, 256, 1, True]
9 -1 1 164608 ultralytics.nn.modules.block.SPPF [256, 256, 5]
10 -1 1 249728 ultralytics.nn.modules.block.C2PSA [256, 256, 1]
11 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest']
12 [-1, 6] 1 0 ultralytics.nn.modules.conv.Concat [1]
13 -1 1 111296 ultralytics.nn.modules.block.C3k2 [384, 128, 1, False]
14 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest']
15 [-1, 4] 1 0 ultralytics.nn.modules.conv.Concat [1]
16 -1 1 32096 ultralytics.nn.modules.block.C3k2 [256, 64, 1, False]
17 -1 1 36992 ultralytics.nn.modules.conv.Conv [64, 64, 3, 2]
18 [-1, 13] 1 0 ultralytics.nn.modules.conv.Concat [1]
19 -1 1 86720 ultralytics.nn.modules.block.C3k2 [192, 128, 1, False]
20 -1 1 147712 ultralytics.nn.modules.conv.Conv [128, 128, 3, 2]
21 [-1, 10] 1 0 ultralytics.nn.modules.conv.Concat [1]
22 -1 1 378880 ultralytics.nn.modules.block.C3k2 [384, 256, 1, True]
23 [16, 19, 22] 1 431842 ultralytics.nn.modules.head.Detect [6, [64, 128, 256]]
/home/ma-user/anaconda3/envs/PyTorch-2.1.0/lib/python3.9/site-packages/torch_npu/utils/storage.py:38: UserWarning: TypedStorage is deprecated. It will be removed in the future and UntypedStorage will be the only storage class. This should only matter to you if you are using storages directly. To access UntypedStorage directly, use tensor.untyped_storage() instead of tensor.storage()
if self.device.type != 'cpu':
YOLO11n summary: 181 layers, 2,591,010 parameters, 2,590,994 gradients, 6.4 GFLOPs
Transferred 448/499 items from pretrained weights
Freezing layer 'model.23.dfl.conv.weight'
AMP: running Automatic Mixed Precision (AMP) checks...
[W compiler_depend.ts:51] Warning: CAUTION: The operator 'torchvision::nms' is not currently supported on the NPU backend and will fall back to run on the CPU. This may have performance implications. (function npu_cpu_fallback)
AMP: checks passed ✅
train: Scanning /home/ma-user/work/yolov11/pcb_data/labels/train2017... 1000 ima
train: New cache created: /home/ma-user/work/yolov11/pcb_data/labels/train2017.cache
albumentations: __init__() got an unexpected keyword argument 'quality_range'
val: Scanning /home/ma-user/work/yolov11/pcb_data/labels/val2017... 500 images,
val: New cache created: /home/ma-user/work/yolov11/pcb_data/labels/val2017.cache
optimizer: 'optimizer=auto' found, ignoring 'lr0=0.01' and 'momentum=0.937' and determining best 'optimizer', 'lr0' and 'momentum' automatically...
optimizer: AdamW(lr=0.001, momentum=0.9) with parameter groups 81 weight(decay=0.0), 88 weight(decay=0.0005), 87 bias(decay=0.0)
Image sizes 640 train, 640 val
Using 4 dataloader workers
Logging results to runs/detect/train
Starting training for 10 epochs...
Closing dataloader mosaic
albumentations: __init__() got an unexpected keyword argument 'quality_range'
Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size
/home/ma-user/anaconda3/envs/PyTorch-2.1.0/lib/python3.9/site-packages/ultralytics/utils/tal.py:236: UserWarning: AutoNonVariableTypeMode is deprecated and will be removed in 1.10 release. For kernel implementations please use AutoDispatchBelowADInplaceOrView instead, If you are looking for a user facing API to enable running your inference-only workload, please use c10::InferenceMode. Using AutoDispatchBelowADInplaceOrView in user code is under risk of producing silent wrong result in some edge cases. See Note [AutoDispatchBelowAutograd] for more details. (Triggered internally at build/CMakeFiles/torch_npu.dir/compiler_depend.ts:74.)
target_scores = torch.where(fg_scores_mask > 0, target_scores, 0)
[W compiler_depend.ts:103] Warning: Non finite check and unscale on NPU device! (function operator())
1/10 7.84G 2.446 4.325 1.547 45 640: 1
Class Images Instances Box(P R mAP50 m|
all 500 3140 0.0231 0.846 0.16 0.0632
...
10 epochs completed in 0.121 hours.
Optimizer stripped from runs/detect/train/weights/last.pt, 5.5MB
Optimizer stripped from runs/detect/train/weights/best.pt, 5.5MB
Validating runs/detect/train/weights/best.pt...
Ultralytics 8.3.80 🚀 Python-3.9.10 torch-2.1.0 CUDA:0 (Ascend910B4, 30208MiB)
YOLO11n summary (fused): 100 layers, 2,583,322 parameters, 0 gradients, 6.3 GFLOPs
Class Images Instances Box(P R mAP50 m
all 500 3140 0.929 0.881 0.946 0.722
open 482 659 0.922 0.929 0.969 0.652
short 368 478 0.828 0.732 0.83 0.559
mousebite 413 586 0.959 0.836 0.959 0.699
spur 373 483 0.969 0.848 0.949 0.698
copper 403 464 0.973 0.966 0.985 0.891
pin-hole 438 470 0.921 0.972 0.985 0.834
Speed: 0.1ms preprocess, 8.0ms inference, 0.0ms loss, 2.6ms postprocess per image
步骤6 loss曲线对比
- 在 ~/work/yolov11目录下,新建 exp/loss_compare_init用于初次loss曲线对比
- 将任务一中保存的 log_gpu.txt文件上传到 exp/loss_compare_init路径
- 将NPU训练日志文件重命名为 log_npu.txt,并复制到 exp/loss_compare_init路径
- 在notebook中执行如下代码,将绘制loss对比曲线并给出平均绝对误差:
输入:
%cd /home/ma-user/work/yolov11/exp/loss_compare_init
import matplotlib.pyplot as plt
import re
def get_train_loss_list(loss_file):
train_loss = []
file = open(loss_file)
for line in file:
match = re.search(r'\(step=(\d+)\) BoxLoss: ([\d\.]+), ClsLoss: ([\d\.]+), DFLLoss: ([\d\.]+), Train Steps/Sec: ([\d\.]+)', line)
if match:
loss = float(match.group(2))
train_loss.append(loss)
return train_loss
gpu_log_path = 'log_gpu.txt'
npu_log_path = 'log_npu.txt'
train_loss_gpu = get_train_loss_list(gpu_log_path)
train_loss_npu = get_train_loss_list(npu_log_path)
######################## 绘制loss对比曲线 ########################
#初始化图片
plt.figure(figsize=(12, 6))
#绘制NPU LOSS曲线
plt.plot(train_loss_npu, label='npu_loss', alpha=0.9, color="blue")
#绘制GPU LOSS曲线
plt.plot(train_loss_gpu, label='gpu_loss', alpha=0.9, color="red")
plt.legend(loc='best')
#保存图片
plt.savefig('./compare_loss_curves.png')
if len(train_loss_npu) < len(train_loss_gpu):
len_loss = len(train_loss_npu)
else:
len_loss = len(train_loss_gpu)
#计算LOSS差距
train_diff = []
train_diff_abs = []
train_diff_percent = []
for i in range(0, len_loss):
diff = train_loss_npu[i] - train_loss_gpu[i]
train_diff.append(diff)
train_diff_abs.append(abs(diff))
train_diff_percent.append(abs(diff) / train_loss_gpu[i])
######################## 绘制loss差距曲线 ########################
#初始化图片2
plt.cla()
plt.clf()
plt.figure(figsize=(12, 6))
#绘制图片
plt.plot(train_diff, label='loss_gap')
plt.legend(loc='best')
#保存图片
plt.savefig('./loss_gap_curves.png')
######################## 输出loss绝对误差平均值 ########################
print("------------------------------- Relative abs Loss Gap -------------------------------")
print(sum(train_diff_abs)/len(train_diff_abs))
print("-------------------------------------------------------------------------------------")
######################## 输出相对误差百分比平均值 ########################
print("--------------------------------- Loss Gap percent ----------------------------------")
print(sum(train_diff_percent)/len(train_diff_percent))
print("-------------------------------------------------------------------------------------")
输出:
------------------------------- Relative abs Loss Gap -------------------------------
0.058726250000000056
-------------------------------------------------------------------------------------
--------------------------------- Loss Gap percent ----------------------------------
0.03379222788625593
由loss曲线对比结果可知,GPU和NPU精度存在差异,且320次step的loss平均误差百分比3.3%。
二:YOLOv11模型精度对齐
步骤1 精度调优工具介绍
在PyTorch训练网络,对同一模型或API调试过程中,遇到API相关的计算精度问题,定位时费时费力。
msprobe为PyTorch精度工具,用来进行PyTorch整网API粒度的数据dump、精度比对和溢出检测,从而定位PyTorch训练场景下的精度问题。
主要的使用场景包括:
- 同一模型,从CPU或GPU移植到NPU中存在精度下降问题,对比NPU芯片中的API计算数值与CPU或GPU芯片中的API计算数值,进行问题定位。
- 同一模型,进行迭代(模型、框架版本升级或设备硬件升级)时存在的精度下降问题,对比相同模型在迭代前后版本的API计算数值,进行问题定位。
精度对比工具,通过在PyTorch模型中注册hook,跟踪计算图中API的前向传播与反向传播时的输入与输出,排查存在计算精度误差,进行问题的精准定位。
精度比对流程:
1.当模型在CPU或GPU上进行正向和反向传播时,分别dump每一层的数值输入与输出。
2.当模型在NPU中进行计算时,采用相同的方式dump下相应的数据。
3.通过对比dump出的数值,计算余弦相似度和最大绝对误差的方式,定位和排查NPU API存在的计算精度问题。如图1所示。
4.比对结果支持的评价指标有:Cosine(余弦相似度)、MaxAbsErr(最大绝对误差)和MaxRelativeErr(最大相对误差)、One Thousandth Err Ratio(双千分之一)和Five Thousandths Err Ratio(双千分之五)。
步骤2 定位精度问题
使用mindstudio-probe工具,dump精度数据后对比发现两处问题导致精度差异:
- GPU环境缺少albumentations包,导致算子Tensor.__truediv__.0.forward存在精度问题。
- 解决方案:NPU环境卸载 albumentations,或者为GPU环境安装 albumentations==1.3.1。
- NPU环境的混合精度训练没有生效,导致算子Tensor.__mul__.2.forward存在精度问题。往上回溯代码,发现是AMP没有生效,这里通过Tensor.__mul__.2.forward.output.0算子的Dtype也可以看到。
- 分析第一个卷积算子Functional.conv2d.0.forward也可以看到,其输入没有精度误差且数据类型均为fp32,然而NPU的输出为fp32,GPU输出为fp16。
- 解决方案:修改ultralytics/engine/trainer.py,大约第390行
# Forward
with torch.cuda.amp.autocast(self.amp):
batch = self.preprocess_batch(batch)
self.loss, self.loss_items = self.model(batch)
...
步骤3 精度对齐后的loss曲线差异
完成精度对齐后,再次训练YOLOv11模型,并运行“任务二-步骤7-对比loss曲线差异”
- 在notebook中执行如下代码,并重新开启单机单卡训练
%cd /home/ma-user/work/yolov11
!python main.py
- 等待训练完成10个epoch,重新运行“任务二-步骤7”将得到如下精度对齐结果:
------------------------------- Relative abs Loss Gap -------------------------------
0.0260521875
-------------------------------------------------------------------------------------
--------------------------------- Loss Gap percent ----------------------------------
0.01769333183521981
-------------------------------------------------------------------------------------
- loss平均误差百分比为1.8%,模型精度已对齐。
三:YOLOv11模型性能调优
步骤1 性能调优工具介绍
昇腾云团队提供了丰富的工具库用于迁移适配,见官方代码库。开发者可以使用pip安装msprof-analyze工具,或者使用notebook集成的advisor界面化插件来分析性能数据。
使用方式 |
官网文档 |
|
msprof-analyze |
手动安装,命令行方式 |
|
advisor界面化插件 |
即开即用,界面化操作 |
此外,针对上百GB性能数据的分析场景,如果下载到本地进行分析,耗时耗力。advisor界面化插件的方式,目前支持两种性能数据来源:notebook容器和OBS桶,可以快速进行性能调优。
advisor是一款昇腾迁移性能问题自动诊断工具,当前支持如下场景的自动诊断:
- 推理场景下的子图数据调优分析,给出对应融合算子的调优建议。
- 推理、训练场景下对Profiling timeline单卡数据进行调优分析,给出相关亲和API替换的调优建议。
- 推理、训练场景下对Profiling单卡数据进行调优分析,给出AICPU相关调优建议。
- 推理、训练场景下对Profiling单卡数据进行调优分析,给出block dim、operator no bound相关AOE配置以及调优建议。
- 支持对昇腾训练、推理环境进行预检,完成相关依赖配置项的提前检查,并在检测出问题时给出相关修复建议。
自动诊断工具可以有效减少人工分析profiling的耗时,降低性能调优的门槛,帮助客户快速识别性能瓶颈点并完成性能优化。推荐用户在采集profiling分析后使用自动诊断工具进行初步性能调优。更进一步的性能调优,可以使用MindStudio-Insight工具进行数据可视化并人工分析瓶颈点。
步骤2 定位性能问题
profiler采集性能数据后,用advisor工具分析得到专家经验。可以看到在“schedule/下发”维度,存在一些“Affinity API Issues/亲和API问题”,同时存在严重的算子下发瓶颈。
问题算子1--optimizer
yolov11训练默认的优化器为“auto”,即通过训练迭代次数来确定使用“SGD”还是“AdamW”,见 trainer.py -- build_optimizer函数;
将torch原生优化器(AdamW),修改为NPU亲和的优化器(NpuFusedAdamW);
- 定位到优化器初始化位置,trainer.py -- build_optimizer函数。本案例数据集较小迭代次数在10000之内,由此默认优化器为 AdamW,将它修改为NPU亲和优化器:
修改前:
if name in {"Adam", "Adamax", "AdamW", "NAdam", "RAdam"}:
optimizer = getattr(optim, name, optim.Adam)(g[2], lr=lr, betas=(momentum, 0.999), weight_decay=0.0)
elif name == "RMSProp":
...
修改后:
if name in {"Adam", "Adamax", "AdamW", "NAdam", "RAdam"}:
if name == "AdamW":
import torch_npu
optimizer = torch_npu.optim.NpuFusedAdamW(g[2], lr=lr, betas=(momentum, 0.999), weight_decay=0.0)
else:
optimizer = getattr(optim, name, optim.Adam)(g[2], lr=lr, betas=(momentum, 0.999), weight_decay=0.0)
elif name == "RMSProp":
...
- 昇腾官方已支持的优化器列表:https://www.hiascend.com/document/detail/zh/Pytorch/60RC2/ptmoddevg/trainingmigrguide/performance_tuning_0028.html
问题算子2--jit_compile
- 固定shape场景:推荐保持默认设置True。根据当前获得的算子信息,进行融合和优化,在线编译出运行性能更优的算子。若设置为False,则编译优化少,性能降低。
- 动态shape场景:推荐配置为False,优先查找当前编译好的算子二进制配置文件,若存在则不在线编译算子;若不存在,再进行在线编译。此时虽然编译优化少,但是没有编译时间,模型训练性能大概率比配置为True时高。
- 在yolov11开始训练之前,关闭算子二进制调优,修改 main.py文件
...
from ultralytics import YOLO
torch_npu.npu.set_compile_mode(jit_compile=False)
torch_npu.npu.config.allow_internal_format = False
model = YOLO("yolo11n.pt")
...
问题算子3--scatter算子
- 定位到问题代码为: tal.py -- get_targets函数 -- 第233行
- 为便于修改代码,可以为其创建软链接或使用vim进行修改
%cd /home/ma-user/work/yolov11
!ln -s /home/ma-user/anaconda/lib/python3.9/site-packages/ultralytics/utils/tal.py tal.py
- 原因分析:scatter算子由于输入数据类型不符合AI Core的要求,导致在更慢的AI CPU上执行,其类型要求见官网文档。
修改前:
...
target_labels = gt_labels.long().flatten()[target_gt_idx] # (b, h*w)
# Assigned target boxes, (b, max_num_obj, 4) -> (b, h*w, 4)
target_bboxes = gt_bboxes.view(-1, gt_bboxes.shape[-1])[target_gt_idx]
# Assigned target scores
target_labels.clamp_(0)
# 10x faster than F.one_hot()
target_scores = torch.zeros(
(target_labels.shape[0], target_labels.shape[1], self.num_classes),
dtype=torch.int64,
device=target_labels.device,
) # (b, h*w, 80)
target_scores.scatter_(2, target_labels.unsqueeze(-1), 1)
...
修改后:
...
target_labels = gt_labels.int().flatten()[target_gt_idx] # (b, h*w) # int64修改为int32
# Assigned target boxes, (b, max_num_obj, 4) -> (b, h*w, 4)
target_bboxes = gt_bboxes.view(-1, gt_bboxes.shape[-1])[target_gt_idx]
# Assigned target scores
target_labels.clamp_(0)
# 10x faster than F.one_hot()
target_scores = torch.zeros(
(target_labels.shape[0], target_labels.shape[1], self.num_classes),
dtype=torch.int32, # int64修改为int32
device=target_labels.device,
) # (b, h*w, 80)
target_scores.scatter_(2, target_labels.unsqueeze(-1), 1)
...
四:YOLO训练加速参数推荐配置
参数名称 |
参数说明 |
推荐值 |
优化效果 |
注意事项 |
--task |
训练任务类型 |
"detect" |
检测任务比分割(segment)快20-30% |
仅在需要实例分割时才选择segment |
--device_target |
训练设备选择 |
"GPU"(NVIDIA)/"Ascend"(华为) |
GPU/Ascend比CPU快10-50倍 |
需安装对应驱动(CUDA/CANN) |
--save_dir |
模型保存路径 |
"/dev/shm/runs"(内存盘)或"/cache/runs"(ModelArts) |
减少I/O延迟,提速5-10% |
内存盘数据重启会丢失,重要结果需定期备份 |
--log_level |
日志打印级别 |
"WARNING" |
减少日志I/O开销,提速3-5% |
调试时可临时设为"INFO" |
--is_parallel |
是否启用分布式训练 |
1 |
多卡训练可实现近线性加速(如8卡提速6-7倍) |
需配合分布式框架使用,确保数据均匀分配 |
--ms_mode |
计算模式选择 |
0(GRAPH_MODE) |
比PYNATIVE_MODE快20-30%,显存减少15-20% |
调试时可临时设为1(PYNATIVE_MODE) |
--max_call_depth |
函数调用最大深度 |
1000(原默认2000) |
减少调用栈深度,轻微提升效率 |
对复杂模型效果更明显 |
--ms_amp_level |
混合精度模式 |
O1(平衡)/O2(激进) |
速度提升30-70%,显存节省25-40% |
O2可能不稳定,O1更适合生产环境 |
--keep_loss_fp32 |
是否保持损失函数为FP32 |
1 |
防止混合精度下损失函数下溢 |
必须与O1/O2配合使用 |
--anchor_base |
是否使用预设 Anchor 框 |
1 |
避免动态计算 Anchor,节省初始化时间 |
自定义数据集需预先计算合适 Anchor 尺寸 |
--ms_loss_scaler |
损失缩放模式 |
dynamic |
自动调整缩放因子,比 static 模式更稳定 |
需配合混合精度训练使用 |
--ms_loss_scaler_value |
静态损失缩放值 |
1024.0 (默认) |
仅 static 模式有效 |
使用 dynamic 模式时可忽略 |
--ms_jit |
是否启用即时编译 |
1 |
加速计算图执行,提升 15-20% 速度 |
兼容性问题极少,建议保持开启 |
--ms_enable_graph_kernel |
是否启用图算融合 |
1 |
算子融合优化,提升 10-15% 速度 |
部分特殊算子可能不支持 |
--ms_datasink |
是否启用数据下沉 |
True (Ascend) / False (GPU) |
Ascend 设备可提升 20% 数据吞吐 |
仅华为 Ascend 芯片有效 |
--overflow_still_update |
梯度溢出时是否更新 |
0 |
严格检查数值稳定性 |
混合精度训练时建议关闭 |
--clip_grad |
是否启用梯度裁剪 |
0 |
减少计算开销 |
仅在训练不稳定时开启 |
--clip_grad_value |
梯度裁剪阈值 |
10 |
控制梯度范围 |
需配合 clip_grad=True 使用 |
--ema |
是否使用指数移动平均 |
False (追求速度) / True (追求精度) |
关闭可提升 5-10% 速度 |
会轻微影响模型收敛性 |
--weight |
预训练权重路径 |
指定预训练模型 |
加速收敛,减少30-50%训练时间 |
需与当前模型结构匹配 |
--ema_weight |
EMA权重路径 |
留空(训练自动生成) |
减少初始化时间 |
迁移学习时可加载 |
--freeze |
冻结层索引 |
[0,1,2](冻结前3层) |
减少20-40%计算量 |
冻结过多会降低模型性能 |
--epochs |
训练总轮次 |
150(原300) |
直接减少50%训练时间 |
配合早停机制使用效果更佳 |
--per_batch_size |
单卡batch size |
显存允许的最大值(如64) |
提升GPU利用率30-70% |
需线性调整学习率(lr = base_lr * bs/64) |
--img_size |
输入图像尺寸 |
[512,512](原640) |
减少35%计算量 |
可能轻微影响小目标检测(mAP降1-2%) |
--nbs |
标准batch size |
与目标等效batch一致(如64) |
保持正常学习率缩放 |
需满足:nbs = bs * accumulate |
--accumulate |
梯度累积步数 |
4(当bs=16时) |
模拟大批量训练 |
显存不足时的最佳解决方案 |
--auto_accumulate |
自动梯度累积 |
0 |
避免意外累积 |
手动控制更精准 |
--log_interval |
日志记录间隔 |
200(原100) |
减少30% I/O开销 |
重要信息仍可完整记录 |
--single_cls |
单类别训练模式 |
True(单类任务时) |
减少10-15%计算量 |
多类任务禁用,会严重影响mAP |
--sync_bn |
同步BatchNorm |
True(多卡训练时) |
提升大batch训练的稳定性 |
单卡训练保持False,可节省5-8%时间 |
--keep_checkpoint_max |
最大保存检查点数 |
10(原100) |
减少90%模型保存开销 |
确保重要epoch不被覆盖 |
--run_eval |
训练中验证 |
0 |
节省20-30%训练时间 |
需在训练后单独验证 |
--conf_thres |
评估置信度阈值 |
0.01(原0.001) |
加速评估阶段30-40% |
可能漏检部分低置信度目标 |
--iou_thres |
评估NMS阈值 |
0.60(原0.65) |
减少NMS计算量15-20% |
可能增加重复检测框 |
--conf_free |
忽略置信度预测 |
0 |
保持正常检测流程 |
设为True会严重降低检测质量 |
--rect |
矩形训练模式 |
1 |
减少图像填充,提升15-20%数据加载速度,显存节省10% |
需数据集支持不同宽高比,可能轻微影响mAP(<0.5%) |
--nms_time_limit |
NMS最大耗时限制 |
10 |
防止异常样本拖慢评估,保障评估阶段稳定性 |
过低可能导致部分检测框被截断 |
--recompute |
激活重计算 |
0 |
避免10-15%速度损失(显存充足时) |
显存不足时设为True并配合recompute_layers使用 |
--recompute_layers |
重计算层数 |
45950 |
显存节省30-50%(当recompute=True时) |
层数过多会显著降低速度 |
--seed |
随机种子 |
固定值 |
确保实验可复现,不影响速度 |
不同种子可能导致mAP波动±0.3% |
--summary |
训练统计收集 |
0 |
减少5-8% CPU开销,提升训练流程稳定性 |
调试时可临时启用 |
--profiler |
性能分析器开关 |
0 |
避免50-70%性能下降(分析器会显著拖慢训练) |
仅在调试性能瓶颈时临时启用 |
--profiler_step_num |
性能分析步数 |
1 |
最小化分析影响范围 |
分析阶段会完全中断训练流程 |
--opencv_threads_num |
OpenCV线程数 |
45755 |
提升20-40%图像预处理速度(需匹配CPU核心数) |
设置过高会导致线程争用(建议设为CPU物理核心数的75%) |
--strict_load |
严格加载预训练权重 |
0 |
加速权重加载,兼容更多模型变体 |
仅当参数完全匹配时才设为True |
- 点赞
- 收藏
- 关注作者
评论(0)