You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CUDA Kernel有效带宽计算异常:结果远超理论值求修正

计算CUDA Kernel有效带宽的错误分析与修正

问题背景

尝试计算以下CUDA Kernel的有效带宽时,得到了远超设备理论带宽(448GB/s)的1.5TB/s结果:

__global__ void compute_flags(int nquarts, int4 *d_flag, int4 * dat_arr, int * arr, int array_size){
    int gi = blockIdx.x * blockDim.x + threadIdx.x;
    if (gi < nquarts) {
        int4 res = {0, 0, 0, 0};
        int4 dat = dat_arr[gi];

        for (int i = 0; i < array_size; i++){
            int p = arr[i];
            res.x = res.x | (p == dat.x);
            res.y = res.y | (p == dat.y);
            res.z = res.z | (p == dat.z);
            res.w = res.w | (p == dat.w);
        }
        d_flag[gi] = res;
    }
}

当前使用的字节访问量计算公式为:

2 * n_kernel_calls * sizeof(int4) + n_kernel_calls * array_size * sizeof(int)

完整测试代码如下:

#include <iostream>
#include <stdlib.h>

#define arr_size 1000000

int round_div_up (int a, int b){
    return (a + b - 1)/b;
}

void cuda_err_check (cudaError_t err, const char *file, int line)
{
    if (err != cudaSuccess)
    {
        fprintf (stderr, "CUDA error: %s (%s:%d)\n", cudaGetErrorString (err), file, line);
        exit (EXIT_FAILURE);
    }
}

__global__ void compute_flags(int nquarts, int4 *d_flag, int4 * dat_arr, int * arr, int array_size){
    int gi = blockIdx.x * blockDim.x + threadIdx.x;
    if (gi < nquarts) {
        int4 res = {0, 0, 0, 0};
        int4 dat = dat_arr[gi];

        for (int i = 0; i < array_size; i++){
            int p = arr[i];
            res.x = res.x | (p == dat.x);
            res.y = res.y | (p == dat.y);
            res.z = res.z | (p == dat.z);
            res.w = res.w | (p == dat.w);
        }
        d_flag[gi] = res;
    }
}

using namespace std;

int main(void){
    int V1 [arr_size] = {};
    int V2 [arr_size] = {};

    // fill with random numbers
    for(int i = 0; i < arr_size; i++){
        V1[i] = rand() % 100;
        V2[i] = rand() % 100;
    }
    
    int4 *d_flag;
    int * d_V1;
    int * d_V2;
    cudaError_t err;
    cudaEvent_t start, stop;

    err = cudaEventCreate(&start); cuda_err_check(err, __FILE__, __LINE__);
    err = cudaEventCreate(&stop); cuda_err_check(err, __FILE__, __LINE__);

    err = cudaMalloc((void **)&d_flag, arr_size * sizeof(int)); cuda_err_check(err, __FILE__, __LINE__);
    err = cudaMalloc((void **)&d_V1, arr_size * sizeof(int)); cuda_err_check(err, __FILE__, __LINE__);
    err = cudaMalloc((void **)&d_V2, arr_size * sizeof(int)); cuda_err_check(err, __FILE__, __LINE__);

    err = cudaMemcpy(d_V1, V1, arr_size * sizeof(int), cudaMemcpyHostToDevice); cuda_err_check(err, __FILE__, __LINE__);
    err = cudaMemcpy(d_V2, V2, arr_size * sizeof(int), cudaMemcpyHostToDevice); cuda_err_check(err, __FILE__, __LINE__);

    uint64_t nquarts = round_div_up(arr_size, 4);
    uint64_t lws = 256;
    uint64_t gws = round_div_up(nquarts, lws);

    err = cudaEventRecord(start); cuda_err_check(err, __FILE__, __LINE__);

    compute_flags<<<gws, lws>>>(nquarts, d_flag, (int4*)d_V1, d_V2, arr_size);

    err = cudaEventRecord(stop); cuda_err_check(err, __FILE__, __LINE__);
    err = cudaEventSynchronize(stop); cuda_err_check(err, __FILE__, __LINE__);

    err = cudaGetLastError(); cuda_err_check(err, __FILE__, __LINE__);
    err = cudaDeviceSynchronize(); cuda_err_check(err, __FILE__, __LINE__);

    uint64_t byte_accesses = 2 * nquarts * sizeof(int4) + nquarts * arr_size * sizeof(int);
    float time;

    err = cudaEventElapsedTime(&time, start, stop); cuda_err_check(err, __FILE__, __LINE__);

    cout << "Time: " << time << " ms" << endl;
    cout << "Bandwidth: " << byte_accesses / time / 1e6 << " GB/s" << endl;

}

核心错误分析

  1. 全局内存访问量重复计算
    公式中n_quarts * array_size * sizeof(int)的逻辑错误:循环中所有线程读取的是同一个全局数组arr,CUDA的L1/L2缓存会缓存该数组数据,全局内存只需要读取一次arr的全部内容,而非每个线程都完整读取一遍。这是导致计算出的带宽远超理论值的根本原因。

  2. 内存分配类型不匹配(代码bug)
    主函数中d_flag是int4*类型,但分配内存时使用了arr_size * sizeof(int),实际需要的是nquarts * sizeof(int4),虽然这不会直接影响带宽计算,但会导致内存越界风险。

正确的带宽计算方法

有效带宽的计算基于实际从全局内存读取/写入的字节总量,正确的字节访问量公式为:

uint64_t byte_accesses = nquarts * sizeof(int4)  // 读取dat_arr的字节数
                       + nquarts * sizeof(int4)  // 写入d_flag的字节数
                       + arr_size * sizeof(int); // 读取arr的字节数(仅一次全局读取)

简化后为:

uint64_t byte_accesses = 2 * nquarts * sizeof(int4) + arr_size * sizeof(int);

公式验证示例

以arr_size=1e6为例,nquarts=250000(1e6/4),sizeof(int4)=16,sizeof(int)=4:
总字节数 = 225000016 + 1e6*4 = 8,000,000 + 4,000,000 = 12,000,000字节(12MB)
假设kernel运行时间为0.027ms,带宽计算为:
12e6 / (0.027e-3) / 1e9 ≈ 444 GB/s,与设备理论带宽448GB/s接近,结果合理。

额外代码修正

修正d_flag的内存分配语句:

err = cudaMalloc((void **)&d_flag, nquarts * sizeof(int4)); cuda_err_check(err, __FILE__, __LINE__);

内容的提问来源于stack exchange,提问作者LukeTheWalker

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 07:32:19