【ELF2开发板】在 RK3588 上利用 VkFFT 实现基于 GPU 的 FFT 计算

引言

在数字信号处理领域,快速傅里叶变换(FFT)是一种极为重要的算法,它能够将时域信号转换为频域信号,广泛应用于音频处理、图像处理、通信等多个领域。前面几篇博客已经分享了一些CPU计算FFT的内容。随着硬件性能的不断提升,利用图形处理器(GPU)加速 FFT 计算成为提高处理效率的有效方式。今天,我们就来探讨一下如何在ELF2开发板上,借助VkFFT 库实现基于 GPU 的 FFT 计算。

VkFFT简介

VkFFT 是一个基于 Vulkan API 的快速傅里叶变换库。Vulkan 是新一代的跨平台图形和计算 API,它提供了高性能、低开销的设备访问,允许开发者直接控制 GPU 资源,实现更高效的并行计算。VkFFT 充分利用 Vulkan 的并行计算能力,将 FFT 计算任务分配到 GPU 的多个计算单元上同时执行,极大地提升了 FFT 计算的速度,特别适合处理大规模数据的 FFT 运算。

前面的博文介绍过RK3588 集成了 ARM Mali-G610 MP4 GPU,这款 GPU 具备强大的并行计算能力。要充分发挥GPU的计算能力,可以使用 OpenCL库。VkFFT 支持多种API,包括Vulkan/CUDA/HIP/OpenCL/Level Zero/Metal,所以也可以在RK3588平台上运行。

RK3588 上利用 VkFFT 实现 FFT 计算的步骤

下载与编译 VkFFT库

​从 VkFFT 的官方代码仓库(GitHub - DTolm/VkFFT: Vulkan/CUDA/HIP/OpenCL/Level Zero/Metal Fast Fourier Transform library)下载最新版本的代码。在下载完成后,进入VkFFT 的源代码目录,创建一个build目录用于存放编译生成的文件。使用cmake工具生成编译配置文件,例如:​​

mkdir build​
cd build​
cmake -DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc \
    -DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++ -DVKFFT_BACKEND=3 \
    -DOpenCL_INCLUDE_DIR=../../opencltest/OpenCL-Headers-2020.03.13 \
    -D OpenCL_LIBRARY=../../opencltest/lib/libmali.so \
    -DCMAKE_EXE_LINKER_FLAGS="-Wl,--allow-shlib-undefined" ..

上述命令会根据系统环境和 VkFFT的配置文件生成相应的 Makefile。然后,使用make命令进行编译:​

make​

​编译完成后,会在build目录下生成 VkFFT测试程序,可以在开发板上测试效果。

VkFFT是一个只有头文件组成的库,编译后不会生成静态或动态库。

测试程序运行结果

下面是部分运行结果。

我也尝试了进行2048点的运算,结果如下:

root@elf2-buildroot:~# ./VkFFT_TestSuite -benchmark_vkfft -X 2048
arm_release_ver: g13p0-01eac0, rk_so_ver: 10
VkFFT System: 2048x1x1 Batch: 1 Buffer: 0 MB avg_time_per_step: 0.909 ms std_error: 0.268 num_iter: 1 benchmark: 17 scaled bandwidth: 0.1 real bandwidth: 0.1

这个时间明显是比前面的CPU测试要长的,这主要是因为2048点FFT太短了,它没法发挥GPU的优势,而CPU和GPU之间的数据传输和GPU调度的开销显得非常大。 

编写 FFT 计算应用程序

我尝试使用DeepSeek生成程序,不过它不完全正确,下面是修改后的程序。程序中没有包括填充数据的部分。

#define VKFFT_BACKEND 3  // 设置为OpenCL后端
#include <vkFFT.h>
#include <CL/cl.h>
#include <iostream>
#include <vector>
#include <chrono>

