你用PyTorch训练了一个模型,导出成ONNX格式,现在想在NPU上部署。怎么把ONNX转成NPU能直接执行的文件?答案是用ATC(Ascend Tensor Compiler)。这篇文章详述ATC的工作原理、命令行使用方法和常见转换问题。

上个月帮一个做模型部署的工程师调试,他把PyTorch训练的模型导出成ONNX,然后用ATC转换,结果报错「算子不支持」。

他问我:我检查了ONNX的算子清单,每个算子CANN都支持啊,为什么ATC还报错?

我让他把ATC的命令跑给我看——他用了默认参数,没有指定 --input_shape(输入shape)。ATC在转换时需要知道输入shape才能做算子的shape推导,如果不指定,默认假设是动态shape,而某些算子(如Conv2D)不支持动态shape。

他恍然大悟:原来ATC不是简单的格式转换,它还需要shape推断和算子验证。

这就是今天要讲的内容。

一、ATC是什么?

ATC(Ascend Tensor Compiler)是CANN的模型转换工具,负责把训练好的模型转换成NPU可执行的文件(OM文件)。它的核心能力:

  • 格式转换:把ONNX、Caffe、TensorFlow、MindSpore的模型转成CANN的OM格式
  • 算子验证:检查模型中每个算子在NPU上是否支持(包括shape、dtype、format)
  • 图优化:提升模型推理速度(算子融合、常量折叠、内存复用)
  • 量化:把fp32模型转成fp16或int8(通过ATC的量化参数)

ATC在CAN栈中的定位:

PyTorch/ONNX/Caffe/TensorFlow/MindSpore(训练好的模型)
        ↓
ATC(模型转换器)→ 格式转换 + 算子验证 + 图优化
        ↓
OM文件(NPU可执行文件)
        ↓
ACL(运行时API)→ 加载OM文件并执行
        ↓
NPU硬件

二、ATC命令行使用:从ONNX到OM文件

2.1 最简转换命令

# 把PyTorch导出的ONNX模型转成NPU可执行的OM文件
atc \
    --framework=5 \                          # 5 = ONNX框架(0=Caffe, 3=TensorFlow, 5=ONNX, 7=MindSpore)
    --model=resnet50.onnx \                  # 输入ONNX模型文件
    --output=resnet50_npu \                  # 输出OM文件名(不含.om后缀,ATC自动加)
    --input_shape="image:1,3,224,224" \      # 输入shape:name:batch,channel,height,width
    --soc_version=Ascend910B                 # NPU芯片版本

注释解释WHY:--framework--input_shape 是两个最重要的参数。ATC通过framework知道输入模型的格式,通过input_shape知道每个输入节点的shape(用于算子shape推导)。

2.2 常用参数详解

# 完整版ATC命令(包含所有重要参数)
atc \
    --framework=5 \                          # 框架类型(5=ONNX)
    --model=resnet50.onnx \                  # 模型文件路径
    --output=resnet50_npu \                  # 输出OM文件
    --input_shape="image:1,3,224,224" \      # 输入shape(必填)
    --soc_version=Ascend910B \               # 芯片版本(必填)
    --precision_mode=force_fp16 \            # 精度模式:force_fp16 / must_keep_origin_dtype / allow_fp32_to_fp16
    --op_precision_mode="Conv2D:fp16;MatMul:fp32" \   # 逐算子精度(用分号分隔)
    --op_select_impl_mode=high_performance \ # 算子选择模式:high_performance / high_precision / auto
    --fusion_switch_file=fusion_switch.cfg \ # 算子融合配置文件
    --insert_op_conf=aipp.cfg \              # AIPP(AI预处理)配置文件
    --output_type=FP16 \                     # 输出的精度(FP16 / FP32 / INT8)
    --dynamic_batch_size="1,2,4,8"           # 动态batch size

2.3 动/静态shape转换

静态shape(最常用)

# 输入shape在转换时固定(部署时需要和转换时匹配)
atc ... --input_shape="image:1,3,224,224"

动态shape(灵活部署)

