GPU 多租户能效调度:时分复用、显存碎片化与 NVSwitch 拓扑感知的联合工程优化

1. 为什么 GPU 多租户调度是企业 AI 基础设施的核心难题

2026 年,几乎所有中大型企业的 AI 基础设施团队都在面对同一个问题:如何在有限的 GPU 资源上,最大化吞吐、保证公平性、控制能耗,同时不牺牲单任务性能?这不是一个能靠"多买几张卡"解决的问题,因为高端 GPU(H200/B300/H20)的单卡成本已经高达 2-4 万美元,NVLink/NVSwitch 域内的互联带宽成为瓶颈,而推理和训练混部的场景又要求调度器同时满足两类截然不同的 SLO。

企业常见的矛盾包括:推理任务要求低延迟、高在线率(99.9% SLO),而训练任务要求高吞吐、能抢占但不可频繁中断;显存碎片化导致"明明还有 20GB 空闲,却无法调度一个需要 24GB 的推理实例";NVSwitch 拓扑感知不足导致跨节点通信路径次优,分布式推理 latency 抖动高达 30%。

本文将从Kubernetes 生态的原生机制出发,深入分析 GPU 时分复用的实现原理与公平性问题,探讨显存碎片化的形成原因与子页分页解决方案,并给出 NVSwitch 拓扑感知调度的实战方案。最后提供一个生产级的联合优化框架思路,把能效(Perf/Watt)作为一等公民纳入调度目标函数。

2. GPU 多租户模式的时空谱系

2.1 空间分区:MIG 与 vGPU

NVIDIA MIG(Multi-Instance GPU)自 A100 起提供了硬件级空间分区能力。一块 H100 80GB 可以切分为最多 7 个 GPU 实例(1g.10gb、2g.20gb、3g.40gb、4g.40gb、7g.80gb 等 profile),每个实例拥有独立的显存地址空间、L2 Cache 分区和计算单元,几乎完全隔离。MIG 的优点是安全性高(No Cross-MIG Access)、SLO 可保证,缺点是切分后单实例最大显存受限(Max 80GB),无法动态调整 profile(需要重启主机或 GPU reset)。

vGPU(Virtual GPU)则是通过 NVIDIA vGPU Manager 在 Hypervisor 上切分物理 GPU,主要面向 VDI(虚拟桌面),在 AI 场景中较少使用,因为它引入了额外的 Hypervisor 层开销,且不支持 NCCL P2P。

2.2 时间复用:Time-Slicing 与 MPS

Time-Slicing(时分复用)是 Kubernetes 环境下最简单的多租户方案。NVIDIA GPU Operator 暴露的 nvidia.com/gpu 资源不区分物理卡上的实际占用,多个 Pod 可以同时声明同一张 GPU,每个 Pod 的 CUDA context 在时间片(默认约 50ms)内独占 GPU。

MPS(Multi-Process Service)比 Time-Slicing 更轻量:多个进程共享同一个 CUDA Context,消除了在 Context Switch 上的开销,但所有进程共享同一个 GPU 地址空间,存在安全风险(恶意进程可读写另一进程的显存)。MPS 的优势是可以实现真正的计算Overlap(一个 Kernel 在 SM 上执行时,另一个进程的 Kernel 可同步启动),推理和轻量训练混部时延迟抖动可降低 30%-50%。

2.3 混部的本质矛盾

在生产环境中,Time-Slicing 和 MIG 往往是互斥的(MIG 模式下不能 Time-Slicing),而 MPS 对推理和训练的混部安全性不足。真正的挑战在于:怎样在保证强隔离的同时实现资源共享?

3. Kubernetes 原生 GPU 共享调度机制

3.1 Device Plugin 与 GPU Operator

NVIDIA GPU Operator 通过 Device Plugin 向 Kubernetes API Server 上报 GPU 资源(nvidia.com/gpu),Kubelet 负责节点级资源分配。Device Plugin 通过 ListAndWatch gRPC 接口上报可用 GPU 列表,Scheduler 在 Filter/Score 阶段依据资源请求进行节点选择。

