Host/Device 编程模型
假设你已了解 CPU vs GPU 架构差异和 SIMT 执行模型,本文将帮助你深入理解 Host 与 Device 在编程中的分工、通信和协作——内存管理、数据传输、流并发、事件同步。
Host 与 Device 的职责
| Host(CPU)职责 | Device(GPU)职责 |
|---|---|
| 程序主逻辑流(顺序代码) | 并行 Kernel 执行 |
| 内存分配与释放 | 大规模数据并行处理 |
| 数据拷贝(Host ↔ Device) | 高吞吐计算 |
| Kernel 配置与启动 | 接收并执行 Kernel |
| 同步与等待 | 返回执行结果 |
关键原则:
- Host 代码是顺序的,Device 代码是并行的
- Host 控制"做什么",Device 负责"怎么做"
- Host 和 Device 拥有独立的内存地址空间
内存管理与数据传输
内存空间
| 内存空间 | 位置 | 访问者 | 特点 |
|---|---|---|---|
| 主机内存 | CPU 系统内存 | CPU | 低延迟,容量大(8-64GB) |
| 设备内存 | GPU 显存 | GPU | 高带宽,容量适中(8-32GB) |
warning
CPU 和 GPU 无法直接访问对方的内存,必须通过 musaMemcpy 显式传输。
内存管理 API
#include <musa_runtime.h>
// 分配设备内存
float* d_data;
musaMalloc(&d_data, size * sizeof(float));
// 释放设备内存
musaFree(d_data);
数据 传输 API
// Host → Device
musaMemcpy(d_data, h_data, size, musaMemcpyHostToDevice);
// Device → Host
musaMemcpy(h_result, d_result, size, musaMemcpyDeviceToHost);
// Device → Device(多 GPU 场景)
musaMemcpy(d_dst, d_src, size, musaMemcpyDeviceToDevice);
// Host ←→ Host(内存初始化)
musaMemcpy(h_data, h_src, size, musaMemcpyHostToHost);
方向枚举
| 枚举值 | 方向 |
|---|---|
musaMemcpyHostToDevice | CPU → GPU |
musaMemcpyDeviceToHost | GPU → CPU |
musaMemcpyDeviceToDevice | GPU → GPU |
musaMemcpyHostToHost | CPU → CPU |
统一内存:musaMallocManaged() 分配统一内存,CPU 和 GPU 共享同一地址空间,无需显式拷贝。但需要了解其性能特性(首次访问可能触发页面迁移)。
内核执行
内核定义
// __global__ 修饰器表示这是一个内核函数
__global__ void myKernel(float* data, int n) {
// 计算全局线程索引
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n) {
data[idx] *= 2.0f;
}
}
内核基础语法见 GPU 并行计算基础。
内核启动
// 配置执行参数
int blockSize = 256;
int gridSize = (n + blockSize - 1) / blockSize;
// 启动内核:<<<gridDim, blockDim>>>
myKernel<<<gridSize, blockSize>>>(d_data, n);
执行配置参数
| 参数 | 含义 |
|---|---|
gridDim | 网格维度(线程块数量) |
blockDim | 线程块维度(每线程块线程数) |
blockIdx | 当前线程块索引 |
threadIdx | 当前线程索引 |