int main() {
    const uint64_t fftSize = 2048;      // FFT点数
    uint64_t bufferSize = fftSize * 2 * sizeof(float);  // 复数数据大小

    // OpenCL初始化
    cl_platform_id platform;
    cl_device_id device;
    cl_context context;
    cl_command_queue queue;
    cl_int err;

    // 获取第一个OpenCL平台
    err = clGetPlatformIDs(1, &platform, NULL);
    if (err != CL_SUCCESS) {
        std::cerr << "获取OpenCL平台失败: " << err << std::endl;
        return 1;
    }

    // 获取第一个GPU设备
    err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, NULL);
    if (err != CL_SUCCESS) {
        std::cerr << "获取OpenCL设备失败: " << err << std::endl;
        return 1;
    }

    // 创建OpenCL上下文
    context = clCreateContext(NULL, 1, &device, NULL, NULL, &err);
    if (!context) {
        std::cerr << "创建OpenCL上下文失败: " << err << std::endl;
        return 1;
    }

    // 创建命令队列
    queue = clCreateCommandQueue(context, device, 0, &err);
    if (!queue) {
        std::cerr << "创建命令队列失败: " << err << std::endl;
        return 1;
    }

    // 配置VkFFT
    VkFFTConfiguration config = {};
    config.FFTdim = 1;         // 1D FFT
    config.size[0] = fftSize;  // FFT点数
    config.doublePrecision = 0; // 单精度
    config.performR2C = 0;      // 复数输入输出
    config.normalize = 0;       // 不进行归一化
    config.bufferSize = &bufferSize;
    
    // OpenCL相关配置
    config.context = &context;
    config.device = &device;
    config.commandQueue = &queue;

    VkFFTApplication app = {};
    VkFFTResult res = initializeVkFFT(&app, config);
    if (res != VKFFT_SUCCESS) {
        std::cerr << "VkFFT初始化失败: " << res << std::endl;
        return 1;
    }

    // 创建OpenCL缓冲区
    cl_mem buffer = clCreateBuffer(context, CL_MEM_READ_WRITE, 
                                 bufferSize, NULL, &err);
    if (!buffer) {
        std::cerr << "创建缓冲区失败: " << err << std::endl;
        return 1;
    }

    // 创建启动参数
    VkFFTLaunchParams launchParams = {};
    launchParams.buffer = &buffer;
    launchParams.commandQueue = config.commandQueue;

    // 执行FFT并计时
    auto start = std::chrono::high_resolution_clock::now();

    res = VkFFTAppend(&app, -1, &launchParams); // 正变换
    if (res != VKFFT_SUCCESS) {
        std::cerr << "FFT执行失败: " << res << std::endl;
        return 1;
    }

    clFinish(queue); // 等待操作完成
    auto end = std::chrono::high_resolution_clock::now();

    // 计算耗时
    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
    std::cout << "FFT执行时间: " << duration.count() << " 微秒" << std::endl;

    // 清理资源
    clReleaseMemObject(buffer);
    deleteVkFFT(&app);
    clReleaseCommandQueue(queue);
    clReleaseContext(context);

    return 0;
}

 程序可以用下面的命令编译:

aarch64-linux-gnu-g++ -std=c++17 test.cpp -o vkfft_ocl -lmali \
    -I../../opencltest/OpenCL-Headers-2020.03.13 \
    -L../../opencltest/lib/  -Wl,--allow-shlib-undefined -I../vkFFT/

结语

在 RK3588 上利用 VkFFT 实现基于 GPU 的 FFT 计算,提供了一种新的计算方式。

然而,在实际应用中,也需要注意一些问题。例如数据传输的开销,在将数据从 CPU 内存传输到 GPU 内存以及将计算结果从 GPU 内存传输回 CPU 内存的过程中,会存在一定的时间开销,需要合理优化数据传输策略。​

总的来说,VkFFT为在 RK3588 等具备强大 GPU 的设备上实现高效的 FFT 计算提供了有力的工具。通过合理的环境搭建、库使用和程序优化,我们能够充分发挥 GPU 的并行计算优势,提升数字信号处理应用的性能。如果你对 GPU 加速计算和 FFT 算法感兴趣,不妨尝试在 RK3588 上使用 VkFFT 进行实践,探索更多的应用可能性。​

以上就是在RK3588 上利用VkFFT实现基于 GPU 的 FFT 计算的详细介绍。如果你在实践中有任何问题,或者想了解更多相关内容,欢迎在评论区留言交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神一样的老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值