Ascend-SACT/Yolov12
模型介绍文件和版本Pull Requests讨论分析
下载使用量0

Yolov12训练推理迁移指导

本文介绍在昇腾910B3上基于torch_npu对Yolov12进行训练和推理的全流程。

一、环境信息

  • 模型:yolov12
  • AI加速卡:910B3
  • CPU架构:ARM
  • CANN:8.5.0
  • numpy:< 2.0

二、准备流程

1、获取镜像包

镜像下载地址 https://www.hiascend.com/developer/ascendhub/detail/af85b724a7e5469ebd7ea13c3439d48f

2、启动容器

docker run -it -u root -d --net=host \
--privileged \
--ipc=host \
--device=/dev/davinci_manager \
--device=/dev/devmm_svm \
--device=/dev/hisi_hdc \
-v /usr/local/Ascend/driver:/usr/local/Ascend/driver \
-v /usr/local/dcmi:/usr/local/dcmi \
-v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
-v /usr/local/sbin:/usr/local/sbin \
--name train_test \
train-images:v1 \
/bin/bash

三、训练指导

如下实施过程均在容器内执行: 进入容器:docker exec -it train_test bash 工作目录在:/home/task

1、安装ultralytics软件包

执行命令: pip install ultralytics

2、获取 yolov12 源代码

git clone https://github.com/sunsmarterjie/yolov12.git

3、下载权重文件

wget https://github.com/sunsmarterjie/yolov12/releases/download/turbo/yolov12n.pt

4、编写训练脚本

#!/usr/bin/env python3
"""
YOLOv12 训练脚本 - 昇腾 910B3 单卡/多卡适配版本
"""

import os
import argparse
import torch
from ultralytics import settings

# 1. 必须最先执行 NPU 劫持
try:
    import torch_npu
    from torch_npu.contrib import transfer_to_npu
    import torch.distributed as dist
except ImportError:
    pass

from ultralytics import YOLO
from ultralytics.utils import LOGGER, colorstr

# 修改默认下载路径
# /root/.config/Ultralytics/settings.json,缺省配置datasets_dir是root根目录
settings.update({'datasets_dir': '/home/task/datasets'})

def set_npu_env():
    """配置 910B3 最佳性能环境变量"""
    npu_envs = {
        'ASCEND_GLOBAL_LOG_LEVEL': '3',
        'HCCL_CONNECT_TIMEOUT': '1800',
        'PYTORCH_NPU_ALLOC_CONF': 'max_split_size_mb:512',
        'ACL_RTM_ENABLE': '1',
        'TASK_QUEUE_ENABLE': '1'
    }
    os.environ.update(npu_envs)
    if hasattr(torch.npu, 'set_compile_mode'):
        torch.npu.set_compile_mode(jit_compile=False)

def train_yolo(args):
    set_npu_env()

    # 获取分布环境变量
    local_rank = int(os.environ.get('LOCAL_RANK', -1))
    world_size = int(os.environ.get('WORLD_SIZE', 1))

    # 处理设备逻辑
    if local_rank != -1:
        # 多卡模式:手动初始化进程组
        device = f'npu:{local_rank}'
        torch.npu.set_device(device)
        if not dist.is_initialized():
            # 昇腾必须使用 hccl 后端
            dist.init_process_group(backend='hccl', rank=local_rank, world_size=world_size)
        LOGGER.info(f"进程 {local_rank} 启动,共 {world_size} 卡,使用设备: {device}")
    else:
        # 单卡模式
        device = args.device
        LOGGER.info(f"单卡启动,使用设备: {device}")

    # 加载模型
    model = YOLO(args.model)

    # 准备训练参数
    train_params = vars(args).copy()
    
    # 彻底移除可能冲突的 key
    for k in ['val_after_train', 'device']:
        train_params.pop(k, None)

    # 执行训练
    # 关键:在 torchrun 环境下,不显式传 device 参数给 train(),
    # 框架会自动识别已初始化的进程组
    if local_rank != -1:
        model.train(**train_params)
    else:
        model.train(**train_params, device=device)

    # 仅在主进程执行验证
    if args.val_after_train and local_rank in [-1, 0]:
        model.val()

