一、NVSHMEM 是什么:从起源到定位

大家好,我是你们这趟 NVSHMEM 之旅的向导。今天咱们聊聊 NVSHMEM 的来龙去脉。

先问个问题:你写 GPU 程序时,最头疼的是什么?

我个人觉得,是数据搬运。你把数据从 CPU 拷到 GPU,算完再拷回来。单卡还好,多卡协作时,那叫一个痛苦。每个 GPU 有自己的显存,数据不能直接访问邻居的。你得手动调用 cudaMemcpy,还得操心同步。

嗯,NVSHMEM 就是来解决这个痛点的。

NVSHMEM 的起源与背景

NVSHMEM 的全称是 NVIDIA Shared Memory,但它不是我们常说的 GPU 上的共享内存(Shared Memory)。它借鉴了另一个经典库——SHMEM。

SHMEM 是什么?它诞生于 90 年代,专为 Cray 超级计算机设计。核心思想很简单:每个处理器有自己的局部内存,但你可以通过一套 API 直接读写别人的内存。不需要像 MPI 那样发消息、收消息,直接 put/get 就行。

NVIDIA 在 2019 年把 SHMEM 搬到了 GPU 上,推出了 NVSHMEM。说白了,就是让 GPU 之间能像访问自己的显存一样,直接读写对方的显存。

核心要点:NVSHMEM 是一套用于 GPU 集群的通信库,它实现了 PGAS(分区全局地址空间) 编程模型。你写代码时,感觉所有 GPU 的显存是一个整体,但实际上每个 GPU 只管理自己那一块。

我记得第一次接触 NVSHMEM 时,心里犯嘀咕:这不就是 MPI 吗?后来发现完全不是一回事。MPI 是消息传递,你发我收。NVSHMEM 是内存访问,你直接写我的地址。性能差距很大。

PGAS 编程模型简介

PGAS,全称 Partitioned Global Address Space。名字挺长,拆开看就懂了:

  • Global Address Space:全局地址空间。所有 GPU 的显存被映射到一个统一的虚拟地址空间里。
  • Partitioned:分区的。每个 GPU 只拥有其中一部分,这部分叫“本地分区”。

你想想看,写代码时你只需要知道数据的“全局地址”,就能直接读写它。至于数据在哪个 GPU 上,库帮你搞定。

举个例子:

// 每个 PE(Processing Element,即 GPU)分配 1024 个 float
float *ptr = (float*)nvshmem_malloc(1024 * sizeof(float));

// PE 0 把数据写到 PE 1 的地址上
if (my_pe == 0) {
    nvshmem_float_put(&ptr[0], &src_data, 1, 1);  // 把 src_data 写到 PE 1 的 ptr[0]
}

// PE 1 直接读取自己的 ptr[0],数据已经在了
if (my_pe == 1) {
    float val = ptr[0];  // 无需同步,数据已就位
}

看到了吗?PE 0 直接往 PE 1 的内存里写数据。PE 1 不需要主动接收。这就是 PGAS 的精髓——通信变成了内存访问

避坑指南:我曾经犯过一个低级错误——以为 nvshmem_malloc 分配的内存是全局可见的,就直接在 CPU 端访问。不行!NVSHMEM 的分配只在 GPU 端有效。CPU 端要访问,得用 cudaMemcpy 或者 nvshmemx_ 开头的对称分配接口。切记。

PGAS 模型有几种变体:

模型 特点 代表
UPC(统一并行 C) 语言扩展,编译时确定数据分布 UPC、CAF
SHMEM 库形式,运行时动态通信 NVSHMEM、OpenSHMEM
GASNet 底层通信层,支持多种模型 GASNet-EX

NVSHMEM 属于 SHMEM 这一支。它不改变你的编程语言,只提供一套 API。你写 CUDA C/C++ 代码,直接调用 nvshmem_put、nvshmem_get 就行。

NVSHMEM 在 GPU 集群中的定位

现在 GPU 集群越来越常见。一个节点插 4 张、8 张 GPU,甚至跨节点互联。怎么让这些 GPU 高效协作?

传统方案有:

  • MPI + CUDA:CPU 端发消息,GPU 端计算。数据要经过 CPU 中转,延迟高。
  • NCCL:专为 GPU 设计的集合通信库,适合 AllReduce、Broadcast 等操作。但它是“集体”的,不适合点对点随机访问。
  • CUDA 感知的 MPI:GPU 可以直接传递指针给 MPI,但底层还是消息传递。

NVSHMEM 的定位是:填补点对点、细粒度、低延迟通信的空白

我举个例子你就明白了。假设你在做图计算,每个 GPU 负责一部分节点。计算时,一个节点可能需要频繁访问邻居节点的数据。邻居可能在别的 GPU 上。

用 MPI 怎么做?你得先打包数据,发送,对方接收,解包。每次通信开销很大。

用 NVSHMEM 呢?直接 put/get。就像访问本地数组一样。延迟能降到微秒级。

NVSHMEM 的典型应用场景:

  • 稀疏线性代数(SpMV、SpGEMM)
  • 图计算(BFS、PageRank)
  • 粒子模拟(PIC、N-body)
  • 深度学习中的模型并行(参数服务器模式)

当然,NVSHMEM 不是万能的。它不适合大数据块传输(那是 NCCL 的强项)。它也不适合 CPU 和 GPU 之间的通信(那是 cudaMemcpy 的事)。

说白了,NVSHMEM 是给那些需要 GPU 之间频繁、小数据量、低延迟通信的场景准备的。

注意:NVSHMEM 要求所有 GPU 必须通过 NVLink 或 InfiniBand 互联。如果只是通过 PCIe 连接,性能会大打折扣。我在一个项目中试过 PCIe 下的 NVSHMEM,延迟比 NVLink 高了 5 倍。所以,硬件条件很重要。

最后,我用一张图来总结 NVSHMEM 在整个 GPU 编程生态中的位置:

NVSHMEM 在 GPU 集群编程生态中的定位 应用层 图计算、稀疏线性代数、粒子模拟、深度学习模型并行 编程模型层 PGAS(分区全局地址空间) 代表:NVSHMEM、OpenSHMEM、UPC 通信库层 点对点通信(put/get)、集合通信(barrier、broadcast) 对比:NCCL(集合通信)、MPI(消息传递) 硬件层 GPU 显存(HBM)、NVLink、InfiniBand、PCIe 要求:GPU 间需通过 NVLink 或 InfiniBand 互联 抽象层次由高到低 NVSHMEM 定位 点对点 低延迟 细粒度 GPU直接 内存访问 不经过CPU

从这张图你能看到,NVSHMEM 位于编程模型层和通信库层之间。它向上提供 PGAS 抽象,向下直接操作 GPU 显存和互联网络。说白了,它让 GPU 集群编程变得像单机多线程一样简单。

好了,这一章就到这里。NVSHMEM 是什么,你应该心里有数了。下一章我们聊聊怎么搭建环境,让你能亲手跑第一个 NVSHMEM 程序。


专注资料整理