RDK x5量化st-gcn后效果失真,应该怎么修改

核心结论

问题定位明确了:hb_verifier 和 hrt_model_exec 都通过,说明模型本身没问题,问题出在 Python pyeasy_dnn 的使用方式上。

关键差异:hrt_model_exec 使用了 --hybrid_dequantize_process=true 参数,但 Python 代码没有正确处理反量化和输入 offset。


问题原因

从你的 hb_verifier 日志看,模型是量化模型(quantized),但 Python 代码直接用 forward(x) 推理,存在两个关键问题:

  1. 缺少 input_offset 设置:featuremap 输入需要设置 offset=0
  2. 输出未反量化:bin 模型输出是 int8 定点数,需要反量化才能得到 float32 结果

解决方案

方案 1:使用 hb_session.run 接口(推荐)

import os
import numpy as np
from hobot_dnn import pyeasy_dnn as dnn

MODEL_PATH = "./stgcn_x5.bin"
INPUT_PATH = "/home/sunrise/Desktop/data/-_pn5NxJmok.bin"
DUMP_DIR = "./dump_manual"
INPUT_SHAPE = (1, 3, 32, 18)

def main():
    os.makedirs(DUMP_DIR, exist_ok=True)
    
    # 加载模型
    models = dnn.load(MODEL_PATH)
    model = models[0]
    
    # 获取输入输出节点名称
    input_names = [inp.name for inp in model.inputs]
    output_names = [out.name for out in model.outputs]
    
    print("input names:", input_names)
    print("output names:", output_names)
    
    # 加载输入数据
    x = np.fromfile(INPUT_PATH, dtype=np.float32).reshape(INPUT_SHAPE)
    x = np.ascontiguousarray(x, dtype=np.float32)
    
    # 构造 feed_dict,使用节点名称
    feed_dict = {input_names[0]: x}
    
    # 关键:设置 input_offset=0(featuremap 模型)
    # 使用 hb_session.run 接口
    outputs = model.hb_session.run(
        output_names, 
        feed_dict, 
        input_offset=0  # featuremap 必须设为 0
    )
    
    # 输出结果
    for i, (name, out) in enumerate(zip(output_names, outputs)):
        arr = np.array(out.buffer).reshape(out.shape)
        print(f"output[{i}] {name} shape:", arr.shape, "dtype:", arr.dtype)
        
        # 保存结果
        txt_path = os.path.join(DUMP_DIR, f"python_output_{i}.txt")
        np.savetxt(txt_path, arr.reshape(-1, 1), fmt="%.5f")
    
    print("done")

if __name__ == "__main__":
    main()

方案 2:检查是否需要手动反量化

如果方案 1 仍然不对,可能需要手动处理反量化:

# 在获取输出后,检查输出的 dtype 和 scale/zero_point
for i, out in enumerate(outputs):
    arr = np.array(out.buffer)
    print(f"output[{i}] dtype:", arr.dtype)
    print(f"output[{i}] shape:", out.shape)
    
    # 如果输出是 int8/uint8,需要反量化
    if arr.dtype in [np.int8, np.uint8]:
        # 获取量化参数(从模型属性或配置文件)
        scale = out.properties.get('scale', 1.0)
        zero_point = out.properties.get('zero_point', 0)
        arr = (arr.astype(np.float32) - zero_point) * scale
    
    # 保存
    np.savetxt(os.path.join(DUMP_DIR, f"output_{i}.txt"), 
               arr.reshape(-1, 1), fmt="%.5f")

方案 3:使用 dnn.DNN 类并设置正确参数

from hobot_dnn import pyeasy_dnn as dnn

# 加载时设置配置
config = {
    'input_offset': 0,  # featuremap 模型
    'output_dtype': 'float32',  # 强制输出 float32
}

models = dnn.load(MODEL_PATH, config=config)
model = models[0]

# 推理
outputs = model.forward(x, input_offset=0)

关键排查点

1. 对比输入数据是否完全一致

# 在 Python 代码中添加
x = np.fromfile(INPUT_PATH, dtype=np.float32).reshape(INPUT_SHAPE)
np.savetxt("./python_input_check.txt", x.reshape(-1, 1), fmt="%.5f")

# 然后和 hrt_model_exec 生成的 dump 对比
# diff python_input_check.txt ./dump_manual/model_infer_input_0_input.txt

2. 检查输出 dtype

在 Python 代码中打印输出 dtype:

for i, out in enumerate(outputs):
    arr = np.array(out.buffer)
    print(f"output[{i}] dtype: {arr.dtype}")  # 应该是 float32
    print(f"output[{i}] shape: {out.shape}")
    print(f"output[{i}] first 5 values: {arr.flatten()[:5]}")

如果 dtype 是 int8 或 uint8,说明输出没有反量化。

3. 检查节点名称匹配

# 打印所有输入输出节点名称
for inp in model.inputs:
    print(f"input name: {inp.name}, shape: {inp.properties.shape}")
for out in model.outputs:
    print(f"output name: {out.name}, shape: {out.properties.shape}")

确保 feed_dict 使用的名称和模型一致。


参考社区资源