def get_args():
    parser = argparse.ArgumentParser(description='YOLOv12 Ascend 910B3 Optimized')
    parser.add_argument('--model', type=str, default='yolov12n.pt')
    parser.add_argument('--data', type=str, default='yolov12/ultralytics/cfg/datasets/coco128.yaml')
    parser.add_argument('--epochs', type=int, default=100)
    parser.add_argument('--batch', type=int, default=16)
    parser.add_argument('--imgsz', type=int, default=640)
    parser.add_argument('--device', type=str, default='0')
    parser.add_argument('--workers', type=int, default=8)
    parser.add_argument('--amp', action='store_true', default=True)
    parser.add_argument('--val_after_train', action='store_true', default=True)
    parser.add_argument('--project', type=str, default='runs/train-npu')
    parser.add_argument('--name', type=str, default='exp')
    return parser.parse_args()

if __name__ == '__main__':
    args = get_args()
    train_yolo(args)

该脚本主要流程包括:

  • 初始化 NPU 环境
  • 重定向数据集路径
  • 构建模型/进程组
  • 开启加速训练
  • 结果归档并启动验证

5、启动训练

5.1 单卡训练

python train.py \--device 0 \--batch 16

第一次启动训练,会自动下载如下数据: 1)训练数据: 参考:yolov12-main\ultralytics\cfg\datasets\coco128.yaml

path: ../datasets/coco128 # dataset root dir
train: images/train2017 # train images (relative to 'path') 128 images
val: images/train2017 # val images (relative to 'path') 128 images
test: # test images (optional)

2)yolo11的权重文件: https://github.com/ultralytics/assets/releases/download/v8.3.0/yolo11n.p

Ultralytics 往往会将上一个稳定版本的优秀权重作为新版本的"起点",这被称为迁移学习(Transfer Learning)

训练过程:

训练完成后生成最佳pt文件:

100 epochs completed in 0.048 hours.
Optimizer stripped from /home/task/runs/train-npu/exp2/weights/last.pt, 5.5MB
Optimizer stripped from /home/task/runs/train-npu/exp2/weights/best.pt, 5.5MB

5.2 多卡训练

使用 4 张 NPU 进行分布式训练:

torchrun \--nproc_per_node 4 \--master_port 29505 train.py \--device 0,1,2,3 \--batch 64

多卡训练会自动使用 HCCL 后端进行分布式通信,每张卡处理batch_size/num_devices 的数据。 训练过程中可以使用 npu-smi info 查看 NPU 使用情况:  

5.3 损失曲线

查看/home/task/runs/train-npu/exp2(exp*取决于训练的次数),获取results.png文件可以查看损失曲线。

6、文件转化

6.1  pt文件转换为onnx文件

选择训练生成的最佳pt,复制到当前的工作目录/home/task。参考如下脚本生成pt2onnx.py脚本,执行python pt2onnx.py,即可在当前工作目录生成best.onnx文件。

from ultralytics import YOLO

# 修改为训练好的权重文件
model = YOLO("best.pt")

# PT文件输出为onnx文件
model.export(
    format="onnx",
    imgsz=640,           # 指定图片尺寸,可提升atc转换效率
    opset=17,            # ONNX opset 版本,最新默认值
    simplify=True,       # 是否简化模型(调用 onnx-simplifier),True可去除冗余节点,降低atc转换失败概率
    dynamic=False,       # 是否启用动态 shape,默认False
    half=False           # 是否使用 FP16(半精度)
)

6.2 使用atc将onnx文件转换为om文件

参考如下脚本,在工作目录/home/task生成convert.sh脚本,执行convert.sh。即可在工作目录生成best_om.om文件。

