OpenCV 4.11.0
开源计算机视觉
加载中…
搜索中…
未找到匹配项
如何为 Halide 后端调度网络

上一教程: 如何启用 Halide 后端以提高效率
下一教程: OpenCV 与 OpenVINO 的使用

原作者Dmitry Kurtaev
兼容性OpenCV >= 3.3

引言

Halide 代码对于我们使用的每个设备都是相同的。但是为了获得令人满意的效率,我们应该正确地调度计算。在本教程中,我们描述了使用 OpenCV 深度学习模块中的 Halide 后端调度网络的方法。

为了更好地理解 Halide 调度,您可能需要阅读教程 @ http://halide-lang.org/tutorials

如果您是第一次在 OpenCV 中使用 Halide,我们建议您从 如何启用 Halide 后端以提高效率 开始。

配置文件

您可以通过编写文本配置文件来调度 Halide 管线的计算。这意味着您可以轻松地矢量化、并行化和管理层计算的循环顺序。在第一次cv::dnn::Net::forward调用之前,将带有特定设备调度指令的文件路径传递到cv::dnn::Net::setHalideScheduler

调度配置文件表示为 YAML 文件,其中每个节点都是一个已调度的函数或调度指令。

relu1
reorder: [x, c, y]
split: { y: 2, c: 8 }
parallel: [yo, co]
unroll: yi
vectorize: { x: 4 }
conv1_constant_exterior
compute_at: { relu1: yi }

考虑使用变量n表示批次维度,c表示通道,y表示行,x表示列。对于拆分后的变量,使用带有相同前缀但oi后缀分别表示外部和内部变量的名称。例如,对于范围为[0, 10)的变量x,指令split: { x: 2 }会生成新的变量xo(范围为[0, 5))和xi(范围为[0, 2))。变量名x在同一个调度节点中不再可用。

您可以在 opencv_extra/testdata/dnn 找到调度示例,并将其用于调度您的网络。

层融合

由于层融合,我们只需调度融合集的顶层即可。因为对于每个输出值,我们都使用融合公式。例如,如果您依次有三个层卷积 + 比例 + ReLU,

conv(x, y, c, n) = sum(...) + bias(c);
scale(x, y, c, n) = conv(x, y, c, n) * weights(c);
relu(x, y, c, n) = max(scale(x, y, c, n), 0);

融合函数类似于

relu(x, y, c, n) = max((sum(...) + bias(c)) * weights(c), 0);

因此,只有名为relu的函数需要调度。

调度模式

有时,网络使用块结构构建,这意味着某些层是相同的或非常相似。如果您想对不同的层应用相同的调度,精确到平铺或矢量化因子,请在调度文件的开头patterns部分定义调度模式。此外,您的模式可以使用一些参数变量。

# 在文件开头
patterns
fully_connected
split: { c: c_split }
fuse: { src: [x, y, co], dst: block }
parallel: block
vectorize: { ci: c_split }
# 在下面的某个位置
fc8
pattern: fully_connected
params: { c_split: 8 }

自动调度

您可以让 DNN 自动调度层。只需跳过cv::dnn::Net::setHalideScheduler的调用。有时它甚至可能比手动调度更有效。但是,如果特定层需要手动调度,您可以同时使用手动和自动调度方式。编写调度文件并跳过您希望自动调度的层。