【问题标题】:CUDA Global Barrier -- Works on Kepler and not FermiCUDA 全球屏障——适用于开普勒而不是费米
【发布时间】:2013-01-09 18:06:51
【问题描述】:

以下全局屏障适用于 Kepler K10 而不是 Fermi GTX580:

__global__ void cudaKernel (float* ref1, float* ref2, int* lock, int time, int dim) {
  int gid  = blockIdx.x * blockDim.x + threadIdx.x;
  int lid  = threadIdx.x;                          
  int numT = blockDim.x * gridDim.x;               
  int numP = int (dim / numT);                     
  int numB = gridDim.x;

  for (int t = 0; t < time; ++t) {
    // compute @ time t
    for (int i = 0; i < numP; ++i) {
      int idx  = gid + i * numT;
      if (idx > 0 && idx < dim - 1)
        ref2 [idx]  = 0.333f * ((ref1 [idx - 1] + ref1 [idx]) + ref1 [idx + 1]);
    }

    // global sync
    if (lid == 0){
      atomicSub (lock, 1);
      while (atomicCAS(lock, 0, 0) != 0);
    }
    __syncthreads();

    // copy-back @ time t
    for (int i = 0; i < numP; ++i) {
      int idx  = gid + i * numT;
      if (idx > 0 && idx < dim - 1)
        ref1 [idx]  = ref2 [idx];
    }

    // global sync
    if (lid == 0){
      atomicAdd (lock, 1);
      while (atomicCAS(lock, numB, numB) != numB);
    }
    __syncthreads();
  }
}

因此,通过查看发送回 CPU 的输出,我注意到一个线程(第一个或最后一个线程)逃脱了屏障并比其他线程更早地恢复执行。我正在使用 CUDA 5.0。块数也总是小于 SM 数(在我的一组运行中)。

知道为什么相同的代码不能在两种架构上运行吗? Kepler 中有哪些新功能有助于实现全球同步?

【问题讨论】:

  • 如果您在断言这行不通的情况下包含更多内容,那就太好了。提供一个特定的完整示例以及您测试的两个设备上的实际和预期输出会很有帮助。根据我所看到的,由于 atomicCAS 指令生成的访问将在线程块之间序列化,我完全希望线程块能够连续退出“障碍”。因此,无论如何,我希望一个线程块比其他线程块更早地恢复执行。因此,我对您定义的差异和“通过”或“失败”感兴趣。
  • Kepler GK110 全局原子操作为significantly faster than Fermi。在从单独的 SM 发出的原子的情况下,它们可能实际上是背靠背的,并且以 Kepler GK110 (K20) 的核心时钟速率完成。我的观点是,这将比 Fermi 序列化/完成快得多。但是,根据您的代码,这并不影响退出屏障仍然是线程块之间的串行操作这一事实。而且K10不是GK110。
  • 所以我编写的代码为每个 CUDA 线程提供了一个索引(例如,索引 i 映射到一维数组 A[i])并要求每个线程获取 3 个相邻的数据元素(A[i-1], A[i], A[i+1]) 从全局内存中计算平均值。稍后,在每个线程在“全局”屏障处停止后,继续更新原始数据数组。这整个事情重复了几次。所以每个线程都执行以下循环: for (t=0 to T) {compute;同步;复制;同步}
  • @Naseria 由于内核正在执行全局内存访问,除了全局屏障之外,您还应该使用全局内存围栏。您可能还需要将指针限定为 volatile 或在禁用 L1 缓存的情况下进行编译。请按照 Robert 的建议提供一个完整的示例。
  • 我用 CUDA 内核更新了原帖。这是一个 1-D 3 点 jacobi 样式代码。输入数组是“ref1”,“ref2”是用于复制回的临时数组,“lock”是全局互斥体,“time”是时间步数,“dim”是数据元素的总数。我已经测试过内存栅栏“__threadfence()”,但没有帮助。但是,“不稳定”的技巧很有帮助。问题似乎在于如何将一些数据元素缓存在 L1 中,并且通过使用“volatile”关键字,我确保这些元素“ref1 和 ref2 数组”不会被缓存在 L1 中。

标签: cuda


【解决方案1】:

所以我怀疑屏障代码本身可能以相同的方式工作。这似乎是在与屏障功能本身无关的其他数据结构上发生的事情。

Niether Kepler 和 Fermi 都有彼此一致的 L1 缓存。您发现(尽管它与您的屏障代码本身无关)是 KeplerFermi 之间的 L1 缓存行为不同。

特别是,Kepler L1 缓存在上述链接中描述的全局负载上不起作用,因此缓存行为在设备范围的 L2 级别处理,因此是连贯的。当 Kepler SMX 读取其全局数据时,它会从 L2 获取一致的值。

另一方面,Fermi 具有也参与全局加载的 L1 缓存(默认情况下 - 尽管可以关闭此行为),并且上面链接中描述的 L1 缓存对于每个 Fermi SM 都是唯一的,并且是非- 与其他 SM 中的 L1 缓存一致。当 Fermi SM 读取其全局数据时,它从 L1 获取值,这可能与其他 SM 中的其他 L1 缓存不一致。

这是您所看到的“一致性”的差异,即您在障碍之前和之后操纵的数据。

正如我所提到的,我相信屏障代码本身在两种设备上的工作方式可能相同。

【讨论】:

  • 我仍然不太相信屏障功能。这段代码的奇怪之处在于,当我不使用“volatile”时,并且对于 t > 1(意味着重新使用已经缓存的数据),每个线程块中只有“一个”线程(第一个或最后一个线程)似乎没有得到“最新的值”,从而弄乱了结果。即使它已经用“易失”解决方案解决了,但为什么只有一个线程仍然是个谜?这就是我第一次开始调查全球障碍的原因。
  • 您说volatile 添加到 ref1 和 ref2 时会修复代码。那是对的吗?如果是这样,我认为这与屏障代码之间没有逻辑联系。如果您确信屏障代码已被破坏,也许您可​​以创建一个简单的证明案例,它不依赖于本示例中令人困惑的其他数据结构。
猜你喜欢
  • 2013-03-06
  • 1970-01-01
  • 2014-03-25
  • 1970-01-01
  • 2015-08-24
  • 1970-01-01
  • 2013-08-01
  • 2019-01-23
  • 1970-01-01
相关资源
最近更新 更多