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 kernels

CUDA 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 stream

Green 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 node

EGM 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 status

VMM 映射绑定具体 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 InteropOpenGL、Direct3D 9–11注册资源,每帧 map→CUDA→unmap→graphics
External Resource InteropVulkan、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

步骤通常是:

  1. 外部 API 以可导出方式创建 memory/resource 和 semaphore;
  2. 导出 OS-specific handle;
  3. CUDA 导入 external memory 和 external semaphore;
  4. 从 external memory 映射 buffer 或 mipmapped array;
  5. 在 CUDA stream 中 wait/signal semaphore;
  6. 所有使用完成后按正确所有权顺序销毁。

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/WindowsDriver 模型、编译器、外部 handle、图形 API、watchdog
WDDM/TCC调度、超时、显示与计算模式、工具支持
x86/Armhost compiler、system memory/HMM、页大小、NUMA
离散/集成 GPUPCIe vs C2C、coherency、memory placement
Bare metal/containerhost 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。

练习

  1. 用两个 stream 模拟前台/后台任务,再用 Green Context 分区,比较吞吐与 P99 延迟。
  2. 查询 locality domains,把 memory pool 与 compute resource 放在同域/异域并比较带宽。
  3. 画出 Vulkan image → CUDA kernel → Vulkan present 的 semaphore value 时间线,标注 ownership。
  4. 比较 NCCL、VMM mapped peer memory 与 Compute Fabric Transport 的抽象和失败语义,说明何时不应自建通信协议。

官方参考