离线推理精度问题分析

背景

本文档适用的场景是传统模型迁移到昇腾设备上(见下图),出现了精度问题,介绍精度问题的定位方法和解决方案。本文档只介绍“Pytorch module -> onnx -> om”场景。

什么是精度问题

离线推理(om模型在昇腾acl推理)的结果和在线推理(如:对原始模型通过pytorch/onnxruntime框架在gpu/cpu的推理)相差比较大,如在开源数据集的精度差距>1%,则认为离线推理精度不达标。这里是以在线推理的结果作为基准参考。

精度问题定界定位

精度问题定界

  • 1.保存在线推理的模型的输入输出:作为精度对比的标杆
  • 2.保存离线推理的模型的输入输出:离线推理需要保证和在线推理的原始输入是同一份数据
  • 3.数据对比:对比在线推理和离线推理的模型输入输出
    • 如果模型输出一致或相似度>99%, 则是后处理的代码问题(对比在线推理后处理逻辑)
    • 如果模型的输入不一致,则是预处理的代码问题(对比在线推理预处理逻辑)
    • 如果模型的输入一致,输出不一致,则是模型推理部分(需要进一步确认是模型转换、还是算子精度等的问题)

模型推理精度定位

确认了是在模型存在精度问题后,先来分析模型推理的过程:Pytorch module -> onnx -> OM

  • 1.先对比pytorch model和onnx model的推理结果,如果结果差异大,则需要分析pytorch export onnx的脚本
  • 2.如果1没问题,那应该就是om model的问题,可能是onnx 使用atc转换om出现问题,或者是om模型算子本身有精度问题

onnx模型精度问题常见原因

  • 模型配置/权重加载方法不一致
  • 未使用model.eval()等方式关闭training模式
  • 导出时forward函数中输入参数不一致
  • 如果确认torch.onnx.export没问题,则可以使用二分法定位onnx模型算子精度问题

om模型精度问题分析

1.确认ATC命令参数设置

如果使用ONNX直接转成的原始OM存在精度问题,首先确认ATC命令的下列参数设置:
–fusion_switch_file:融合规则(包括图融合和UB融合)开关配置文件路径以及文件名,修改融合规则,可能引起精度及性能变化。
–precision_mode:设置整个网络模型的精度模式,若精度异常时,可尝试整网设置为FP32计算,从而判断是否为计算精度引起的误差。
–op_precision_mode:指定算子内部处理时的精度模式
–modify_mixlist:在内置优化策略基础上进行调整,自行指定哪些算子允许降精度,哪些算子不允许降精度。
–optypelist_for_implmode:设置optype列表中算子的实现模式,算子实现模式包括high_precision、high_performance两种
–customize_dtypes:模型编译时自定义某个或某些算子的计算精度。
–keep_dtype:保持原始网络模型编译时个别算子的计算精度不变。

建议尝试设置--precision_mode=force_fp32,其他参数不设置。
2.是否开启AIPP功能

如果使用了–insert_op_conf参数,建议使用无AIPP的OM进行精度比对,以保证输入数据一致

3.是否使用量化

如果使用了AMCT/ModelSlim/–compression_optimize_conf参数做量化,建议使用原始模型确认精度,排查量化引入的精度问题

4.是否使用Onnx改图

建议使用改图前的原始onnx转om来确认精度,排查由于改图引入的精度问题

