详情

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

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

佚名 2026-09-01 17:39:59

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 TDsubgraph 静态分配A1[GPU 0] -->|独占| B1[训练任务A]A2[GPU 1] -->|独占| B2[训练任务B]A3[GPU 2] -->|独占| B3[推理服务C]A4[GPU 3] -->|空闲| B4[无任务]endsubgraph MPS多进程服务C1[GPU 0] -->|共享| D1[任务A: 60%算力]C1 -->|共享| D2[任务B: 40%算力]C1 -->|问题| E1[无内存隔离,一方OOM全部崩溃]endsubgraph MIG多实例GPUF1[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]endsubgraph 时分复用I1[GPU 0] -->|时间片1| J1[训练任务A]I1 -->|时间片2| J2[推理服务B]I1 -->|时间片3| J1I1 -->|上下文切换开销| K1[约5-10ms/次]endsubgraph 动态调度L1[任务提交] --> M1{调度器决策}M1 -->|推理任务| N1[优先级高,立即分配]M1 -->|训练任务| N2[可抢占,低峰执行]M1 -->|资源不足| N3[排队等待/抢占低优先级任务]N1 --> O1[分配GPU实例]N2 --> O1N3 --> O1end

静态分配:最简单的 GPU 分配模式,每个任务独占一张或多张 GPU。优点是性能隔离完美,不存在资源争抢;缺点是 GPU 利用率低,空闲时间无法被其他任务利用。K8s 中通过 nvidia.com/gpu: 1 资源请求实现,调度器保证一个 GPU 只分配给一个 Pod。

MPS(Multi-Process Service):NVIDIA 提供的 GPU 共享方案,允许多个 CUDA 进程共享同一张 GPU。MPS 通过共享 GPU 的计算资源(SM)和内存空间,实现多个任务的并行执行。优点是计算资源利用率高;致命缺陷是缺乏内存隔离——一个进程的内存越界会导致所有进程崩溃。因此 MPS 仅适用于同一团队内的可信任务,不适合多租户环境。

MIG(Multi-Instance GPU):A100/H100 支持的硬件级 GPU 切分技术。A100 80GB 可以切分为最多 7 个独立实例,每个实例拥有独立的 SM、L2 Cache 和显存,硬件级隔离确保一个实例的故障不影响其他实例。切分方案包括:1g.10gb(1/7 SM,10GB 显存)、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_ID=0# 场景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); donvidia-smi mig -i ${GPU_ID} --create-gpu-instance 1g.10gb -C 2>/dev/nullecho "已创建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 0echo "已切换为整卡模式"}# 场景3:混合模式 - 2个推理实例 + 1个训练实例configure_mixed_mode() {echo "配置混合模式:2g.20gb + 3g.40gb + 2g.20gb"nvidia-smi -i ${GPU_ID} --gpu-reset -m 0 2>/dev/nullnvidia-smi -i ${GPU_ID} -m 1# 创建推理实例(2个2g.20gb)nvidia-smi mig -i ${GPU_ID} --create-gpu-instance 2g.20gb -C 2>/dev/nullnvidia-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/nullecho "已创建混合模式实例"}# 根据当前时段自动切换HOUR=$(date +%H)if [ ${HOUR} -ge 9 ] && [ ${HOUR} -lt 21 ]; thenconfigure_inference_modeelif [ ${HOUR} -ge 21 ] || [ ${HOUR} -lt 6 ]; thenconfigure_training_modeelseconfigure_mixed_modefi

K8s MIG 设备插件配置:

# nvidia-mig-device-plugin.yamlapiVersion: v1kind: ConfigMapmetadata:name: nvidia-mig-confignamespace: kube-systemdata:config.yaml: |version: v1flags:migStrategy: "mixed" # mixed模式:同时暴露整卡和MIG实例sharing:timeSlicing:resources:- name: nvidia.com/gpureplicas: 1 # 整卡不共享- name: nvidia.com/mig-1g.10gbreplicas: 1 # MIG实例不共享- name: nvidia.com/mig-2g.20gbreplicas: 1- name: nvidia.com/mig-3g.40gbreplicas: 1---# 推理服务Pod:请求1个MIG小实例apiVersion: apps/v1kind: Deploymentmetadata:name: llm-inference-smallspec:replicas: 4template:spec:containers:- name: inferenceimage: llm-server:v2.1resources:limits:nvidia.com/mig-1g.10gb: 1# 请求1个1g.10gb实例requests:nvidia.com/mig-1g.10gb: 1---# 训练任务Pod:请求整卡apiVersion: batch/v1kind: Jobmetadata:name: model-trainingspec:template:spec:containers:- name: trainingimage: training-runner:v1.0resources:limits:nvidia.com/gpu: 1# 请求整卡requests:nvidia.com/gpu: 1restartPolicy: OnFailure

GPU 资源调度器:

# gpu_scheduler.pyimport timeimport threadingfrom dataclasses import dataclass, fieldfrom enum import Enumfrom typing import List, Optional, Dictfrom collections import defaultdictclass TaskType(Enum):INFERENCE = "inference" # 推理任务:优先级高,不可抢占TRAINING = "training" # 训练任务:优先级低,可被抢占FINETUNING = "finetuning" # 微调任务:优先级中@dataclassclass GPUTask:"""GPU任务描述"""task_id: strtask_type: TaskTypegpu_memory_mb: int# 所需显存(MB)gpu_compute_pct: float# 所需计算资源百分比priority: int # 优先级:数值越小越高submit_time: float = field(default_factory=time.time)can_preempt: bool = False # 是否可被抢占max_wait_sec: int = 300 # 最大等待时间(秒)@dataclassclass GPUDevice:"""GPU设备状态"""device_id: inttotal_memory_mb: intused_memory_mb: int = 0compute_utilization: float = 0.0running_tasks: List[str] = field(default_factory=list)mig_enabled: bool = Falsemig_instances: Dict[str, dict] = field(default_factory=dict)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(key=lambda t: (t.priority, t.submit_time))return Nonedef _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_mbif (available_memory >= task.gpu_memory_mband device.compute_utilization + task.gpu_compute_pct <= 100):return device_idreturn Nonedef _preempt_for_inference(self, task: GPUTask) -> Optional[int]:"""为推理任务抢占训练任务的GPU资源策略:选择计算利用率最低的训练任务所在GPU"""best_device = Nonebest_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_idbest_util = device.compute_utilizationif 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_devicedef _allocate(self, device_id: int, task: GPUTask):"""分配GPU资源给任务"""device = self.devices[device_id]device.used_memory_mb += task.gpu_memory_mbdevice.compute_utilization += task.gpu_compute_pctdevice.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 模型的检查点大小约 140GB(FP16),保存到 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 分钟时考虑扩容。
相关资讯
点击查看更多
游戏推荐
推荐专题
热门阅读
推荐下载