高通的OpenCL驱动程序支持cl_qcom_recordable_queues扩展。这个扩展引入了一组新的处理流程来记录入队的内核序列,这样序列只需要生成一次,但可以多次分发调度。记录的序列称为recording,后面只需修改recording中任何内核的任何参数,而无需重新记录整个命令序列。对于重复入队固定序列的应用程序,现在只需对参数进行少量更改,就可以进行内核分发,这样节省了CPU并改善调度延迟。QCOM OpenCL SDK提供了示例,展示了应用程序如何使用可记录队列。
高通的OpenCL驱动程序还支持用于加速机器学习操作的cl_qcom_ml_ops扩展(CLML)。cl_qcom_ml_ops扩展可与cl_qcom_recordable_queues扩展一起用于记录ml ops。示例程序-clml_mobilenet_recordables已包含在OpenCL ML SDK中。此示例演示如何使用CLML和可记录队列来构建和运行mobilenet图像分类网络。
在clml_mobilenet_recordable示例中,cl_qcom_recordable_queues扩展允许更新ML Ops tensor数据,节省了重新创建op的开销。它演示了如何记录mobilenet模型,然后使用可记录队列API函数更新模型的输入tensor。
具体来说,我们将5个独立的内存区域依次配置到已记录的模型第一层输入tensor中,这样在不同的输入图像数据上连续运行模型5次。为了实现这一点,我们构造了一个cl_ml_tensor_memory_desc_qcom数组,该数组包含5个独立的cl_mem与单输入tensor的配对,即[{input_tensor, backing_memory_1}, {input_tensor, backing_memory_2}, ... ]。然后,我们用随机数据填充每个cl_mem,得到5组独立的输入数据,以便更新到模型的记录中。
现在,让我们看看具体实现:
1. 由于记录功能是由OpenCL可记录队列扩展提供的,因此我们需要加载/设置它。第一步是查询CL_DEVICE_EXTENSIONS,确认设备是否支持CL_qcom_recordable_queues功能
clGetDeviceInfo(device_id, CL_DEVICE_EXTENSIONS, extensions_size, buf.data(), NULL);
2. 创建两个命令队列,普通队列用于向gpu提交cmd,可记录队列用于cmd记录
queue = clCreateCommandQueue(context, device_id, queue_props, &result);
recordable_queue = clCreateCommandQueue(context, device_id, CL_QUEUE_RECORDABLE_QCOM, &result);
3. 然后我们开始记录。此记录有效期间(从现在起直到调用clEndRecordingQCOM)入队的任何操作都将添加到记录中
recording = clNewRecordingQCOM(recordable_queue, &result);
由于记录处于活动状态,因此对clEnqueueMLOpQCOM的入队cmd不执行,而是将其注册到记录队列中
for (size_t i = 0; i < operations.size(); ++i){
result = h_ClmlIntf->clEnqueueMLOpQCOM(recordable_queue, operations[i], descriptorSet, 0, NULL, NULL);
}
clEndRecordingQCOM结束记录。在先前调用clNewRecordingQCOM和本次调用clEndRecordingQCOM之间排队的所有操作及其相应tensor都已添加到recording中
result = clEndRecordingQCOM(recording);
4. 我们为5个输入图像(由来自用户的两个连续图像组成)分配5个cl 内存对象。modelInputDescs数组充当模型输入tensor的“指针”,我们使用它来查询tensor的大小和分配内存。
for (int i = 0; i < 5; ++i){
result = allocateTensorMemory(h_ClmlIntf, context, &(modelInputDescs[i]));
}
tensorMemDescs.push_back(modelInputDescs[0]);
5. 现在使用记录队列运行模型5次。
在第一次运行时,我们使用原始记录队列的输入,不更新任何记录的tensor
result = h_ClmlIntf->clEnqueueRecordingMLOpQCOM(queue, recording, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, NULL);
对于后面4次运行中的每一次,我们将使用其它图像内存更新输入tensor。modelInputDescs数组中的第一个值是我们原始记录的输入tensor,因此我们将把它支持得mem对象替换为具有不同输入数据的mem对象:
// Now we run the model 5 times using our recording.
// On the first run, we use the original recorded input and don't update any of the recording's tensors.
// But on each of the 4 following runs, we update the input tensor with the other input memory allocations.
for (int j = 0; j < 5; j++) {
cl_uint dispatch_index = 0;
cl_ml_tensor_memory_desc_qcom replacementDesc[] = {{modelInputDescs[0].tensor, modelInputDescs[j].memory}};
const cl_ml_op_array_arg_qcom replace_args[] = {{dispatch_index, &replacementDesc[0], 1}};
result = h_ClmlIntf->clEnqueueRecordingMLOpQCOM(queue, recording, 0, NULL, 1, replace_args, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, NULL);
现在我们已经介绍了CLML可记录队列,有关详细信息,请下载CLML sdk Qualcomm®Adreno™ OpenCL ML SDK,此功能通过较少的SetKernelArg/Enqueue操作来提高应用程序性能,从而在输入更改较少的情况下,让重复运行的ML模型获得更好的性能。
其他相关内容:
作者:Ya Kong