s100p 量化部署精度问题

OE版本3.7.0,系统版本4.0.5

使用PTQ量化方式将onnx模型转换成hbm部署到s100p上,精度损失了很多,我在宿主机测试了原始onnx、转换过程生成的onnx的指标精度,在s100p上测试了hmb模型的指标精度,对比结果如下:

原始onnx:0.809
original_float_model.onnx:0.809
optimized_float_model.onnx:0.778
calibrated_model.onnx:无法正常运行
ptq_model.onnx:无法正常运行
hbm:0.748

现在有两个疑问,(1)calibrated_model.onnx和ptq_model.onnx在服务器上无法输出正确结果,可能是什么原因造成的?(2)对比中间结果与最终结果,可以看出指标是逐步损失的,这里有个疑问,按照我的理解optimized_float_model.onnx只是合并不同算子,理论上应该不会带来精度损失,可能的原因是什么?有什么工具可以进行进一步分析。

针对你遇到的 S100P 量化部署精度损失问题,结合社区已有案例和工具链文档,以下是详细分析和排查建议:

核心结论

  1. calibrated_model.onnxptq_model.onnx 无法运行:通常是因为输入数据预处理不匹配量化参数异常导致。
  2. optimized_float_model.onnx 精度损失:虽然只是算子融合,但算子融合可能改变数值计算顺序,引入浮点误差累积,尤其是当模型对数值精度敏感时。

问题 1:calibrated_model.onnxptq_model.onnx 无法正常运行

可能原因

原因 说明 排查方法
输入数据预处理不匹配 校准模型期望的输入是经过特定预处理(如归一化、颜色空间转换)后的数据,如果直接喂入原始数据会导致输出异常 检查 YAML 配置中的 preprocess 节点,确认是否插入了预处理算子
量化参数异常 校准数据不足或分布不均匀,导致某些节点的量化参数(scale/threshold)计算异常 使用 PTQ Debug 工具检查量化敏感度
算子不支持 某些算子在量化后不被 BPU 支持,导致推理失败 检查模型转换日志中的算子支持情况

排查步骤

# 1. 检查模型转换日志,确认是否有算子不支持的警告
cat model_convert_log.txt | grep -i "unsupported"

# 2. 使用 PTQ Debug 工具分析校准模型
python3 << EOF
import horizon_nn.debug as dbg

# 获取节点量化敏感度
node_message = dbg.get_sensitivity_of_nodes(
    model_or_file='./calibrated_model.onnx',
    calibrated_data='./calibration_data/',
    metrics=['cosine-similarity', 'mse'],
    verbose=True
)

# 保存敏感度分析结果
import json
with open('sensitivity_analysis.json', 'w') as f:
    json.dump(node_message, f, indent=2)
EOF

# 3. 检查校准数据是否经过正确预处理
python3 << EOF
import numpy as np
import os

calib_data_path = './calibration_data/input1/0.npy'
data = np.load(calib_data_path)
print(f"校准数据形状: {data.shape}")
print(f"校准数据范围: [{data.min()}, {data.max()}]")
print(f"校准数据类型: {data.dtype}")
EOF

问题 2:optimized_float_model.onnx 精度损失

原因分析

虽然 optimized_float_model 只是算子融合,但以下情况会导致精度损失:

  1. 浮点计算顺序改变:算子融合可能改变计算顺序,浮点运算不满足结合律,导致误差累积
  2. 数值稳定性问题:某些算子融合后可能引入数值不稳定(如除法、指数运算)
  3. 预处理节点插入:如果 YAML 中配置了预处理,optimized_float_model 可能已插入预处理节点,导致输入数据被重复处理

验证方法

# 对比 original_float 和 optimized_float 的输出差异
import onnxruntime as ort
import numpy as np

# 加载两个模型
original_session = ort.InferenceSession('original_float_model.onnx')
optimized_session = ort.InferenceSession('optimized_float_model.onnx')

# 准备测试输入
test_input = np.random.randn(1, 3, 640, 480).astype(np.float32)

