PMPP-第二十章:异构计算集群编程
前言
第十九章总结了并行编程的思维方法。第二十章将视野扩展到计算集群(Computing Cluster)——多台计算机通过高速网络连接,每台计算机可能配备多个 GPU。这是当今超级计算机和数据中心的典型架构。本章讨论如何使用 **MPI(Message Passing Interface,消息传递接口)**与 CUDA 结合,实现跨节点的异构并行计算。掌握这些技术,就能编写可扩展到数千个 GPU 的大规模并行程序。
📦 配套资源:本系列文章配有完整的 GitHub 仓库,包含每章的练习题解答、CUDA 代码实现和详细注释。所有代码都经过测试,可以直接运行。
异构计算集群架构
什么是异构集群
异构集群:由多个计算节点组成,每个节点包含:
- CPU(主机)
- 一个或多个 GPU(加速器)
- 本地内存
- 网络接口
节点之间通过高速网络(如 InfiniBand、NVLink)连接。
典型架构
1 | ┌─────────────────────────────────────────────────────────┐ |
编程挑战
- 分布式内存:每个节点有独立的内存空间,不能直接访问
- 数据通信:需要显式地在节点间传递数据
- 同步协调:多个进程需要协调工作
- 故障容错:单个节点故障不应导致整个计算崩溃
MPI 基础
什么是 MPI
MPI(Message Passing Interface):一种标准化的消息传递编程模型。
- 定义了进程间通信的 API
- 支持点对点通信和集合通信
- 与硬件无关,可移植性好
常见实现:OpenMPI、MPICH、Intel MPI。
基本概念
进程(Process):MPI 程序的基本执行单元。每个进程有:
- 唯一的秩(Rank):0 到 N-1
- 独立的地址空间
- 可以运行在不同的物理节点上
通信子(Communicator):定义参与通信的进程组。
MPI_COMM_WORLD:包含所有进程的默认通信子
MPI 程序骨架
1 |
|
运行方式:
1 | mpirun -np 4 ./my_program # 启动 4 个进程 |
点对点通信
基本发送和接收
1 | // 发送 |
阻塞与非阻塞
阻塞通信:函数返回时,操作已完成或缓冲区可安全复用。
MPI_Send:可能阻塞直到接收方准备好(取决于实现)MPI_Recv:阻塞直到消息到达
非阻塞通信:函数立即返回,后续检查完成状态。
1 | MPI_Request request; |
Send-Receive 组合
避免死锁的常用模式:
1 | MPI_Sendrecv( |
同时发送和接收,系统自动处理顺序。
MPI + CUDA 编程
基本策略
每个 MPI 进程管理一个或多个 GPU:
1 | int rank; |
数据流模式
典型的 MPI + CUDA 计算流程:
1 | 1. MPI 进程接收输入数据(主机内存) |
示例:分布式向量加法
1 | void distributed_vector_add(float *a, float *b, float *c, int n, int rank, int size) { |
Halo 交换与边界通信
什么是 Halo
在模板计算(stencil)、有限差分等应用中,每个点的计算依赖于邻近点。
当数据分布在多个进程时,边界点的计算需要相邻进程的数据。
Halo(光晕/幽灵区域):存储来自邻居进程的边界数据。
1 | 进程 0 的数据 进程 1 的数据 |
3D 模板计算示例
以 25 点模板为例(每个方向延伸 4 个点):
1 | // 计算每个进程需要多少 Halo 点 |
Halo 交换实现
1 | void exchange_halos(float *data, int dimx, int dimy, int dimz, |
计算与通信重叠
问题
通信需要时间,如果先计算完再通信,GPU 会空闲等待。
解决方案
思路:重叠计算与通信。
- 先计算边界区域(通信需要的数据)
- 边界计算完成后,开始通信
- 同时计算内部区域
- 通信完成后,所有计算都完成了
CUDA 流实现
1 | cudaStream_t stream_boundary, stream_internal; |
时间线分析
1 | 时间 → |
CUDA-Aware MPI
传统方式的问题
1 | // 传统方式:必须经过主机内存 |
问题:额外的内存拷贝开销。
CUDA-Aware MPI
CUDA-Aware MPI:MPI 实现能直接识别 GPU 指针。
1 | // 直接传递 GPU 指针——不需要手动拷贝 |
MPI 库自动处理:
- 通过 GPUDirect RDMA 直接 GPU 到 GPU 传输
- 如果不支持,自动回退到经过主机的方式
环境配置
1 | # 编译时链接 CUDA-Aware MPI |
使用 CUDA-Aware MPI 改写
1 | // 无需 host buffer,直接使用 device buffer |
优势:
- 减少内存拷贝
- 更低延迟
- 代码更简洁
数据服务器模式
问题
大规模集群中,I/O 可能成为瓶颈。每个计算节点都从存储读数据会导致争用。
解决方案
数据服务器模式:一个进程专门负责 I/O,其他进程专门计算。
1 | ┌──────────────────────────────────────────────────────┐ |
实现
1 | void data_server(int dimx, int dimy, int dimz, int nreps) { |
完整示例:MPI + CUDA 模板计算
程序结构
1 | int main(int argc, char *argv[]) { |
计算节点实现
1 | void compute_node_stencil(int dimx, int dimy, int dimz, int nreps) { |
性能优化
通信优化
| 技术 | 描述 | 效果 |
|---|---|---|
| 非阻塞通信 | 使用 MPI_Isend/MPI_Irecv |
重叠通信与计算 |
| 集合通信 | 使用 MPI_Allreduce 而非循环 P2P |
利用优化的算法 |
| CUDA-Aware MPI | 直接传递 GPU 指针 | 减少内存拷贝 |
| 固定内存 | cudaHostAlloc |
加速 H2D/D2H 传输 |
负载均衡
确保每个节点的工作量大致相等:
1 | // 处理不能整除的情况 |
可扩展性分析
对于 25 点模板计算:
| 节点数 | 通信量(每节点) | 计算量(每节点) | 计算/通信比 |
|---|---|---|---|
| 16 | 2×64×64×4 = 32K | 64×64×128 = 512K | 16:1 |
| 64 | 2×64×64×4 = 32K | 64×64×32 = 128K | 4:1 |
| 256 | 2×64×64×4 = 32K | 64×64×8 = 32K | 1:1 |
观察:节点越多,通信开销占比越高。这是强扩展的典型特征。
常见问题与解决
死锁
原因:所有进程都在等待接收,没有进程发送。
1 | // 错误示例——会死锁! |
解决:使用 MPI_Sendrecv 或非阻塞通信。
GPU 内存不足
原因:每个节点分配的数据太多。
解决:
- 增加节点数
- 使用统一内存自动管理
- 分批处理
性能不佳
诊断:使用 Nsight Systems 分析 MPI + CUDA 程序。
1 | nsys profile --trace=cuda,mpi mpirun -np 4 ./my_program |
查看是否有:
- 过长的 MPI 等待时间
- 未重叠的计算和通信
- GPU 空闲时间
小结
第二十章扩展了并行编程的视野,从单 GPU 扩展到多节点集群:
异构集群架构:每个节点包含 CPU 和 GPU,节点间通过网络连接。分布式内存模型要求显式通信。
MPI 基础:消息传递编程模型。进程通过发送/接收消息通信。点对点通信(Send/Recv)和集合通信(Broadcast/Reduce)。
MPI + CUDA:每个 MPI 进程管理一个或多个 GPU。数据在主机内存和 GPU 内存之间传输,在进程间通过 MPI 传输。
Halo 交换:模板计算中,边界数据需要与邻居进程交换。使用 Sendrecv 避免死锁。
计算与通信重叠:利用 CUDA 流,边界计算完成后立即开始通信,同时进行内部计算。显著减少总执行时间。
CUDA-Aware MPI:MPI 库直接接受 GPU 指针,利用 GPUDirect 技术减少内存拷贝。
数据服务器模式:一个进程专门负责 I/O,减少存储争用。
掌握 MPI + CUDA 编程,你就能编写可扩展到数千 GPU 的应用程序——这是当今 AI 训练、科学计算、天气预报等领域的核心技术。
🚀 下一步
- 搭建一个简单的多节点 GPU 集群环境,配置 MPI 和 CUDA-Aware MPI
- 实现一个分布式矩阵乘法,学习数据分割和结果收集
- 掌握 Halo 交换模式,实现分布式模板计算(如热传导方程)
- 学习集合通信操作:Allreduce、Allgather、Alltoall
- 探索性能分析工具:Nsight Systems 分析 MPI + CUDA 程序的性能瓶颈
- 了解现代 HPC 框架:NCCL(多 GPU 通信)、Horovod(分布式深度学习)
📚 参考资料
- PMPP 第四版 Chapter 20
- 第二十章:异构计算集群编程
- Hwu, W., Kirk, D., & El Hajj, I. (2022). Programming Massively Parallel Processors: A Hands-on Approach (4th Edition). Morgan Kaufmann.
- MPI Forum. MPI: A Message-Passing Interface Standard. https://www.mpi-forum.org/
- NVIDIA. CUDA-Aware MPI. https://developer.nvidia.com/blog/introduction-cuda-aware-mpi/
学习愉快! 🎓
本文 GitHub 仓库: https://github.com/psmarter/PMPP-Learning
