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

使用cudaMalloc指针引用时出现非法内存访问的解决方法

CUDA中通过引用共享设备端对象的问题及解决方法

问题背景

我希望在A类中创建一个P对象,并让B类持有P的引用。相关代码定义如下:

P结构体定义

struct P
{
    int* p_;

    P() { CC(cudaMalloc(&p_, sizeof(int))); }

    __device__ void print() { printf("p: %d\n", p_[0]); }
};

A、B类定义

struct A
{
    P a_;
};

struct B
{
    B(A& a) : b_(a.a_) {}
    P& b_;
};

设备端核函数实现

__global__ void Ka(A a)
{
    a.a_.p_[0] = 399;
    a.a_.print();
}

__global__ void Kb(B b)
{
    b.b_.print();
}

主机端调用方式

A a;
Ka <<<1, 1>>> (a);
CC(cudaDeviceSynchronize());
CC(cudaGetLastError());

B b(a);
Kb <<<1, 1>>> (b);
CC(cudaDeviceSynchronize());
CC(cudaGetLastError());

调试显示二者内存中是同一个对象,但第二个Kb核函数调用时出现非法内存访问。需要在无需复制P(实际场景中复制成本极高)的前提下实现类似功能。


问题原因

核心问题是主机端的引用无法直接传递到设备端:

  • 主机端创建的B对象中,b_引用指向的是主机端A对象里的a_;
  • 当把B对象作为参数传递给核函数时,传递的是B的副本,其中的引用地址仍然是主机端内存地址;
  • 设备端线程无法直接访问主机端内存,因此这个引用在设备端是无效地址,触发非法内存访问。

解决方案

方案1:用指针替代引用,传递设备端对象地址

引用本质是语法糖,在CUDA设备端场景下,指针更适合传递设备内存地址。需要先将A对象拷贝到设备端,再让B持有设备端P对象的指针:

修改后代码

struct P
{
    int* p_;

    P() { CC(cudaMalloc(&p_, sizeof(int))); }
    // 添加析构函数避免内存泄漏
    ~P() { if (p_) CC(cudaFree(p_)); }

    __device__ void print() { printf("p: %d\n", p_[0]); }
};

struct A
{
    P a_;
};

struct B
{
    P* b_;
    // 接收设备端A的指针,直接指向其内部的P对象
    B(A* dev_a) : b_(&dev_a->a_) {}
};

__global__ void Ka(A* a)
{
    a->a_.p_[0] = 399;
    a->a_.print();
}

__global__ void Kb(B b)
{
    b.b_->print();
}

主机端调用

A host_a;
A* dev_a;
// 分配设备端内存并拷贝A对象
CC(cudaMalloc(&dev_a, sizeof(A)));
CC(cudaMemcpy(dev_a, &host_a, sizeof(A), cudaMemcpyHostToDevice));

// 调用核函数修改设备端P对象
Ka<<<1, 1>>>(dev_a);
CC(cudaDeviceSynchronize());
CC(cudaGetLastError());

// 创建B对象,持有设备端P的指针
B host_b(dev_a);
Kb<<<1, 1>>>(host_b);
CC(cudaDeviceSynchronize());
CC(cudaGetLastError());

// 清理资源
CC(cudaFree(dev_a));

方案2:使用统一内存(Unified Memory)简化管理

如果你的CUDA版本支持统一内存(Compute Capability 3.0及以上),可以利用统一内存让主机和设备共享对象地址,无需手动拷贝:

修改后代码

struct P
{
    int* p_;

    // 使用cudaMallocManaged分配统一内存
    P() { CC(cudaMallocManaged(&p_, sizeof(int))); }
    ~P() { if (p_) CC(cudaFree(p_)); }

    __device__ void print() { printf("p: %d\n", p_[0]); }
};

struct A
{
    P a_;
};

struct B
{
    P& b_;
    B(A& a) : b_(a.a_) {}
};

__global__ void Ka(A* a)
{
    a->a_.p_[0] = 399;
    a->a_.print();
}

__global__ void Kb(B b)
{
    b.b_.print();
}

主机端调用

// 分配统一内存的A对象
A* dev_a;
CC(cudaMallocManaged(&dev_a, sizeof(A)));

Ka<<<1, 1>>>(dev_a);
CC(cudaDeviceSynchronize());
CC(cudaGetLastError());

// B直接引用统一内存中的P对象
B host_b(*dev_a);
Kb<<<1, 1>>>(host_b);
CC(cudaDeviceSynchronize());
CC(cudaGetLastError());

// 清理资源
CC(cudaFree(dev_a));

统一内存会自动处理主机与设备间的内存同步,对象地址在两端一致,因此引用可以正常在设备端使用。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 10:05:20