如何使用可变内核大小制作高性能 AOT 模糊?
How to make performant AOT blur with variable kernel size?
对于这种类型的代码,什么是有效的单线程调度?
我正在尝试定义模糊但在 AOT 中使用可变内核大小。我尝试了 https://github.com/halide/Halide/issues/180 解决方案,但我想不出一个好的方法来安排它,这将使我获得与将内核大小设置为 GeneratorParam 并使用不同值进行预编译相同的性能。
这是带有 GeneratorParam 的片段:
// GeneratorParam<int32_t> kernelSize{"kernelOffset", 1};
int32_t kernelSize = 2*kernelOffset + 1;
{
Halide::Expr sum = input(x, y);
for (int i=1;i<kernelSize;i++) {
sum = sum + Halide::cast<uint16_t>(input(x, y+i));
}
blur_y(x, y) = sum/kernelSize;
}
{
Halide::Expr sum = blur_y(x, y);
for (int i=1;i<kernelSize;i++) {
sum = sum + blur_y(x+i, y);
}
blur_x(x, y) = sum/kernelSize;
}
...
// And the schedule
blur_x.compute_root();
blur_y.compute_at(blur_x, y);
output.vectorize(x, 16);
并使用https://github.com/halide/Halide/issues/180解决方案
Halide::RDom box (0, kernelSize, "box");
blur_y(x, y) = Halide::undef<uint16_t>();
{
Halide::RDom ry (yMin+1, yMax-yMin, "ry");
blur_y(x, yMin) = Halide::cast<uint16_t>(0);
blur_y(x, yMin) += Halide::cast<uint16_t>(input(x, yMin+box))/kernelSize;
blur_y(x, ry) = blur_y(x, ry-1) + input_uint16(x, ry+kernelOffset-1)/kernelSize - input_uint16(x, ry-1-kernelOffset)/kernelSize;
}
blur_x(x, y) = Halide::undef<uint16_t>();
{
Halide::RDom rx (xMin+1, xMax-xMin, "rx");
blur_x(xMin, y) = Halide::cast<uint16_t>(0);
blur_x(xMin, y) += blur_y(xMin+box, y)/kernelSize;
blur_x(rx, y) = blur_x(rx-1, y) + blur_y(rx+kernelOffset, y)/kernelSize - blur_y(rx-1-kernelOffset, y)/kernelSize;
}
在固定和可变半径之间获得相同速度的唯一方法是使用 specialize 调度指令为特定半径生成固定代码。如果您可以 JIT 并且在相同半径处模糊大量像素,那么针对给定半径 JIT 特定滤镜可能会有利可图。
通常非常快,任意半径,模糊使用自适应方法,其中大半径由迭代框过滤等处理,中间级别使用可分离卷积,非常小的半径可能使用不可分离卷积。模糊通常通过结合多种方法的多次传递完成。
对于这种类型的代码,什么是有效的单线程调度? 我正在尝试定义模糊但在 AOT 中使用可变内核大小。我尝试了 https://github.com/halide/Halide/issues/180 解决方案,但我想不出一个好的方法来安排它,这将使我获得与将内核大小设置为 GeneratorParam 并使用不同值进行预编译相同的性能。
这是带有 GeneratorParam 的片段:
// GeneratorParam<int32_t> kernelSize{"kernelOffset", 1};
int32_t kernelSize = 2*kernelOffset + 1;
{
Halide::Expr sum = input(x, y);
for (int i=1;i<kernelSize;i++) {
sum = sum + Halide::cast<uint16_t>(input(x, y+i));
}
blur_y(x, y) = sum/kernelSize;
}
{
Halide::Expr sum = blur_y(x, y);
for (int i=1;i<kernelSize;i++) {
sum = sum + blur_y(x+i, y);
}
blur_x(x, y) = sum/kernelSize;
}
...
// And the schedule
blur_x.compute_root();
blur_y.compute_at(blur_x, y);
output.vectorize(x, 16);
并使用https://github.com/halide/Halide/issues/180解决方案
Halide::RDom box (0, kernelSize, "box");
blur_y(x, y) = Halide::undef<uint16_t>();
{
Halide::RDom ry (yMin+1, yMax-yMin, "ry");
blur_y(x, yMin) = Halide::cast<uint16_t>(0);
blur_y(x, yMin) += Halide::cast<uint16_t>(input(x, yMin+box))/kernelSize;
blur_y(x, ry) = blur_y(x, ry-1) + input_uint16(x, ry+kernelOffset-1)/kernelSize - input_uint16(x, ry-1-kernelOffset)/kernelSize;
}
blur_x(x, y) = Halide::undef<uint16_t>();
{
Halide::RDom rx (xMin+1, xMax-xMin, "rx");
blur_x(xMin, y) = Halide::cast<uint16_t>(0);
blur_x(xMin, y) += blur_y(xMin+box, y)/kernelSize;
blur_x(rx, y) = blur_x(rx-1, y) + blur_y(rx+kernelOffset, y)/kernelSize - blur_y(rx-1-kernelOffset, y)/kernelSize;
}
在固定和可变半径之间获得相同速度的唯一方法是使用 specialize 调度指令为特定半径生成固定代码。如果您可以 JIT 并且在相同半径处模糊大量像素,那么针对给定半径 JIT 特定滤镜可能会有利可图。
通常非常快,任意半径,模糊使用自适应方法,其中大半径由迭代框过滤等处理,中间级别使用可分离卷积,非常小的半径可能使用不可分离卷积。模糊通常通过结合多种方法的多次传递完成。