推理服务的 GPU 资源池化管理:基于 CUDA MPS 的多模型混部与隔离策略

📅 2026/7/9 0:13:44
推理服务的 GPU 资源池化管理:基于 CUDA MPS 的多模型混部与隔离策略
推理服务的 GPU 资源池化管理基于 CUDA MPS 的多模型混部与隔离策略一、三张 A100 跑 5 个模型GPU 显存碎片导致总有模型加载失败在一个多模型推理服务的部署中。需要在 3 张 A100每张 80GB上部署 5 个模型。模型的显存需求分别为 45GB、30GB、25GB、20GB、15GB含 KV Cache。单看总显存 240GB 总需求 135GB。但实际部署时显存碎片导致总有模型无法加载。问题在于默认的 GPU 分配策略是按整卡分配。每个模型独占一张或多张 GPU。45GB 模型需要独占一张 80GB 卡。30GB 和 25GB 模型无法共存于同一张卡。20GB 和 15GB 模型可以共享一张。3 张卡不够分。出现了一张卡上有大量空闲显存却无法利用的情况。这就是 GPU 资源池化要解决的问题。通过 CUDA MPSMulti-Process Service或 MIGMulti-Instance GPU。让多个模型在一张 GPU 上共享计算资源和显存。实现细粒度的资源复用。二、CUDA MPS 与 MIG 的架构对比与适用场景两种 GPU 虚拟化技术有根本不同的设计目标。graph TD A[GPU 资源池化策略] -- B[CUDA MPSbr/多进程服务] A -- C[MIGbr/多实例 GPU] subgraph MPS 特性 B -- B1[逻辑空间共享] B -- B2[显存共享计算时分复用] B -- B3[故障隔离弱一个进程的 CUDA 错误影响全局] B -- B4[性能可通过 MPS 控制并发度] end subgraph MIG 特性 C -- C1[物理切分 GPU] C -- C2[显存和算力硬隔离] C -- C3[故障隔离强实例间完全独立] C -- C4[性能分配后不可动态调整] end subgraph 适用场景 D1[MPS: 模型需要弹性资源br/同质化计算负载] D2[MIG: 模型需要硬隔离br/多租户严格隔离] end B -- D1 C -- D2CUDA MPS 是一种软件层面的多进程共享机制。多个 CUDA 进程的逻辑上下文通过 MPS Server 合并。MPS Server 将不同进程的 CUDA Kernel 在时间维度上交错执行。显存空间是共享的。没有硬性的显存隔离。MIG 是硬件层面的切分A100/A30/H100 支持。将一张 GPU 物理切分为多个独立实例。每个实例有独立的显存、缓存和计算单元。实例间完全隔离一个实例的崩溃不影响其他实例。关键差异在于隔离性和灵活性。MPS 提供更高的资源利用率因为共享更灵活。但隔离性差。MIG 提供强隔离。但切分后不能动态调整。如果需要在一个 GPU 实例上同时跑大小差异大的模型。MPS 更合适。如果需要多租户严格隔离。MIG 是更好的选择。三、基于 MPS 的资源调度实现#!/bin/bash # GPU 资源池化管理脚本 # 目标在有限 GPU 资源上部署多个模型 # # 阶段一启用 CUDA MPS # # 设置 MPS 守护进程 export CUDA_VISIBLE_DEVICES0 # 启动 MPS Control Daemon nvidia-cuda-mps-control -d # 验证 MPS 是否启动 echo get_server_list | nvidia-cuda-mps-control # 设置 MPS 的活跃线程百分比 # 为什么限制 80%预留 20% 给操作系统和系统守护进程 echo set_default_active_thread_percentage 80 | nvidia-cuda-mps-control # # 阶段二模型部署分配策略 # # GPU 0 (80GB)部署两个模型 # Model A: 45GB 4GB KV Cache 49GB # Model B: 25GB 2GB KV Cache 27GB # Total: 76GB / 80GB利用率 95% CUDA_VISIBLE_DEVICES0 ollama serve --model llama3:70b-q4 CUDA_VISIBLE_DEVICES0 ollama serve --model qwen2:14b-q4 # GPU 1 (80GB)部署两个模型 # Model C: 30GB 3GB KV Cache 33GB # Model D: 20GB 2GB KV Cache 22GB # Model E: 15GB 2GB KV Cache 17GB # Total: 72GB / 80GB利用率 90% CUDA_VISIBLE_DEVICES1 ollama serve --model mistral:7b-q8 CUDA_VISIBLE_DEVICES1 ollama serve --model qwen2:7b-q4 CUDA_VISIBLE_DEVICES1 ollama serve --model bge-large:fp16 # # 阶段三显存监控与告警 # # 持续监控脚本 monitor_gpu_memory() { while true; do echo $(date) nvidia-smi --query-gpuindex,name,memory.used,memory.free,utilization.gpu \ --formatcsv,noheader # 检查显存使用是否超过阈值 USED$(nvidia-smi --query-gpumemory.used --formatcsv,noheader,nounits -i 0) if [ $USED -gt 76000 ]; then # 76GB / 80GB 95% echo 警告GPU 0 显存使用超过 95% # 触发告警发送到监控系统 fi sleep 30 done } monitor_gpu_memory /// GPU 资源管理器 /// /// 职责 /// 1. 追踪每张 GPU 的显存使用情况 /// 2. 为模型分配最优的 GPU /// 3. 在显存不足时触发降级或驱逐策略 use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock; #[derive(Debug, Clone)] struct GpuInfo { gpu_id: usize, total_memory_mb: u64, free_memory_mb: u64, utilization_percent: u32, /// 此 GPU 上当前运行的模型 models: VecString, } #[derive(Debug, Clone)] struct ModelRequirement { model_name: String, /// 模型所需显存含 KV Cache required_memory_mb: u64, /// 是否需要独占 GPU exclusive: bool, /// 优先级数字越小优先级越高 priority: u32, } /// GPU 资源池管理器 struct GpuResourcePool { gpus: ArcRwLockVecGpuInfo, /// 模型到 GPU 的映射 model_placement: ArcRwLockHashMapString, usize, } impl GpuResourcePool { /// 为模型分配 GPU /// /// 分配策略 /// 1. 优先查找已有模型的 GPU提高利用率 /// 2. 使用 Best-Fit 算法减少碎片 /// 3. 独占模型跳过共享 GPU async fn allocate_gpu( self, requirement: ModelRequirement, ) - Resultusize, String { let gpus self.gpus.read().await; if requirement.exclusive { // 独占模型找一张空闲的 GPU for gpu in gpus.iter() { if gpu.models.is_empty() gpu.free_memory_mb requirement.required_memory_mb { return Ok(gpu.gpu_id); } } return Err(无空闲 GPU 可用于独占模型.to_string()); } // 共享模型Best-Fit 算法 // 优先选择已有模型的 GPU提高资源利用率 let mut best_fit: Optionusize None; let mut best_waste u64::MAX; for gpu in gpus.iter() { if gpu.free_memory_mb requirement.required_memory_mb { let waste gpu.free_memory_mb - requirement.required_memory_mb; // 偏好已有模型的 GPU 空闲 GPU let score if gpu.models.is_empty() { waste 1024 // 惩罚空闲 GPU鼓励共享 } else { waste }; if score best_waste { best_waste score; best_fit Some(gpu.gpu_id); } } } best_fit.ok_or_else(|| { format!( 模型 {} 需要 {}MB 显存所有 GPU 均无足够空间, requirement.model_name, requirement.required_memory_mb ) }) } /// 显存不足时的降级策略 /// /// 选项 /// 1. 减少低优先级模型的 KV Cache 大小 /// 2. 卸载低优先级模型Swapping /// 3. 拒绝新模型加载 async fn handle_oom(self, gpu_id: usize, shortage_mb: u64) - Result(), String { let mut gpus self.gpus.write().await; let mut placements self.model_placement.write().await; if let Some(gpu) gpus.iter_mut().find(|g| g.gpu_id gpu_id) { // 查找可降级的低优先级模型 // 此处简化卸载第一个非独占模型 let evict_model gpu.models.iter() .find(|name| !name.contains(high-priority)) .cloned(); if let Some(model) evict_model { gpu.models.retain(|m| m ! model); placements.remove(model); gpu.free_memory_mb shortage_mb; // 简化计算 Ok(()) } else { Err(无可降级模型GPU 显存不足.to_string()) } } else { Err(GPU 未找到.to_string()) } } }分配策略采用 Best-Fit 算法。在各种碎片化的显存空洞中选择最紧凑的那个。这减少了显存碎片的产生。惩罚系数鼓励模型共享 GPU。将多个小模型放在同一张卡上。为独占模型保留整张空闲卡。OOM 处理中的降级策略是生产环境必需的容错机制。当显存不足时优先减少低优先级模型的 KV Cache 或卸载。而不是让新部署直接失败。四、MPS 的隔离缺陷与生产级注意事项MPS 的资源共享带来了利用率的好处。但引入了隔离性的风险。首先是故障传播。在 MPS 模式下一个 CUDA 进程的致命错误如非法内存访问、越界 Kernel可能导致 MPS Server 崩溃。进而影响同一 GPU 上所有其他进程。需要额外的进程监控和自动重启机制。其次是性能干扰。共享 GPU 的竞争可能导致表现不稳定的延迟抖动。当高优先级模型推理时。如果低优先级模型的 Kernel 正在执行。高优先级模型需要等待。MPS 不提供优先级调度。需要通过限制低优先级模型的并发度来间接控制。第三是 MPS 和 MIG 不能同时使用。这是硬件层面的限制。需要在规划和设计阶段就做出选择。最后是资源碎片随时间恶化。随着模型的加载和卸载。显存中产生不可用的小碎片。类似于操作系统的内存碎片问题。定期或在低峰期执行模型的卸载和重新加载。可以整理碎片。五、总结CUDA MPS 通过软件层面多进程共享 GPU。实现显存和算力的细粒度复用。提高 GPU 利用率。MIG 通过硬件切分提供强隔离。适合多租户场景。但不能动态调整分配。GPU 池化分配采用 Best-Fit 算法减少显存碎片。并惩罚空闲 GPU 鼓励模型共享。MPS 的故障隔离性弱。一个 CUDA 进程的错误可能影响同 GPU 上所有进程。需要进程监控和自动重启。定期模型重新加载可以整理显存碎片。建议在低峰期执行。避免影响在线服务。