☰
大模型推理从单卡到千卡:负载均衡与KV Cache亲和性调度实战
2026/10/2 4:38:12 网站建设 项目流程

去年一次大模型推理集群的扩容压测,让我印象特别深: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精度大约占14GB,13B大约占26GB,A100 80G都放得下。但真正把显存吃干抹净的,是KV Cache——也就是自注意力计算过程中需要缓存的Key和Value矩阵。

KV Cache的大小随并发请求数和上下文长度线性增长。每个token的KV Cache开销大约是:2 × 层数 × KV头数 × 头维度 × 2字节。拿常见模型估一下,7B模型单token缓存大约在500多KB,如果每个请求的平均上下文是4000 token,那么一个请求要占掉接近2GB;80G显存减去权重之后,剩下60多GB能被请求塞满吗?当然能,30个请求就能打满。这意味着单卡真正能稳定支撑的并发,根本不是“显存总容量 ÷ 模型权重”,而是“显存总容量 ÷(权重 + 每请求KV Cache × 并发数)”。

这也是为什么推理集群的负载均衡不能只看请求数量:两个请求可能都算“一个请求”,但如果一个只有几百token,另一个有上万token,KV 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、头维度128,FP16精度:单token KV Cache约512KB,上下文4000 token时单请求约2GB;A100 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。

举个例子:用户发一条消息,请求被路由到副本A,A生成了响应,同时在显存里缓存了这轮对话的KV Cache。用户紧接着追问一句,理想情况是把第二条消息继续发给副本A,这样A能复用刚才的缓存,省去重新处理前文的时间;如果被调度器发到副本B,B没有缓存,只能从头处理整个上下文,响应变慢不说,还多占了显存。

所以在路由层我们引入“软亲和性”策略:默认情况下,同一个会话尽量发往同一个副本;但如果副本已经过载,就放弃亲和性,选一个负载低的副本,代价是缓存失效,重新做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本身,而在这些容易被忽略的调度细节。希望这篇分享能帮你少踩几个坑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询