5.常见精度问题
  • 数据溢出问题
    现象:如果FP16场景有精度问题,FP32场景精度正常,则可能是数据溢出或算子在FP16场景下存在精度问题。
    方案
    数据溢出导致的精度问题:
    1)常量溢出可通过改图将取值缩小至FP16范围,常见于Attention Mask的计算中;
    2)数据计算结果超出FP16范围,使用–keep_dtype或–customize_dtypes参数指定问题算子为FP32。
    某些算子在FP16场景存在精度问题:与算子负责人确认算子精度问题,可暂时使用–keep_dtype或–customize_dtypes参数指定问题算子为FP32来规避精度问题。

  • 内存踩踏问题
    现象: 模型推理结果与预期结果差距较大,且数值无规律,可能是内存踩踏导致的精度问题。
    判断: 可尝试在ATC命令中添加参数–buffer_optimize=off_optimize 关闭内存复用,或设置 --disable_reuse_memory=1关闭数据缓存优化。若关闭数据缓存或内存复用后正常,说明精度问题为内存踩踏问题。
    方案: 内存踩踏问题可优先联系GE接口人确认内存复用是否存在问题,如数据调用正常,可联系内存踩踏的算子负责人确认算子的数据调用方法是否正常。

  • 算子精度问题
    算子精度问题的处理步骤(代码参考下文):

    1. dump数据,对比ONNX和OM的算子输入输出,找到第一个出现精度问题的算子。
    2. 验证单算子精度:构造单算子模型,使用相同输入,验证输出精度。
    3. 如果问题算子的单算子精度正常,则说明算子输入有问题,或可能是累积误差导致的精度下降,需要前向二分定位关键算子。
    4. 如果ONNX含有自定义算子无法推理,可dump在线推理结果作为标杆进行对比,或改图删除自定义算子重复以上步骤。

精度问题分析实践

以Reset50为样例, 提供在线推理,离线推理、结果比对:
参考ACL_PyTorch docs:01~04步骤

import numpy as np
import torch
from torchvision.io import read_image
from torchvision.models import resnet50, ResNet50_Weights

import onnxruntime as ort

from ais_bench.infer.interface import InferSession

# pytorch在线推理
class PytorchInferencer:
    def __init__(self):
        weights = ResNet50_Weights.DEFAULT
        self.model = resnet50(weights=weights)
        self.model.eval()
        self.transforms = weights.transforms()
        self.categories = weights.meta["categories"]

    def preprocess(self, image_path):
        """预处理"""
        # print(self.transforms)
        img = read_image(image_path)
        model_input = self.transforms(img).unsqueeze(0)
        return model_input

    def model_inference(self, model_input):
        """执行推理"""
        with torch.no_grad():
            model_output = self.model(model_input)
        return model_output

    def postprocess(self, model_output):
        """后处理"""
        model_output = model_output.squeeze(0).softmax(0)
        class_id = model_output.argmax().item()
        score = model_output[class_id].item()
        category_name = self.categories[class_id]
        return dict(category=category_name, class_id=class_id, score=score)

    def e2e_inference(self, image_path):
        """端到端推理"""
        model_input = self.preprocess(image_path)
        model_output = self.model_inference(model_input)
        prediction = self.postprocess(model_output)
        return prediction
    
    def export_to_onnx(self, image_path):
        torch.onnx.export(
            self.model,                # pytorch网络模型
            self.preprocess(image_path),          # 随机的模拟输入
            "resnet.onnx",        # 导出的onnx文件位置
            export_params=True,   # 导出训练好的模型参数
            verbose=True,         # verbose=True,支持打印onnx节点和对应的PyTorch代码行
            training=torch.onnx.TrainingMode.EVAL,  # 导出模型调整到推理状态,将dropout,BatchNorm等涉及的超参数固定
            input_names=["input_data"],    # 为静态网络图中的输入节点设置别名,在进行onnx推理时,将input_names字段与输入数据绑定
            output_names=["output_data"],  # 为输出节点设置别名
            # 如果不设置dynamic_axes,那么对于输入形状为[1, 3, 224, 224],在以后使用onnx进行推理时也必须输入[1, 3, 224, 224]
            # 下面设置了输入的第0维是动态的,以后推理时batch_size的大小可以是其他动态值
            #dynamic_axes={
            #    "input_data": {0: "-1"},
            #    "output_data": {0: "-1"}
            #},
            keep_initializers_as_inputs=None,  #是否将模型参数作为输入数据的一部分进行导出
            opset_version=17                  # ONNX 运算符的版本号
        )
        print("export onnx model successfully!!")


