柯依力YOLO训练调优

举报
HuaweiCloudDeveloper 发表于 2025/04/21 09:40:58 2025/04/21
【摘要】 本文介绍柯依力 YOLO 训练调优,包括 NPU 环境准备与训练,如创建桶、授权、Notebook 等;精度对齐,用 msprobe 工具定位解决精度问题;性能调优,用 advisor 工具解决亲和 API 等问题;还给出训练加速参数推荐配置,提升训练效率。

一:NPU环境准备+训练

步骤1 进入OBS管理控制台创建桶

  1. 点击进入“OBS”服务页面,选择“桶列表”,然后点击右上角“创建桶”;
  1. 创建桶的相关配置:主要关注项:区域(选 贵阳一)、桶名称、数据冗余存储策略,其他保持默认即可。

步骤2 进入ModelArts管理控制台

ModelArts上做AI应用开发依赖OBSSWR等服务,需获取依赖服务的授权后,才能正常使用。因此第一次在某区域使用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
存储配置:云硬盘EVS50G
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曲线对比结果可知,GPUNPU精度存在差异,且320steploss平均误差百分比3.3%

二:YOLOv11模型精度对齐

步骤1 精度调优工具介绍

PyTorch训练网络,对同一模型或API调试过程中,遇到API相关的计算精度问题,定位时费时费力。

msprobePyTorch精度工具,用来进行PyTorch整网API粒度的数据dump、精度比对和溢出检测,从而定位PyTorch训练场景下的精度问题。

主要的使用场景包括:

  • 同一模型,从CPU或GPU移植到NPU中存在精度下降问题,对比NPU芯片中的API计算数值与CPU或GPU芯片中的API计算数值,进行问题定位。
  • 同一模型,进行迭代(模型、框架版本升级或设备硬件升级)时存在的精度下降问题,对比相同模型在迭代前后版本的API计算数值,进行问题定位。

精度对比工具,通过在PyTorch模型中注册hook,跟踪计算图中API的前向传播与反向传播时的输入与输出,排查存在计算精度误差,进行问题的精准定位。

精度比对流程:

1.当模型在CPUGPU上进行正向和反向传播时,分别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精度数据后对比发现两处问题导致精度差异:

  1. GPU环境缺少albumentations包,导致算子Tensor.__truediv__.0.forward存在精度问题。

  • 解决方案:NPU环境卸载 albumentations,或者为GPU环境安装 albumentations==1.3.1
  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

手动安装,命令行方式

MindStudio Training Tools端到端性能调优工具

advisor界面化插件

即开即用,界面化操作

基于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":

    ...

问题算子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/AscendCPU10-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

计算模式选择

0GRAPH_MODE

PYNATIVE_MODE20-30%,显存减少15-20%

调试时可临时设为1PYNATIVE_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%计算量

可能轻微影响小目标检测(mAP1-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

 

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。