GPU 资源动态分配:从静态切分到时分复用的 AI 集群容量规划实战

📅 2026/7/1 4:05:01
GPU 资源动态分配:从静态切分到时分复用的 AI 集群容量规划实战
GPU 资源动态分配从静态切分到时分复用的 AI 集群容量规划实战一、GPU 空闲率与排队等待AI 集群资源调度的双重困境在 AI 基础设施中GPU 是最昂贵且最稀缺的资源。一张 A100 显卡的月租成本在万元级别但通过监控发现生产集群中 GPU 的平均利用率通常只有 40%-60%——大量时间 GPU 处于空闲或低负载状态。这种低利用率的核心原因是静态分配模式每个训练任务或推理服务独占一张或多张 GPU即使任务在数据加载阶段 GPU 利用率接近 0%其他任务也无法使用这些空闲的 GPU。与 GPU 空闲并存的是排队等待问题。当集群中所有 GPU 都被分配完毕后新提交的任务只能排队等待。通过统计发现在 8 卡 A100 集群中训练任务的平均排队等待时间为 15-45 分钟推理服务的扩容等待时间为 3-5 分钟。对于需要快速迭代的算法团队排队等待直接拖慢了模型迭代速度。容量规划视角的核心矛盾是GPU 静态分配保证资源独占和性能隔离但利用率低下动态分配提升利用率但引入了资源争抢和性能干扰。如何在保证训练任务和推理服务 SLA 的前提下最大化 GPU 资源的利用效率是 AI 集群调度的核心命题。二、GPU 资源分配的底层机制从 MPS 到 MIG 到时分复用flowchart TD subgraph 静态分配 A1[GPU 0] --|独占| B1[训练任务A] A2[GPU 1] --|独占| B2[训练任务B] A3[GPU 2] --|独占| B3[推理服务C] A4[GPU 3] --|空闲| B4[无任务] end subgraph MPS多进程服务 C1[GPU 0] --|共享| D1[任务A: 60%算力] C1 --|共享| D2[任务B: 40%算力] C1 --|问题| E1[无内存隔离,一方OOM全部崩溃] end subgraph MIG多实例GPU F1[A100 GPU] --|硬件级切分| G1[实例1: 4g.20gb] F1 --|硬件级切分| G2[实例2: 3g.40gb] F1 --|硬件级切分| G3[实例3: 1g.10gb] G1 --|硬件隔离| H1[推理服务A] G2 --|硬件隔离| H2[推理服务B] G3 --|硬件隔离| H3[轻量任务C] end subgraph 时分复用 I1[GPU 0] --|时间片1| J1[训练任务A] I1 --|时间片2| J2[推理服务B] I1 --|时间片3| J1 I1 --|上下文切换开销| K1[约5-10ms/次] end subgraph 动态调度 L1[任务提交] -- M1{调度器决策} M1 --|推理任务| N1[优先级高,立即分配] M1 --|训练任务| N2[可抢占,低峰执行] M1 --|资源不足| N3[排队等待/抢占低优先级任务] N1 -- O1[分配GPU实例] N2 -- O1 N3 -- O1 end静态分配最简单的 GPU 分配模式每个任务独占一张或多张 GPU。优点是性能隔离完美不存在资源争抢缺点是 GPU 利用率低空闲时间无法被其他任务利用。K8s 中通过nvidia.com/gpu: 1资源请求实现调度器保证一个 GPU 只分配给一个 Pod。MPSMulti-Process ServiceNVIDIA 提供的 GPU 共享方案允许多个 CUDA 进程共享同一张 GPU。MPS 通过共享 GPU 的计算资源SM和内存空间实现多个任务的并行执行。优点是计算资源利用率高致命缺陷是缺乏内存隔离——一个进程的内存越界会导致所有进程崩溃。因此 MPS 仅适用于同一团队内的可信任务不适合多租户环境。MIGMulti-Instance GPUA100/H100 支持的硬件级 GPU 切分技术。A100 80GB 可以切分为最多 7 个独立实例每个实例拥有独立的 SM、L2 Cache 和显存硬件级隔离确保一个实例的故障不影响其他实例。切分方案包括1g.10gb1/7 SM10GB 显存、2g.20gb、3g.40gb、4g.40gb、7g.80gb整卡。MIG 是目前多租户 GPU 共享的最佳方案但仅支持 A100/H100 等少数架构。时分复用Time-Slicing通过 NVIDIA 的 GPU 时间片轮转机制让多个任务交替使用 GPU。每个时间片约 5-10ms上下文切换开销约 5-10ms。优点是兼容所有 GPU 架构无需硬件支持缺点是上下文切换开销大且多个任务共享同一显存空间存在 OOM 风险。时分复用适合低负载的推理服务不适合计算密集型的训练任务。三、GPU 动态分配的生产级配置与调度代码MIG 实例动态配置#!/bin/bash # mig_config.sh # A100 GPU MIG实例动态配置脚本 GPU_ID0 # 场景1白天推理高峰 - 切分为多个小实例支撑多个推理服务 configure_inference_mode() { echo 配置推理模式7个1g.10gb实例 # 先清除现有MIG配置 nvidia-smi -i ${GPU_ID} --gpu-reset -m 0 2/dev/null # 启用MIG模式 nvidia-smi -i ${GPU_ID} -m 1 # 创建7个1g.10gb实例 for gi in $(seq 0 6); do nvidia-smi mig -i ${GPU_ID} --create-gpu-instance 1g.10gb \ -C 2/dev/null echo 已创建GPU实例 ${gi} done } # 场景2夜间训练 - 使用整卡最大化训练吞吐 configure_training_mode() { echo 配置训练模式整卡模式 # 清除MIG配置 nvidia-smi -i ${GPU_ID} --gpu-reset -m 0 2/dev/null # 禁用MIG模式 nvidia-smi -i ${GPU_ID} -m 0 echo 已切换为整卡模式 } # 场景3混合模式 - 2个推理实例 1个训练实例 configure_mixed_mode() { echo 配置混合模式2g.20gb 3g.40gb 2g.20gb nvidia-smi -i ${GPU_ID} --gpu-reset -m 0 2/dev/null nvidia-smi -i ${GPU_ID} -m 1 # 创建推理实例2个2g.20gb nvidia-smi mig -i ${GPU_ID} --create-gpu-instance 2g.20gb -C 2/dev/null nvidia-smi mig -i ${GPU_ID} --create-gpu-instance 2g.20gb -C 2/dev/null # 创建训练实例3g.40gb nvidia-smi mig -i ${GPU_ID} --create-gpu-instance 3g.40gb -C 2/dev/null echo 已创建混合模式实例 } # 根据当前时段自动切换 HOUR$(date %H) if [ ${HOUR} -ge 9 ] [ ${HOUR} -lt 21 ]; then configure_inference_mode elif [ ${HOUR} -ge 21 ] || [ ${HOUR} -lt 6 ]; then configure_training_mode else configure_mixed_mode fiK8s MIG 设备插件配置# nvidia-mig-device-plugin.yaml apiVersion: v1 kind: ConfigMap metadata: name: nvidia-mig-config namespace: kube-system data: config.yaml: | version: v1 flags: migStrategy: mixed # mixed模式同时暴露整卡和MIG实例 sharing: timeSlicing: resources: - name: nvidia.com/gpu replicas: 1 # 整卡不共享 - name: nvidia.com/mig-1g.10gb replicas: 1 # MIG实例不共享 - name: nvidia.com/mig-2g.20gb replicas: 1 - name: nvidia.com/mig-3g.40gb replicas: 1 --- # 推理服务Pod请求1个MIG小实例 apiVersion: apps/v1 kind: Deployment metadata: name: llm-inference-small spec: replicas: 4 template: spec: containers: - name: inference image: llm-server:v2.1 resources: limits: nvidia.com/mig-1g.10gb: 1 # 请求1个1g.10gb实例 requests: nvidia.com/mig-1g.10gb: 1 --- # 训练任务Pod请求整卡 apiVersion: batch/v1 kind: Job metadata: name: model-training spec: template: spec: containers: - name: training image: training-runner:v1.0 resources: limits: nvidia.com/gpu: 1 # 请求整卡 requests: nvidia.com/gpu: 1 restartPolicy: OnFailureGPU 资源调度器# gpu_scheduler.py import time import threading from dataclasses import dataclass, field from enum import Enum from typing import List, Optional, Dict from collections import defaultdict class TaskType(Enum): INFERENCE inference # 推理任务优先级高不可抢占 TRAINING training # 训练任务优先级低可被抢占 FINETUNING finetuning # 微调任务优先级中 dataclass class GPUTask: GPU任务描述 task_id: str task_type: TaskType gpu_memory_mb: int # 所需显存MB gpu_compute_pct: float # 所需计算资源百分比 priority: int # 优先级数值越小越高 submit_time: float field(default_factorytime.time) can_preempt: bool False # 是否可被抢占 max_wait_sec: int 300 # 最大等待时间秒 dataclass class GPUDevice: GPU设备状态 device_id: int total_memory_mb: int used_memory_mb: int 0 compute_utilization: float 0.0 running_tasks: List[str] field(default_factorylist) mig_enabled: bool False mig_instances: Dict[str, dict] field(default_factorydict) class GPUScheduler: GPU资源动态调度器 核心策略推理任务优先 训练任务可抢占 MIG实例按需分配 def __init__(self, devices: List[GPUDevice]): self.devices {d.device_id: d for d in devices} self.wait_queue: List[GPUTask] [] self.lock threading.Lock() def submit_task(self, task: GPUTask) - Optional[int]: 提交GPU任务尝试立即分配或加入等待队列 返回分配的GPU设备ID或None表示排队中 with self.lock: # 推理任务不可抢占直接寻找空闲资源 if task.task_type TaskType.INFERENCE: device_id self._find_available_device(task) if device_id is not None: self._allocate(device_id, task) return device_id # 无空闲资源尝试抢占训练任务 device_id self._preempt_for_inference(task) if device_id is not None: self._allocate(device_id, task) return device_id # 训练/微调任务寻找空闲资源或排队 device_id self._find_available_device(task) if device_id is not None: self._allocate(device_id, task) return device_id # 资源不足加入等待队列 self.wait_queue.append(task) self.wait_queue.sort(keylambda t: (t.priority, t.submit_time)) return None def _find_available_device(self, task: GPUTask) - Optional[int]: 寻找有足够资源的GPU设备 for device_id, device in self.devices.items(): available_memory device.total_memory_mb - device.used_memory_mb if (available_memory task.gpu_memory_mb and device.compute_utilization task.gpu_compute_pct 100): return device_id return None def _preempt_for_inference(self, task: GPUTask) - Optional[int]: 为推理任务抢占训练任务的GPU资源 策略选择计算利用率最低的训练任务所在GPU best_device None best_util float(inf) for device_id, device in self.devices.items(): # 检查该设备上是否有可抢占的训练任务 has_preemptible any( task_id.startswith(training_) for task_id in device.running_tasks ) if has_preemptible and device.compute_utilization best_util: best_device device_id best_util device.compute_utilization if best_device is not None: # 执行抢占移除训练任务释放资源 device self.devices[best_device] preempted [ t for t in device.running_tasks if t.startswith(training_) ] for task_id in preempted: device.running_tasks.remove(task_id) device.used_memory_mb max(0, device.used_memory_mb - 0) device.compute_utilization max(0, device.compute_utilization - 50) print(f[PREEMPT] 抢占训练任务 {task_id} f释放GPU {best_device}) return best_device def _allocate(self, device_id: int, task: GPUTask): 分配GPU资源给任务 device self.devices[device_id] device.used_memory_mb task.gpu_memory_mb device.compute_utilization task.gpu_compute_pct device.running_tasks.append(task.task_id) print(f[ALLOCATE] 任务 {task.task_id} 分配到GPU {device_id} f显存: {task.gpu_memory_mb}MB计算: {task.gpu_compute_pct}%) def release(self, device_id: int, task: GPUTask): 释放GPU资源 with self.lock: device self.devices[device_id] device.used_memory_mb max(0, device.used_memory_mb - task.gpu_memory_mb) device.compute_utilization max(0, device.compute_utilization - task.gpu_compute_pct) if task.task_id in device.running_tasks: device.running_tasks.remove(task.task_id) print(f[RELEASE] 任务 {task.task_id} 释放GPU {device_id}) # 尝试调度等待队列中的任务 self._schedule_waiting() def _schedule_waiting(self): 调度等待队列中的任务 scheduled [] for task in self.wait_queue: device_id self._find_available_device(task) if device_id is not None: self._allocate(device_id, task) scheduled.append(task) for task in scheduled: self.wait_queue.remove(task) def get_cluster_status(self) - dict: 获取集群GPU资源状态 total_memory sum(d.total_memory_mb for d in self.devices.values()) used_memory sum(d.used_memory_mb for d in self.devices.values()) avg_util (sum(d.compute_utilization for d in self.devices.values()) / len(self.devices)) return { total_gpus: len(self.devices), total_memory_mb: total_memory, used_memory_mb: used_memory, memory_utilization: used_memory / total_memory * 100, avg_compute_utilization: avg_util, waiting_tasks: len(self.wait_queue) }四、GPU 动态分配的代价与适用边界GPU 动态分配在提升资源利用率的同时引入了若干不可忽视的架构代价。MIG 切分的粒度限制。MIG 的切分方案是固定的1g.10gb、2g.20gb、3g.40gb、4g.40gb无法按需任意切分。如果推理服务需要 15GB 显存只能分配 2g.20gb 实例20GB浪费 5GB。更严重的是MIG 切分需要重启 GPU切换时间约 10-30 秒不适合频繁切换的场景。这意味着 MIG 配置通常是静态的如白天推理模式、夜间训练模式无法做到按需实时切分。时分复用的性能干扰。多个任务通过时间片共享 GPU 时上下文切换开销约 5-10ms/次。在推理延迟敏感的场景下这个开销可能占 P99 延迟预算的 5%-10%。更严重的是一个计算密集型任务如训练会显著拖慢同一 GPU 上推理任务的响应时间。通过压测验证训练任务和推理任务共享 GPU 时推理 P99 延迟增加 50%-200%。因此时分复用仅适合多个轻量推理任务之间的共享不适合训练与推理的混合部署。抢占的检查点开销。训练任务被抢占时需要保存模型检查点Checkpoint到磁盘以便恢复训练。70B 模型的检查点大小约 140GBFP16保存到 SSD 约需 30-60 秒。频繁抢占会导致大量时间花在检查点保存/加载上反而降低训练效率。因此抢占策略应设置最小运行时间如至少运行 30 分钟后才可被抢占避免频繁抢占。适用边界MIG 适合 A100/H100 集群中多推理服务的共享部署硬件级隔离保证性能不受干扰。时分复用适合低负载推理服务的轻量共享不适合训练任务。抢占式调度适合训练任务与推理任务的混合集群但需设置最小运行时间和检查点机制。对于纯训练集群静态分配仍是最简单可靠的方案。五、总结GPU 资源动态分配是 AI 基础设施降本增效的核心手段但必须在资源利用率、性能隔离和调度延迟之间精细权衡。核心落地建议如下分配策略选型A100/H100 集群优先使用 MIG 切分硬件级隔离保证多推理服务互不干扰。非 MIG 架构使用时分复用但仅限轻量推理任务间共享。MIG 配置策略按时段动态切换 MIG 配置——推理高峰切分为小实例训练低峰使用整卡。切换间隔不低于 1 小时避免频繁切换的开销。抢占式调度推理任务优先级高于训练任务资源不足时可抢占训练任务的 GPU。训练任务需设置最小运行时间30 分钟和自动检查点机制。容量规划GPU 集群利用率目标设为 70%-80%而非 100%预留缓冲应对突发需求。推理服务的基础 GPU 数量按峰值 QPS 的 80% 规划弹性部分通过 MIG 动态分配覆盖。监控与调优部署 GPU 利用率、显存占用和任务排队时间的监控面板利用率持续低于 50% 时考虑缩容排队时间持续超过 10 分钟时考虑扩容。