RDK 量化工具 每层预选相似度都是0.97以上,但是量化前后的onnx推理置信度分数相差0.6左右

根据知识库内容和通用量化经验,Cosine Similarity(余弦相似度)主要衡量的是张量分布形状的稳定性,而非绝对数值的准确性。即使每层相似度都在 0.97 以上,如果某些对数值敏感的层(如 BatchNorm、Softmax 前的 Logits、或检测头输出)发生了微小的偏移,经过后续的非线性激活或后处理放大后,会导致最终置信度剧烈变化甚至类别丢失。

针对 YOLOv11n 模型出现此类问题,请按照以下优先级进行排查和解决:

核心结论

相似度达标不代表精度无损。 0.97 的相似度对于中间层是可以接受的,但对于输出层(Output Layer)关键特征融合层,建议追求 >= 0.99。YOLO 系列模型对检测头(Head)部分的量化噪声非常敏感,尤其是当某个类别的置信度本身处于阈值边缘时量化误差极易导致该类别被过滤掉。


排查与解决方案

1. 检查数据预处理一致性(最高频原因)

知识库明确指出:original_float_model.onnx 中包含了 HzPreprocess 算子。如果在推理 Float ONNX 和 Int8 ONNX 时,Python/C++ 代码中的预处理逻辑不一致,会导致输入数据分布不同,进而导致结果巨大差异。

  • 排查步骤:

    1. 使用 Netron 打开 ***_optimized_float_model.onnx
    2. 查看第一个节点 HzPreprocess 的属性,确认 data_format (RGB/BGR/NV12) 和 input_type
    3. 严格对比你推理 Float 模型时的预处理代码与推理 Int8 模型时的预处理代码。
    4. 关键点: 确保 Resize 算法(双线性/最近邻)、归一化系数(Mean/Std)、以及是否做了 -128 偏移(如果模型输入要求 int8)完全一致。
  • 建议操作:
    直接使用知识库提供的 HB_ONNXRuntime 示例代码分别测试 Float 和 Int8 模型,排除自行编写预处理代码带来的误差。

# 务必使用统一的预处理函数
def preprocess(input_name, img_path):
    img = cv2.imread(img_path)
    # 根据 HzPreprocess 算子属性调整颜色空间
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 
    # Resize 到模型输入尺寸,注意插值方式
    img = cv2.resize(img, (640, 640), interpolation=cv2.INTER_LINEAR)
    # 归一化 (如果模型内部已包含归一化,此处不要重复做;若未包含,需按训练时参数做)
    img = img.astype(np.float32) / 255.0
    
    # HWC -> CHW
    img = img.transpose(2, 0, 1)
    
    # 注意:如果模型输入节点 dtype 是 int8,可能需要减去 128 并转为 int8
    # 具体取决于 yaml 配置中 input_type 的设置
    return np.expand_dims(img, axis=0)

2. 分析校准数据集(Calibration Dataset)的质量

PTQ(训练后量化)的效果高度依赖校准数据。如果校准集中缺少“难例”特定类别的样本分布不均,量化参数(Scale/Zero-point)将无法准确覆盖该类别的特征分布。

  • 现象解释: 如果丢失的类别在校准集中出现次数少,或者其特征激活值范围未被充分采集,量化器可能会将该类别的关键特征映射到错误的量化区间。
  • 解决建议:
    1. 增加校准集多样性: 确保校准集包含至少 100-500 张图片,且必须包含所有目标类别,特别是那些量化后丢失或置信度降低的类别。
    2. 加入难例: 加入小目标、遮挡严重、光照极端的图片进入校准集。
    3. 重新校准: 修改校准集路径,重新运行量化工具。

3. 针对敏感层进行混合精度或层跳过(Layer Skip)

YOLO 的检测头(Detect Head)通常包含大量的逐元素操作和激活函数,对量化误差极其敏感。

  • 排查思路:
    虽然整体相似度尚可,但需要定位是哪一层导致了最终的偏差。
  • 解决建议:
    1. 跳过检测头量化: 在量化配置文件(yaml)中,尝试将 YOLOv11 的最后一层卷积(输出框和类别分数的层)及其前后的激活层设置为不量化(Float)。这通常能显著恢复精度,同时仅增加极少的计算开销。
    2. 配置示例(参考):
      # 在 quantization_config 或 layer_config 中指定
      skip_quantize_layers:
        - "model.22.cv2.2.2"  # 示例:指向具体的输出卷积层名称,需用Netron确认
        - "model.22.cv3.2.2"
      
    3. 使用敏感层分析工具: 如果工具链支持,查看每一层的 MSE (均方误差) 而不仅仅是 Cosine Similarity。MSE 更能反映数值偏差的大小。

4. 检查后处理逻辑(NMS 与 置信度阈值)

量化后的分数分布可能会发生整体平移或缩放。

  • 排查步骤:
    1. 打印量化前后模型的原始输出 Tensor(即 NMS 之前的 Box 和 Score)。
    2. 对比同一张图片下,Float 和 Int8 的原始 Score 数值。
    3. 如果原始 Score 差异不大(例如 0.85 vs 0.82),但最终结果一个有框一个没框,说明是**后处理阈值(Conf Threshold)**设置过于临界。
    4. 调整: 适当降低推理时的置信度阈值(例如从 0.5 降至 0.45),观察该类别是否重新出现。

5. 验证优化图(Optimized Graph)的正确性

知识库提到 ***_optimized_float_model.onnx 应该与原始模型精度一致。

  • 操作:
    1. 先推理 ***_original_float_model.onnx(如果有)或原始导出的 ONNX。
    2. 再推理 ***_optimized_float_model.onnx
    3. 如果这两者结果就有差异,说明模型转换过程中的图优化(Graph Optimization)引入了问题,与量化无关。此时需检查算子支持情况,或联系技术支持。
    4. 如果这两者一致,但与 Int8 差异大,则确认为量化问题,回到第 2、3 点解决。

总结行动清单

  1. 第一步(必做): 使用 Netron 检查 HzPreprocess,确保 Float 和 Int8 推理代码中的预处理(Resize, Normalize, Color Space)完全字节级一致
  2. 第二步(推荐): 检查校准集,确保丢失的类别在校准集中有足够多且具有代表性的样本,重新校准。
  3. 第三步(进阶): 如果前两步无效,在量化配置中跳过 YOLOv11 检测头(Head)部分算子的量化,保留为 FP16/FP32。
  4. 第四步(调试): 对比 NMS 前的原始 Score 输出,判断是量化误差过大还是后处理阈值问题。

如果尝试以上步骤后问题依旧,建议收集以下信息:

  • ***_original_float_model.onnx***_quantized_model.onnx 文件。
  • 导致问题的具体测试图片。
  • 量化使用的 YAML 配置文件。
  • Float 和 Int8 推理出的原始 Tensor 数据对比截图。

希望这些建议能帮你解决精度落差问题!如有进一步报错日志,欢迎继续提问。