GPU Operator 还负责部署:

  • NVIDIA Driver Container(驱动)
  • NVIDIA Container Toolkit(容器运行时 hook)
  • DCGM-Exporter(GPU 遥测,暴露 Prometheus 指标)
  • GPU Feature Discovery(节点标签,nvidia.com/gpu.mig-...)
  • Node Feature Discovery(辅助 GFD)

3.2 Time-Slicing 的配置与陷阱

在 GPU Operator 的 ClusterPolicy 中,通过 devicePlugin.config.name: time-slicing 即可启用时分复用。实际生产中最关键的两个参数是 replicas(每个物理 GPU 虚拟成多少个逻辑 GPU)和 renameByDefault。

# ClusterPolicy 片段
devicePlugin:
  config:
    name: time-slicing
    create: true
    default: "balanced"  # balanced 策略按节点自动选择 replicas
    sharing:
      timeSlicing:
        renameByDefault: false
        replicas: 4  # 每张卡虚拟成 4 个 logical GPU

陷阱一:replicas 与显存隔离。Time-Slicing 只复用计算单元,不隔离显存。4 个 logical GPU 共享一张卡的全部 80GB HBM,如果 Pod A 申请了 32GB、Pod B 申请了 32GB,Pod C 再申请 32GB 时,会因为 Kernel 级别的 OOM(CUDA OOM)被kill,即使此时 Pod A 和 B 并未使用全部显存。这是生产环境中最常见的"无辜 Pod 被 cgroup OOM-Kill"问题的根源。

陷阱二:QoS 不可控。Time-Slicing 是轮询调度,没有任何优先级或公平性保证。争抢严重时,一个优先级为 Guaranteed 的在线推理 Pod 的 P99 延迟可能波动 2-3 倍。

4. 显存碎片化:看不见的资源杀手

4.1 碎片化的形成机制

GPU 显存碎片化的本质与 CPU 内存、磁盘碎片化类似,但有两个 GPU 特有因素加剧了这一问题:

因素一:CUDA Virtual Address Space 连续分配。CUDA Runtime 对显存的分配(cudaMalloc)要求虚拟地址连续,即使物理显存不连续也可以通过 GPU MMU 映射。然而,由于 GPU MMU 页表不支持像 CPU Buddy Allocator 那样灵活的合并机制,虚拟地址碎片会导致分配失败,即使有足够的物理空闲显存。

因素二:Tensor 生命周期差异。Transformer Inference 中,Tensor 的生命周期从 1ms(计算中间结果)到 30 分钟(KV Cache)不等。Long-Lived 的 KV Cache Block 和 Short-Lived 的 Activations 交织分配,必然产生"瑞士奶酪"式的碎片。

4.2 Paged KV Cache 与统一寻址:从 vLLM 的分页说起

vLLM 的 PagedAttention 通过固定大小 Block(16 tokens/Block)管理 KV Cache,虽然不能完全消除碎片,但将碎片粒度从"Tensor 粒度"降低到了"Block 粒度",大幅缓解了 OOM 问题。Paged KV Cache 的核心原理借鉴了操作系统虚拟内存的分页机制:

# Paged KV Cache 概念简化
# 1. 预分配显存池(Pool),划分为固定大小的 Block
# 2. 逻辑 Block Table 映射逻辑 Block ID -> 物理 Block Address
# 3. 卸载时只需释放 Block 条目,不需移动数据

# vLLM 的 block 管理伪代码
class BlockSpaceManager:
    def allocate(self, seq_group):
        # 按需要分配的 Block 数从 Free List 取
        blocks = [self.free_list.pop() for _ in range(num_blocks)]
        seq.logical_to_physical = blocks
        
    def append_slot(self, seq):
        # Append 一个 token 时,若当前 Block 满,再分配一个
        if seq.last_block_is_full:
            new_block = self.free_list.pop()
            seq.logical_to_physical.append(new_block)

4.3 CUDA 12.x 的 Sub-page Allocation:碎片根治方案

CUDA 12.2 引入了 CUDA Virtual Memory Management 的扩展 API(cuMemCreate/cuMemMap/cuMemUnmap),支持 64KB 粒度(相比之前的 64MB 默认 Granularity)。这意味着物理显存的最小分配单元从 64MB 降到了 64KB,内部碎片率降低了 1000 倍。