# onnx在线推理
class OnnxInferencer(PytorchInferencer):
    def __init__(self, onnx_path):
        super(OnnxInferencer, self).__init__()
        # 可以构造预处理不一样,如修改归一化均值
        # self.transforms.__dict__["mean"] = [0.1, 0.1, 0.1]
        self.session = ort.InferenceSession(onnx_path)

    def model_inference(self, model_input):
        input_data = {"input_data": model_input.numpy()}
        model_output = self.session.run([], input_data)
        print(f"onnx output type: {type(model_output)}, onnx output[0] type: {type(model_output[0])}")
        model_output = torch.from_numpy(model_output[0])
        return model_output


# om离线推理
class OmInferencer(PytorchInferencer):
    def __init__(self, om_path, device_id=0):
        super(OmInferencer, self).__init__()
        self.session = InferSession(device_id=device_id, model_path=om_path)

    def model_inference(self, model_input):
        # 可以取值'static'(静态模型)、'dymbatch'(动态batch模型)、'dymhw'(动态分辨率模型)、'dymdims'(动态dims模型)、'dymshape'(动态shape模型)
        mode = "static" 
        # om模型推理输入输出都是numpy.array格式,而在线推理的模型输入输出是torch.Tensor,注意转换。
        model_output = torch.from_numpy(self.session.infer(feeds=[model_input.numpy()], mode=mode)[0])
        return model_output


inferencer = PytorchInferencer()
print(inferencer.e2e_inference("ILSVRC2012_val_00006083.jpeg"))
# {'category': 'Yorkshire terrier', 'score': 0.2925560474395752}

# export onnx model
inferencer.export_to_onnx("ILSVRC2012_val_00006083.jpeg")


# 在shell执行atc模型转换
"""
atc --framework=5 --model=resnet.onnx --output=resnet50_bs1 --input_format=NCHW --input_shape=input_data:1,3,224,224 --log=error --soc_version=Ascend910B4
"""

inferencer = OmInferencer('./resnet50_bs1.om')
print(inferencer.e2e_inference("./ILSVRC2012_val_00006083.jpeg"))
# {'category': 'Yorkshire terrier', 'score': 0.2925560474395752}


# 在线推理和离线推理结果对比
def precision_compare(pth_output, om_output):
    pth_output1 = pth_output.flatten().astype(np.float64)
    om_output1 = om_output.flatten().astype(np.float64)
    cosine_similarity = np.dot(pth_output1, om_output1) \
                        / (np.linalg.norm(om_output1) * np.linalg.norm(om_output1))
    absolute_errors = np.abs(om_output1 - pth_output1)
    relative_errors = absolute_errors / pth_output1 * 100
    print('余弦相似度:', cosine_similarity)
    print('最大绝对误差:', absolute_errors.max())
    print('最大相对误差:', relative_errors.max() )

def run_precision():
    pth_inferencer = PytorchInferencer()
    om_inferencer = OmInferencer('./resnet50_bs1.om')
    model_input = pth_inferencer.preprocess('./ILSVRC2012_val_00006083.jpeg')
    pth_output = pth_inferencer.model_inference(model_input).numpy()
    om_output = om_inferencer.model_inference(model_input).numpy()
    precision_compare(pth_output, om_output)

模型输入输出导出

# 输入输出统一转成numpy.array格式,保存为.npy文件

# 1.pytorch在线推理,模型输入输出格式为torch.Tensor
import numpy as np
np.save("input_pth.npy", pth_input.numpy())
np.save("output_pth.npy", pth_output.numpy())

# 2.onnx在线推理,模型输入格式为np.array,输出为list,取索引0,格式为np.array
np.save("input_onnx.npy", onnx_input)
np.save("output_onnx.npy", onnx_output[0])

# 3. om离线推理,输入输出格式为np.array
np.save("input_om.npy", om_input)
np.save("output_om.npy", om_output)

# 也可以通过ais_bench工具dump 输出
'''
python -m ais_bench --model ./resnet50_bs1.om --input ./input_pth.npy --output ./ais_bench_out --outfmt NPY
'''

模型输入输出比对

import numpy as np
from scipy import spatial


