日期:2026-08-10
客户是一家省属集团,下面十几家二级公司,各自建了自己的 AI 应用:客服问答、合同抽取、报销单识别、图文审核、几个部门自己微调的小模型,还有两三个跑批用的评分模型。这些应用陆续上线,每上一个就申请一张卡,到我们进场的时候,集团机房里三十多张推理卡,平均利用率长期在个位数。
信息中心的负责人给我看过一张监控截图,凌晨三点,二十九张卡的显存占用条都是满的,算力利用率全在百分之二到五之间晃。他的原话是:显存都被占着,可实际上什么活都没在干。
想合卡,但没人敢动。前一年他们自己试过一次,把两个模型塞到一张卡上,跑了三天,其中一个模型遇到长文本请求显存暴涨,把同卡的另一个服务直接 OOM 打挂,那个服务是给客服热线用的,出了故障工单。从那以后共卡这事就成了禁区。
我们接的活就是把这个禁区重新打开,前提是得有隔离,出事不能连坐。
方案在新普 AI 私有化部署底座的算力调度层上做,整体分成几块。
显存配额切分。每个模型实例申请时必须声明三个数:权重占用、KV 缓存上限、峰值余量。调度器按声明值做装箱,不是按卡数分配。声明的 KV 上限会硬性下发到推理框架,vLLM 这边通过 gpu_memory_utilization 和 max_num_seqs 两个参数卡住,超了就排队,不让它自己去抢。
共卡实例编排。装箱策略不只看显存够不够,还看模型的负载画像是否互补。我们给每个模型打了标签:在线交互型(低延迟、流量白天高)、批处理型(吞吐优先、可以夜里跑)、低频型(一天几十次调用)。编排时优先把在线型和低频型放一起,批处理型单独成组走夜间时段,避免两个在线型互相抢。
抢占与驱逐。当某张卡上出现显存压力时,调度器按优先级驱逐。低频模型和批处理任务是可驱逐的,在线服务不可驱逐。驱逐不是杀进程了事,会先摘流量、等在途请求跑完(有超时上限)、再释放显存,然后在别的卡上重建。
故障隔离与快速重建。每个模型实例跑在独立容器里,显存超限由框架层拦截,不让它走到驱动层的 OOM。真出了不可恢复的故障,监控探针秒级发现,健康检查失败即摘流,重建走预热镜像,冷启动时间压到一分钟出头。
利用率监控。Prometheus 采卡级和实例级双份指标,Grafana 上做了一块共卡看板,谁在一张卡上、各占多少、抖动多少,一眼能看清。这块看板后来成了信息中心跟二级公司谈资源的依据。
显存超卖到底能不能做。 这是最纠结的一个决定。理论上按峰值分配非常浪费,因为各模型峰值不同时发生。但超卖一旦踩空,代价是服务挂掉。我们最后的做法是分层超卖:权重部分绝不超卖,实打实占住;KV 缓存部分允许超卖,但超卖率按卡上模型的类型组合动态定,全是在线型的卡超卖率不超过百分之十五,混合型可以到百分之三十五。同时留了一块百分之八的"缓冲池"不分配给任何实例,专门吸收瞬时抖动。这个缓冲池救过至少两次场。
算力争抢导致的延迟抖动。 显存隔离了,算力还是共享的。同卡上批处理任务一跑,在线服务的 P99 延迟能翻一倍多。这个问题比显存难,因为 GPU 的时间片调度不像 CPU 那样有成熟的优先级机制。我们试过几条路:用 MPS 做资源限制、用 MIG 做硬切分、纯靠应用层限流错峰。MIG 的隔离最干净,但切分粒度固定,小模型放进去还是浪费,而且不是所有卡型都支持。最后落地的是组合拳:能上 MIG 的卡(客户机房里有一部分)给对延迟最敏感的两个服务用硬隔离;其余卡靠应用层做,批处理任务只在夜间窗口调度,白天如果必须跑,限制其并发批次大小并在检测到同卡在线服务 P99 超阈值时自动降速。
爆炸半径。 客户被上一次事故吓怕了,对这个格外在意。我们定了一条硬规则:同一个业务的主备实例,绝不允许落在同一张卡、同一台机器上。调度器里做成反亲和约束。另外每张卡上的实例数设了上限,最多四个,不是技术限制,是心理限制,实例太多一旦整卡故障影响面太大。
冷模型的换入换出。 有几个模型一天就调几十次,常驻很浪费。我们做了按需加载:闲置超过设定时长(默认二十分钟)就卸载权重释放显存,请求来了再加载。麻烦的是首次请求要等加载,几十秒的等待用户受不了。解决办法是加了个预测预热:根据历史调用的时间分布,在大概率会来请求的时间段前几分钟提前加载。这个预测很粗糙,就是按小时统计的调用概率,但对这几个规律性很强的内部应用够用了。命中率大概七成五,剩下两成五的用户还是要等,我们在前端加了个"模型启动中"的提示,没再有人抱怨。
案例片段(已脱敏):一张卡上的共卡编排结果
``` GPU-07 (80GB) inst-a 合同抽取-7B type=online weight=14.2G kv_cap=18.0G prio=high inst-b 报销单识别-3B type=lowfreq weight=6.4G kv_cap=6.0G prio=low inst-c 文本分类-1.5B type=lowfreq weight=3.1G kv_cap=4.0G prio=low buffer_pool 6.4G (8%)
已分配 52.1G / 80G KV超卖率 22% 反亲和:inst-a 备实例在 GPU-15 ```
案例片段(已脱敏):一次显存压力下的驱逐日志
[10:41:02] GPU-07 mem_used 74.8G/80G (93.5%) -> PRESSURE_WARN [10:41:02] inst-a kv_blocks 使用率 96%,请求队列 12 [10:41:03] 选择驱逐目标:inst-c (prio=low, 近5分钟调用 0 次) [10:41:03] inst-c 摘流 -> 等待在途请求(0) -> 释放显存 7.1G [10:41:11] GPU-07 mem_used 67.7G/80G (84.6%) 恢复正常 [10:41:14] inst-c 在 GPU-12 重建完成,耗时 8.3s,流量恢复 [说明] 全程 inst-a 未受影响,P99 延迟波动 +11ms
分两批推的,先拿三张卡上的五个低风险服务试了一个月,没出事之后再全量。到现在整体跑了五个多月。
GPU 平均利用率从百分之七点二提到百分之三十四点六。这个数看着还是不高,但对以在线交互为主的负载来说已经接近合理区间,再往上压延迟就不好看了。
单卡承载模型数从一提到平均二点八,三十多张卡里回收了十一张,其中六张调给了新上的业务,五张作为弹性池待命。原本年度预算里的六张新卡采购取消了。
P99 延迟波动这个指标我们盯得最紧。合卡前后,在线服务的 P99 上浮控制在百分之八以内,夜间批处理窗口期内上浮到百分之十九,超出了我们最初定的百分之十五的目标,后来把夜间窗口的批次大小再调小才压回百分之十二。
OOM 次数是零。五个多月里显存压力告警触发过四十七次,全部由驱逐或缓冲池吸收,没有一次演变成服务不可用。这一条对客户来说比利用率数字更重要。
冷模型的按需加载让常驻显存又省下约百分之九,预热命中率稳定在七成五左右。数据为项目复盘口径,已脱敏。
共卡这件事,技术方案往往不是最大的障碍,信任才是。客户被打挂过一次,任何方案讲得再好听都推不动。我们的做法是先挑最不重要的几个服务、在最闲的几张卡上跑一个月,让他们自己看监控。一个月没出事,后面的推进就顺了。先立信用,再谈规模。
显存和算力要分开看,别混为一谈。显存靠配额和框架层参数能管得比较死,算力的隔离手段目前都有代价:硬切分不灵活,软限流靠自觉。承认这一点之后,方案设计就务实了,我们的选择是按敏感度分级用不同手段,而不是找一个统一答案。
缓冲池那百分之八是我认为最值的一笔投入。不分配给任何人,看起来是浪费,实际上它把所有瞬时抖动都吸收掉了。做资源调度容易有一种把每一块都榨干的冲动,留一点余量反而让整个系统敢跑得更满。
按需加载的预热预测,我一度觉得做得太简陋,就是按小时统计概率,连个模型都算不上。上线后回头看,对内部应用这种作息极其规律的场景,简单方法完全够用。当时如果真去做时序预测模型,多花两周不说,效果未必更好。
眼下还没解决的是跨机的显存池化,客户有几个大模型单卡放不下,现在是固定占用整机,弹性很差。这块要么等硬件互联方案成熟,要么改推理架构,我们还在评估,暂时没动。