# 推理
original_output = original_session.run(None, {'input': test_input})[0]
optimized_output = optimized_session.run(None, {'input': test_input})[0]

# 计算差异
diff = np.abs(original_output - optimized_output)
print(f"最大差异: {diff.max()}")
print(f"平均差异: {diff.mean()}")
print(f"余弦相似度: {np.dot(original_output.flatten(), optimized_output.flatten()) / (np.linalg.norm(original_output.flatten()) * np.linalg.norm(optimized_output.flatten()))}")

推荐工具:PTQ 精度 Debug 工具

使用地平线官方提供的 PTQ 精度 Debug 工具 进行系统性分析:

一键运行全部分析

# 进入 GPU Docker(可选,加速分析)
# 运行一键分析
hmct-debugger runall \
    ./model_output/calibrated_model.onnx \
    ./model_output/calibration_data \
    --save_dir ./debug_results

关键分析步骤

import horizon_nn.debug as dbg

# 1. 绘制累积误差曲线(判断是激活量化还是权重量化问题)
dbg.plot_acc_error(
    save_dir='./debug_results',
    model_or_file='./calibrated_model.onnx',
    calibrated_data='./calibration_data/',
    quantize_node=['weight', 'activation'],
    metric='cosine-similarity'
)

# 2. 获取节点量化敏感度排序
node_message = dbg.get_sensitivity_of_nodes(
    model_or_file='./calibrated_model.onnx',
    calibrated_data='./calibration_data/',
    metrics=['cosine-similarity'],
    node_type='node',
    verbose=True
)

# 3. 分析敏感节点的数据分布
sensitive_nodes = list(node_message.keys())[:5]  # 取 top5 敏感节点
dbg.plot_distribution(
    save_dir='./debug_results',
    model_or_file='./calibrated_model.onnx',
    calibrated_data='./calibration_data/',
    nodes_list=sensitive_nodes
)

# 4. 检查通道间数据分布(判断是否需要 per-channel 量化)
dbg.get_channelwise_data_distribution(
    save_dir='./debug_results',
    model_or_file='./calibrated_model.onnx',
    calibrated_data='./calibration_data/',
    nodes_list=sensitive_nodes
)

优化建议

根据分析结果,可尝试以下优化手段:

问题类型 优化方案
激活量化敏感 对敏感节点设置 int16 量化或 run_on_cpu
权重量化敏感 使用 QAT(量化感知训练)重新训练
通道间分布不均 启用 per_channel 量化
预处理不匹配 检查 YAML 中的 preprocess 配置,确保输入数据一致性

YAML 配置示例(设置敏感节点不量化)

quantization_config:
  node_quantize_config:
    - node_name: "Conv_60"
      run_on_cpu: true
    - node_name: "Conv_48"
      quantize_type: "int16"

相关社区资源


下一步行动

  1. 先运行 PTQ Debug 工具,定位敏感节点
  2. 检查输入数据预处理,确保校准数据和推理数据一致
  3. 根据分析结果,调整量化策略(int16/run_on_cpu/per_channel)
  4. 如问题仍未解决,建议将模型转换工程打包上传到社区求助帖,便于官方技术支持复现分析

需要我帮你进一步分析具体的 Debug 结果吗?

calibrated_model.onnx和ptq_model.onnx 中间插入了优化算子,需要用HB ONNX runtime来推理,详细可以参考手册

参考手册也有提供 精度debug工具,也可以求助 天工 获得工具链对应支持

我看手册中给出的以下示例用的都是HBRuntime,与HB ONNX runtime不是一个库吗?
import numpy as np

加载地平线依赖库

from horizon_tc_ui.hb_runtime import HBRuntime

准备模型运行的输入,此处`input.npy`为处理好的数据

data = np.load(“input.npy”)

加载模型文件,根据实际模型进行设置

ONNX模型

sess = HBRuntime(“model.onnx”)

HBIR模型

sess = HBRuntime(“model.bc”)

HBM模型

