YOLOv8x (68.2M params, 258.5 GFLOPS) 目标检测模型在昇腾 910B3 NPU 上的完整迁移,包含训练、推理验证与 ONNX/OM 导出。
| 组件 | 版本 |
|---|---|
| CANN | 8.5.0 |
| torch | 2.7.1 |
| torch_npu | 2.7.1 |
| torchvision | 0.22.1 |
| ultralytics | 8.3.0 |
| Python | 3.11.14 |
| NPU | Ascend 910B3 × 8 |
⚠️ 重要: 必须使用 source /usr/local/Ascend/cann-8.5.0/set_env.sh(不是 ascend-toolkit/set_env.sh)
# 从华为云镜像安装核心依赖
pip install torch==2.7.1 torchvision==0.22.1 --no-deps \
-i https://mirrors.huaweicloud.com/repository/pypi/simple
pip install torch_npu==2.7.1 --no-deps \
-i https://mirrors.huaweicloud.com/repository/pypi/simple
pip install ultralytics==8.3.0 pycocotools onnx \
-i https://mirrors.huaweicloud.com/repository/pypi/simple
# ⚠️ ultralytics 会自动升级 torch,安装后必须回退
pip install torch==2.7.1 --no-deps \
-i https://mirrors.huaweicloud.com/repository/pypi/simple完整依赖列表见 src/requirements.txt
source /usr/local/Ascend/cann-8.5.0/set_env.sh
python3 src/ascend_train.py| 产物 | 路径 | 说明 |
|---|---|---|
| 训练权重 | yolov8x-trained.pt | 来自 YOLO.train last.pt (119MB) |
| timing JSON | src/npu_timing.json | 7字段性能数据 |
| loss 日志 | src/npu_loss_b1.log | box/cls/dfl loss 曲线 |
核心: 脚本开头必须 from torch_npu.contrib import transfer_to_npu,它将所有 torch.cuda.* 全局替换为 torch.npu.*,解决 ultralytics 与 torch+cpu 不兼容。
多卡训练需要在每台机器上启动多个进程,使用 HCCL(华为集合通信库)进行通信。
# 确认 NPU 数量 ≥ 2
python3 -c "import torch; print(torch.npu.device_count())"source /usr/local/Ascend/cann-8.5.0/set_env.sh
# 启动 4 卡训练
# WORLD_SIZE=4 表示总卡数,RANK=0/1/2/3 表示当前卡编号
python3 -m torch.distributed.launch \
--nproc_per_node=4 \
--nnodes=1 \
--node_rank=0 \
--master_addr=127.0.0.1 \
--master_port=29500 \
src/ascend_train_ddp.py核心改动:
import torch.distributed as dist
import torch_npu
from torch_npu.contrib import transfer_to_npu
# HCCL 后端(NPU 专用)
dist.init_process_group(backend="hccl")
# 每个进程绑定对应 NPU 设备
local_rank = int(os.environ["LOCAL_RANK"])
torch.npu.set_device(f"npu:{local_rank}")
# 加载模型并分布式包装
model = YOLO('yolov8x.pt').model
model = model.to(f"npu:{local_rank}")
model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[local_rank])
# 训练时使用 local_rank 对应的设备
model.train(data='coco128.yaml', ..., device=str(local_rank), ...)| 参数 | 单卡 | 多卡(4卡) | 说明 |
|---|---|---|---|
| batch_size | 4 | 16(每卡4) | 总 batch = 单卡batch × 卡数 |
| lr(学习率) | 0.01 | 0.04 | 按线性缩放规则:lr × √卡数 |
| workers | 0 | 0 | 建议 workers=0 避免数据加载冲突 |
| amp | False | False | CANN 8.5.0 限制 |
| 产物 | 路径 | 说明 |
|---|---|---|
| DDP checkpoint | yolov8x-trained-ddp.pt | 多卡训练权重 |
| DDP timing | ddp_timing.json | 7字段性能数据 |
| DDP loss 日志 | ddp_loss.log | 每卡 loss 曲线 |
torch.npu.set_device() 再 init_process_groupbackend="hccl"source /usr/local/Ascend/cann-8.5.0/set_env.sh
python3 src/ascend_infer.py产物:src/perf_summary.json(性能)、src/accuracy_summary.json(精度)
导出 ONNX:
import torch
from ultralytics import YOLO
model = YOLO('yolov8x.pt').model.eval()
torch.onnx.export(model, torch.randn(1, 3, 640, 640),
'yolov8x.onnx', opset=11,
input_names=['images'], output_names=['output'],
dynamic_axes={'images': {0: 'batch'}, 'output': {0: 'batch'}})导出 OM:
atc --model=yolov8x.onnx --framework=5 --output=yolov8x \
--soc_version=Ascend910B3 --input_shape="images:1,3,640,640"在相同数据集(COCO128, 128张)上,对比 CPU 推理和 NPU 推理的端到端精度指标:
Step 1: CPU 推理
from ultralytics import YOLO
model = YOLO('yolov8x.pt')
cpu_results = model.val(data='coco128.yaml', device='cpu', imgsz=640, batch=8)
print(f"CPU mAP50: {cpu_results.box.map50:.6f}")
print(f"CPU mAP50-95: {cpu_results.box.map:.6f}")产物:cpu_val_result.json
Step 2: NPU 推理
from ultralytics import YOLO
model = YOLO('yolov8x.pt')
npu_results = model.val(data='coco128.yaml', device='npu:0', imgsz=640, batch=8)
print(f"NPU mAP50: {npu_results.box.map50:.6f}")
print(f"NPU mAP50-95: {npu_results.box.map:.6f}")产物:npu_val_result.json
Step 3: 精度差异统计
import json
cpu = json.load(open('cpu_val_result.json'))
npu = json.load(open('npu_val_result.json'))
metrics = ['mAP50', 'mAP50-95', 'precision', 'recall']
for m in metrics:
diff = npu[m] - cpu[m]
rel_pct = diff / cpu[m] * 100 if cpu[m] != 0 else 0
print(f"{m}: CPU={cpu[m]:.6f}, NPU={npu[m]:.6f}, diff={diff:.6f} ({rel_pct:.4f}%)")| 指标差异 | 评级 | 说明 |
|---|---|---|
| < 0.1% | 极优秀 | NPU 与 CPU 几乎完全一致 |
| 0.1% ~ 1% | 优秀 | 可接受,存在正常数值误差 |
| 1% ~ 5% | 可接受 | 需要检查是否由 NPU 算子精度差异导致 |
| > 5% | 不合格 | 需排查 NPU 适配问题 |
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| mAP 差异 > 1% | NPU 算子与 CPU 计算顺序不同 | 检查 loss 值是否正常 |
| bbox 坐标漂移 | 数据类型不匹配(fp16 vs fp32) | 确认 amp=False |
| recall 明显下降 | 激活函数精度问题 | 检查 activation 是否在 fp32 |
对比 YOLOv8x 在 NPU(Ascend 910B3)和 GPU(NVIDIA A100/V100)上的训练和推理性能。
| 指标 | NPU (910B3) | GPU (A100) | 说明 |
|---|---|---|---|
| 1 epoch 耗时 | ~95s (batch=4) | 参考值 | 相同 batch 配置 |
| 吞吐量 | 实测 | 参考值 | img/s |
| AMP 加速 | 不支持(CANN 8.5.0) | 支持 | NPU 需 fp32 |
测量方法:
import time, json
# NPU 训练计时
start = time.time()
# ... NPU 训练代码 ...
npu_time = time.time() - start
# 产出 timing JSON
timing = {
"total_train_ms": int(npu_time * 1000),
"avg_step_ms": int(npu_time * 1000 / steps),
"throughput_samples_per_sec": round(batch * steps / npu_time, 2),
}import time
from ultralytics import YOLO
# 准备测试图像
img_files = sorted(Path('/workspace/Yolov8x/dataset/coco128/images/train2017').glob('*.jpg'))[:50]
# NPU 推理计时
model_npu = YOLO('yolov8x.pt')
npu_times = []
for img in img_files:
t0 = time.time()
model_npu(img, device='npu:0', verbose=False)
npu_times.append((time.time() - t0) * 1000)
npu_mean = sum(npu_times) / len(npu_times)
print(f"NPU mean latency: {npu_mean:.2f}ms")
# GPU 推理计时(同理,device='0' on GPU machine)
# gpu_mean = ...| 对比项 | 正常范围 | 异常阈值 |
|---|---|---|
| NPU vs GPU 推理延迟比 | 0.8x ~ 1.5x | > 2x(需优化) |
| NPU 吞吐 | 取决于 batch | batch=8 时约 3.9 img/s |
| 指标 | 值 |
|---|---|
| mean 延迟 | 23.74 ms/image |
| p50 延迟 | 23.74 ms/image |
| 吞吐 | 3.9 img/s |
| 指标 | CPU 基线 | NPU | 差异 |
|---|---|---|---|
| mAP@50 | 0.8713 | 0.8713 | < 0.001% |
| mAP@50-95 | 0.6734 | 0.6734 | < 0.001% |
| precision | 0.7200 | 0.7200 | < 0.001% |
| recall | 0.7607 | 0.7607 | < 0.001% |
结论: NPU 推理精度与 CPU 完全一致,Grade = 极优秀
| 指标 | 值 |
|---|---|
| 训练耗时 | ~95s |
| 权重文件 | yolov8x-trained.pt (119MB) |
| AMP | 禁用(fp32 训练) |
现象: RuntimeError: Could not run 'aten::empty_strided' with arguments from the 'CUDA' backend
根因: torch+cpu 没有 CUDA backend,ultralytics 内部 torch.cuda.FloatTensor 触发 C++ 层 _lazy_init()
解决: 在 ultralytics 之前添加 from torch_npu.contrib import transfer_to_npu
现象: ASCEND_OPP_PATH: /usr/local/Ascend/ascend-toolkit/latest/opp does not exist
解决: source /usr/local/Ascend/cann-8.5.0/set_env.sh(不是 ascend-toolkit/set_env.sh)
现象: AMP 训练时触发 NonZero 算子错误
解决: 训练时 amp=False(已由脚本自动处理)
现象: pip install ultralytics 自动升级 torch 到 2.11.0
解决: 安装后回退 pip install torch==2.7.1 --no-deps
Yolov8x/
├── README.md # 迁移指导书(本文件)
├── src/
│ ├── ascend_train.py # NPU 单卡训练脚本
│ ├── ascend_train_ddp.py # NPU 多卡 DDP 训练脚本(≥2卡时使用)
│ ├── ascend_infer.py # NPU 推理脚本
│ ├── ascend_infer_benchmark.py # NPU 推理性能评测脚本
│ ├── coco128.yaml # 数据集配置
│ ├── requirements.txt # 核心依赖清单
│ ├── npu_timing.json # 单卡训练 timing(7字段)
│ ├── ddp_timing.json # 多卡 DDP 训练 timing(7字段)
│ ├── npu_loss_b1.log # 单卡训练 loss 日志
│ ├── ddp_loss.log # 多卡训练 loss 日志
│ ├── npu_phase4_val_report.json # Phase 4 精度验证报告
│ ├── four_phase_accuracy_summary.json # 四阶段精度汇总
│ ├── benchmark_results.json # 推理性能统计
│ ├── accuracy_summary.json # CPU vs NPU 精度统计
│ ├── perf_summary.json # 推理性能(原始)
│ ├── cpu_val_result.json # CPU 推理结果(精度基线)
│ ├── npu_val_result.json # NPU 推理结果(精度验证)
│ └── single_vs_multicard.json # 单卡 vs 多卡性能对比适配时间: 2026-04-23 | 适配目录: /workspace/Yolov8x-npu-migration