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**