def compare(path1, path2):
    # 1.直接比较是否完全一致
    print("是否完全一致:", np.array_equal(path1, path2))

    # 2.浮点数的容差比较
    print("是否近似相同:", np.allclose(path1, path2))

    # 3.余弦相似度(适合向量/特征),常用的判断标准为余弦相似度大于0.99
    cos_sim = 1 - spatial.distance.cosine(path1.reshape(-1), path2.reshape(-1))
    print("余弦相似度:", cos_sim)

def input_output_compare(ref1, ref2):
    print(f"{ref1} vs {ref2} input compare: ")
    input1 = np.load(f"input_{ref1}.npy")
    input2 = np.load(f"input_{ref2}.npy")
    compare(input1, input2)
    
    print("---" * 30)

    print(f"{ref1} vs {ref2} output compare: ")
    input1 = np.load(f"output_{ref1}.npy")
    input2 = np.load(f"output_{ref2}.npy")
    compare(input1, input2)

    print("---" * 30)

for values in [("pth", "onnx"), ("pth", "om"), ("onnx", "om")]:
    input_output_compare(*values)

OM模型精度定位

Mindstudio精度调试工具:链接

1.定位问题算子

详细说明参考:result_analyse

精度分析工具:msit debug compare
使用msit debug compare功能:使用指导

msit debug compare -gm ./resnet.onnx -om ./resnet50_bs1.om -i ./input_onnx.npy -o ./msit_compare

查看result_{timestamp}.csv
result各字段说明及分析说明:链接

使用专家建议:

msit debug compare -gm ./resnet.onnx -om ./resnet50_bs1.om -i ./input_onnx.bin -o ./msit_compare/advisor --advisor

由于我测试的onnx和om无精度差异问题,故专家建议也是认为没问题。

2.验证单算子精度

构造单算子模型,使用相同的输入,验证输出精度

  • 通过工具提取单算子模型(对onnx模型)
  • 使用om的算子输入作为onnx的单算子模型输入,获取onnx单算子推理结果
  • 对比onnx单算子输出结果和om算子输出结果,如输出对比结果不满足精度要求,则确认是om算子问题;如比对结果满足要求,则表示是输入的问题,需要进一步往前定位om的算子,参考下面《3.累计误差问题定位》。

提取单算子:
方式1:使用msit debug surgeon工具提取算子:链接

msit debug surgeon extract --input resnet.onnx --output-file sub_ops.onnx --start-node-names "/layer2/layer2.3/conv1/Conv" --end-node-names "/layer2/layer2.3/conv1/Conv"

方式2:使用改图工具构造单算子

from auto_optimizer import OnnxGraph

# 创建单算子ONNX
op_model = OnnxGraph('conv.onnx')
# 读取原ONNX模型
model = OnnxGraph.parse('resnet50.onnx')
# 找到问题算子
node = model['Conv_1']

# 复制算子至单算子模型
op_model.add_node(node.name, node.op_type, inputs=node.inputs, outputs=node.outputs, attrs=node.attrs)
init = [node.name for node in model.initializers]
for inp in node.inputs:
    if inp in init:
        op_model.add_initializer(inp, model[inp].value)
    else:
        op_model.add_input(inp, dtype='float32', shape=[]) # shape若为空,则转OM时需指定input_shape
for out in node.outputs:
    op_model.add_output(out, dtype='float32', shape=[])

# 保存单算子模型
op_model.save('conv.onnx')

3.累计误差问题定位

如果问题算子的单算子精度正常,则说明算子输入有问题,或可能是累积误差导致的精度下降,需要前向二分定位关键算子。

  1. 将ONNX模型截断为两部分,第一个ONNX模型转为OM,并使用OM做第一步推理,OM的输出作为输入使用ONNX做第二步推理。因为ONNX模型为精度的标杆,可认为第二步推理的精度完全正确。

  2. 如果ONNX的最终输出有问题,则说明OM的输出已有累积误差问题,OM中包含问题算子,截断位置变为输入与当前截断位置的中间;如果ONNX输出结果正常,说明OM的输出正常,问题算子在ONNX中,截断位置变为当前截断位置与精度下降位置的中间。

  3. 重复步骤1、2直到定位出最小问题区间,确认单算子精度,如果单算子精度正常,则说明数据对算子精度比较敏感,可使用FP32或不使用fusion pass融合算子等方法提高算子精度,使用方法参考ONNX转OM。