sess = HBRuntime(“model.hbm”)

获取输入&输出节点名称

input_names = sess.input_names
output_names = sess.output_names

准备输入数据,根据实际输入类型和layout进行准备,配置格式要求为字典形式,输入名称和输入数据组成键值对

如模型仅有一个输入

input_feed = {input_names[0]: data}

如模型有多个输入

input_feed = {input_names[0]: data1, input_names[1]: data2}

进行模型推理,推理的返回值是一个list,依次与output_names指定名称一一对应

output = sess.run(output_names, input_feed)

你好!这是一个非常关键的问题,很多开发者在初次使用工具链时都会产生这个疑惑。

核心结论

HBRuntimeHB ONNX Runtime 是同一个库的不同称呼,但关键在于模型格式运行环境的区别。


1. 库的关系

名称 说明
HBRuntime Python 包名 (horizon_tc_ui.hb_runtimehorizon_nn)
HB ONNX Runtime 指支持 ONNX 模型推理的 HBRuntime 后端
HBRuntime (C++) 底层 C++ 推理引擎

本质是同一个推理引擎,只是根据加载的模型格式不同,内部调用不同的执行路径。


2. 关键区别:模型格式决定推理后端

from horizon_tc_ui.hb_runtime import HBRuntime

# 情况 1:加载 ONNX 模型(包括 calibrated_model.onnx)
sess = HBRuntime("calibrated_model.onnx")  
# → 内部使用 ONNX Runtime 后端(CPU/GPU)
# → 需要在 x86 服务器或板端 CPU 上运行
# → 不能发挥 BPU 加速

# 情况 2:加载 HBM 模型(BPU 专用格式)
sess = HBRuntime("model.hbm")  
# → 内部使用 BPU Runtime 后端
# → 必须在 S100P 板端运行
# → 发挥 BPU 加速

3. 为什么 calibrated_model.onnx 无法正常运行?

根据你的描述,问题可能出在以下几点:

原因 1:运行环境不匹配

  • calibrated_model.onnx 包含 HzCalibration 节点,这是地平线工具链插入的特殊校准节点
  • 这些节点只能在地平线定制的 ONNX Runtime中执行
  • 如果你用标准 ONNX Runtime (onnxruntime-gpu) 加载,会报错或输出错误

:white_check_mark: 正确做法

# 必须使用地平线的 HBRuntime 加载
from horizon_tc_ui.hb_runtime import HBRuntime

sess = HBRuntime("calibrated_model.onnx")
output = sess.run(sess.output_names, input_feed)

原因 2:输入数据预处理不匹配

  • calibrated_model.onnx 可能已经插入了预处理节点(归一化、颜色空间转换)
  • 如果你再次手动预处理,会导致双重处理,输出异常

:white_check_mark: 排查方法

# 检查 YAML 配置中是否有 preprocess 相关配置
cat your_config.yaml | grep -A 10 "model_parameters"

# 检查模型输入节点名称
from horizon_tc_ui.hb_runtime import HBRuntime
sess = HBRuntime("calibrated_model.onnx")
print("输入节点:", sess.input_names)
print("输出节点:", sess.output_names)

原因 3:校准数据不足或分布异常

  • 如果校准数据太少或分布不均匀,某些节点的量化参数(scale/threshold)会计算异常
  • 导致 HzCalibration 节点输出错误

:white_check_mark: 排查方法

# 使用 PTQ Debug 工具检查校准数据
import horizon_nn.debug as dbg

node_message = dbg.get_sensitivity_of_nodes(
    model_or_file='./calibrated_model.onnx',
    calibrated_data='./calibration_data/',
    metrics=['cosine-similarity'],
    verbose=True
)

4. 正确的验证流程

# 步骤 1:在 x86 服务器上验证 original_float 和 optimized_float
python3 << EOF
from horizon_tc_ui.hb_runtime import HBRuntime
import numpy as np

# 加载原始模型
sess_orig = HBRuntime("original_float_model.onnx")
sess_opt = HBRuntime("optimized_float_model.onnx")