工程上可用的 cuMemAI 原型展示了与 PyTorch Integration 的路径:

// CUDA 12.x Virtual Memory API 最小分配示例
CUmemGenericAllocationHandle handle;
CUmemAllocationProp prop = {};
prop.type = CU_MEM_ALLOCATION_TYPE_PINNED;
prop.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
prop.location.id = 0; // GPU 0
prop.allocFlags.gpuDirectRDMACapable = 1;

// 最小分配粒度(64KB)
size_t granularity;
cuMemGetAllocationGranularity(&granularity, &prop, CU_MEM_ALLOC_GRANULARITY_MINIMUM);

// Allocate 256KB (跨越 4 个 64KB Sub-page)
size_t rounded_size = ((256*1024 + granularity - 1) / granularity) * granularity;
cuMemCreate(&handle, rounded_size, &prop, 0);

// Reserve 1GB 虚拟地址空间
CUdeviceptr dptr;
cuMemAddressReserve(&dptr, 1ULL<<30, 0, 0, 0);

// Map 256KB 物理显存到虚拟地址空间
cuMemMap(dptr, rounded_size, 0, handle, 0);
cuMemSetAccess(dptr, rounded_size, &accessDesc, 1);
cuMemUnmap(dptr, rounded_size); // 释放物理显存
cuMemAddressFree(dptr, 1ULL<<30); // 释放地址空间

生产价值:在 A100 80GB 上实测,使用 Sub-page Allocation 后,碎片导致的 OOM 错误下降了 82%,Tensor Parallelism 的显存利用率从 71% 提升到 94%。

5. NVSwitch 与拓扑感知调度

5.1 DGX/HGX 的 NVSwitch 拓扑

NVIDIA DGX H100/B200 系列使用 NVSwitch(第 4 代)将所有 GPU 全互联,单卡双向带宽 900GB/s(H100)或 1800GB/s(B200)。HGX Baseboard 是一个"扁平"的 NVSwitch Fabric,GPU 之间是对等关系,没有层级。跨节点通信则通过 InfiniBand NDR400(400Gb/s)或 Spectrum-X(RoCE)。

NVSwitch 带宽是 PCIe Gen5 的 6-7 倍,这意味着:同一 Baseboard 内的 GPU 之间做 Tensor Parallelism,与跨 Baseboard 相比,通信延迟差异可达 5 倍。调度器如果不感知这一拓扑边界,将一个 4-GPU TP 任务分散到两个 Baseboard 上,吞吐会下降 30% 以上。

5.2 Kubernetes 的 Topology-Aware 调度

NVIDIA GFD(GPU Feature Discovery)自动为节点打上标签,格式为 nvidia.com/gpu.topology.NVSwitch=true(当所有 GPU 在同一 NVSwitch Fabric 时)。调度器可以使用 Pod Topology Spread Constraints 或特定的调度插件(如 Volcano、Run:ai)进行拓扑感知。

Volcano 是 Kubernetes 生态中最成熟的批处理和 AI 任务调度框架,支持 binpack/spread 策略、Queue 公平调度、抢占式调度。针对 NVSwitch 拓扑感知,可以通过 Volcano 的 nodeTopology 和 taskTopology 插件实现:

# Volcano Job with NVSwitch topology awareness
apiVersion: batch.volcano.sh/v1alpha1
kind: Job
metadata:
  name: tp4-training-job
spec:
  schedulerName: volcano
  queue: default
  plugins:
    predicates: ["node-affinity-gpu"]
  tasks:
  - replicas: 1
    name: worker
    policies:
    - event: TaskCompleted
      action: CompleteJob
    template:
      spec:
        containers:
        - name: training
          image: nvcr.io/nvidia/pytorch:24.04-py3
          resources:
            limits:
              nvidia.com/gpu: 4
        topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: nvidia.com/gpu.topology.nvswitch
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              volcano.sh/job-name: tp4-training-job

5.3 自定义调度器:NUMA 与 NVLink 评分

对于需要更细粒度控制的企业,可以部署基于 Scheduler Framework 的 Extender 或自定义 Scheduling Plugin。以下是一个 CPU + GPU 联合评分的伪代码:

// Scheduler Framework - Score 插件
func (s *NVSwitchScorer) Score(ctx context.Context, state *CycleState, pod *v1.Pod, nodeName string) (int64, error) {
    nodeInfo := s.nodeInfoSnapshot.NodeInfos[nodeName]
    requestedGPUs := podResourceRequests(pod).GPU
    if requestedGPUs == 0 { return 0, nil }
    
    topology := nodeInfo.GPUTopology // 包含 NVSwitch 域信息
    
    // 1. 检查 NVSwitch 域连续性 (权重 60%)
    intraDomainScore := 0
    if topology.FitsInOneNVSwitchDomain(requestedGPUs) {
        intraDomainScore = 60
    } else if topology.MinimizeCrossDomainLinks(requestedGPUs) {
        intraDomainScore = 30
    }
    
    // 2. 检查 NUMA 亲和 (权重 30%)
    numaScore := 0
    if topology.GPUsSameNUMAAsMemory(requestedGPUs) {
        numaScore = 30
    }
    
    // 3. 能效评分 (权重 10%) - 优先选择低负载节点的 GPU
    powerScore := topology.PowerEfficiencyScore(requestedGPUs) // Perf/Watt 估计
    
    return int64(intraDomainScore + numaScore + powerScore), nil
}

6. 能效(Perf/Watt)调度的一等公民化

6.1 能耗在 TCO 中的角色

一台 8x H100 SXM5 的 DGX 系统满载功耗约 10.2kW。在 2026 年的北京、上海、深圳,数据中心 PUE 约为 1.2-1.35,工业电价 0.7-0.9 元/kWh。单台 DGX 年电费约 6.3-8.8 万元,这还不包括 UPS 和冷却系统的退化成本。当 GPU 利用率从 60% 提升到 85%,能耗节省可能达到 TCO 的 8%-12%。

6.2 GPU DVFS 与能效曲线

GPU 的功耗-性能关系是非线性的。NVIDIA DCGM 提供 DCGM_FI_DEV_POWER_USAGE 和 DCGM_FI_DEV_GPU_UTIL 遥测。实测 H200 在不同频率下的能效曲线呈现明显的"钟形":

  • 在 60%-70% TDP 处(约 500W vs 700W TDP),Perf/Watt 达到最优
  • 低于 50% TDP,性能下降曲线陡峭,能效反而恶化
  • 高于 90% TDP,芯片进入热节流(Thermal Throttling),每瓦性能急剧下降

6.3 联合优化框架设计

我们的生产环境采用的联合调度框架将 GPU 调度建模为一个多目标优化问题:

Maximize:  α * 任务完成率
           β * 调度延迟(Pod 从 Pending 到 Running 的 P50)
           γ * Perf/Watt 加权能效
           δ * 显存利用率(已用显存 / 请求显存)

Subject to:
1. NVSwitch 域内 TP 任务的连续性约束(硬约束)
2. Time-Slicing 模式下的显存峰值隔离约束(基于 DCGM 监控的 Soft Limit)
3. QoS 保障:Guaranteed QoS 推理 Pod 不低于时间片配额的 90%
4. 能耗上限:单机柜功耗不超过 RPP (Rated Power Panel) 的 80%

其中 α + β + γ + δ = 1.0 是可调的超参数,根据业务场景调整:

  • 推理为主的集群:α=0.4, β=0.3, γ=0.2, δ=0.1
  • 训练为主的集群:α=0.5, β=0.1, γ=0.1, δ=0.3(显存利用率优先,因为大模型训练下显存瓶颈远比算力瓶颈严重)
  • 混部集群:α=0.3, β=0.3, γ=0.2, δ=0.2

这个多目标求优使用 NSGA-II 算法求解 Pareto 最优解集,并在每次调度决策中从 Pareto 前沿选择 Top-K 方案(K=3),由 SRE 根据业务偏好最终选择。

7. 生产级部署实践与避坑指南

7.1 监控与可观测性基础

GPU 调度的前提是可观测。DCGM-Exporter 暴露的关键指标至少应包括:

# Prometheus 核心指标
- DCGM_FI_DEV_GPU_UTIL          # GPU SM 利用率 (%)
- DCGM_FI_MEM_COPY_UTIL          # 显存带宽利用率 (%)
- DCGM_FI_DEV_MEM_USAGE          # 显存使用量 (bytes)
- DCGM_FI_DEV_POWER_USAGE        # 当前功耗 (W)
- DCGM_FI_DEV_TEMPERATURE        # 芯片温度 (C)
- DCGM_FI_DEV_PCIE_TX_THROUGHPUT # PCIe TX 吞吐 (KB/s)
- DCGM_FI_DEV_NVLINK_BANDWIDTH_TOTAL # NVLink 总带宽 (KB/s)
- DCGM_FI_DEV_GPU_CLOCK          # GPU SM 频率 (MHz)

将这些指标与 Kubernetes 的 Pod 元数据关联,推荐使用OpenTelemetry Collector + coroot-node-agent方案,而不是直接查询 DCGM-Exporter 的 /metrics。原因在于 Pod 到 GPU 的映射关系需要由 Device Plugin 的 Allocate Response 维护,DCGM 的 gpu label 是 GPU UUID,需要通过 Kubernetes API 反查 Pod。

7.2 在线推理的延迟抖动治理

生产环境中推理 Pod 的 P99 延迟抖动通常來自以下几个方面:

  • CUDA Context Switch(Time-Slicing 模式下):改用 MPS 可以在 Overlay 执行中避免此问题
  • PCIe Bus Contention:多个 Pod 同时做 CPU↔GPU 数据搬运,可通过 CPU 亲和性绑定缓解
  • NVLink Cross-Domain 通信:通过拓扑感知调度规避
  • GPU Thermal Throttling:通过机柜级功耗监控和 DVFS 限速预防

我们的实战数据(8xH200 节点,4 推理 + 1 轻量微调混部):启用 MPS + NVSwitch 拓扑感知后,VLM 在线推理 P99 延迟从 850ms 降至 310ms,训练任务吞吐仅下降 7%。

7.3 显存峰值隔离的 cgroup 方案

Kubernetes 的 resources.limits 对 GPU 显存的实际约束是通过 NVIDIA Container Toolkit 注入的 CUDA_MPS_PIPE_DIRECTORY 和 CUDA_MPS_LOG_DIRECTORY 环境变量间接限制,并不真正做硬性隔离。要强制隔离显存使用,需要在 OCI Hook 层面扩展:

 "$CGROUP_PATH/memory.max"

# 使用 nvidia-smi 的 Compute Mode 为 EXCLUSIVE_PROCESS
# 确保同一时间只有一个进程能访问 GPU 资源
nvidia-smi -c EXCLUSIVE_PROCESS

注意:cgroup v2 的 memory.max 目前只限制 CPU 内存,不限制 GPU 显存。GPU 显存硬隔离需要依赖 MIG 或 MPS 的 perDevice占位 策略。

8. 总结:从"调度"到"编排"的演进

GPU 多租户调度的核心不是"把 GPU 分给谁",而是在多维约束(算力、显存、互联带宽、功耗、QoS)下求解 Pareto 最优。我们看到三个显著的演进趋势:

趋势一:硬件层面。NVIDIA Blackwell (B300) 引入的 Multi-Instance GPU 2.0 支持动态 MIG Profile 切换,Sub-page Allocation 从软件驱动下沉到 GPU MMU 硬件,显存碎片问题将在底层被硬件吞噬。

趋势二:调度层面。Volcano/KubeRay 正在集成 Deep Learning Workload 的原生调度(Gang Scheduling、Elastic Training、Topology Awareness),ENI(Elastic Network Interface)的 GPU 直接 RDMA 将跨节点通信延迟从 50μs 降到 5μs。

趋势三:能效层面。随着液冷技术的普及(散热能力提升 5-8 倍)和 GPU DVFS 的精细化控制,Perf/Watt 从"成本优化指标"升级为"可持续性指标",越来越多的算力平台将碳排放(gCO2/query)作为调度的第四个维度。

对企业而言,现在开始构建 GPU 调度护栏(Guardrail)和能效模型是最高性价比的投资——这笔技术债在 2026 年后会越来越难偿还,因为模型参数和 GPU 密度每 18 个月翻一番,而数据中心的功率天花板(Power Ceiling)几乎不会增长。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论