NVSHMEM 基础回顾:NVSHMEM 简介、对称内存与 PE 概念、基本的 put/get 操作

各位同学,咱们今天聊聊 NVSHMEM 的基础。说实话,我刚开始接触这东西的时候,也觉得有点绕。但别急,咱们一步步来。

NVSHMEM 是什么?

NVSHMEM,全称是 NVIDIA Shared Memory Model。说白了,它是一套让 GPU 之间可以直接通信的库。你想想看,传统做法里,GPU 之间要交换数据,得先拷回 CPU 内存,再拷过去。多麻烦啊!

NVSHMEM 就不一样了。它让 GPU 能直接读写另一个 GPU 的内存。就像两个邻居,不用通过邮局,直接隔墙喊话就行。

核心思想:NVSHMEM 把多个 GPU 的内存虚拟成一个统一的全局地址空间。每个 GPU 都能直接访问这个空间里的任何位置。

我在项目中遇到过这样一个场景:8 张 GPU 卡做并行计算,每张卡只处理自己那部分数据。但边界上的数据需要互相交换。用传统 MPI 的话,代码写起来又长又容易出错。换成 NVSHMEM 后,代码量直接砍了一半。

对称内存与 PE 概念

好,接下来咱们聊聊两个核心概念:对称内存和 PE。

什么是对称内存?

对称内存,就是每个 PE(Processing Element,处理单元)上都有一块大小相同、布局相同的内存区域。嗯,这里要注意:不仅仅是大小一样,连里面的数据结构布局都得一样。

举个例子:

// 每个 PE 上都会分配 1024 个 float
float *buf = (float*)nvshmem_malloc(1024 * sizeof(float));

这行代码一执行,每个 PE 上都会有一块 1024 个 float 的空间。而且它们在虚拟地址空间中的偏移量是一样的。

我的经验:对称内存是 NVSHMEM 的基石。你想想看,如果每个 PE 上的内存布局不一样,那一个 PE 怎么知道另一个 PE 的数据放在哪?所以,设计数据结构时,一定要保证对称性。

PE 是什么?

PE 就是处理单元。在 NVSHMEM 的世界里,一个 PE 可以是一个 GPU,也可以是一个 CPU 线程。每个 PE 都有一个唯一的编号,从 0 到 npes-1。

我习惯把 PE 想象成一个个独立的小房间。每个房间里有自己的柜子(对称内存),房间之间有条走廊(NVSHMEM 通信通道)。

概念 类比 说明
PE 小房间 每个 GPU 或 CPU 线程
对称内存 房间里的柜子 每个 PE 上大小相同的内存
my_pe 房间号 当前 PE 的编号
npes 房间总数 所有 PE 的数量

基本的 put/get 操作

好了,有了对称内存和 PE 的概念,咱们来看看怎么实际干活。NVSHMEM 提供了两个最基本的操作:put 和 get。

put 操作:把数据放过去

put 操作,就是把当前 PE 的数据,写到另一个 PE 的对称内存里。

// 把本地 src 中的数据,写到 PE 1 的 dest 位置
float src[4] = {1.0, 2.0, 3.0, 4.0};
nvshmem_float_put(&dest[0], &src[0], 4, 1);

这行代码的意思很直白:把本地 src 数组里的 4 个 float,写到 PE 1 的 dest 数组里。

避坑指南:我曾经犯过一个错误——忘了 put 操作是异步的。也就是说,nvshmem_float_put 返回时,数据可能还没真正到达目标 PE。如果你紧接着就去读目标 PE 上的数据,可能会读到旧值。记得用 nvshmem_barrier() 或 nvshmem_quiet() 来同步。

get 操作:把数据拿过来

get 操作正好相反,是从另一个 PE 的对称内存里,把数据读到当前 PE。

// 从 PE 2 的 src 位置,读 4 个 float 到本地 dest
float dest[4];
nvshmem_float_get(&dest[0], &src[0], 4, 2);

get 操作也是异步的。嗯,这里要注意:get 操作虽然异步,但数据最终会到达本地内存。你可以在 get 之后调用 nvshmem_barrier() 确保数据到位。

知识体系总览

下面这张图,是我自己画的 NVSHMEM 基础知识结构。你看一眼,心里就有谱了。

NVSHMEM 基础知识体系 NVSHMEM 简介 GPU 直接通信库 对称内存 & PE 统一地址空间 put / get 操作 数据移动原语 基于 SHMEM 规范 支持多 GPU 直接通信 每个 PE 上大小相同 布局完全一致 PE 编号从 0 到 npes-1 put: 本地 → 远端 get: 远端 → 本地 异步操作需同步 三者结合:实现高效 GPU 间数据交换

一个小例子:两个 PE 之间的数据交换

咱们来看个完整的例子。假设有两个 PE,PE 0 和 PE 1。PE 0 想把一个数发给 PE 1。

#include <nvshmem.h>
#include <stdio.h>

int main(void) {
    nvshmem_init();
    
    int my_pe = nvshmem_my_pe();
    int npes = nvshmem_n_pes();
    
    // 分配对称内存
    float *data = (float*)nvshmem_malloc(sizeof(float));
    *data = 0.0f;
    
    if (my_pe == 0) {
        *data = 3.14f;
        // PE 0 把数据发给 PE 1
        nvshmem_float_put(data, data, 1, 1);
        nvshmem_quiet();  // 确保 put 完成
    }
    
    nvshmem_barrier_all();  // 全局同步
    
    if (my_pe == 1) {
        printf("PE 1 收到数据: %f\n", *data);
    }
    
    nvshmem_free(data);
    nvshmem_finalize();
    return 0;
}

这个例子很简单,但包含了 NVSHMEM 的核心流程:初始化、分配对称内存、put 操作、同步、收尾。

我的建议:刚开始学 NVSHMEM 时,先把这个例子跑通。然后试着改一改,比如改成 get 操作,或者改成多个 PE 之间的通信。动手实践是最好的学习方式。

好了,NVSHMEM 的基础就聊到这儿。对称内存和 PE 的概念,加上 put/get 操作,就是后续所有高级功能的地基。你把这些搞明白了,后面学光晕交换就轻松多了。


专注资料整理