尧图精选

大模型推理从单卡到千卡:负载均衡与KV Cache亲和性调度实战

🕒 发布时间:2026/10/2 4:38:15 📁 来源:尧图网络
去年一次大模型推理集群的扩容压测让我印象特别深7B模型在单机上跑得好好的并发打到40多还很稳等流量翻倍我们把副本从几个扩展到几十个之后问题接二连三地冒出来——总卡数越多单卡利用率反而往下掉有副本排队排到几十条请求有副本空转等活儿整条链路像极了高峰期的无秩序调度。那之后我才真正意识到把大模型推理从“单卡跑通”变成“千卡负载均衡”并不是简单加机器而是要把入口、路由、执行、弹性扩缩容这些环节重新设计一遍。这篇文章就用我实际踩过的坑和最终沉淀下来的方案思路把从单卡推理到千卡集群这条路上的核心问题讲清楚单卡为什么撑不住多机上量会遇到哪些隐性瓶颈千卡集群的分层架构怎么搭以及等开销负载均衡具体怎么落地。适合正在做推理服务化、或者打算把单机推理改造成多副本集群的读者参考。1. 单卡推理的真实瓶颈性能天花板到底卡在哪里很多人对“大模型推理跑在单卡上”的理解停留在“能跑就是行”。但生产环境不是“能跑”而是“能在多少并发下跑多稳”。单卡推理的瓶颈从来不是显存能不能塞下模型而是请求一多卡内部的资源就开始打架。1.1 Prefill和Decode两种完全不同的资源画像一次大模型请求可以拆成两个阶段Prefill阶段处理整个输入序列把用户的prompt变成中间状态Decode阶段逐个token往后生成。这两个阶段的资源特征完全不同。Prefill是典型的计算密集型阶段大量矩阵乘同时执行GPU算力能跑到很高的利用率但显存带宽相对没那么吃紧Decode则反过来每次只生成一个token单次计算量小但需要把权重和KV Cache反复搬运瓶颈几乎压在显存带宽上。这就好比做一顿饭备菜环节要的是案板和刀工算力炒菜出锅环节要的是锅够大、火够猛带宽。理解这个区别的价值在于决定单卡吞吐上限的往往是Decode阶段的显存带宽而不是理论算力。我们现在用的A100 80G标称算力很强但实际跑7B模型的decode阶段吞吐量也就是每秒一两千个token的量级分摊到每个请求头上每个用户能分到的生成速度并没有想象中那么快。1.2 真正的对手是KV Cache不是模型权重单卡推理时人们最先警觉的是模型权重的显存占用。7B模型用FP16精度大约占14GB13B大约占26GBA100 80G都放得下。但真正把显存吃干抹净的是KV Cache——也就是自注意力计算过程中需要缓存的Key和Value矩阵。KV Cache的大小随并发请求数和上下文长度线性增长。每个token的KV Cache开销大约是2 × 层数 × KV头数 × 头维度 × 2字节。拿常见模型估一下7B模型单token缓存大约在500多KB如果每个请求的平均上下文是4000 token那么一个请求要占掉接近2GB80G显存减去权重之后剩下60多GB能被请求塞满吗当然能30个请求就能打满。这意味着单卡真正能稳定支撑的并发根本不是“显存总容量 ÷ 模型权重”而是“显存总容量 ÷权重 每请求KV Cache × 并发数”。这也是为什么推理集群的负载均衡不能只看请求数量两个请求可能都算“一个请求”但如果一个只有几百token另一个有上万tokenKV Cache占用能差出一个数量级。1.3 单卡能承载多少并发一套可手算的估算方法分享一个我常用的粗算方法不用复杂的profiling工具就能得出量级范围内的结论。第一步确认模型权重显存。FP16权重大约等于参数量×2字节。第二步估算每token的KV Cache大小公式就是2 × 层数 × KV头数 × 头维度 × 2字节。第三步设定目标平均上下文长度比如4000 token和目标并发数计算KV Cache总量。第四步显存余量 总显存 - 权重 - KV Cache总量 - 激活值和CUDA context内存余量建议至少留出15%空余。以7B模型为例32层、KV头32、头维度128FP16精度单token KV Cache约512KB上下文4000 token时单请求约2GBA100 80G减掉权重14GB和CUDA预留实际可用的并发大概在30到40之间。你可以拿这个公式套自己的模型基本能判断“我这卡到底能吃多少请求”很多看起来“突然OOM”的问题提前算一下就能规避。2. 从单卡到多机为什么“堆卡”不是复制粘贴那么简单既然单卡并发有限最自然的想法就是“多加几张卡”。但推理集群的扩展路径真不是随意加卡就能保住吞吐的。2.1 数据并行、张量并行的取舍先横掰显存再堆副本并行方案主要有两条路张量并行Tensor Parallelism把模型权重切开放到多张卡上共同算一个请求数据并行Data Parallelism则是每张卡或每组卡放完整的模型副本各自处理不同请求。我的实践经验是先分情况讨论。模型权重太大、单卡放不下时用张量并行比如70B模型在A100 80G上至少需要4卡张量并行但张量并行每算一步都需要跨卡通信通信量很大扩展效率会随着卡数增加而下降。权重放得下时优先用数据并行。数据并行扩展逻辑最干净副本越多能同时处理的请求就越多吞吐几乎线性增长。我们当时的架构策略很简单——先用张量并行把模型塞进一个“最小单位”比如8卡一组然后用数据并行把这个最小单位复制成几十份。这就是从单卡到集群最稳妥的起步形态也是后续“千卡池”的基本单位概念。2.2 推理并行和训练并行真的是两码事很多人会直接把训练集群的并行方式搬到推理上这是个大坑。训练的并行目标是“把一个大模型快速训完”因此会用大规模流水线并行、多种并行叠加推理则完全相反目标是“低延迟、高吞吐、弹性伸缩”。推理请求是持续涌进的不是固定batch一次性跑完。推理集群要的是按需拉起、快速调度、及时释放训练集群则讲究确定性相对稳定的资源占用和网络拓扑。所以设计推理集群时不要照搬训练框架的思路比如“整集群一个分布式session”这种思路在推理场景几乎行不通——你得把分布式session切成“小单元副本”让请求落在哪个副本变成一个路由问题。2.3 网络拓扑的隐性瓶颈机内带宽和多机通信只要跨卡做张量并行网络就成了一个绕不开的隐性瓶颈。8卡之间用NVSwitch互联带宽可以达到几百GB/s但一旦跨机哪怕用高速网卡单链路带宽也就几十GB/s起差一个数量级。这个差距意味着张量并行尽量控制在单机8卡以内跨机做张量并行会非常痛苦。我们当时给千卡集群划分资源时强制约定“计算单元”的粒度不超过单机8卡跨单元的通信量被压到非常低。数据并行副本之间基本不需要高频通信最多就是心跳、指标上报、KV Cache调度信息。这么设计之后网络瓶颈从“每请求都卡”变成“只在路由和上报层面有少量开销”整个系统的可扩展性一下子就上来了。3. 千卡集群的分层架构入口层、路由层、执行层如何配合千卡集群不是指一个模型占满一千张卡更常见的是多个模型共享一个资源池池子里可能有几十个7B副本、十几个13B副本、几个70B副本再加上一些embedding小模型。这种混部场景下架构设计会决定稳定性的上限。3.1 四层链路从DNS到GPU核心的完整数据流以我们线上用下来的分层为例整个链路分四层入口接入层负责DNS/Anycast、证书卸载、基础四层转发这层只关心“把请求送到集群内部”不做任何业务判断。路由调度层核心决策单元维护副本健康状态、实时负载信息、KV Cache亲和性表决定新请求应该落到哪个副本。执行层真正跑推理引擎的GPU实例每个实例对应一组卡比如8卡一组内部运行vLLM这类推理服务。控制面负责副本生命周期管理——扩缩容、模型发布、灰度、监控采集。路由调度层是整个架构的“大脑”。请求到达后不是随机发给某个GPU实例而是先进入调度器的决策逻辑由它给出“最优目标实例地址”再由客户端或网关转发过去。这个设计的核心意义在于调度逻辑和流量转发逻辑解耦你可以随时调整路由策略不用动底层数据链路。3.2 KV Cache亲和性路由负载均衡不能只看并发数纯按并发数或连接数做负载均衡在普通Web服务里够用在大模型推理里会翻车原因就是KV Cache。举个例子用户发一条消息请求被路由到副本AA生成了响应同时在显存里缓存了这轮对话的KV Cache。用户紧接着追问一句理想情况是把第二条消息继续发给副本A这样A能复用刚才的缓存省去重新处理前文的时间如果被调度器发到副本BB没有缓存只能从头处理整个上下文响应变慢不说还多占了显存。所以在路由层我们引入“软亲和性”策略默认情况下同一个会话尽量发往同一个副本但如果副本已经过载就放弃亲和性选一个负载低的副本代价是缓存失效重新做Prefill。这比硬亲和性好用得多——硬亲和性会为了“续上缓存”把某些副本压垮反而整体吞吐更差。亲和性打分是动态的结合负载综合判断。3.3 PD分离与混合部署先拆后合的资源编排思路前面提到Prefill和Decode两种资源画像差异很大千卡集群里可以更激进一点把Prefill和Decode拆到不同的实例池里中间用调度器做接力。Prefill池的卡专门处理输入长的请求生成完中间状态后把状态转交给Decode池继续迭代生成。这就是PD分离。PD分离的好处在于两类资源不再互相抢占。如果不分离一个大Prompt请求进来会把一批Decode阶段的请求拖慢用户感知就是“生成速度突然变慢”。分离之后Prefill突发只影响Prefill池Decode池的产出节奏保持稳定整体吞吐和延迟都更可控。当然这也会增加复杂度——请求状态要在不同实例间转移调度器要具备“接力”能力。现实中可以先做小规模验证如果你们的请求平均输入都很短不考虑PD分离也完全没问题如果输入普遍很长且对首token延迟敏感PD分离是值得投入的方向。4. 千卡负载均衡的五大设计要点从等并发到等开销调度负载均衡是千卡集群里被讨论最多、也最容易做糙的部分。做糙的表现是用轮询、用最少连接数看起来把请求分散了实际效果却是长尾延迟严重。4.1 指标选型GPU利用率不是金标准很多人监控负载只看GPU利用率这在大模型推理场景会骗人。GPU利用率高有两种可能一种是正在高效处理大批请求一种是batch已经塞满但都在等最慢的那条流式输出也就是利用率虚高。反过来有时候利用率看起来不高是因为批调度周期还没开始新请求正排队。更好的负载指标应该包含当前正在处理的请求数、当前排队中的请求数、每个在线请求的平均剩余生成长度、KV Cache占用比例、最近一分钟的实际平均解码吞吐。把这些指标综合成一个“实例忙度”分数再用于路由比单看GPU利用率靠谱得多。4.2 等开销调度让负载描述回到“预期完成时间”“等开销负载均衡”这个词本质上回答的问题不是“每个实例分到几个请求”而是“哪个实例能最快把我这个请求干完”。换句话说调度器选择的不是请求数最少的实例而是预期完成开销最小的实例。我们的打分模型大致是这样的先估算排队中请求的总剩余解码时间再估算当前正处理请求的剩余完成时间加上新请求本身的预期处理时间再叠加上KV Cache亲和性带来的成本折扣如果命中缓存相当于省掉一次Prefill开销。这几项通过加权算出一个综合分每次路由选综合分最低的副本。实现上不复杂每个副本定期上报自己的状态快照即可关键是调度器要能拿到比较准的“剩余生成长度”而不是光看请求数。一个只有两个长回答在生成的副本和一个有二十个短回答在排队的副本到底谁更忙用等开销调度才能算清楚。4.3 一致性哈希和动态权重怎么结合只按等开销总是选最闲的实例有个问题同一会话的多次请求可能会被分到不同实例导致KV Cache频繁失效浪费大量算力。所以我们在调度层做了两层先做“亲和性哈希”把同一会话映射到优先副本集合再在这个集合内用等开销分排序。实践中还可以加动态权重。比如副本A启动时间短显存还很充裕初始权重就高跑了一段时间后如果出现显存碎片、某些batch卡住权重自动下调。定期从监控系统拉指标每10秒重算一次权重效果比静态权重好了很多。4.4 过载保护与背压队列上限和熔断千卡集群最怕的不是流量上来而是流量上来后所有副本都处于排队状态新请求越积越多系统进入“濒死”状态。等开销调度能选择最闲的副本但如果没有过载保护最终所有副本都会变忙调度器只能矮子里拔将军。我们的做法是给每个副本设置队列上限排队数量超过阈值后该副本在调度器那里标记为“不接受新请求”不再参与路由。上游网关检测到整体可用副本少于一定比例时会直接拒绝部分请求并发回503避免雪崩。另外客户端SDK里也做了熔断——连续失败超过N次就把请求切到其他副本组。4.5 弹性扩缩容冷启动与预热策略推理实例的扩缩容和无状态Web完全不一样拉起一个新副本需要加载模型权重、构建CUDA kernel、做显存预热动辄一两分钟。如果等流量打满再扩容早就超时了。因此我们要做“预测式扩容”根据流量曲线提前15分钟判断副本需求在高峰期到来前把副本拉起并预热。Kubernetes里的HPA基于CPU的套路在这里往往不够用更有效的方案是基于排队长度指标的定时伸缩叠加对高峰期流量的日常规律学习结果。GPU卡很贵宁可稍微多备20%的余量来扛突发也不要被一两个突发流量打穿整个集群的延迟SLA。5. 实测中容易踩的坑五个典型案例与排查链路最后一章分享几个我们真实踩过的坑。每一个都导致了压测失败或线上事故排查过程也走过弯路希望你能绕过。5.1 巨型帧与NCCL通信换个VLAN掉速现象某几个Pod扩容之后跨机通信速度骤降模型生成速度直接对半砍。排查过程先怀疑代码后来发现单机内通信正常跨机通信异常再查交换机配置发现新增网段的MTU没有统一开启巨型帧导致NCCL通信时报文被拆碎严重影响带宽。调整MTU并保证所有跨机网络路径上的交换机配置一致后速度恢复。教训网络团队和GPU集群团队之间的配置对齐是最容易被忽略的“隐形台账”。5.2 流式响应的代理兼容问题比想象中更隐蔽现象大模型流式输出偶尔断流前端表现是“说一半卡住”。排查过程路由网关默认开了响应缓冲导致SSE流式响应的数据包被攒着不发同时还有一层负载均衡器设置了空闲超时模型生成超过这个时间就被断开。最终方案是把网关层改成纯透传模式关闭缓冲调大空闲超时并尽量用TCP四层方式转发流式数据。教训大模型推理的流量特征和普通API差异巨大所有中间层都要按“长连接大响应流式”来重新评估。5.3 慢请求就是队头阻塞的元凶现象当有用户发了一个超长生成任务同批处理的其他短请求全部变慢。排查过程发现动态批处理机制下同一个batch内部所有请求要等最慢的完成才能整体释放长请求把整个batch的时间拉得很长短请求就被“绑架”了。解决方向把超长生成任务单独分到一个低优先级队列或者对超长任务做生成中断与恢复机制。同时调度层对“剩余生成长度”的预估越准这种现象越可控。教训延迟SLA不仅要看平均更要看P95甚至P99长尾坏请求会拉崩整体体验。5.4 显存碎片与模型冷热不均现象集群中明明有副本显存很空但新请求仍然被路由到显存快满的副本上。排查过程一开始我们只上报“显存剩余总量”没区分“已分配但空闲”的显存碎片。推理引擎动态加载和卸载模型、不同长度请求的KV Cache频繁占用释放之后显存会碎片化总量看着够实际无法再分配大块内存。后来在调度指标里增加了“可分配大块显存”指标并且定时重启碎片率高的副本。教训显存管理对推理集群的影响比训练集群更敏感因为推理请求生命周期的频繁变化是常态。5.5 多租户之间的NCCL串扰初始化慢的根源现象集群中同时跑多个模型服务时偶尔出现某个模型服务首次请求极慢。排查过程发现多个推理实例共享同一个GPU时GPU显存和使用率相互干扰尤其是NCCL通信互相抢占网络资源导致初始化过程拉长。通过给不同模型划分独立的GPU Device Set避免共享物理卡并约束同一节点的副本数问题大幅改善。教训做推理集群时“一个Pod占用一张完整卡”比“一张卡上虚拟多个实例”更稳除非你的业务对成本极其敏感且有完善的隔离方案。写在最后的一点体会如果让我重新走一遍从单卡到千卡的过程我会少走很多弯路先把单卡并发模型算清楚再决定并行方案集群架构必须把路由调度层单独摘出来做不能图省事加一个通用负载均衡器负载均衡的选型从一开始就要用等开销思维而不是“看起来分散”就行。千卡集群的难点不在GPU本身而在这些容易被忽略的调度细节。希望这篇分享能帮你少踩几个坑。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →