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

GPU创建矩阵并CPU打印的CUDA代码错误排查求助

排查CUDA代码中的内存分配与拷贝错误

咱们来一步步拆解这段代码里的问题,主要集中在设备内存分配、线程配置和数据拷贝这几个核心环节:

1. 设备二维数组的分配逻辑错误

你在主机端的a是典型的指针数组(每个元素指向一行内存),但设备端的d_a是存放在设备内存中的指针数组,主机代码不能直接访问d_a[i](这属于设备内存空间,主机只能通过CUDA API操作)。看你这段循环:

for (int i =0; i < N; i++){
    cudaMalloc((void**)&d_a,N*sizeof(int));
}

这里每次都是把新分配的设备内存地址直接赋值给d_a本身,完全没有给d_a这个指针数组的元素赋值——相当于你分配了N次设备内存,但最后只保留了最后一次的地址,还丢了之前的,同时设备端的指针数组d_a里的元素全是无效值,kernel访问时必然出错。

正确的做法是:

  • 先在主机上创建一个临时指针数组h_d_a,用来存储每一行设备内存的地址
  • 循环为每一行分配设备内存,把地址存在h_d_a[i]
  • 再把h_d_a整个拷贝到设备端的d_a中,让设备端的指针数组能正确指向每一行的内存

2. 线程配置与kernel逻辑不匹配

你的kernel里用了threadIdx.x和threadIdx.y来定位矩阵的(i,j)元素,但启动kernel时用的是<<<1, N>>>——这意味着线程块里只有N个一维线程,所有线程的threadIdx.y都是0,当j>0时就会触发越界访问,直接导致CUDA错误。

需要改成二维线程配置:<<<1, dim3(N, N)>>>,这样每个线程对应矩阵的一个(i,j)位置,和kernel里的索引逻辑匹配。另外最好在kernel里加个边界判断,避免线程索引超出矩阵范围(比如当N不是线程块维度的整数倍时,但这里我们刚好是N×N,加了更稳妥)。

3. 数据拷贝逻辑完全错误

主机的a是指针数组,每个元素指向独立的主机内存块;设备的d_a也是指针数组,每个元素指向独立的设备内存块。你现在直接用:

cudaMemcpy(a, d_a, (N*N)*sizeof(int),cudaMemcpyDeviceToHost);

这会把设备上的指针数组本身(N个指针地址,共N×8字节)拷贝到主机的a数组里,而不是矩阵的实际数据——之后你打印a[i][j]时,访问的是无效地址,必然崩溃或者输出垃圾值。

正确的做法是逐行拷贝:循环遍历每一行,把设备上对应行的内存拷贝到主机对应行的内存中。

4. 内存泄漏问题

你只调用了cudaFree(d_a),但完全没有释放循环中分配的每一行设备内存,会造成设备内存泄漏。需要先释放每一行的设备内存,再释放指针数组d_a本身(注意要通过主机端的h_d_a来获取每行的设备地址)。


修正后的完整代码

#include <stdio.h>
#include <stdlib.h>

__global__ void create(int **d_a){
    int i = threadIdx.x;
    int j = threadIdx.y;
    // 边界判断,避免线程索引越界
    if (i < N && j < N) {
        d_a[i][j] = 1;
    }
}

void errorCheck(){
    cudaError_t error = cudaGetLastError();
    if(error != cudaSuccess){
        printf("CUDA error: %s\n", cudaGetErrorString(error));
        exit(-1);
    }
}

#define N 5

int main(){
    int **d_a, **a;
    int **h_d_a; // 主机端临时存储设备每行的内存地址

    // 分配主机端二维数组
    a = (int**)malloc(N * sizeof(int*));
    for (int i = 0; i < N; i++){
        a[i] = (int*)malloc(N * sizeof(int));
    }

    // 分配设备端的指针数组(存储每行的设备地址)
    cudaMalloc((void***)&d_a, N * sizeof(int*));
    // 主机端临时数组,用来暂存每行的设备内存地址
    h_d_a = (int**)malloc(N * sizeof(int*));
    for (int i = 0; i < N; i++){
        cudaMalloc((void**)&h_d_a[i], N * sizeof(int));
    }
    // 将每行的设备地址拷贝到设备端的指针数组d_a中
    cudaMemcpy(d_a, h_d_a, N * sizeof(int*), cudaMemcpyHostToDevice);
    errorCheck();

    // 启动二维线程块,每个线程对应矩阵的一个元素
    create <<<1, dim3(N, N)>>>(d_a);
    errorCheck();

    // 逐行拷贝设备数据到主机内存
    for (int i = 0; i < N; i++){
        cudaMemcpy(a[i], h_d_a[i], N * sizeof(int), cudaMemcpyDeviceToHost);
    }
    errorCheck();

    // 打印矩阵
    for (int i = 0; i < N; i++ ){
        for (int j = 0; j < N; j++ ){
            printf("%d ", a[i][j]);
        }
        printf("\n");
    }

    // 释放设备内存:先释放每行的内存,再释放指针数组
    for (int i = 0; i < N; i++){
        cudaFree(h_d_a[i]);
    }
    cudaFree(d_a);
    // 释放主机内存
    free(h_d_a);
    for (int i = 0; i < N; i++){
        free(a[i]);
    }
    free(a);

    return 0;
}

额外优化建议

其实在CUDA中,使用连续的二维内存(比如把矩阵 flatten 成一维数组,或者用cudaMallocPitch分配对齐的连续内存)会比指针数组更高效,因为指针数组的内存是分散的,会增加内存访问的延迟。比如可以把kernel改成用一维索引:

__global__ void create(int *d_a){
    int idx = threadIdx.x * N + threadIdx.y;
    if (idx < N*N) {
        d_a[idx] = 1;
    }
}

这样分配内存和拷贝都会更简单,性能也更好。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 05:24:02