CUDA 19 · 系统集成、资源隔离与平台互操作
系统级 CUDA 在解决什么
当 GPU 被推理服务、图形渲染、通信库和批处理作业共同使用时,单 kernel 优化不再足够。需要回答:谁能使用哪些 SM/显存/队列,数据物理上靠近谁,跨 API 和跨节点如何交换资源,以及故障能否被隔离。
一个大型 GPU 系统
├─ 资源隔离:MIG / MPS / Green Context / stream priority
├─ 局部性:NUMA / locality domain / NVLink-C2C / HBM
├─ 进程与节点:IPC / VMM / NCCL / NVSHMEM / Compute Fabric
└─ 外部 API:OpenGL / Direct3D / Vulkan / external memory + semaphore这些机制解决的层次不同,不能互相代替。例如 stream priority 不会为任务硬隔离 SM,MIG 也不会自动建立跨实例通信。
资源共享机制对比
| 机制 | 隔离/控制粒度 | 适合场景 | 关键限制 |
|---|---|---|---|
| Stream priority | 同一 context 的调度提示 | 紧急小任务插队 | 不保证抢占已运行 block,不是资源预留 |
| Green Context | 进程内选择 SM/WQ 子集 | 低延迟服务与后台任务分区 | 仍共享部分 GPU 资源,能力依设备 |
| MPS | 多进程共享 GPU 执行资源 | HPC 多 rank、小任务并发 | 隔离与故障语义不同于 VM/MIG |
| MIG | 硬件级 GPU 实例分区 | 多租户、容量与故障隔离 | 固定配置、拓扑和功能限制 |
| 独占进程模式 | 限制 context 所有者 | 简单批处理/HPC 管理 | 利用率可能较低 |
选择前先定义 SLA:是需要平均吞吐、P99 延迟、显存配额、故障域、安全边界,还是仅仅想提高利用率?
Green Context
Green Context 是与一组指定 GPU 资源绑定的轻量 context。当前可划分的主要资源包括 SM 与 work queue;投递到其 stream 的 kernel 只在分配的资源上执行。
GPU SMs: [0 1 2 3 4 5 6 7]
└── GC A ──┘ └── GC B ──┘
stream A → execution context A → latency-sensitive kernels
stream B → execution context B → background kernelsCUDA 13.1 起,Runtime API 通过 execution context 抽象公开 Green Context,官方建议新代码优先使用 Runtime 路线。大致流程:
query device resources
↓
split/configure SM and workqueue resources
↓
generate resource descriptor
↓
create green/execution context
↓
create stream for that context
↓
launch work on the streamGreen Context 不需要修改 kernel,但 kernel 资源用量、cluster shape 和分得的 SM 数仍必须兼容。把 SM 减半不代表时间严格翻倍;cache、带宽、work queue、并发和启动延迟都会改变。
Green Context 适用与误区
适用:
- 为低延迟 kernel 留出一组 SM,降低与批处理任务的调度干扰;
- 在一个进程中构造明确的计算分区;
- 测试算法在较少 SM 下的扩展性;
- 配合 locality domain,使 compute 靠近目标 memory。
误区:
- 它不是强安全租户边界;
- 它不保证所有共享 cache/带宽完全隔离;
- 它不能代替数据依赖和 stream/event 同步;
- 它不让超过资源上限的 cluster 自动降级;
- 分配更多 SM 仍不保证 kernel 有足够 grid 并行度。
MIG 与 MPS
MIG:一个物理 GPU → 多个硬件隔离实例
每实例具有划分的计算、显存等资源
MPS:多个 CUDA 进程 → MPS server → 共享一个 GPU/context 服务能力
重点是并发与利用率,不是同等级硬隔离MIG 配置由系统管理面完成,应用看到的是可见 CUDA device。应用仍需查询其 SM、内存、cluster 等实际能力,不能按完整 GPU 规格分配。
MPS 适合单个 MPI rank/进程无法填满 GPU 的场景,也能减少多 context 时间切换。但需要评估 active thread percentage、显存限制、错误传播、调试工具与作业调度器集成。
容器中的 CUDA_VISIBLE_DEVICES 只控制可见性/枚举,不是硬件安全隔离机制。
Locality Domain
多 chip GPU 或复杂封装内,设备可能包含多个 locality domains,每个 domain 有更靠近的一组 SM 与 device memory。CUDA 可把计算资源和 memory allocation 放在同一 domain:
GPU
├─ Locality Domain 0: SM group 0 + memory region 0
└─ Locality Domain 1: SM group 1 + memory region 1
bad: compute LD0 ─────────→ memory LD1
good: compute LD0 → memory LD0流程:查询 cudaDevAttrLocalityDomainCount,用 device/locality ID 描述 memory location,同时把 Green Context 的 SM resource group 定位到同一 ID。Locality ID 只在对应 device 内有意义。
普通 cudaMalloc 和 Unified Memory 未必提供显式 locality 选择;当前文档中 localized allocation 主要通过带 location 的 memory pool 或 Driver VMM 创建。
CPU NUMA 与 GPU 亲和性
多 socket 服务器上,host thread、pinned buffer、NIC 和 GPU 的拓扑影响传输:
CPU socket 0 ─ local DRAM 0 ─ PCIe root ─ GPU 0 / NIC 0
│ inter-socket link
CPU socket 1 ─ local DRAM 1 ─ PCIe root ─ GPU 1 / NIC 1实践中要一起绑定:
- 负责 GPU 0 的 host worker 尽量运行在相近 CPU NUMA node;
- pinned staging buffer 在合适的 host NUMA node 分配/首次触碰;
- 通信选择邻近 NIC/GPU;
- 使用
nvidia-smi topo、系统 NUMA 工具和 profiler 验证,而非按设备编号猜拓扑。
Extended GPU Memory
EGM 面向采用高带宽 NVLink-C2C 的集成 CPU-GPU 系统,使 GPU 能高效访问体系中的 system memory,并扩展到支持的多 GPU/多节点 NVLink fabric。
GPU HBM ─ NVLink/NVSwitch ─ GPU HBM
│ │
NVLink-C2C NVLink-C2C
│ │
CPU memory / NUMA node ─ fabric ─ CPU memory / NUMA nodeEGM allocation 可通过 VMM cuMemCreate 或 stream-ordered memory pool 表达 host NUMA location。关键类型包括 CU_MEM_LOCATION_TYPE_HOST_NUMA / cudaMemLocationTypeHostNuma,location id 是 OS NUMA node id,不是 CUDA device ordinal。
EGM 不是普通 PCIe 主机内存的通用别名。必须查询平台、location/handle type 与 fabric 支持,并遵守 allocation granularity、页大小和跨进程映射规则。
Address-centric 与 Resource-centric Fabric
两类跨 GPU 访问模型:
VMM / Fabric Memory(address-centric)
peer physical memory → map into local VA → ordinary load/store
Compute Fabric Transport(resource-centric)
logical endpoint id + offset → async put/get/atomic/reduction → completion statusVMM 映射绑定具体 allocation;owner 更换 backing 后,peer 通常要重新导入/映射。Compute Fabric Transport 的 peer 导入 endpoint,owner 可以在同步协议下重新绑定 backing,peer 继续使用同一个 endpoint id。
Compute Fabric Transport
它面向 NVLink fabric 上的大规模 GPU 通信,主要服务 NCCL/NVSHMEM 等通信库作者,普通应用应优先使用这些成熟库。
Owner process
reserve endpoint id → create endpoint → bind memory range
│ export fabric IPC handle
▼
Peer process
reserve local id → import endpoint
│
GPU thread: fabric_try_put/get/red/atom(id, offset)
│
wait completion → inspect status → retry/reroute/fail与 pointer access 不同,fabric operation 是显式异步且可报告每次操作的完成状态。观察目标最后一个字节改变,不代表整个乱序传输成功完成;必须等待 completion 并检查 status。
跨进程 endpoint 要求 fabric IPC handle、IMEX daemon/channel 和设备 clique 支持。越界 offset、在 unbind/rebind 窗口访问、未完成就复用 buffer 都是协议错误。
外部 API 互操作的两个模型
| 模型 | 支持方向 | 工作方式 |
|---|---|---|
| 传统 Graphics Interop | OpenGL、Direct3D 9–11 | 注册资源,每帧 map→CUDA→unmap→graphics |
| External Resource Interop | Vulkan、Direct3D 11/12、其他支持 API | 通过 OS handle 导入 memory 与 semaphore |
Direct3D 12 使用 external resource 路线,不属于传统 D3D register/map 接口。
OpenGL/Direct3D 注册映射流程
graphics API creates buffer/image
↓ register once with CUDA
frame loop:
graphics finished resource
↓ map CUDA graphics resource
CUDA gets device pointer or cudaArray
↓ kernel/read/write
unmap resource
↓ graphics API consumes resource
shutdown:
unregister before graphics object destruction资源在 CUDA 映射期间又被 OpenGL/Direct3D 或另一个 context 访问,会产生未定义结果。Register/unregister 通常不应每帧重复。
Buffer 常映射成 device pointer;texture/renderbuffer 常映射成 CUDA array,并通过 texture/surface object 访问。格式、通道数、写权限和设备匹配都受 API 限制。
External Memory + Semaphore
现代 Vulkan/D3D12 互操作将存储和顺序分开导入:
Graphics queue CUDA stream
render/write image
signal semaphore G→C ───────────────→ wait external semaphore
CUDA kernel uses imported memory
wait semaphore C→G ←─────────────── signal external semaphore
sample/present image步骤通常是:
- 外部 API 以可导出方式创建 memory/resource 和 semaphore;
- 导出 OS-specific handle;
- CUDA 导入 external memory 和 external semaphore;
- 从 external memory 映射 buffer 或 mipmapped array;
- 在 CUDA stream 中 wait/signal semaphore;
- 所有使用完成后按正确所有权顺序销毁。
Memory handle 只共享存储,semaphore 才建立跨 API 顺序。不能用 cudaDeviceSynchronize 猜测 graphics queue 的状态。
Handle 与所有权陷阱
- Linux file descriptor 与 Windows handle 的 ownership/duplication 规则不同;
- 某些导入 API 接管 OS handle,另一些要求调用者关闭,必须查对应函数说明;
- 导入的 CUDA 对象销毁不一定销毁原 graphics allocation;
- 外部资源的 size、offset、format、dedicated allocation 标志必须完全匹配;
- timeline/binary semaphore 的 value 语义不同;
- 多 GPU 系统需确认 graphics device 与 CUDA device 对应同一物理 adapter/UUID。
GPUDirect 的位置
GPUDirect RDMA/Storage 让 NIC 或存储设备更直接地与 GPU memory 交换数据,减少 CPU staging。它们涉及额外驱动、拓扑、IOMMU、安全和第三方 API,不等于普通 CUDA P2P。
传统网络:NIC ↔ host buffer ↔ cudaMemcpy ↔ GPU
GPUDirect: NIC ───────── DMA / supported path ────────↔ GPU memory在应用层优先使用已集成 GPUDirect 的 MPI、UCX、NCCL、存储库等;只有开发基础设施时才直接处理注册、pinning 和 completion 协议。
Lazy Loading、环境与错误日志
系统部署还应关注:
- CUDA Lazy Loading 延后 module/kernel 加载,减少启动占用但移动首次使用延迟;
CUDA_MODULE_LOADING、JIT cache、visible devices 等环境变量会改变行为;- Driver/Runtime 的错误日志能力可提供比单个返回码更完整的异步诊断;
- 环境变量应进入运行日志,但不要输出敏感路径或凭据;
- 生产环境不要长期依赖
CUDA_LAUNCH_BLOCKING=1,它会改变并发与性能。
调试开关用于定位,不应成为修复竞态的方式。
Error Log Management
普通错误码经常只说明 invalid argument,Error Log Management 会补充哪个参数违反了什么限制。可设置 CUDA_LOG_FILE=stdout|stderr|path,也可用 Runtime/Driver API 注册 callback 或把内部 ring buffer dump 到 memory/file。
cudaLogIterator cursor{};
CUDA_CHECK(cudaLogsCurrent(&cursor, 0));
// 执行需要诊断的一组 CUDA API;每个返回码仍要正常检查。
char log_buffer[25600];
std::size_t size = sizeof(log_buffer);
CUDA_CHECK(cudaLogsDumpToMemory(&cursor, log_buffer, &size, 0));
if (size != 0) std::fwrite(log_buffer, 1, size, stderr);日志是诊断补充,不代替返回码处理。内部只保留有限条目,且不是所有 API 都能生成详细消息;callback 的调用线程/时机也不能凭假设使用。
Driver Entry Point Access
插件或兼容层可通过 cuGetProcAddress 或 Runtime 的 cudaGetDriverEntryPointByVersion 获取 Driver API 函数指针,类似 dlsym/GetProcAddress,但额外处理 CUDA ABI 版本和 default-stream 语义。
#include <cudaTypedefs.h>
PFN_cuMemAllocAsync_v11020 fn = nullptr;
CUdriverProcAddressQueryResult symbol_status{};
CU_CHECK(cuGetProcAddress(
"cuMemAllocAsync",
reinterpret_cast<void**>(&fn),
11020, // 与 typedef 的 ABI 版本严格一致
CU_GET_PROC_ADDRESS_DEFAULT,
&symbol_status));不要把运行时查询到的最新 Driver version 随手作为 ABI 参数,否则未来同名 API 出现新签名时可能取得与旧 typedef 不匹配的符号。需要 per-thread default stream 语义时,也必须请求对应 flag/variant。无版本的 cudaGetDriverEntryPoint 已在 CUDA 13.0 标记弃用,新代码使用 ByVersion 接口。
平台差异
| 维度 | 需要核对 |
|---|---|
| Linux/Windows | Driver 模型、编译器、外部 handle、图形 API、watchdog |
| WDDM/TCC | 调度、超时、显示与计算模式、工具支持 |
| x86/Arm | host compiler、system memory/HMM、页大小、NUMA |
| 离散/集成 GPU | PCIe vs C2C、coherency、memory placement |
| Bare metal/container | host Driver 挂载、device nodes、权限、fabric daemon |
| MIG/MPS | 可见资源、P2P/cluster/工具约束、故障域 |
任何“在我的 GPU 上可用”的结论,都必须加上平台和 Driver/Toolkit 条件。
系统设计决策树
多个工作负载共享 GPU?
├─ 强隔离/多租户 → 评估 MIG + 调度器
├─ 多进程小任务 → 评估 MPS
└─ 单进程延迟分区 → Green Context / stream priority
跨 GPU 数据?
├─ 标准 collective → NCCL
├─ GPU 发起 PGAS → NVSHMEM
├─ 指定 allocation 映射 → VMM/IPC
└─ 通信库级可恢复操作 → Compute Fabric Transport
跨 graphics/compute API?
├─ OpenGL/D3D11 简单资源 → graphics register/map
└─ Vulkan/D3D12/显式同步 → external memory + semaphore系统级验证清单
- 查询 feature,不按型号猜测;
- 记录 physical GPU UUID、MIG instance、NUMA、fabric/clique 信息;
- 压测并发租户下 P50/P99,而非只测单任务;
- 注入进程退出、通信失败、handle 导入失败和 GPU reset;
- 验证所有外部资源的 acquire/release 与销毁顺序;
- 用 Systems 时间线同时观察 CUDA、graphics、CPU、通信;
- 检查权限和隔离是否满足威胁模型;
- 保留无 Green Context、无 fabric、单 GPU 等 fallback。
练习
- 用两个 stream 模拟前台/后台任务,再用 Green Context 分区,比较吞吐与 P99 延迟。
- 查询 locality domains,把 memory pool 与 compute resource 放在同域/异域并比较带宽。
- 画出 Vulkan image → CUDA kernel → Vulkan present 的 semaphore value 时间线,标注 ownership。
- 比较 NCCL、VMM mapped peer memory 与 Compute Fabric Transport 的抽象和失败语义,说明何时不应自建通信协议。
官方参考
- CUDA Programming Guide: Green Contexts
- CUDA Programming Guide: Locality Domains
- CUDA Programming Guide: Compute Fabric Transport
- CUDA Programming Guide: Extended GPU Memory
- CUDA Programming Guide: CUDA Interoperability with APIs
- CUDA Programming Guide: Error Log Management
- CUDA Programming Guide: Driver Entry Point Access
- CUDA Programming Guide: Multi-GPU Systems
- CUDA Multi-Process Service
- NVIDIA Multi-Instance GPU User Guide