搜索一下社区关于 pyeasy_dnn 的使用案例:

核心结论

问题已定位:pyeasy_dnn 对 featuremap 类型模型存在已知兼容性问题。

从你的测试结果看:

  • :white_check_mark: hb_verifier 通过 → 模型量化/编译正确
  • :white_check_mark: hrt_model_exec 通过 → C++ 接口推理正确
  • :cross_mark: pyeasy_dnn 异常 → Python 接口对 featuremap 模型支持不完善

这是社区已知的老问题,参考案例:板端 bin 文件推理输出和原模型输出差异过大


解决方案

方案 1:使用 C/C++ 编写推理代码(推荐)

这是官方推荐的 production-ready 方案。参考官方文档:Runtime 程序编写详解

核心代码框架:

#include "hobot/hb_media/hb_media.h"
#include "hobot/hb_dnn/hb_dnn.h"
#include <iostream>
#include <vector>

int main() {
    // 1. 加载模型
    hbDNNModel_t model;
    hbDNNLoadModel(&model, "stgcn_x5.bin");
    
    // 2. 创建推理实例
    hbDNNHandle_t handle;
    hbDNNCreateHandle(&handle, &model);
    
    // 3. 准备输入数据 (featuremap 类型)
    hbDNNInputTensor_t input;
    input.dtype = HB_DNN_FLOAT32;  // 关键:featuremap 用 float32
    input.layout = HB_DNN_LAYOUT_NCHW;
    input.shape = {1, 3, 32, 18};
    input.data = malloc(1 * 3 * 32 * 18 * sizeof(float));
    
    // 读取你的 bin 输入文件
    FILE* f = fopen("/home/sunrise/Desktop/data/-_pn5NxJmok.bin", "rb");
    fread(input.data, sizeof(float), 1*3*32*18, f);
    fclose(f);
    
    // 4. 推理
    hbDNNTensor_t output;
    hbDNNInferFeatureMap(handle, &input, &output);  // 关键:用 hbDNNInferFeatureMap
    
    // 5. 获取结果
    float* result = (float*)output.data;
    // 处理输出...
    
    // 6. 释放资源
    hbDNNReleaseHandle(handle);
    hbDNNUnloadModel(&model);
    
    return 0;
}

编译命令:

aarch64-linux-gnu-g++ -o infer infer.cpp \
  -I${OE_PACKAGE_PATH}/include \
  -L${OE_PACKAGE_PATH}/lib \
  -lhbdnn -lhbmisc \
  -Wl,-rpath,${OE_PACKAGE_PATH}/lib

方案 2:使用 HB_ONNXRuntime 推理 quantized.onnx(临时方案)

如果必须用 Python,可以绕过.bin 模型,直接推理量化后的 ONNX 文件:

from horizon_tc_ui import HB_ONNXRuntime
import numpy as np

# 加载 quantized.onnx(不是.bin)
sess = HB_ONNXRuntime(model_file="stgcn_x5_quantized.onnx")

# 准备输入
input_data = np.fromfile("/home/sunrise/Desktop/data/-_pn5NxJmok.bin", dtype=np.float32)
input_data = input_data.reshape(1, 3, 32, 18)

# 推理 - 关键:featuremap 模型用 run_feature,input_offset=0
output_names = sess.get_output_names()
outputs = sess.run_feature(output_names, {"input": input_data}, input_offset=0)

print(outputs[0])

注意:

  • 此方案性能不如.bin 模型(无法充分利用 BPU 硬件加速)
  • 仅适合调试/验证,不适合 production

方案 3:等待新 Python 接口发布

根据社区官方回复,新的 Python 接口已在开发中,将完整支持 featuremap 模型。可以关注社区公告。


验证步骤

  1. 先用方案 2 验证(最快):

    python3 -c "
    from horizon_tc_ui import HB_ONNXRuntime
    import numpy as np
    sess = HB_ONNXRuntime(model_file='stgcn_x5_quantized.onnx')
    data = np.fromfile('/home/sunrise/Desktop/data/-_pn5NxJmok.bin', dtype=np.float32).reshape(1,3,32,18)
    out = sess.run_feature(sess.get_output_names(), {'input': data}, input_offset=0)
    print('top5:', np.argsort(out[0].flatten())[-5:][::-1])
    print('logits:', out[0].flatten()[np.argsort(out[0].flatten())[-5:][::-1]])
    "
    

    如果结果和 hrt_model_exec 一致,确认是 pyeasy_dnn 的问题。

  2. 部署时切换到 C++ 方案(方案 1)


为什么 hb_verifier 通过但 pyeasy_dnn 失败?

hb_verifier 内部使用的是 C++ 推理接口(hbDNNInferFeatureMap),而 pyeasy_dnn 是 Python 封装层,对 featuremap 类型的输入/输出处理存在 bug(主要是数据 layout 和反量化逻辑)。


建议的下一步

  1. 立即用方案 2 验证,确认结果是否正常
  2. 如果正常:开发阶段继续用 HB_ONNXRuntime + quantized.onnx,部署时切换 C++
  3. 如果需要 C++ 帮助:可以提供你的具体应用场景,我帮你完善 C++ 代码框架

相关资源