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