【问题标题】:Vulkan subgroupBarrier does not synchronize invokationsVulkan 子组屏障不同步调用
【发布时间】:2021-11-07 16:15:56
【问题描述】:

我有一个包含嵌套循环和subgroupBarrier 的复杂过程。 在简化的形式中,它看起来像

while(true){
   while(some_condition){
      if(end_condition){
          atomicAdd(some_variable,1);
          debugPrintfEXT("%d:%d",gl_SubgroupID,gl_SubgroupInvocationID.x);
          subgroupBarrier();
          if(gl_SubgroupInvocationID.x==0){
              debugPrintfEXT("Finish! %d", some_variable);
              // do some final stuff
          }
          return; // this is the only return in the entire procedure
      }
      // do some stuff
   }
   // do some stuff
}

总体而言,该过程是正确的,并且符合预期。所有子组线程 总是最终到达结束条件。但是,在我的日志中我看到了

0:2
0:3
0:0
Finish! 3
0:1

这不仅仅是日志显示无序的问题。我执行原子加法,它似乎也是错误的。在打印Finish! 之前,我需要所有线程完成所有原子操作。如果subgroupBarrier() 工作正常,它应该打印4,但在我的情况下它打印3。我一直在关注本教程 https://www.khronos.org/blog/vulkan-subgroup-tutorial 它说

void subgroupBarrier() 执行完整的内存和执行屏障 - 基本上,当调用从 subgroupBarrier() 返回时,我们保证每次调用在任何返回之前都执行屏障,并且这些调用的所有内存写入对所有调用都可见子组。

有趣的是,我尝试将if(gl_SubgroupInvocationID.x==0) 更改为其他数字。例如if(gl_SubgroupInvocationID.x==3) 产生

0:2
0:3
Finish! 2
0:0
0:1

所以看起来subgroupBarrier() 完全被忽略了。

嵌套循环可能是问题的原因还是其他原因?

编辑:

我这里提供更详细的代码

#version 450
#extension GL_KHR_shader_subgroup_basic : enable
#extension GL_EXT_debug_printf : enable

layout (local_size_x_id = GROUP_SIZE_CONST_ID) in; // this is a specialization constant whose value always matches the subgroupSize

shared uint copied_faces_idx;

void main() {
    const uint chunk_offset = gl_WorkGroupID.x;
    const uint lID = gl_LocalInvocationID.x;
    // ... Some less important stuff happens here ...
    const uint[2] ending = uint[2](relocated_leading_faces_ending, relocated_trailing_faces_ending);
    const uint[2] beginning = uint[2](offset_to_relocated_leading_faces, offset_to_relocated_trailing_faces);
    uint part = 0;
    face_offset = lID;
    Face face_to_relocate = faces[face_offset];
    i=-1;
    debugPrintfEXT("Stop 1: %d %d",gl_SubgroupID,gl_SubgroupInvocationID.x);
    subgroupBarrier(); // I added this just to test see what happens
    debugPrintfEXT("Stop 2: %d %d",gl_SubgroupID,gl_SubgroupInvocationID.x);
    while(true){
        while(face_offset >= ending[part]){
            part++;
            if(part>=2){
                debugPrintfEXT("Stop 3: %d %d",gl_SubgroupID,gl_SubgroupInvocationID.x);
                subgroupBarrier();
                debugPrintfEXT("Stop 4: %d %d",gl_SubgroupID,gl_SubgroupInvocationID.x);
                for(uint i=lID;i<inserted_face_count;i+=GROUP_SIZE){
                    uint offset = atomicAdd(copied_faces_idx,1);
                    face_to_relocate = faces_to_be_inserted[i];
                    debugPrintfEXT("Stop 5: %d %d",gl_SubgroupID,gl_SubgroupInvocationID.x);
                    tmp_faces_copy[offset+1] = face_to_relocate.x;
                    tmp_faces_copy[offset+2] = face_to_relocate.y;
                }
                subgroupBarrier(); // Let's make sure that copied_faces_idx has been incremented by all threads.
                if(lID==0){
                    debugPrintfEXT("Finish! %d",copied_faces_idx);
                    save_copied_face_count_to_buffer(copied_faces_idx);
                }
                return; 
            }
            face_offset = beginning[part] + lID;
            face_to_relocate = faces[face_offset];
        }
        i++;
        if(i==removed_face_count||shared_faces_to_be_removed[i]==face_to_relocate.x){
            remove_face(face_offset, i);
            debugPrintfEXT("remove_face: %d %d",gl_SubgroupID,gl_SubgroupInvocationID.x);
            face_offset+=GROUP_SIZE;
            face_to_relocate = faces[face_offset];
            i=-1;
        }
    }
}