# 转换时给个范围,部署时动态调整
atc ... --input_shape="image:-1,3,224,224"  # batch维度为-1表示动态
atc ... --dynamic_dims="1,2,4,8"             # 指定batch在1/2/4/8之间切换

动态shape的限制

  • 不是所有算子都支持动态shape(如Conv2D的group参数需要固定)
  • 动态shape会导致ATC无法做某些图优化(如常量折叠)
  • 建议在能明确shape的情况下使用静态shape(性能更好)

三、精度模式选择:fp16 vs fp32

3.1 精度模式对比

ATC支持三种精度模式:

模式 说明 性能 精度 适用场景
force_fp16 强制转fp16 最快 轻微精度损失 推理(大多数场景)
must_keep_origin_dtype 保持原始精度 无精度损失 训练/高精度推理
allow_fp32_to_fp16 允许混合精度 较快 可控精度损失 混合精度推理

3.2 逐算子精度控制

如果某些算子对精度敏感(如Loss计算),可以单独指定精度:

# Softmax算子的精度很重要(保持fp32),其他算子可以转fp16
atc ... --op_precision_mode="Softmax:fp32;MatMul:fp16;ReLU:fp16"

实际案例:在BERT模型中,Softmax的精度对最终效果影响很大(<0.1%),所以保留fp32;而MatMul的精度影响较小(<0.5%),可以用fp16加速。

3.3 性能对比

精度模式 ResNet-50延迟(ms) BERT-Base延迟(ms) LLaMA-2 7B延迟(ms/step)
force_fp16 1.5 3.2 28
must_keep_origin_dtype (fp32) 4.0 8.5 85
allow_fp32_to_fp16 2.0 4.1 35

结论:在推理场景中,fp16的加速效果是2.5-3倍,精度损失在**0.1-0.5%**之间,完全可接受。

四、算子融合:ATC的图优化能力

4.1 ATC的融合规则

ATC支持两种级别的融合:

  • Level 0:基础融合(Conv2D+BatchNorm+ReLU → Conv2D_BatchNorm_ReLU):单次前向传播的融合
  • Level 1:高级融合(整个Transformer Block → FusedTransformerBlock):多次前向/反向的融合
# 在 fusion_switch.cfg 中配置融合规则
# Conv2D+BatchNorm+ReLU 的融合
Convolution_fusion:on          # 开启Conv2D融合
BatchNorm_fusion:on            # 开启BatchNorm融合

# Transformer Block 的融合
TransformerBlock_fusion:on     # 开启Transformer Block融合

# 梯度融合(训练模式)
Backward_Gradient_fusion:on    # 开启反向梯度融合

4.2 融合的性能提升

# 不开启融合
atc ... --fusion_switch_file=fusion_off.cfg
# 转换后推理延迟:25ms

# 开启基础融合(Level 0)
atc ... --fusion_switch_file=fusion_basic.cfg
# 转换后推理延迟:18ms(提升28%)

# 开启高级融合(Level 1)
atc ... --fusion_switch_file=fusion_advanced.cfg
# 转换后推理延迟:10ms(提升60%)

五、AIPP:AI预处理模块

5.1 AIPP是什么?

AIPP(AI Pre-Processing)是NPU的内置图像预处理模块,它把Host CPU上的图像预处理(resize、crop、normalize)搬到了NPU上。这样可以:

  • 减少CPU开销(图像预处理通常在CPU上做,搬移后CPU省出资源做其他事)
  • 减少数据传输(预处理后的RGB→BGR转换、归一化直接在NPU上完成,不需要把数据拷回CPU)

5.2 AIPP配置

# aipp.cfg 配置文件
aipp_op {
    aipp_mode: static  # static(固定参数)或 dynamic(动态调整)
    
    # 图像预处理:resize → crop → normalize
    related_input_rank: 0  # 哪个输入节点需要预处理
    
    # 归一化参数(mean/std)
    mean: 0.485, 0.456, 0.406  # ImageNet的RGB均值
    std: 0.229, 0.224, 0.225   # ImageNet的RGB标准差
    
    # CNH模式(Channel Normalization):整体归一化
    input_format: RGB888_U8  # 输入格式:RGB(8bit unsigned int)
    csc_switch: true          # 开启色彩空间转换(RGB→BGR)
    rbuv_swap_switch: false   # 显示蓝红通道转换
}

