多模型推理平台的智能调度:从轮询到 GPU 感知负载均衡的演进之路
多模型推理平台的智能调度从轮询到 GPU 感知负载均衡的演进之路一、模型农场Model Farm的调度困境大小模型混部的资源错配现代 AI 平台通常同时托管数十个模型——从轻量的文本分类100MB BERT到重量级的多模态大模型140GB Llava。当这些大小模型混部在同一个 GPU 集群中时朴素的轮询调度策略几乎必然导致严重的资源错配。轮询Round-Robin调度的隐含假设是所有模型的资源消耗是均匀的、可互换的。但在 GPU 推理场景中这个假设完全不成立。一个 BERT-base 推理请求在 T4 GPU 上耗时 10ms、占用 2GB 显存而一个 Llama-70B 推理请求在 A100 上耗时 200ms、占用 70GB 显存。当轮询调度器把一个 BERT 请求分配给了正在跑 Llama-70B 的 A100 GPU 时该 GPU 的显存已经被大模型占满——BERT 要么排队等待要么触发 CUDA Out of Memory 崩溃。资源错配在批量调度场景中表现得更极端。假设集群中有 4 张 A100 和 8 张 T4正在运行 3 个不同模型。轮询调度器按请求到达顺序分配 GPU导致 A100 上可能停着只占 3GB 显存的小模型推理而 T4 上被塞满了需要 20GB 的 Bert-Large 推理请求——不断触发 OOM。在这种混乱分配下集群的整体 GPU 利用率可能低于 30%。更微妙的问题是碎片化效应。单张 A100 有 80GB 显存当前已用 72GB跑一个 70B 模型。剩余 8GB 显存无法容纳任何标准模型形成 8GB 的“死显存”。随着时间推移每张卡的碎片化显存累积起来可能达到集群总显存的 15%-25%。这些碎片可以从物理上容纳一个小模型如 2GB BERT但由于缺乏跨模型实例的显存池化机制它们被永久浪费。二、多维度感知的调度决策显存、算力、并发与亲和性解决上述问题的关键在于引入多维度调度决策——模型调度不是简单的请求分发而是需要综合显存需求、GPU 算力匹配、当前并发度和历史亲和性的多背包问题。调度决策的三个核心约束维度约束一显存容量配比。模型调度器需要维护每张 GPU 的显存水位线。每个模型的显存需求不是固定值而是随并发请求数动态增长——因为 KV Cache 随序列长度和批量数线性膨胀。调度器需使用公式估算峰值显存estimated_memory model_size k × (batch_size × seq_len × kv_bytes_per_token)其中 k 是安全系数建议 1.3。当available_memory estimated_memory时该 GPU 不应接受新请求。约束二Affinity 亲和性。已加载某模型的 GPU 具有天然的亲和性优势——缓存的模型权重避免了重新加载。一个简单的 LRU Cache 策略当 GPU 显存紧张需要淘汰已加载模型时优先淘汰上次请求时间最久的模型。反过来新请求到达时优先调度到已加载该模型的 GPU即使其他 GPU 的负载更低。约束三反向亲和性Anti-Affinity。同一模型的不同副本应尽量分布在不同 GPU 卡甚至不同节点上以避免单卡或单节点故障导致该模型完全不可用。这在生产环境高可用的场景下具有更重要的优先性有时甚至优先于资源利用率的优化。三、自定义调度器的工程实现基于 Go 的 GPU 资源分配引擎以下是一个简化但完整的调度器核心逻辑实现// 多模型推理平台的 GPU 感知调度器 package scheduler // GPUCapability 表示单张 GPU 的实时资源状态 type GPUCapability struct { UUID string // GPU 唯一标识 TotalMemoryMB int // 总显存MB FreeMemoryMB int // 当前可用显存MB GPUUtilization float64 // GPU SM 利用率0-1 LoadedModels []string // 已加载模型的名称列表LRU 序 ActiveRequests int // 当前正在处理的推理请求数 MaxConcurrency int // 该卡的最大并发请求数 NodeName string // 所属 K8s Node } // InferenceRequest 一次推理请求的上下文 type InferenceRequest struct { ModelName string // 模型名称 EstimatedMemoryMB int // 峰值显存估算含 KV Cache MaxBatchSize int // 最大批处理大小 PriorityClass string // 优先级high/normal/low } // GPUScheduler 多维度感知的调度决策引擎 // 调度决策遵循优先级约束 → 显存约束 → 亲和性优化 → 负载均衡 func (s *GPUScheduler) Schedule(req InferenceRequest) *GPUCapability { candidates : s.gpuPool.GetAvailable() // 第一轮硬约束筛选——去除显存不足、并发打满的 GPU feasible : s.hardFilter(candidates, req) if len(feasible) 0 { // 所有 GPU 都不满足硬约束尝试触发模型淘汰 s.evictModel(req.ModelName) return nil // 请求需排队重试 } // 第二轮亲和性排序——优先复用已加载模型的 GPU // 权重ModelAffinity × 0.5 FreeMemoryRatio × 0.3 GPUUtilGap × 0.2 sorted : s.affinitySort(feasible, req) // 第三轮反亲和性检查——避免模型所有副本集中在单节点 // 如果 GPU 已在同模型的副本列表中降低其排序优先级 sorted s.antiAffinityReOrder(sorted, req.ModelName) if len(sorted) 0 { return sorted[0] } return nil }调度器与 GPU Operator 之间的双向数据流需要精确设计。GPU Operator 暴露每张卡的显存用量和 SM 利用率到 Prometheus 指标。调度器以 5 秒间隔采集这些指标维护内存中的 GPU 资源视图。但当 GPU 利用率在 5 秒内从 30% 跳到 95%如突发流量时调度器的指标视图已经滞后。为解决这个问题在 GPU 上挂载一个 eBPF 探针监听 CUDA Kernel 启动事件将 GPU 负载变化以事件方式实时推送到调度器。这种方式将调度延迟从平均 5 秒降至 200ms。四、多模型混部的稳定性难题OOM 预防与优雅降级模型的显存需求有时是动态变化的。当用户输入从一段简短句子变为一段 8000 Token 的文档时KV Cache 的显存需求可能从 500MB 暴涨到 4GB。如果调度器用固定显存估算做调度OOM 是迟早的事。OOM 预防的第一道防线是CUDA 显存水位监控。在推理服务中注册 NVIDIA 的 gpudirect 回调当显存使用率超过 85% 时触发pre-oom信号。此时调度器暂停接收新请求并将已有请求异步迁移到其他 GPU——对于大模型场景这一迁移成本极高。更优先推荐的防护方案是请求级显存预留。在推理服务收到请求时不是按cuMemAlloc显式分配 KV Cache而是用cuMemAddressReserve做虚拟地址预留。这个 API 只占虚拟地址空间不占物理显存。当实际需要写入 KV Cache 时通过 CUDA 的虚拟内存管理VMMAPI 按需提交物理页面。若提交物理页面时 OOMCUDA 的 VMM 会返回明确错误码推理服务可以拒绝该请求并返回 429 状态码而不是整个进程崩溃。另一个稳定性策略是模型驱逐的优雅降级。当某个模型长时间 30 分钟无请求时调度器标记它为冷模型。冷模型的权重占用可能被驱逐以释放显存给热模型。但驱逐不可草率——需检查当前正在处理的请求数确保active_requests 0才能安全驱逐。此外驱逐决策需要预估重新加载的成本如果某个冷模型的请求间隔是 35 分钟而加载成本是 2 分钟 GPU 时间驱逐之后马上重新加载相当于白白浪费了 2 分钟的 GPU。五、总结多模型推理调度从轮询到智能调度的演进核心在于引入了三个维度的感知能力GPU 显存状态、模型亲和性和节点故障域。硬约束显存、并发保障 OOM 不崩溃软约束亲和性、负载均衡保证 GPU 的高利用率和低延迟。在工程落地中调度器设计的优先级是正确性 亲和性 负载均衡。显存超配和 OOM 是不可逆转的故障进程崩溃而亲和性错配和负载不均只是性能损失。在实现过程中必须在硬约束层建立绝对的可靠性屏障再在软约束层做优化。显存碎片化是多模型混部的长期挑战。Efficient CUDA 内存管理VMM API 页面按需提交和模型驱逐策略的合理组合可将碎片率从 25% 控制在 10% 以下。但碎片化问题无法 100% 消除——只要大小模型还在混部不同的显存占用模式就必然产生碎片。持续监控各卡显存碎片率定期进行模型重调度周期性驱逐冷模型并重整显存布局是以时间换空间的有效策略。最后自定义调度器的延迟5 秒轮询 → 200ms 事件驱动和 GPU 指标的准确性prometheus 滞后 → eBPF 实时推送是决定调度质量的基础。没有高准度的资源视图任何调度算法都会被错误数据的噪声淹没。