基本上这段代码的作用相当于

outer1:for(every face X in polygon beginning){
   for(every face Y to be removed from polygons){
      if(X==Y){
         remove_face(X);
         continue outer1;
      }
   } 
}
outer2:for(every face X in polygon ending){
   for(every face Y to be removed from polygons){
      if(X==Y){
         remove_face(X);
         continue outer2;
      }
   } 
}
for(every face Z to be inserted in the middle of polygon){
   insertFace(Z);
}
save_copied_face_count_to_buffer(number_of_faces_copied_along_the_way);

我的代码看起来如此复杂的原因是因为我以一种更可并行化的方式编写它,并试图最大限度地减少非活动线程的数量(考虑到通常同一子组中的线程必须执行相同的指令)。

我还添加了更多调试打印和更多障碍,只是为了看看会发生什么。这是我得到的日志

Stop 1: 0 0
Stop 1: 0 1
Stop 1: 0 2
Stop 1: 0 3
Stop 2: 0 0
Stop 2: 0 1
Stop 2: 0 2
Stop 2: 0 3
Stop 3: 0 2
Stop 3: 0 3
Stop 4: 0 2
Stop 4: 0 3
Stop 5: 0 2
Stop 5: 0 3
remove_face: 0 0
Stop 3: 0 0
Stop 4: 0 0
Stop 5: 0 0
Finish! 3   // at this point value 3 is saved (which is the wrong value)
remove_face: 0 1
Stop 3: 0 1
Stop 4: 0 1
Stop 5: 0 1 // at this point atomic is incremented and becomes 4 (which is the correct value)

【问题讨论】:

  • "我执行了原子加法,它似乎也是错误的。" 你怎么知道的?也就是除了的打印顺序,你凭什么说原子加法不正确呢?我对调试打印了解不多,但我不知道它们以任何方式尊重执行顺序。
  • 因为原子操作是在Finish 调试打印之前执行的。如果在打印之前调用了 3 个原子操作,在打印之后调用了 1 个,那么日志将包含数字 3。我希望在打印之前执行所有原子操作,因此它应该在日志中显示 4。整个问题是屏障应该确保所有原子在打印之前执行
  • 应该吗?标准中是否有任何内容要求打印语句遵循任何类型的同步?
  • 子组屏障。如果您如此反对使用调试打印的想法,还有另一件事。我真的很关心some_variable 的最终值,我将它保存到缓冲区中,以便以后使用。我保存到缓冲区的点发生在屏障之后的 if 语句中。因此,错误的不仅仅是印刷。这也是我保存到该缓冲区中的值。
  • 您是否启用了内存模型扩展?每次调用的some_conditionend_condition 的值是多少?我可以假设这是计算着色器吗?

标签: gpgpu vulkan barrier gpu-atomics


【解决方案1】:

我找到了我的代码不起作用的原因。所以事实证明我误解了subgroupBarrier() 究竟是如何决定要同步哪些线程的。如果线程处于非活动状态,则它不会参与屏障。不活动的线程稍后是否会变为活动并最终到达屏障并不重要。

这两个循环是不等价的(尽管看起来它们是等价的)

while(true){
   if(end_condition){
      break;
   }
}
subgroupBarrier();
some_function();

while(true){
   if(end_condition){
      subgroupBarrier();
      some_function();
      return;
   }
}

如果所有线程在完全相同的迭代中达到结束条件,则没有问题,因为所有线程同时处于活动状态。

当不同的线程可能在不同的迭代中退出循环时,就会出现此问题。如果线程 A 在 2 次迭代后通过了结束条件并且线程 B 3次迭代后通过结束条件,那么当A处于非活动状态并等待B完成时,它们之间将有一个完整的迭代。

在第一种情况下,A 将首先到达 break,然后 B 将到达 break 第二,最后两个线程将退出循环并到达屏障。

在第二种情况下,A 会先到达结束条件并执行 if 语句,而 B 将处于非活动状态,等待 A 完成。当 A 到达屏障时,它将是该时间点唯一的活动线程,因此它将通过屏障而不与 B 同步。然后 A 将完成执行 if 语句的主体到达返回并变为非活动状态。然后 B 实际上将再次变得活跃并完成执行它的迭代。然后在下一次迭代中,它将达到结束条件和屏障,并且 ti 将再次成为唯一的活动线程,因此屏障不必同步任何内容。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-03-09
    • 2021-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-22
    相关资源
    最近更新 更多