xref: /OK3568_Linux_fs/external/rknpu2/doc/RKNN_Dynamic_Shape_Usage.md (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1# RKNN动态形状输入使用说明
2动态形状输入是指模型输入数据的形状在运行时可以改变。它可以帮助处理输入数据大小不固定的情况,增加模型的灵活性。动态形状输入在图像处理和序列模型推理中具有重要的作用。以下是动态形状输入的使用说明:
3
4## RKNN SDK版本要求和限制
5* RKNN-Toolkit2版本>=1.5.0
6* RKNPU Runtime库(librknnrt.so)版本>=1.5.0
7* RK3566/RK3568/RK3588/RK3588S/RK3562平台的NPU支持该功能
8
9## 使用步骤:
10## 1. 确认模型支持动态形状输入
11首先,您需要确认模型支持动态形状输入。不是所有的模型都支持动态形状输,例如,常量的形状无法改变,RKNN-Toolkit2工具在转换过程会报错,因此您需要查看您的模型文件或者使用工具来确认模型是否支持动态形状输入。如果您的模型不支持动态形状输入,您需要重新训练模型,以支持动态形状输入。
12
13## 2. 创建动态形状输入RKNN模型
14在使用RKNN C API进行推理之前,需要先将模型转换成RKNN格式。您可以使用RKNN-Toolkit2工具来完成这个过程。如果您希望使用动态形状输入,可以设置转换出的RKNN模型可供使用的多个形状列表。对于多输入的模型,每个输入的形状个数要保持一致。例如,在使用RKNN-Toolkit2转换Caffe模型时,您可以使用以下代码:
15```
16    dynamic_input = [
17        [[1,3,224,224]],    # set the first shape for all inputs
18        [[1,3,192,192]],    # set the second shape for all inputs
19        [[1,3,160,160]],    # set the third shape for all inputs
20    ]
21
22    # Pre-process config
23    rknn.config(mean_values=[103.94, 116.78, 123.68], std_values=[58.82, 58.82, 58.82], quant_img_RGB2BGR=True, dynamic_input=dynamic_shapes)
24```
25这将告诉RKNN-Toolkit2生成支持3个形状的动态形状输入的RKNN模型。
26
27完整的创建动态形状输入RKNN示例,请参考**https://github.com/rockchip-linux/rknn-toolkit2/tree/master/examples/functions/dynamic_input**
28
29## 3. 查询RKNN模型支持的输入形状组合
30得到动态形状输入RKNN模型后,接下来开始使用RKNPU C API进行部署,通过rknn_query可以查询到RKNN模型支持的输入形状列表,每个输入支持的形状列表信息以rknn_input_range结构体形式返回,它包含了每个输入的名称,layout信息,形状个数以及具体形状。例如,您可以使用以下代码:
31```
32    // 查询模型支持的输入形状
33    rknn_input_range dyn_range[io_num.n_input];
34    memset(dyn_range, 0, io_num.n_input * sizeof(rknn_input_range));
35    for (uint32_t i = 0; i < io_num.n_input; i++)
36    {
37        dyn_range[i].index = i;
38        ret = rknn_query(ctx, RKNN_QUERY_INPUT_DYNAMIC_RANGE, &dyn_range[i], sizeof(rknn_input_range));
39        if (ret != RKNN_SUCC)
40        {
41            fprintf(stderr, "rknn_query error! ret=%d\n", ret);
42            return -1;
43        }
44        dump_input_dynamic_range(&dyn_range[i]);
45    }
46```
47注意:对于多输入的模型,所有输入的形状按顺序一一对应,例如,假设有两个输入、多种形状,第一个输入的第一个形状与第二个输入的第一个形状组合有效,不存在交叉的形状组合。
48
49
50## 4.设置输入形状
51加载动态形状输入RKNN模型后,您可以在运行时动态修改输入的形状。通过调用rknn_set_input_shape接口,传入包含形状信息的rknn_tensor_attr指针可以设置当前次推理的形状。例如,使用rknn_query获取的输入形状设置输入时,您可以使用以下代码:
52
53```
54    for (int s = 0; s < shape_num; ++s)
55    {
56        for (int i = 0; i < io_num.n_input; i++)
57        {
58            for (int j = 0; j < input_attrs[i].n_dims; ++j)
59            {
60                input_attrs[i].dims[j] = shape_range[i].dyn_range[s][j];
61            }
62
63            ret = rknn_set_input_shape(ctx, &input_attrs[i]);
64            if (ret < 0)
65            {
66                fprintf(stderr, "rknn_set_input_shape error! ret=%d\n", ret);
67                return -1;
68            }
69        }
70    }
71```
72其中,shape_num是支持的形状个数,shape_range[i]是第i个输入的rknn_input_range结构体,input_attrs[i]是第i个输入的rknn_tensor_attr结构体。
73
74在设置输入形状后,可以再次调用rknn_query查询当前次推理成功设置后的输入和输出形状,例如,您可以使用以下代码:
75```
76        // 获取当前次推理的输入和输出形状
77        rknn_tensor_attr cur_input_attrs[io_num.n_input];
78        memset(cur_input_attrs, 0, io_num.n_input * sizeof(rknn_tensor_attr));
79        for (uint32_t i = 0; i < io_num.n_input; i++)
80        {
81            cur_input_attrs[i].index = i;
82            ret = rknn_query(ctx, RKNN_QUERY_CURRENT_INPUT_ATTR, &(cur_input_attrs[i]), sizeof(rknn_tensor_attr));
83            if (ret < 0)
84            {
85                printf("rknn_init error! ret=%d\n", ret);
86                return -1;
87            }
88            dump_tensor_attr(&cur_input_attrs[i]);
89        }
90
91        rknn_tensor_attr cur_output_attrs[io_num.n_output];
92        memset(cur_output_attrs, 0, io_num.n_output * sizeof(rknn_tensor_attr));
93        for (uint32_t i = 0; i < io_num.n_output; i++)
94        {
95            cur_output_attrs[i].index = i;
96            ret = rknn_query(ctx, RKNN_QUERY_CURRENT_OUTPUT_ATTR, &(cur_output_attrs[i]), sizeof(rknn_tensor_attr));
97            if (ret != RKNN_SUCC)
98            {
99                printf("rknn_query fail! ret=%d\n", ret);
100                return -1;
101            }
102            dump_tensor_attr(&cur_output_attrs[i]);
103        }
104```
105
106**注意**
107对于动态形状输入RKNN模型,rknn_query接口暂不支持所有带NATIVE的输入输出属性查询命令。
108
109## 进行推理
110在设置输入形状之后,可以使用普通API或者零拷贝API进行推理。每次切换输入形状时,需要再设置一次新的形状,并设置对应形状的输入数据。以普通API为例,您可以使用以下代码进行推理:
111
112```
113    // 设置输入信息
114    rknn_input inputs[io_num.n_input];
115    memset(inputs, 0, io_num.n_input * sizeof(rknn_input));
116    for (int i = 0; i < io_num.n_input; i++)
117    {
118        int height = cur_input_attrs[i].fmt == RKNN_TENSOR_NHWC ? cur_input_attrs[i].dims[1] : cur_input_attrs[i].dims[2];
119        int width = cur_input_attrs[i].fmt == RKNN_TENSOR_NHWC ? cur_input_attrs[i].dims[2] : cur_input_attrs[i].dims[3];
120        cv::resize(imgs[i], imgs[i], cv::Size(width, height));
121        inputs[i].index = i;
122        inputs[i].pass_through = 0;
123        inputs[i].type = RKNN_TENSOR_UINT8;
124        inputs[i].fmt = RKNN_TENSOR_NHWC;
125        inputs[i].buf = imgs[i].data;
126        inputs[i].size = imgs[i].total() * imgs[i].channels();
127    }
128
129    // 将输入数据转换成正确的格式后,放到输入缓冲区
130    ret = rknn_inputs_set(ctx, io_num.n_input, inputs);
131    if (ret < 0)
132    {
133        printf("rknn_input_set fail! ret=%d\n", ret);
134        return -1;
135    }
136
137    // 进行推理
138    printf("Begin perf ...\n");
139    double total_time = 0;
140    for (int i = 0; i < loop_count; ++i)
141    {
142        int64_t start_us = getCurrentTimeUs();
143        ret = rknn_run(ctx, NULL);
144        int64_t elapse_us = getCurrentTimeUs() - start_us;
145        if (ret < 0)
146        {
147            printf("rknn run error %d\n", ret);
148            return -1;
149        }
150        total_time += elapse_us / 1000.f;
151        printf("%4d: Elapse Time = %.2fms, FPS = %.2f\n", i, elapse_us / 1000.f, 1000.f * 1000.f / elapse_us);
152    }
153    printf("Avg FPS = %.3f\n", loop_count * 1000.f / total_time);
154
155    // 获取输出结果
156    rknn_output outputs[io_num.n_output];
157    memset(outputs, 0, io_num.n_output * sizeof(rknn_output));
158    for (uint32_t i = 0; i < io_num.n_output; ++i)
159    {
160        outputs[i].want_float = 1;
161        outputs[i].index = i;
162        outputs[i].is_prealloc = 0;
163    }
164
165    ret = rknn_outputs_get(ctx, io_num.n_output, outputs, NULL);
166    if (ret < 0)
167    {
168        printf("rknn_outputs_get fail! ret=%d\n", ret);
169        return ret;
170    }
171```
172
173总之,RKNN动态形状输入可以帮助您处理可变大小的输入数据,提高模型的灵活性和效率。通过以上步骤,您可以使用RKNN C API进行动态形状输入的推理。完整的动态形状输入C API Demo请参考**https://github.com/rockchip-linux/rknpu2/tree/master/examples/rknn_dynamic_shape_input_demo**