<think>嗯,用户想了解ais_bench的使用指南和相关工具。首先,我需要确认ais_bench是什么,以及它的主要功能。根据引用内容,ais_bench是华为昇腾社区的一个推理工具,用于性能测试和精度验证。接下来,用户可能需要安装和使用指南,以及相关工具的信息。 用户提供的引用中有几个关键点:ais_infer工具和mindx工具。引用[1]和[2]提到了ais_infer的获取和安装链接,而引用[3]推荐使用mindx,因为它更完善,支持Python和C++开发。此外,引用[4]详细描述了安装步骤,包括依赖检查、安装命令等。这些信息需要整理成清晰的步骤,方便用户参考。 用户的问题可能还涉及如何下载、安装和使用这些工具,特别是ais_bench。需要分步骤说明安装过程,可能包括依赖安装、软件包获取、执行命令等。同时,相关工具如ais_infer和mindx的区别和适用场景也需要解释清楚,帮助用户选择适合的工具。 另外,用户可能需要示例代码来快速入门,比如Python的推理示例,这样他们可以立即开始使用工具。示例应展示加载模型、处理输入数据、执行推理和获取结果的基本流程。 最后,生成的相关问题需要覆盖用户可能遇到的后续疑问,比如性能分析精度验证、日志解读、与其他工具的比较以及错误处理等。这些问题应基于用户提供的引用内容,确保答案的准确性和相关性。 需要确保回答符合格式要求,使用正确的中文表达,行内数学公式用$...$,独立公式用$$...$$,但当前内容中没有数学公式,所以主要注意代码块和引用标识的添加。同时,引用标识要自然插入在相关段落末尾,如安装步骤后引用[^4],工具对比后引用[^3]等。</think>### ais_bench 使用指南与相关工具说明 ais_bench 是昇腾社区提供的推理性能测试工具,主要用于模型推理的**性能分析**和**精度验证**。以下是关键信息整理: --- #### 一、ais_bench 安装步骤 1. **获取工具包** 通过昇腾社区仓库下载: ```bash git clone https://gitee.com/ascend/tools.git cd tools/ais-bench_workload/tool/ais_infer ``` [^2] 2. **安装依赖** 执行以下命令检查系统环境(需提前配置好昇腾基础驱动和CANN套件): ```bash ./ais_infer.py --check ``` 若依赖缺失,按提示安装必要组件。 3. **运行推理测试** 示例命令(以ResNet-50模型为例): ```bash ./ais_infer.py --model ./resnet50.om --input ./input_data/ ``` 输出包括推理时延、吞吐量等性能指标[^1]。 --- #### 二、相关工具对比 1. **ais_infer** - **功能**:基础推理工具,支持单模型性能测试。 - **适用场景**:快速验证模型在昇腾芯片上的推理性能。 2. **MindX SDK** - **功能**:全栈式开发套件,支持Python/C++多语言和流式处理。 - **优势**:提供更完整的API和文档,适合复杂业务场景。 --- #### 三、Python 推理示例 ```python from ais_bench import InferenceSession # 加载模型 session = InferenceSession("resnet50.om") # 准备输入数据 input_data = np.random.randn(1, 3, 224, 224).astype(np.float32) # 执行推理 outputs = session.run(input_data) # 输出结果 print("推理耗时:", session.sumary().avg_time) ``` --- #### 四、常见问题 1. **环境依赖错误** 若安装时报错`libascend_hal.so not found`,需检查CANN套件是否安装正确。 2. **性能优化建议** 通过调整`--batchsize`参数优化吞吐量,例如: ```bash ./ais_infer.py --model resnet50.om --batchsize 64 ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值