【问题标题】:opencl kernel implementing a simple mathematical formulaopencl 内核实现一个简单的数学公式
【发布时间】:2015-02-19 12:16:49
【问题描述】:

在实现定义为的错误函数时要考虑哪些最佳实践

使用 OpenCL 内核?

A、B 和 C 是 3D 浮点数组,\delta 是 Kronecker delta。

(N, M) = (2, 7) 或 (N, M) = (3, 23) 的典型值。

简单的实现(如下所示)比 CPU 版本慢几个数量级。

谢谢,

T.

__kernel void cl_bilinear_alg(
                            __global float * A,
                            __global float * B,
                            __global float * C,
                            __global const int M,
                            __global const int N,
                            __global float * R)
{
    int index = get_global_id(0);
    int N2 = N * N;
    int mat_offset = index * N2 * M;
    float s1, s2, err = 0.0f;

    for (int i = 0; i < N; ++i)
    {
        for (int j = 0; j < N; ++j)
        {
            for (int k = 0; k < N; ++k)
            {
                for (int l = 0; l < N; ++l)
                {
                    for (int m = 0; m < N; ++m)
                    {
                        for (int n = 0; n < N; ++n)
                        {
                            s1 = (n == i) * (j == k) * (l == m);
                            s2 = 0;

                            for (int r = 0; r < M; ++r)
                            {
                                s2 += A[mat_offset + r * N2 + i * N + j] *
                                      B[mat_offset + r * N2 + k * N + l] *
                                      C[mat_offset + r * N2 + m * N + n];
                            }
                            err += (s2 - s1) * (s2 - s1);
                        }
                    }
                }
            }
        }
    }
    R[index] = err;
}

更新

主要目标是 Geforce GTX 570,但未来可能会改变。

更新2

在对代码进行矢量化、将位移动到本地内存、展开一些循环并将预先计算的 Kronecker 乘积显式传递给内核之后,代码如下所示:

__kernel void cl_bilinear_alg(__global const float * A,
                              __global const float * B,
                              __global const float * C,
                              __global const int N,
                              __global const int M,
                              __global const float * kron,
                              __global float * R) 
{
    __private int index = get_global_id(0);
    __private int cM = ceil(M / 4.0f);
    __private int N2 = N*N;
    __private int N4 = N2*N2;
    __private int mat_offset = index * N2 * M;
    __private float s1, s2, err = 0;
    __private float4 vzero = (float4) (0.0f, 0.0f, 0.0f, 0.0f);
    __local float4 va[54], vb[54], vc[54];

for (int ij = 0, k = 0; ij < N2; ++ij)
{
    int r = 0;
    for (; r < M / 4; r += 4, ++k)
    {
        int idx0 = mat_offset + N2 * r + ij;
        int idx1 = mat_offset + N2 * (r + 1) + ij;
        int idx2 = mat_offset + N2 * (r + 2) + ij;
        int idx3 = mat_offset + N2 * (r + 3) + ij;
        va[k] = (float4) (A[idx0], A[idx1], A[idx2], A[idx3]);
        vb[k] = (float4) (B[idx0], B[idx1], B[idx2], B[idx3]);
        vc[k] = (float4) (C[idx0], C[idx1], C[idx2], C[idx3]);
    }

    if (M % 4)
    {
        float buffa[4] = {0}, buffb[4] = {0}, buffc[4] = {0};
        for (; r < M; ++r)
        {
            int idx = mat_offset + N2 * r + ij;
            buffa[r % 4] = A[idx];
            buffb[r % 4] = B[idx];
            buffc[r % 4] = C[idx];
        }
        va[k] = vload4(0, buffa);
        vb[k] = vload4(0, buffb);
        vc[k++] = vload4(0, buffc);
    }
}    

for (int ij = 0; ij < N2; ++ij)
{
    for (int kl = 0; kl < N2; ++kl)
    {
        for (int mn = 0; mn < N2; ++mn)
        {
            s1 = kron[ij * N4 + kl * N2 + mn];
            s2 = 0;
            for (int r = 0; r < cM; ++r)
                s2 += dot(va[cM * ij + r], mad(vb[cM * kl + r], vc[cM * mn + r], vzero));

            //the most expensive line
            err += (s2 - s1) * (s2 - s1);
        }
    }
}

R[index] = err;
}

与幼稚的实施相比,通过应用这些更改,我们发现速度提高了 4 倍。此外,据透露,最昂贵的线路是错误更新,即

err += (s2 - s1) * (s2 - s1);

有什么建议吗?

【问题讨论】:

  • 设备的架构是什么?如果是带有矢量寄存器的 intel,您可以使用 SIMD 寄存器来获得更快的速度,最高可提高 5 倍
  • “naive”版本作为单个工作项运行?如果是这样,你浪费了 GPU 总算术峰值容量的 99.8%,所以它很慢也就不足为奇了.....
  • @talonmies:不,每个工作项都在处理一个单独的问题实例。
  • @user92382 有多少工作项? (即索引的范围是多少?)如果这不是数百或数千,则考虑在 2D 或 3D 工作项之间传播一些循环,尤其是短矩阵步幅。
  • @pmjordan:索引在 20,000 - 500,000 范围内

标签: opencl


【解决方案1】:

通常你会想打破其中一些循环......很多...... - 外部循环分成多个workgroups,它们在自己的计算单元上运行(每个 GPU 大约有 16 个计算单元,不多) - 接下来的几个循环将被拆分到每个工作组内的不同线程中

如果您尝试同时运行所有计算,它们都将尝试同时将数据加载到内存中,这将非常糟糕。 GPU 的内存非常有限。当然,全局内存听起来足够大,几 GB,但全局 GPU 内存很慢。您希望将数据放入本地内存中,按计算单元计算,大小约为 32-64KB,仅此而已。

您通常希望以某种方式将您的任务划分为非常小的任务,并为每个工作组执行以下操作:

  • 将一块内存从全局内存加载到本地内存
    • 线程的整个工作组扭曲都可以使用合并访问参与复制
  • 在这个内存上做一些工作,比如做一些求和,等等
  • 将结果写回全局内存
  • 然后,可以进行一点迭代,或者干脆退出,让其他工作组来处理其他工作

在 CPU 上,数学运算往往是主要瓶颈,但在 GPU 上,通常内核大多无用地旋转,同时等待数据从全局内存逐渐到达它们。无论您如何优化此过程、防止需求冲突等,都将显着提高内核速度。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-06
    • 2014-10-08
    • 2018-01-05
    • 1970-01-01
    • 2014-04-28
    • 2016-05-22
    • 1970-01-01
    相关资源
    最近更新 更多