有什么方法可以将 Funds 组合成一个具有更多维度的 Function 吗?

Is there any way to combine Funcs into a Func has one more dimension?

我从上个月开始学习Halide。 终于遇到大问题了

我正在尝试在 Halide 中实现类似于 C 类代码的功能。

for( int y = 0; y < 3; ++y ){
    for( int x = 0; x < 3; ++x ){
        out(x, y) = out(x-1, y-1) + 1;
    }
}

所以假设初始图像如下。

0 0 0
0 0 0
0 0 0

输出图像将是……(0 超出范围)

1 1 1
1 2 2
1 2 3

所以我想到了两种可能的解决方案。

・解决方案1

像这个递归函数一样定义上面的算法。

Func algorithm1(Func input, int n)
{
    Func src, clamped, dst;
    Var x, y;

    if(n == 1){
            src = input;
    }else{
            src = algorithm1(input, n-1);
            src.compute_root();
    }

    clamped = BoundaryConditions::constant_exterior(src, 0, 0, SIZE, 0, SIZE);
    dst(x, y) = clamped(x-1, y-1) + 1;

    return dst;
}

并像下面的代码一样使用上面的函数。

Func input, output;
input(x, y) = src(x, y);
output = algorithm1(input, SIZE);
output.realize(src);

这个实现几乎不起作用。但明显反弹。 因为每个阶段(Func)的大部分计算结果都与最终结果不匹配,尽管每个 Func 都在整个图像上进行计算。 而且我需要处理更多大(正常)图像。 所以我想到了另一种可能的解决方案。

・解决方案2

在第二个解决方案的第一个。
声明一个函数定义一列和另一列之间的关系。

Func algorithm2(Func src)
{
    Func clamped, dst;
    Var x;

    clamped = BoundaryConditions::constant_exterior(src, 0, 0, SIZE);
    dst(x) = clamped(x-1) + 1;

    return dst;
}

那么,我们结合这个吧。

Func output[3];
output[0](x) = cast<uint32_t>(0);
for(int i = 1; i < SIZE; ++i){
    output[i] = algorithm2(output[i-1]);
}

好吧……问题来了。如何将这个 Func 数组组合成一个 Func?

当然,如果我在每个 func 处实现这个 Funcs 数组指向列头的指针,我可以获得一个图像。但是如果我想把它传给下一个Func呢?

这些天我查看了整个 Halide 示例(测试、应用程序)。但我认为没有类似的例子。 你可能已经注意到我对英语的不适,实际上我是日本人。所以如果这个问题有有用的例子,我很抱歉。如果是这样,请告诉我它在哪里。如果还有其他好的实现思路,请教教我。不管怎样,我需要别人的帮助!

感谢您的阅读。

[编辑 2]

编辑 1 是我的愚蠢问题。我可以安排 compute_root()。 我决定把它们留在这里,虽然真的很尴尬。 我希望这对另一个愚蠢的人有所帮助。

[编辑 1]

我衷心感谢您快速而详细的回复!

很抱歉回复晚了,我想在成功实现我的算法后回复你。但是,我的 Halide 代码仍然无法正常工作,我需要确认一些事情。

首先,我想告诉你,多亏了你,我才意识到我对 Halide 的误解。在我的算法的实现步骤的第一步,我只使用纯“Var”来编写定义。 所以我得到了以下错误。

All of a functions recursive references to itself must contain the same pure variables in the same places as on the left-hand-side.

我认为这个错误的发生是因为日程安排的灵活性。如果允许这样的定义并调度它拆分,这意味着调度改变了算法。这个理解对吗?从这样的理解来看,虽然我已经阅读了教程的缩减部分和模糊示例,但我误解了我无法访问所有 Func 定义中的相邻像素。虽然我不知道为什么。

由于同样的原因,无法拆分缩减域。我想我现在明白了。

你的代码还有一个问题。感谢您的 Halide 实现示例,我几乎已经成功地实现了我想做的事情而无需考虑。然而,这个实现非常慢,尽管我正在处理 20x20 的裁剪图像以便于调试。

我认为这种缓慢是由缩减域引起的。在您的示例中,例如在计算值 g(10, 10) 时,Halide 计算从 f(0, 0) 到 f(0, 0) 并最终获得该值。另一方面,C 实现只是加载 g(9, 9) 处的值并递增它。我们可以通过打印循环嵌套来确认这样的计算。

produce g:
  for y:
    for x:
      produce f:
        for y:
          for x:
            f(...) = ...
        for range:
          for range:
            f(...) = ...
      consume f:
        g(...) = ...

我想确认避免这种重新计算是不可能的?所以你建议了?

我想问你另一个简单的问题。如果有这样的反向依赖,

for( int y = 2; y > 0; --y ){
    for( int x = 2; x > 0; --x ){
        out(x, y) = out(x+1, y+1) + 1;
    }
}

Halide 可以表达这个代码吗?

这里的algorithm1和algorithm2部分我不是很清楚。我理解最初的问题陈述并且英语看起来不错,所以我会努力提供一些帮助来回答我认为你问的问题。我将通过说明一些您可能不知道或在这里使用起来不明显的 Halide 机制来做到这一点。希望这会有所帮助。

首先,要将 Halide Func 的维度映射到不同的表达式,您几乎必须使用 select 语句:

Var x, y, n;
Func f_0, f_1, f_both;
f_0(x, y) = ...;
f_1(x, y) = ...;
f_both(x, y, n) = select(n == 0, f_zero, f_one);

这可以通过向 select 添加参数来扩展到更多情况。这对于分段计算比递归结构更有用,但似乎是标题中问题的最直接答案。

第二种机制是Tuple。这允许 Func 具有多个值,这些值可以用编译时常量进行索引。我认为这不是您正在寻找的答案,但我在 tutorial/lesson_13_tuples.cpp .

中找到了答案

最后,Halide 支持归约,旨在处理第一个代码示例中的情况。看起来像这样:

Var x, y;
Func f, g;
RDom range(0, 3, 0, 3); // Form is min/extent, not start/end

f(x, y) = 0; // Initial condition
f(range.x, range.y) = f(range.x - 1, range.y - 1) + 1;

g(x, y) = f(x, y);

Buffer<int32t> result = g.realize(3, 3);

这应该会产生第一个示例的输出。减少,或 "update definitions" 包含在 tutorial/lesson_09_update_definitions.cpp .