# ATC 转换时指定混合精度
atc --model=best.onnx \
    --framework=5 \
    --output=best_om \
    --input_format=NCHW \
    --input_shape="images:1,3,640,640" \
    --soc_version=Ascend910B3 \
    --precision_mode=allow_mix_precision  # 允许推理时混合精度
[root@syn-136 task]# ls -lt |grep best
-rw-------. 1 root root  8308007 Mar 19 04:17 best_om.om
-rw-r--r--. 1 root root 10586083 Mar 19 04:13 best.onnx
-rw-r--r--. 1 root root  5539475 Mar 19 04:13 best.pt

四、推理指导

1、性能评估

参考如下脚本,在工作目录/home/task生成jpg_bin.py脚本,执行python jpg_bin.py,将图片转化为二进制输入。

import cv2
import numpy as np
import glob
import os

# 创建输出目录
os.makedirs("bin", exist_ok=True)

# 匹配图片路径
image_files = glob.glob("/home/task/datasets/coco128/images/train2017")
print(f"找到 {len(image_files)} 张图片,开始转换...")

for f in image_files:
    img = cv2.imread(f)
    if img is None:
        continue

    # 1. BGR 转 RGB (YOLOv12 训练时使用的是 RGB)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

    # 2. Resize 到模型要求的尺寸
    img = cv2.resize(img, (640, 640))

    # 3. 归一化: 0-255 -> 0.0-1.0 (关键步骤!)
    img = img.astype(np.float32) / 255.0

    # 4. 维度转置: [H, W, C] -> [C, H, W] 并增加 Batch 维度 [1, C, H, W]
    img = img.transpose(2, 0, 1)[None, ...]

    # 5. 保存为二进制文件
    bin_name = os.path.join("bin", os.path.basename(f).rsplit('.', 1)[0] + ".bin")
    img.tofile(bin_name)

print("转换完成,文件保存在 bin/ 目录下。")

在工作目录home/task执行如下命令即可进行推理性能评估。

python -m ais_bench \--model best_om.om \--input bin/ \--output ascend_out/ \--loop 100 \--batchsize 5

指定待测试的二进制图片文件,--output ascend_out/ 指定输出结果目录,--loop 100为重复次数,--batchsize 5表示并发。  

2、推理验证

参考如下代码生成脚本:infer_om.py

import numpy as np
import cv2
import argparse
from pathlib import Path
from ais_bench.infer.interface import InferSession

# 标准 COCO 80 类别 (确保与你的数据定义一致)
CLASSES = ['person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush']

