GPU基础:GPU架构、CUDA/ROCm基础与GPU虚拟化技术概述

核心原理:GPU的并行计算架构

GPU(图形处理器)最初为图形渲染设计,其本质是一个大规模并行计算引擎。与CPU追求低延迟、复杂控制逻辑不同,GPU将大量晶体管用于算术逻辑单元(ALU)和寄存器,采用“单指令多线程”(SIMT)的执行模型。理解GPU架构是掌握容器化GPU资源分配的前提,因为资源隔离的粒度、性能损耗的根源都源于硬件结构。

以NVIDIA Ampere架构为例,其硬件层级自上而下为:

  • GPU芯片(Die):包含多个图形处理簇(GPC),每个GPC内含多个流式多处理器(SM)。
  • SM(Streaming Multiprocessor):核心计算单元,包含CUDA核心、Tensor Core、共享内存(Shared Memory)、寄存器堆和调度器。
  • CUDA核心:执行浮点/整数运算的最小单元,一个SM通常包含128个CUDA核心。
  • 显存(VRAM):通过高速总线与SM连接,容量和带宽决定数据吞吐上限。

AMD的CDNA架构(用于Instinct系列)在逻辑上类似,但将计算单元(CU)作为SM的对应物,每个CU包含多个流处理器(Stream Processor)。关键差异在于软件栈:NVIDIA使用CUDA,AMD使用ROCm(Radeon Open Compute)。

GPU硬件层级与软件栈映射 GPU 芯片(物理设备) GPC 0(图形处理簇) SM 0 CUDA×16 CUDA×16 Tensor Tensor SM 1 CUDA×16 CUDA×16 Tensor Tensor GPC 1(图形处理簇) SM 2 CUDA×16 CUDA×16 Tensor Tensor SM 3 CUDA×16 CUDA×16 Tensor Tensor 显存(HBM2e / GDDR6)— 容量与带宽 L2 缓存 → 内存控制器 → 物理显存颗粒 软件栈(容器视角) 容器(Container) CUDA 11.8 / ROCm 5.6 运行时 用户态驱动(libcuda.so / libhsa-runtime) API 调用 → 上下文管理 内核态驱动(nvidia.ko / amdgpu.ko) 显存管理、命令提交、中断处理 PCIe / NVLink 总线 DMA 引擎、BAR 映射 容器通过驱动栈访问GPU硬件 虚拟化层位于用户态与内核态之间

CUDA与ROCm:异构计算软件栈

容器化GPU资源分配的核心在于软件栈的隔离与透传。CUDA和ROCm都遵循“运行时库 + 驱动”的分层模型,但实现细节差异显著。

层次NVIDIA CUDAAMD ROCm
编程语言CUDA C/C++(NVCC编译器)HIP C++(可移植到CUDA)
运行时APIlibcudart.so(cudaMalloc, cudaMemcpy)libamdhip64.so(hipMalloc, hipMemcpy)
驱动接口libcuda.so(CUDA Driver API)libhsa-runtime64.so(HSA Runtime)
内核模块nvidia.ko(专有)amdgpu.ko(开源)+ ROCk
容器支持NVIDIA Container ToolkitROCm Docker / AMDToolkit

在容器场景中,宿主机只需安装内核态驱动,而容器内可携带用户态运行时。例如,宿主机安装515.76驱动,容器内可运行CUDA 11.8或12.0的镜像,只要用户态库与内核驱动版本兼容(通常要求驱动版本≥运行时要求的最低版本)。

GPU虚拟化技术:从直通到MIG

GPU虚拟化经历了从“整卡直通”到“细粒度切片”的演进,容器化资源分配正是建立在以下三种技术之上:

  1. API重定向(API Forwarding):如NVIDIA vGPU的前身,在宿主机拦截CUDA调用并转发到物理GPU。容器内无需物理GPU驱动,但性能损耗约10%-20%。
  2. 整卡直通(Passthrough):通过PCIe直通将整块GPU分配给单个容器/虚拟机。性能接近裸机,但无法切分,且容器迁移时GPU必须随迁。
  3. 硬件级切片(MIG / SR-IOV):NVIDIA A100/H100支持多实例GPU(MIG),可将物理GPU划分为最多7个独立实例,每个实例拥有专属SM、显存带宽和L2缓存。AMD的MxGPU(基于SR-IOV)提供类似能力。
关键概念:MIG的隔离是硬件级的——不同实例的SM和显存物理隔离,故障互不影响。而软件级隔离(如CUDA MPS)仅做时间片调度,一个容器的非法内存访问可能导致整个GPU崩溃。

案例:容器中分配MIG设备

假设宿主机有一块A100 40GB GPU,需创建两个容器分别获得20GB和10GB的MIG切片。操作流程如下:

# 1. 在宿主机启用MIG并创建实例(需root权限)
nvidia-smi mig -cgi 19,19,9,9,9 -C
# 解释:19代表20GB切片(对应1g.5gb),9代表10GB切片(对应1g.5gb+)

# 2. 查看生成的MIG设备UUID
nvidia-smi -L
# MIG 3g.20gb: GPU-4a2b1c3d...

# 3. 启动容器并指定MIG设备
docker run --rm --gpus '"device=GPU-4a2b1c3d"' \
  -v /data:/data nvcr.io/nvidia/cuda:12.0-base \
  nvidia-smi -L
# 容器内仅看到该MIG实例,显存20GB,SM数受限

对于ROCm,AMD提供了类似工具:

# AMD GPU拓扑导出与设备选择
rocm-smi --showtopo
export HIP_VISIBLE_DEVICES=0,1  # 容器内只暴露GPU 0和1

误区与复盘:容器内nvidia-smi的“假象”

常见误区:在容器内执行 nvidia-smi 看到显存总量为整卡容量,就认为容器独占了整张GPU。实际上,默认情况下容器共享宿主机的GPU上下文,多个容器可同时向GPU提交任务,显存和SM资源是抢占式共享的。

复盘一个真实故障:某团队在Kubernetes中部署两个推理容器,均未设置显存上限。容器A申请了2GB显存,但实际推理峰值需要6GB;容器B同样未限制。当两者同时运行时,GPU显存溢出(OOM),导致两个容器同时被内核杀死。根因是未使用MIG或CUDA MPS进行资源隔离,仅依赖--gpus参数只能实现“设备可见性”而非“资源配额”。

正确的做法是:

  • 使用MIG为每个容器分配固定显存切片(硬件隔离);
  • 或使用CUDA MPS设置显存上限(CUDA_MPS_PIPE_DIRECTORY + CUDA_MPS_ACTIVE_THREAD_PERCENTAGE);
  • 在Kubernetes中通过nvidia.com/gpu.memory和nvidia.com/gpu.sm资源字段声明需求(需配合Device Plugin)。

理解GPU架构的层级关系(SM→GPC→芯片)是理解MIG切片粒度的基础——MIG的最小切片单位是1个GPC内的SM子集,而非任意数量的CUDA核心。这解释了为何MIG实例的规格是离散的(如1g.5gb、2g.10gb、3g.20gb),而非连续可调。