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