airockchip / rknn-toolkit2

Other
996 stars 104 forks source link

使用rknnlite2在板端推理lightGLue结果正常,使用c++代码推理时结果差异较大 #200

Open liuqinglong110 opened 4 weeks ago

liuqinglong110 commented 4 weeks ago

您好,我正在把lightGlue模型移植到RK3588板端,目前使用rknnlite2的Python脚本在板端推理符合预期。但是使用c++代码推理同样的模型时,直接得到的输出结果与rknnlite2的Python脚本结果差异较大。

以下是我的详细操作: 1、从onnx转换成rknn模型后,输入输出维度如下所示:有四个输入,一个输出。 转换rknn模型时,设置了QUANTIZE_ON = False # 设置为False时,表示将原始模型转成 fp16。 image 2、在rk3588板端,使用rknnlite2的python脚本推理,运行结果正确。具体代码如下所示: `import cv2

import torch

import numpy as np import matplotlib import matplotlib.pyplot as plt from rknnlite.api import RKNNLite from typing import List, Optional, Union

rknn_model = './model/RK3588/superpoint_lightglue.rknn'

def normalize_keypoints(kpts: np.ndarray, h: int, w: int) -> np.ndarray: size = np.array([w, h]) shift = size / 2 scale = size.max() / 2 kpts = (kpts - shift) / scale return kpts.astype(np.float32)

读取数据的函数,处理带逗号的文件并增加类型转换为 int64 或 float32

def load_from_txt(filepath, shape, dtype=np.int64):

使用 np.genfromtxt 读取,指定逗号为分隔符

data = np.genfromtxt(filepath, delimiter=' ', dtype=dtype).reshape(shape)  # 读取并重塑数据
return data  # 数据已经是指定的类型

if name == 'main':

# kpts0 = np.load('./testData/kpts0.npy')
# kpts1 = np.load('./testData/kpts1.npy')
# desc0 = np.load('./testData/desc0.npy')
# desc1 = np.load('./testData/desc1.npy')

# 从各自的文件读取数据
kpts0 = load_from_txt('./testData/kpts0.txt', (1, 300, 2), dtype=np.int64)
kpts1 = load_from_txt('./testData/kpts1.txt', (1, 300, 2), dtype=np.int64)
# desc0 = load_from_txt('./testData/desc0.txt', (1, 300, 256), dtype=np.float32)
# desc1 = load_from_txt('./testData/desc1.txt', (1, 300, 256), dtype=np.float32)    
desc0 = np.load('./testData/desc0.npy')
desc1 = np.load('./testData/desc1.npy')

# 对特征点坐标进行归一化
norm_kpts0 = normalize_keypoints(kpts0, 366, 512)  # class 'numpy.ndarray'
norm_kpts1 = normalize_keypoints(kpts1, 366, 512)  # class 'numpy.ndarray'

# print(">>>>>> Before export_rknn_inference!!!")
# print("expand_kpts0.shape:", expand_kpts0.shape)  # (1, 1, 300, 2)
# print("expand_kpts1.shape:", expand_kpts1.shape)  # (1, 1, 300, 2)
# print("desc0.shape:", desc0.shape)                # (1, 1, 300, 256)
# print("desc1.shape:", desc1.shape)                # (1, 1, 300, 256)

rknn_lite = RKNNLite()
# Load RKNN model
print(f'--> Load RKNN model: {rknn_model}')
ret = rknn_lite.load_rknn(rknn_model)
if ret != 0:
    print('Load RKNN model failed')
    exit(ret)
print('done')

# Init runtime environment
print('--> Init runtime environment')
# Run on RK356x / RK3576 / RK3588 with Debian OS, do not need specify target.
# For RK3576 / RK3588, specify which NPU core the model runs on through the core_mask parameter.
ret = rknn_lite.init_runtime(core_mask=RKNNLite.NPU_CORE_0)
if ret != 0:
    print('Init runtime environment failed')
    exit(ret)
print('done')