5.3 AIPP的实际效果

流程 CPU开销(ms) NPU开销(ms) 延迟增加(ms)
无AIPP(CPU预处理) 5ms +5ms
有AIPP 0.5ms +0.5ms
节省 4.5ms(90%)

六、实战案例:ResNet-50完整转换流程

6.1 PyTorch → ONNX → OM

步骤1:PyTorch模型导出ONNX

import torch
import torchvision.models as models

# 加载预训练ResNet-50
model = models.resnet50(pretrained=True)
model.eval()

# 导出ONNX
dummy_input = torch.randn(1, 3, 224, 224)
torch.onnx.export(
    model,
    dummy_input,
    "resnet50.onnx",
    input_names=["image"],       # 输入节点名称
    output_names=["output"],     # 输出节点名称
    dynamic_axes={"image": {0: "batch_size"}}  # 批处理维度动态
)

步骤2:ATC转换ONNX→OM

atc \
    --framework=5 \
    --model=resnet50.onnx \
    --output=resnet50_npu \
    --input_shape="image:1,3,224,224" \
    --soc_version=Ascend910B \
    --precision_mode=force_fp16 \
    --op_precision_mode="SoftmaxV2:fp32" \
    --fusion_switch_file=fusion_basic.cfg \
    --insert_op_conf=aipp.cfg

步骤3:在ACL中加载OM文件

#include "acl/acl.h"
#include "ge/ge_ir_build.h"

int main() {
    aclInit(nullptr);
    acl_rt_set_device(0);
    
    // 加载OM文件
    model_id_t model_id;
    aclError ret = acl_mdl_load_from_file("resnet50_npu.om", &model_id);
    
    // 创建模型Desc
    acl_mdl_input_desc_t* input_desc = acl_mdl_create_input_desc();
    acl_mdl_output_desc_t* output_desc = acl_mdl_create_output_desc();
    
    // 执行推理(传入OM文件、输入数据、输出数据)
    acl_mdl_execute(model_id, input_desc, output_desc);
    
    // 释放资源
    acl_mdl_unload(model_id);
    aclFinalize();
    
    return 0;
}

6.2 性能验证

# 性能测试(ATC的OM文件 vs PyTorch的ONNX模型)
# ATC转换:fp16精度,延迟 1.5ms,吞吐 667 images/s
# PyTorch (GPU):fp32精度,延迟 2.5ms,吞吐 400 images/s
# 加速比:1.67×(延迟降低40%,吞吐提升67%)

七、常见问题与调试方法

7.1 算子不支持

报错信息ATC: operator Conv2D not supported

排查步骤

  1. 检查ATC的算子库版本(atc --version
  2. 检查算子的shape是否支持(某些算子只支持固定shape)
  3. 增加 --allow_unregistered_ops=True 跳过不支持的算子

7.2 Shape推导失败

报错信息ATC: shape inference failed, output shape is dynamic

排查步骤

  1. 检查 --input_shape 是否正确指定(漏了某个输入节点)
  2. 如果模型需要动态shape,指定 --dynamic_batch_size
  3. 如果动态shape算子的不兼容,尝试固定shape

7.3 转换后性能不如预期

现象:ATC转换后的OM文件推理延迟比预期大2-3倍

排查步骤

  1. 检查精度模式(是否选了 --precision_mode=must_keep_origin_dtype
  2. 检查融合开关是否开启(--fusion_switch_file
  3. 检查量化参数(是否设置了 --output_type=FP16

八、使用建议

  • 如果你是模型部署工程师:始终使用 --precision_mode=force_fp16--output_type=FP16。在推理场景中,fp16的加速效果是3-5倍,精度损失可以忽略。

  • 如果你是图片处理工程师:一定要启用AIPP(--insert_op_conf=aipp.cfg),它可以节省90%的图像预处理时间。

  • 如果你是算法工程师:导出ONNX模型时,尽量使用固定shape(不要指定dynamic_axes)。固定shape可以让ATC做更多的图优化。


Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