开发时在 CPU 上编写代码,实时运行时在 GPU 上运行 - 哪种方法?

计算科学 C++ 软件 显卡 软件推荐
2021-12-16 20:45:49

在我的模拟中,我经常使用密集矩阵向量乘法和 2D-​​fft 变换,矩阵大小为 8kx8k 及以上。因此,我认为使用 GPU 有利于加速我的代码。
但问题是,我的开发 PC 没有外部 GPU,也不支持添加 GPU。目前无法购买可支持 GPU 的新 PC 进行开发。因此,我目前的方法是使用 ArrayFire,它允许根据我链接的库切换后端,从而允许我在 CPU 上编写和运行代码进行测试,但仍然可以在期间切换到 CUDA/OpenCL生产。

不过,我想知道是否还有其他更好的选择?例如,我查看了 kokkos,但我必须为 FFT 编写自己的包装器。
还是我应该改用完全不同的方法来解决这些问题?

编辑:代码是用 C++ 编写的,因此我想避免切换到其他语言。

2个回答

ArrayFire有一个C++ API和一个Python API您可以在多个后端之间切换,包括 CPU、CUDA 和 OpenCL。它还将为您处理内存移动和内核融合。一个例子:

/*******************************************************
 * Copyright (c) 2014, ArrayFire
 * All rights reserved.
 *
 * This file is distributed under 3-clause BSD license.
 * The complete license agreement can be obtained at:
 * http://arrayfire.com/licenses/BSD-3-Clause
 ********************************************************/

#include <arrayfire.h>
#include <math.h>
#include <stdio.h>
#include <cstdlib>

using namespace af;

// create a small wrapper to benchmark
static array A;  // populated before each timing
static void fn() {
    array B = fft2(A);  // matrix multiply
    B.eval();           // ensure evaluated
}

int main(int argc, char** argv) {
    try {
        setBackend(AF_BACKEND_CPU);
        //setBackend(AF_BACKEND_CUDA); //Choose one!
        info();

        printf("Benchmark N-by-N 2D fft\n");
        for (int M = 7; M <= 12; M++) {
            int N = (1 << M);

            printf("%4d x %4d: ", N, N);
            A             = randu(N, N);
            double time   = timeit(fn);  // time in seconds
            double gflops = 10.0 * N * N * M / (time * 1e9);

            printf(" %4.0f Gflops\n", gflops);
            fflush(stdout);
        }
    } catch (af::exception& e) { fprintf(stderr, "%s\n", e.what()); }

    return 0;
}
```

一种方法是使用 Julia。Julia 的CUDAnative.jl允许使用 LLVM PTX 后端将非常通用的代码自动重新编译到 GPU。它只适用于标准 Julia 代码,因此类型、调度等都很好:大多数情况下,您不必更改原始代码即可使其正常工作。这已被证明具有性能竞争力,甚至比原始 CUDA 编译器好很多倍,因此它是一个很好的高级但快速的工作环境。

最重要的是,可以使用许多抽象层。如果您只想编译标量循环,GPUifyLoops.jl有一些额外的工具,而KernelAbstractions.jl是它的下一个迭代。此外,如果您只想要数组原语,您可以使用CuArrays.jl,它将利用 BLAS 和所有这些。此外,与其他高级 GPU 库相比,它的性能往往非常好,因为它不仅调用预先构建的内核,而且使用 codegen 工具,例如E .= A .* B .+ C .+ sin.(D)将生成和编译单个非分配 GPU 内核,而不是调用 5 个内核并分配临时内核,例如 cuPy、PyTorch 等(因为它们在二进制运算符上调用预先编写的 CUDA 代码)。Julia 也有一个这样的堆栈,用于带有AMDGPUnative.jlROCArrays.jl的 AMD GPU 。

如果您想坚持使用ArrayFire ,也可以使用ArrayFire.jl ,但它不执行代码生成,因此它可能无法在所有情况下都表现出色。

Julia 堆栈的好处是 Julia 中的工具通常是兼容的,这意味着您可以获取这些数组并在其他现有代码中使用它们!例如,如果您将DifferentialEquations.jl的初始条件设为CuArray,则整个微分方程求解器将重新编译以在 GPU 上执行其每个操作。因此,在许多情况下(即您没有直接定义标量索引的情况),迁移到 GPU 只是调用cu(x)函数的输入。

现在,OpenCL 更普遍担心的是内核的性能。在 2020 年,使用 CUDA 仍有一些主要优势。CuBLAS(xt) 优化得很好,然后 CuDNN 真的没有替代品。这意味着,即使一张卡被评为快速,也并不意味着它会像使用带有 CUDA 的最新 NVIDIA 卡一样快,因为如果所有时间都花在卷积核中(即卷积核神经网络),那么 CuDNN 可以比当前的替代方案提供 10 倍的加速,这就是人们坚持使用 CUDA 的原因,而不一定是硬件。也就是说,在这一点上,其他 BLAS 实现还可以(不是标准,但还可以),所以使用 OpenCL 来做一堆 matmuls 很好(你可以在这上面挖掘大量的性能数据)