并行编程实战——CUDA编程的内存建议

一、内存优化中的内存建议

统一内存的优势是让开发者在面对内存只是看到一个内存而不是要区别主机和设备内存,这样更有利于应用和底层管理的操作。毕竟,复杂度的降低往往都是一种技术的进步。

而统一内存出现后,就对内存的优化提供了统一的路径,除了前面提到的内存的预取,另外一个就是本文将要分析的内存建议,memory advise。内存建议是与统一内存配合使用的一种高级的内存优化技术。其关键的技术点在于向CUDA运行时提供了内存访问模式的提示(hints)。它可以让CUDA整体上在一个较长的周期内做出更优的数据迁移策略,减少内存的抖动缺页中断等,提高运行的性能。

内存建议其实就是在保证统一内存管理的前提下,通过显式的说明,让CUDA框架确定统一内存的适配对象、应用范围等等。即在一个整体的宏观的方向上,为CUDA运行时提供一个对内存操作的可能性。这样CUDA运行时就可以为其提供稳定的内存布局决策,防止短期的优化操作。其定义为:

c 复制代码
cudaError_t cudaMemAdvise(
    const void* devPtr,
    size_t count,
    cudaMemoryAdvise advice,
    int device
);

这个函数的关键在于第三个参数即cudaMemoryAdvise advice,它是一个枚举,用于确定建议的种类。

二、CUDA的内存建议种类

在CUDA接口cudaMemAdvise中,内存建议cudaMemoryAdvise枚举主要有三种情况:

  1. 只读建议
    cudaMemAdviseSetReadMostly,用于在每个访问该数据的处理器上创建只读副本,避免数据的迁移。它只适合于共享读即读多写少的场景,否则反而会适得其反
  2. 首选建议
    cudaMemAdviseSetPreferredLocation,将数据的物理存储位置固定在设备上(CPU或GPU),其它设备访问只建议映射而不进行迁移。它适合对设备应用明确,其它设备操作偶发的情况下应用。但需要注意的是,如果硬件不支持远程映射,则仍然会引发数据迁移。
  3. 访问设备建议
    cudaMemAdviseSetAccessedBy,设定目标设备(GPU)预先建立直接的内存映射,从而确保映射的数据对物理存储的透明和完备性,保证不会因为缺页而触发中断。它一般在多GPU协同工作的场景下,两个GPU间可能会频繁的进行数据的交互。不过映射会导致些许的性能损失,这一点要考虑到

再次强调,内存建议是非强制笥的,可能被忽略,所以要根据情况来实际确定。具体的可以通过Nsight Systems工具进行可视化的内存迁移监控,确定内存建议是否生效。

三、分析说明

如果说内存预取一般可立马看到效果,但内存建议未必。毕竟"建议"这两个字本身就代表着非强制性意思。在一些文档中,针对主流的平台测试(intel x86 +pascal gpu)可以看到,在显存满足应用的场景下,内存建议导致的性能的提升几乎可以忽略。而在显存超50%的负荷后,使用内存建议可以获得较高的提升(大约25%);而在另外一个平台上(IBM Power9+Volta GPU)则恰恰相反。

这意味着,这项内存增强技术与硬件平台耦合紧密,在实际应用时,需要认真的对硬件平台和软件应用场景进行评估。

另外,内存建议只适合于统一内存(cudaMallocManaged)的优化和增强并对相关的硬件设备和CUDA版本有一定的要求,不要想当然的进行应用和操作。

四、应用场景

在掌握了内存建议的内容后,就可以发现与之相应的开发场景,主要有:

  1. 需要显式的参数预加载和预指定
    在与内存预取协同的情况下,通过内存建议应用在大模型的推理和训练中。根据实际的不同可以将数据预加载到指定的CPU和GPU上。可以有效的防止首次加载的数据迁移。
  2. 分布式GPU应用
    AI中的大模型中,多GPU的分布式处理已经成为一种主流,这对于某些场景下GPU的交互使用内存建议中的"访问设备建议"就非常重要。特别是当多个GPU共享参数时,就有明显的效果。
  3. CPU-GPU协同处理
    在实地的应用场景中,可能需要CPU和GPU协同操作,比如CPU也可能访问运算的中间态(结果),数据在哪个设备上更有优势,这都是内存建议可以发挥其作用的地方。

CUDA虽然应用场景很多,但现在AI风头正盛,其它的一切都在它的应用前变得黯淡无光,所以基本上,谈CUDA不能离开AI的应用。

五、例程

看一个内存建议的简单例子:

c 复制代码
#include "cuda_runtime.h"
#include "device_launch_parameters.h"

#include <stdio.h>

__global__ void write(int* ret, int a, int b) {
    ret[threadIdx.x] = a + b + threadIdx.x;
}

__global__ void append(int* ret, int a, int b) {
    ret[threadIdx.x] += a + b + threadIdx.x;
}

int  main() {
    int* ret;
    cudaMallocManaged(&ret, 1000 * sizeof(int));
    cudaMemLocation location = { cudaMemLocationTypeHost };
    cudaMemAdvise(ret, 1000 * sizeof(int), cudaMemAdviseSetAccessedBy, location);  // set direct access hint

    write <<< 1,1000 >>> (ret, 10, 100);            // pages populated in GPU memory
    cudaDeviceSynchronize();
    for (int i = 0; i < 1000; i++)
        printf("%d: A+B = %d\n", i, ret[i]);        // directManagedMemAccessFromHost=1: CPU accesses GPU memory directly without migrations
    // directManagedMemAccessFromHost=0: CPU faults and triggers device-to-host migrations
    append << < 1, 1000 >> > (ret, 10, 100);            // directManagedMemAccessFromHost=1: GPU accesses GPU memory without migrations
    cudaDeviceSynchronize();                        // directManagedMemAccessFromHost=0: GPU faults and triggers host-to-device migrations
    cudaFree(ret);
    return 0;
}

代码来自官网。上面的注释很清楚,就不重复说明了。

六、总结

技术应用的默认设置往往是超于保守的,目的是为了兼容性和安全性的控制。但在某些明确目标的情况下,开发者是可以显式的屏蔽短板的情况的,这样就可以使用内存建议这种技术对内存进行优化。优化技术是一个体系,所以内存建议经常和内存预取等优化技术一起协同工作,从整体上完成对内存的优化。达到设计的目的和并满足实际的需求。

相关推荐
瓦特what?2 小时前
希 尔 排 序
开发语言·c++
落羽的落羽2 小时前
【Linux系统】磁盘ext文件系统与软硬链接
linux·运维·服务器·数据库·c++·人工智能·机器学习
StandbyTime3 小时前
《算法笔记》练习记录-2.5-问题 D: 习题6-12 解密
c++·算法笔记
ADDDDDD_Trouvaille3 小时前
2026.2.18——OJ86-88题
c++·算法
_nirvana_w_3 小时前
Qt项目链接库时遇到的坑:-l选项的正确用法
开发语言·c++·qt·qt框架·elawidgettools
我命由我123453 小时前
Visual Studio - Visual Studio 修改项目的字符集
c语言·开发语言·c++·ide·学习·visualstudio·visual studio
郝学胜-神的一滴4 小时前
Python变量本质:从指针哲学到Vibe Coding优化
开发语言·c++·python·程序人生
s_w.h4 小时前
【 C++ 】搜索二叉树
java·开发语言·c++·算法
俩娃妈教编程4 小时前
2023 年 09 月 二级真题(2)--数字黑洞
c++·算法·while