def preprocess(img, sz=(640, 640)):
    """Letterbox 预处理:保持比例缩放并填充背景"""
    h, w = img.shape[:2]
    r = min(sz[0] / h, sz[1] / w)
    new_unpad = int(round(w * r)), int(round(h * r))
    dw, dh = (sz[1] - new_unpad[0]) / 2, (sz[0] - new_unpad[1]) / 2
    img_res = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
    img_pad = cv2.copyMakeBorder(img_res, int(round(dh - 0.1)), int(round(dh + 0.1)), 
                                 int(round(dw - 0.1)), int(round(dw + 0.1)), 
                                 cv2.BORDER_CONSTANT, value=(114, 114, 114))
    # BGR 转 RGB, 归一化, HWC 转 CHW
    tensor = cv2.cvtColor(img_pad, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
    return np.ascontiguousarray(np.transpose(tensor, (2, 0, 1))[None, ...]), r, (dw, dh)

def postprocess(pred, conf_thru, iou_thru):
    """后处理:解析 [84, 8400] 形状的输出"""
    pred = np.squeeze(pred) # [84, 8400]
    boxes = pred[:4, :].T   # [8400, 4] (x,y,w,h)
    scores = np.max(pred[4:, :], axis=0) # [8400]
    class_ids = np.argmax(pred[4:, :], axis=0)
    
    # 阈值过滤
    idx = scores > conf_thru
    boxes, scores, class_ids = boxes[idx], scores[idx], class_ids[idx]
    if len(boxes) == 0: return [], [], []

    # XYWH 转 XYXY
    xyxy = np.copy(boxes)
    xyxy[:, 0], xyxy[:, 1] = boxes[:, 0] - boxes[:, 2]/2, boxes[:, 1] - boxes[:, 3]/2
    xyxy[:, 2], xyxy[:, 3] = boxes[:, 0] + boxes[:, 2]/2, boxes[:, 1] + boxes[:, 3]/2
    
    # NMS (OpenCV 自带的更简洁)
    indices = cv2.dnn.NMSBoxes(xyxy.tolist(), scores.tolist(), conf_thru, iou_thru)
    if len(indices) > 0:
        indices = indices.flatten()
        return xyxy[indices], scores[indices], class_ids[indices]
    return [], [], []

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--model', '-m', required=True, help='OM model path')
    parser.add_argument('--source', '-s', required=True, help='Image or Directory')
    parser.add_argument('--out', '-o', default='./results', help='Output directory')
    parser.add_argument('--conf', type=float, default=0.25)
    args = parser.parse_args()

    # 初始化
    session = InferSession(device_id=0, model_path=args.model)
    out_path = Path(args.out); out_path.mkdir(exist_ok=True, parents=True)
    src = Path(args.source)
    files = list(src.glob('*.jpg')) if src.is_dir() else [src]

    print(f"🚀 Processing {len(files)} images...")
    for f in files:
        im0 = cv2.imread(str(f))
        if im0 is None: continue
        
        # 推理
        tensor, ratio, (dw, dh) = preprocess(im0)
        raw_out = session.infer([tensor])[0]
        boxes, scores, clss = postprocess(raw_out, args.conf, 0.45)

        # 绘图
        for box, score, cls in zip(boxes, scores, clss):
            x1 = int((box[0] - dw) / ratio)
            y1 = int((box[1] - dh) / ratio)
            x2 = int((box[2] - dw) / ratio)
            y2 = int((box[3] - dh) / ratio)
            
            label = f"{CLASSES[int(cls)]} {score:.2f}"
            cv2.rectangle(im0, (x1, y1), (x2, y2), (0, 255, 0), 2)
            cv2.putText(im0, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)

        cv2.imwrite(str(out_path / f.name), im0)
    
    print(f"✨ Finished. Results in {args.out}")

if __name__ == '__main__':
    main()

下载coco8训练数据:

wget https://github.com/ultralytics/assets/releases/download/v0.0.0/coco8.zip

解压缩,并放到datasets/coco8目录下

执行如下命令用于图片推理:

# 推理指定目录下的所有图片,并将结果存入 ./batch_out
python infer_om.py -m best_om.om -s datasets/coco8/images/train -o ./result

# 将置信度阈值提高到 0.6,只保留高可信度的检测框
python infer_om.py -m best_om.om -s datasets/coco8/images/train -o ./result --conf 0.6

# 针对单张图片进行推理,验证模型对特定目标的识别精度
python infer_om.py -m best_om.om -s datasets/coco8/images/train/000000000025.jpg -o ./result

如:

[root@syn-136 task]# python infer_om.py -m best_om.om -s datasets/coco8/images/train -o ./result --conf 0.6
[INFO] acl init success
[INFO] open device 0 success
[INFO] create new context
[INFO] load model best_om.om success
[INFO] create model description success
🚀 Processing 4 images...
✨ Finished. Results in ./result
[INFO] unload model success, model Id is 1
[INFO] end to reset device 0
[INFO] end to finalize acl
[root@syn-136 task]#

将result下载到本地,查看识别的图片:

五、总结

本案例完成了yolov12模型在昇腾 910B NPU 上的完整适配工作,包括:

  1. 训练验证:成功完成单卡和多卡训练,训练过程稳定,loss 正常收敛
  2. 模型转换:实现 PyTorch → ONNX → OM 的完整转换流程
  3. 推理部署:OM 模型在 NPU 上推理正常,检测结果准确 该适配方案可推广到其他基于 PyTorch 的目标检测模型,为昇腾 NPU生态提供参考。