# 准备输入(注意:不要重复预处理)
input_data = np.load("input.npy")  # 已经是预处理后的数据
input_feed = {sess_orig.input_names[0]: input_data}

# 推理
output_orig = sess_orig.run(sess_orig.output_names, input_feed)
output_opt = sess_opt.run(sess_opt.output_names, input_feed)

# 对比
diff = np.abs(output_orig[0] - output_opt[0]).max()
print(f"original vs optimized 最大差异:{diff}")
EOF

# 步骤 2:验证 calibrated_model(需要校准数据)
python3 << EOF
from horizon_tc_ui.hb_runtime import HBRuntime
import numpy as np

sess_calib = HBRuntime("calibrated_model.onnx")
input_data = np.load("calibration_data/input1/0.npy")  # 使用校准数据
input_feed = {sess_calib.input_names[0]: input_data}

output_calib = sess_calib.run(sess_calib.output_names, input_feed)
print(f"calibrated_model 输出形状:{output_calib[0].shape}")
print(f"calibrated_model 输出范围:[{output_calib[0].min()}, {output_calib[0].max()}]")
EOF

# 步骤 3:在板端验证 HBM 模型
# 将模型和测试数据拷贝到 S100P 板端
scp model.hbm input.npy root@<board_ip>:/data/

# 在板端运行
ssh root@<board_ip>
python3 << EOF
from horizon_tc_ui.hb_runtime import HBRuntime
import numpy as np

sess_hbm = HBRuntime("model.hbm")
input_data = np.load("input.npy")
input_feed = {sess_hbm.input_names[0]: input_data}

output_hbm = sess_hbm.run(sess_hbm.output_names, input_feed)
print(f"HBM 模型输出形状:{output_hbm[0].shape}")
EOF

5. 关于 optimized_float_model.onnx 精度损失

你提到从 0.809 降到 0.778,这确实不正常。可能原因:

原因 1:算子融合改变了计算顺序

  • 浮点运算不满足结合律,(a + b) + c ≠ a + (b + c)
  • 某些模型对数值误差非常敏感(如 BatchNorm、LayerNorm)

原因 2:预处理节点被插入

  • 检查 YAML 中是否配置了 mean_valuescale_value
  • 如果配置了,optimized_float_model 会自动插入预处理节点
  • 你的测试代码可能重复预处理

:white_check_mark: 验证方法

# 检查模型结构差异
import onnx

orig_model = onnx.load("original_float_model.onnx")
opt_model = onnx.load("optimized_float_model.onnx")

print("原始模型节点数:", len(orig_model.graph.node))
print("优化模型节点数:", len(opt_model.graph.node))

# 检查输入节点名称是否变化
print("原始模型输入:", [i.name for i in orig_model.graph.input])
print("优化模型输入:", [i.name for i in opt_model.graph.input])

6. 推荐排查路径

# 1. 确认使用 HBRuntime 加载所有 ONNX 模型
# 2. 检查输入数据是否经过正确预处理(不要重复)
# 3. 使用 PTQ Debug 工具分析 calibrated_model
hmct-debugger runall ./calibrated_model.onnx ./calibration_data --save_dir ./debug_results

# 4. 查看累积误差曲线,判断是激活量化还是权重量化问题
# 5. 根据敏感度分析,对敏感节点设置 int16 或 run_on_cpu

7. 相关社区资源


下一步建议

  1. 确认你使用的是 horizon_tc_ui.hb_runtime 而不是标准 onnxruntime
  2. 检查 YAML 配置中的预处理参数,确保输入数据一致性
  3. 运行 PTQ Debug 工具,定位敏感节点
  4. 如问题仍未解决,建议将模型转换工程(含 YAML、ONNX、校准数据)打包,发帖或联系天工支持

需要我帮你进一步分析具体的错误日志或 Debug 结果吗?

明白 那就使用HBRuntime来验证即可,ptq_model.onnx 理论上跟HBM的表现会一致