input_list = [norm_kpts0, norm_kpts1, desc0,  desc1]   # desc0 class 'numpy.ndarray',   desc1 class 'numpy.ndarray'

# Inference
print('--> Running model')
scores = rknn_lite.inference(inputs=input_list)
print('done')
print("scores[0].shape:", scores[0].shape)
np.set_printoptions(threshold=np.inf, suppress=True)
print("scores[0]:", scores[0])
`

最后,scores[0]输出结果: image 3、使用c++代码在板端实现推理,具体推理代码: 初始化代码: ` bool init(SegRgbParams &params) { rknn_context ctx;

    std::string model_path = params.model_path;
    /* Create the neural network */
    int model_data_size = 0;
    unsigned char *model_data = load_model(model_path.c_str(), &model_data_size);
    if (model_data == NULL)
    {
        printf("load_model fail!\n");
        return -1;
    }

    int ret = rknn_init(&ctx, model_data, model_data_size, 0, NULL);
    if (ret < 0)
    {
        printf("rknn_init error ret=%d\n", ret);
        return -1;
    }
    // 释放内存
    if (model_data)
    {
        free(model_data);
    }

    rknn_sdk_version version; // 查询模型的输入输出属性
    ret = rknn_query(ctx, RKNN_QUERY_SDK_VERSION, &version, sizeof(rknn_sdk_version));
    if (ret < 0)
    {
        printf("rknn_query error ret=%d\n", ret);
        rknn_destroy(ctx);
        return -1;
    }
    printf("sdk version: %s driver version: %s\n", version.api_version, version.drv_version);

    // Get Model Input Output Number
    rknn_input_output_num io_num; // 查询模型的输入输出属性
    ret = rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num));
    if (ret < 0)
    {
        printf("rknn_query error ret=%d\n", ret);
        rknn_destroy(ctx);
        return -1;
    }
    printf("model input num: %d, output num: %d\n", io_num.n_input, io_num.n_output);

    // Get Model Input Info
    printf("input tensors:\n");
    rknn_tensor_attr input_attrs[io_num.n_input];
    memset(input_attrs, 0, sizeof(input_attrs));
    for (int i = 0; i < io_num.n_input; i++)
    {
        input_attrs[i].index = i; // 查询模型的输入输出属性
        ret = rknn_query(ctx, RKNN_QUERY_INPUT_ATTR, &(input_attrs[i]), sizeof(rknn_tensor_attr));
        if (ret < 0)
        {
            printf("rknn_query error ret=%d\n", ret);
            rknn_destroy(ctx);
            return -1;
        }
        dump_tensor_attr(&(input_attrs[i]));
    }

    // Get Model Output Info
    printf("output tensors:\n");
    rknn_tensor_attr output_attrs[io_num.n_output];
    memset(output_attrs, 0, sizeof(output_attrs));
    for (int i = 0; i < io_num.n_output; i++)
    {
        output_attrs[i].index = i; // 查询模型的输入输出属性
        ret = rknn_query(ctx, RKNN_QUERY_OUTPUT_ATTR, &(output_attrs[i]), sizeof(rknn_tensor_attr));
        if (ret != RKNN_SUCC)
        {
            printf("rknn_query fail! ret=%d\n", ret);
            rknn_destroy(ctx);
            return -1;
        }
        dump_tensor_attr(&(output_attrs[i]));
    }

    // Set to context
    app_ctx.rknn_ctx = ctx;

    // 指定NPU核心数量,仅3588支持
    // npu_core_mask: 0: auto,          1: npu core1,      2: npu core2,     4: npu core3,
    //                3: npu core1&2,   7: npu core1&2&3
    // Only RK3588 support core mask.
    int core_mask = params.npu_core_mask;
    rknn_set_core_mask(app_ctx.rknn_ctx, (rknn_core_mask)core_mask);

    // TODO
    if (output_attrs[0].qnt_type == RKNN_TENSOR_QNT_AFFINE_ASYMMETRIC && output_attrs[0].type != RKNN_TENSOR_FLOAT16)
    {
        // app_ctx.is_quant = true;
        std::cout << "true!!!!  app_ctx.is_quant:" << app_ctx.is_quant << std::endl;
    }
    else
    {
        app_ctx.is_quant = false;
        std::cout << "false!!!  app_ctx.is_quant:" << app_ctx.is_quant << std::endl;
    }
    std::cout << "\n *********************************** " << std::endl;
    app_ctx.io_num = io_num;
    app_ctx.input_attrs = (rknn_tensor_attr *)malloc(io_num.n_input * sizeof(rknn_tensor_attr));
    memcpy(app_ctx.input_attrs, input_attrs, io_num.n_input * sizeof(rknn_tensor_attr));
    app_ctx.output_attrs = (rknn_tensor_attr *)malloc(io_num.n_output * sizeof(rknn_tensor_attr));
    memcpy(app_ctx.output_attrs, output_attrs, io_num.n_output * sizeof(rknn_tensor_attr));

    if (input_attrs[0].fmt == RKNN_TENSOR_NCHW)
    {
        printf("model is NCHW input fmt\n");
        // // app_ctx.model_channel = input_attrs[0].dims[1];
        // // app_ctx.model_height = input_attrs[0].dims[2];
        // // app_ctx.model_width = input_attrs[0].dims[3];
    }
    else
    {
        printf("model is NHWC input fmt\n");
        // app_ctx.model_height = input_attrs[0].dims[1];
        // app_ctx.model_width = input_attrs[0].dims[2];
        app_ctx.model_channel = input_attrs[0].dims[0];
        app_ctx.model_height = input_attrs[0].dims[1];
        app_ctx.model_width = input_attrs[0].dims[2];
        std::cout << "input_attrs[0].dims[0]=" << input_attrs[0].dims[0] << std::endl;
        std::cout << "input_attrs[0].dims[1]=" << input_attrs[0].dims[1] << std::endl;
        std::cout << "input_attrs[0].dims[2]=" << input_attrs[0].dims[2] << std::endl;
        std::cout << "input_attrs[0].dims[3]=" << input_attrs[0].dims[3] << std::endl;
    }

    printf("model input channel=%d\n", app_ctx.model_channel);

    return 0;
}

