如何在 3d OpenCl 内核中访问不同维度的索引?

How to access different dimension's index in 3d OpenCl kernel?

我对 openCL 完全陌生。我试图将连续的 3d 矩阵代码转换为 3d 矩阵的 openCL 版本。我已经实现了 openCL 的基本功能,但仍停留在 OpenCL 内核中。我得到了 3d 矩阵的所有索引,但无法理解如何访问不同维度的索引。有人可以帮我吗?

也欢迎您向我建议更好的方法来解决这个问题。 提前致谢。

这是我的一小部分代码。

顺序码:

const int depth = 3;
const int row = 4;
const int column = 4;
float A[depth][row][column];

for (int k = 0; k < depth; k++){
    for (int i = 0; i < row; i++){
        for (int j = 0; j < column; j++){
            if (k == 0){
                A[k][i][j] = (float)i / ((float)j + 1.00);
            }else if (k == 1){
                A[k][i][j] = 1.00;
            }else{
                A[k][i][j] = (float)j / ((float)i + 1.00);
            }
        }
    }
 }

OpenCL内核代码:

__kernel void ThreeDimArray(__global float *const output1) {
  const int x = get_global_id(0);
  const int y = get_global_id(1);
  const int z = get_global_id(2);
  const int max_x = get_global_size(0);
  const int max_y = get_global_size(1);
  const int max_z = get_global_size(2);
  
  const int idx = x * max_y * max_z + y * max_z + z;

  output1[idx] = 1.00;
};

顺序代码输出:

Baseline matrix k = 0
0.00    0.00    0.00    0.00 
1.00    0.50    0.33    0.25 
2.00    1.00    0.67    0.50
3.00    1.50    1.00    0.75

Baseline matrix k = 1
1.00    1.00    1.00    1.00
1.00    1.00    1.00    1.00
1.00    1.00    1.00    1.00
1.00    1.00    1.00    1.00

Baseline matrix k = 2
0.00    1.00    2.00    3.00
0.00    0.50    1.00    1.50
0.00    0.33    0.67    1.00
0.00    0.25    0.50    0.75

编辑: 如果我们想用其他索引值更新特定索引怎么办。 例如:

for (int t = 0; t < 24; t++){
        for (int i = 1; i < row; i++){
            for (int j = 0; j < column; j++){
                A[1][i][j] = A[1][i][j] + (1 / (sqrt(A[0][i + 1][j] + A[2][i - 1][j])));
            }
        }
    }

我这样试过(内核代码):

const int idk0 = 0 * row * column + i * column + j; 
const int idk1 = 1 * row * column + i * column + j; 
const int idk2 = 2 * row * column + i * column + j;

for (int t = 0; t < 24; t++) {
    A[idk1] = A[idk1] + (1 / (sqrt(A[idk0 + 1] + A[idk2 - 1])));
  }

您已经拥有了所需的一切。完成的内核看起来像:

__kernel void ThreeDimArray(__global float* A) {
  const int k = get_global_id(0);
  const int i = get_global_id(1);
  const int j = get_global_id(2);
  //const int depth  = get_global_size(0); // unused here
  const int row    = get_global_size(1);
  const int column = get_global_size(2);
  
  const int idx = k * row * column + i * column + j; // linear index to access matrix A in 1D form

  if(k == 0) {
      A[idx] = (float)i / ((float)j + 1.00f);
  } else if(k == 1) {
      A[idx] = 1.00;
  } else {
      A[idx] = (float)j / ((float)i + 1.00f);
  }
};

编辑:为了在矩阵大小方面获得最佳性能和最佳灵活性,我建议仅对内核范围使用一维索引。您还可以使用分支将不同的值写入一个矩阵地址。您可以为此使用三元运算符 (?:)。

__kernel void ThreeDimArray(__global float* A, const int depth, const int row, const int column) {
  const int idx = get_global_id(0); // 1D kernel range is depth*row*column
  const int t=n%(column*row), j=t%column, i=t/column, k=n/(column*row);
  A[idx] = k==0 ? (float)i/((float)j+1.0f) : k==1 ? 1.0f : (float)j/((float)i+1.0f);
};

编辑 2:要仅更新少量精选值,您有 2 个选择:

  1. 在 CPU 上执行并复制矩阵 from/to GPU。这适用于 1 次初始化。
  2. 写一个单独的内核,在内核中,在内核顶部有一个像if(k!=0) return;这样的语句。然后只有 k!=0 的线程才会继续并向矩阵写入一些东西。如果这些特殊值依赖于相邻矩阵值(如 A[0][i+1][j]),请确保这些邻居已经初始化(因此第二个单独的内核用于初始化特殊值)。确保不要向线程i,j,k中的单元格i,j,k写入新值,而相邻线程i,j+1,k从其参考点读取此值i,j-1,k,否则线程不清楚i,j+1,k 读取旧值或更新值,因为确切的执行顺序是随机的(所谓的竞争条件)。如果您反复需要写入这些特殊矩阵值并且 A 是一个非常大的矩阵,则此方法很好。在这种方法中,您根本不需要将 A 从 CPU 复制到 GPU 并返回,如果 A 很大,这可能会非常慢。