推理代码: cv::Mat lightGlue(uint32_t img_width, uint32_t img_height, uint8_t *data, uint64_t img_channels, std::vector input_list) { int ret; int cell = 8; // Size of each output cell.Keep this fixed.rga_buffer_t src; float conf_thresh = 0.015; // Detector confidence threshold(default : 0.015).rga_buffer_t dst; int nms_dist = 4; // Non Maximum Suppression(NMS) distance(default : 4). int border_remove = 4; // Remove points this close to the border. int num_channels = 65; // outputs[0]的通道数,即推理结果中,特征点坐标tensor的通道数。 int desc_cols = 256; // outputs[1]的列数,即推理结果中,特征描述子tensor的列数。

    rknn_input inputs[4];
    memset(inputs, 0, sizeof(inputs));

    std::cout << "app_ctx.model_channel === :" << app_ctx.model_channel << std::endl;
    // std::cout << "input_list[0]: " << input_list[0] << std::endl;
    Eigen::MatrixXf norm_kpts0 = normalize_keypoints(input_list[0], 366, 512); // 特征点都需要归一化
    Eigen::MatrixXf norm_kpts1 = normalize_keypoints(input_list[1], 366, 512); // 特征点都需要归一化
    // std::cout << "norm_kpts0: " << norm_kpts0 << std::endl;
    // std::cout << "norm_kpts1: " << norm_kpts1 << std::endl;
    // std::cout << "desc0: " << input_list[2].row(0) << std::endl;
    // std::cout << "desc1: " << input_list[3].row(0) << std::endl;

    inputs[0].index = 0;
    inputs[0].type = RKNN_TENSOR_FLOAT32;
    inputs[0].size = app_ctx.model_channel * 300 * 2 * 8;
    inputs[0].fmt = RKNN_TENSOR_UNDEFINED;
    inputs[0].pass_through = 0;

    inputs[1].index = 1;
    inputs[1].type = RKNN_TENSOR_FLOAT32;
    inputs[1].size = app_ctx.model_channel * 300 * 2 * 8;
    inputs[1].fmt = RKNN_TENSOR_UNDEFINED;
    inputs[1].pass_through = 0;

    inputs[2].index = 2;
    inputs[2].type = RKNN_TENSOR_FLOAT32;
    inputs[2].size = app_ctx.model_channel * 300 * 256 * 8;
    inputs[2].fmt = RKNN_TENSOR_UNDEFINED;
    inputs[2].pass_through = 0;

    inputs[3].index = 3;
    inputs[3].type = RKNN_TENSOR_FLOAT32;
    inputs[3].size = app_ctx.model_channel * 300 * 256 * 8;
    inputs[3].fmt = RKNN_TENSOR_UNDEFINED;
    inputs[3].pass_through = 0;

    // You may not need resize when src resulotion equals to dst resulotion
    void *resize_buf = nullptr;
    // ************************************************************************************* //
    cv::Mat image = cv::Mat(img_height, img_width, CV_8UC1, data);

    inputs[0].buf = norm_kpts0.data();    // 使用归一化后的数据
    inputs[1].buf = norm_kpts1.data();    // 使用归一化后的数据
    inputs[2].buf = input_list[2].data(); // 特征描述子不需要归一化
    inputs[3].buf = input_list[3].data(); // 特征描述子不需要归一化
    // ************************************************************************************* //
    ret = rknn_inputs_set(app_ctx.rknn_ctx, app_ctx.io_num.n_input, inputs); // 设置输入数据
    if (ret < 0)
    {
        printf("rknn_input_set fail! ret=%d\n", ret);
    }
    std::cout << "deeplabv3_seg : rknn_inputs_set !\n"
              << std::endl;
    rknn_output outputs[app_ctx.io_num.n_output];
    memset(outputs, 0, sizeof(outputs));
    for (int i = 0; i < app_ctx.io_num.n_output; i++)
    {
        outputs[i].want_float = 1;
    }
    std::cout << "deeplabv3_seg : rknn_run start !" << std::endl;
    ret = rknn_run(app_ctx.rknn_ctx, NULL);
    if (ret < 0)
    {
        printf("ERORR: rknn_run error %d\n", ret);
        rknn_destroy(app_ctx.rknn_ctx);
    }
    std::cout << "deeplabv3_seg : rknn_run end !" << std::endl;
    // 获取推理结果数据
    ret = rknn_outputs_get(app_ctx.rknn_ctx, app_ctx.io_num.n_output, outputs, NULL);
    if (ret < 0)
    {
        printf("ERORR: rknn_outputs_get error %d\n", ret);
        rknn_destroy(app_ctx.rknn_ctx);
    }
    // 后处理部分
    float *scores_buf = (float *)outputs[0].buf;
    // 使用 Eigen::Map 将 float* 转换为 Eigen::MatrixXf
    Eigen::Map<const Eigen::MatrixXf> scores(scores_buf, 300 * 1, 300); // 映射为 (1*300, 300) 矩阵
    std::cout << "=== scores: " << scores.row(0) << std::endl;

}` c++代码推理结果:最后的scores输出结果与rknnlite2结果相差较大。 image

目前,我已经对比验证了rknnlite2和c++代码的输入数据,包括数据输入给模型之前的归一化结果。两套代码中的归一化结果也是完全一样的,也就是说从开始输入rknn的api之前数据都一致。那么基本上就确定是c++代码的问题了。 请您帮忙看看问题可能出现在哪里了?

yuyun2000 commented 4 weeks ago

inputs[0].size = app_ctx.model_channel 300 2 * 8; 这个size是什么意思? 第一个参数的通道数是几?后面为什么要乘以8?

liuqinglong110 commented 4 weeks ago

inputs[0].size = app_ctx.model_channel 300 2 * 8; 这个size是什么意思? 第一个参数的通道数是几?后面为什么要乘以8?

第一个问题:这个inputs[0]是为了给rknn_inputs_set()函数准备的输入,input[0].size表示的是第0个输入数据所占内存的大小。 我参考了《02_Rockchip_RKNPU_User_Guide_RKNN_SDK_V2.2.0_CN.pdf》文档的第80页,仿照推理代码示例写的: image 另外,我还在其他地方找了相似的写法 https://www.easy-eai.com/document_details/18/614#3.7%20rknn_run 具体写法: image 第二个问题:第一个参数的通道数,我设置成了1。我截图的图片中有打印:app_ctx.model_channel === :1 我的模型输入有四个输入,都不是图片类型。四个参数分别是两张图片中特征点的坐标,都是300个点:(300, 2)、(300, 2)),另外两个输入分别是对应的图片的特征描述子,维度是:(300, 256)、(300, 256)。转换模型之后,rknn中显示如下: image 第三个问题:最后乘以8是由于代码在板端运行报错,我自己试了几次,应该是申请的内存大小与输入变量大小不匹配。一般会出现如下错误: image 而且我还观察到,我的模型四个输入是fp16,因为我没有进行int8量化。乘以8,也和我设置inputs[0].type = RKNN_TENSOR_FLOAT32;的数据类型有关系。 image 这里我有些混乱了。

yuyun2000 commented 4 weeks ago

你设置的输入类型是fp32,那么你输入的数组应该是float的,对于第一个输入,他的size应该是13002*4的,passthrough=0是正确的,sdk内部会把你输入的fp32转成fp16;或者你自己在外边输入fp16的数据,此时passthrough设置为1,表示直接给模型你的数据,不要进行转换

liuqinglong110 commented 4 weeks ago

你设置的输入类型是fp32,那么你输入的数组应该是float的,对于第一个输入,他的size应该是1_300_2*4的,passthrough=0是正确的,sdk内部会把你输入的fp32转成fp16;或者你自己在外边输入fp16的数据,此时passthrough设置为1,表示直接给模型你的数据,不要进行转换

嗯嗯。我尝试把fp32的数据转成fp16,问题太多,还是继续使用fp32作为原始输入类型吧。我已经改成了13002*4。 image 其中,在把输入数据赋值给inputs[0].buf之前,我的归一化函数normalize_keypoints()已经把坐标点都处理成了fp32类型。归一化之后的数据,我都和python的rknnlite2对比过了,数据也是相同的。甚至我检查了inputs[0].buf、inputs[1].buf、inputs[2].buf、inputs[3].buf被赋值之后的数据,也都是符合要求的。还是没有排查出来到底哪个步骤出了问题。

归一化函数如下: // 归一化关键点坐标的函数

Eigen::MatrixXf normalize_keypoints(const Eigen::MatrixXf &kpts, int h, int w)
{
    // std::cout << "kpts: " << kpts << std::endl;
    Eigen::Vector2f size(w, h);
    Eigen::Vector2f shift = size / 2;
    float scale = shift.maxCoeff();
    // std::cout << "shift: " << shift << std::endl;
    // std::cout << "scale: " << scale << std::endl;

    Eigen::MatrixXf normalized_kpts = (kpts.array().rowwise() - shift.transpose().array()) / scale;
    // std::cout << "normalized_kpts: " << normalized_kpts << std::endl;
    return normalized_kpts;
}
yuyun2000 commented 4 weeks ago

Eigen::Map<const Eigen::MatrixXf> scores(scores_buf, 1, 300 * 300); // 映射为 (1, 90000) 矩阵 改成这样试试?

liuqinglong110 commented 4 weeks ago

(1, 90000)

改成如下代码: image 运行结果好像没有什么变换: image c++代码的运行结果在绝对值上总是和rknnlite2的结果差4倍左右,所有,我感觉是不是还是和数据类型有关系。 image

yuyun2000 commented 4 weeks ago

很奇怪啊,lite底层也是用的so库,怎么会结果不一样...肯定是自己写的c++的输入或者输入配置和python的不一样

liting1045 commented 2 weeks ago

@liuqinglong110 你好,想问下您那边rknnlite2和C++推理,两者除了精度不一致,速度一样吗?C++是否可以提升效率呢?

liuqinglong110 commented 2 weeks ago

@liuqinglong110 你好,想问下您那边rknnlite2和C++推理,两者除了精度不一致,速度一样吗?C++是否可以提升效率呢?

你好。我看前面@yuyun2000的回复,“lite底层也是用的so库”,理论上和c++推理速度一样的,我没有详细的比较。因为后处理很多矩阵运算,我自己用c++和eigen库实现后,执行效率太低,反倒不如直接在rk3588上用lite跑Python代码。我最后就是用Python跑lite。

liting1045 commented 2 weeks ago

@liuqinglong110 你好,想问下您那边rknnlite2和C++推理,两者除了精度不一致,速度一样吗?C++是否可以提升效率呢?

你好。我看前面@yuyun2000的回复,“lite底层也是用的so库”,理论上和c++推理速度一样的,我没有详细的比较。因为后处理很多矩阵运算,我自己用c++和eigen库实现后,执行效率太低,反倒不如直接在rk3588上用lite跑Python代码。我最后就是用Python跑lite。

哦哦,那如果速度差不多的话,用rknnlite推理应该也是一样的。我给你发邮箱,咱们交流吧~

yuyun2000 commented 2 weeks ago

说悄悄话是吧

liting1045 commented 2 weeks ago

说悄悄话是吧

大神,那我咨询下您吧,rknnlite2和c++推理的底层是同一个库吗?他们推理效率是否一致呀?

yuyun2000 commented 2 weeks ago

python是用的so库的接口,纯推理的速度是一样的,前后处理肯定是c的效率更高

liting1045 commented 2 weeks ago

python是用的so库的接口,纯推理的速度是一样的,前后处理肯定是c的效率更高

好的好的,感谢呀

liuqinglong110 commented 2 weeks ago

说悄悄话是吧

哈哈哈,大佬误会了。他们可能是感觉现在交流效率太低了,比较着急。我工作时间也不太可能聊微信的。我们还是在这里讨论。大佬有空的时候,可以帮我们指导指导。

xiaooo-jian commented 2 weeks ago

佬,我想请教个问题。lightglue的onnx中存在NonZero算子,无法转为RKNN格式。这里您是通过自定义算子的方式实现转换的吗?

liuqinglong110 commented 2 weeks ago

佬,我想请教个问题。lightglue的onnx中存在NonZero算子,无法转为RKNN格式。这里您是通过自定义算子的方式实现转换的吗?

不支持的算子操作,你在导出onnx的时候,把它拿出网络就行了。放到后处理中。

xiaooo-jian commented 2 weeks ago

佬,我想请教个问题。lightglue的onnx中存在NonZero算子,无法转为RKNN格式。这里您是通过自定义算子的方式实现转换的吗?

不支持的算子操作,你在导出onnx的时候,把它拿出网络就行了。放到后处理中。

感谢佬的回复。目前看来也只有这种方法才最方便了。

yuyun2000 commented 2 weeks ago

还有一个办法也很方便啊,你把不支持的模型用onnx跑就可以了,不用自己实现c++算子,而且你实现的也不一定比onnx快

Liuhehe2019 commented 1 week ago

还有一个办法也很方便啊,你把不支持的模型用onnx跑就可以了,不用自己实现c++算子,而且你实现的也不一定比onnx快

大佬,rknn toolkit lite什么时候能够支持自定义算子

yuyun2000 commented 1 week ago

估计没希望吧,rknnlit为啥要支持自定义,rknnlite直接用python环境加载onnx模型不就可以了吗?

Assets01 commented 1 day ago

您好! superpoint_lightglue.rknn可以分享一下吗, 我从onnx转rknn 总是失败