开局先讲个真实场景。前段时间帮团队搭一套大模型推理集群,模型是72B的MoE,量化后单卡A100勉强能塞下,但一压测就露馅:单卡每秒只能处理两三个请求,排队一长,首字延迟直接飙到十几秒。业务方当场就问了句——“你们这集群,和单卡跑有什么区别?”
这个问题其实问到了点子上。很多人一说“大模型推理集群”,就觉得是“多买几张卡,起多个服务,前面挂个Nginx”。真这么干,卡越多越乱,钱花了不少,延迟反而更难看。千卡负载均衡这事,难的不是“把请求分到不同卡上”,而是让每一张卡都在干它最擅长的事,让请求在集群里的等待时间和排队时间都能算得清清楚楚。
这篇就沿着“单卡到底缺什么→多卡怎么扩→千卡怎么组织→均衡怎么落地”这条线,把推理集群从设计到排障的完整逻辑讲清楚。适合正在做大模型服务化、或者准备从单机部署往多机集群走的同学参考。
1. 单卡推理的真实边界:先搞懂瓶颈在哪
1.1 一次大模型推理请求的本质拆解
别一上来就聊集群,先回到单卡。一次完整的大模型推理请求,在引擎内部实际上是两段:Prefill(预填充)和Decode(解码)。
Prefill阶段,模型要把你输入的Prompt完整计算一遍,生成每个Token对应的中间状态(K和V),存进KV Cache。这个过程是密集的矩阵乘法,GPU算力利用率能拉到很高,但一次请求如果输入很长,这步就会非常耗时。
Decode阶段才是真正“一个词一个词往外吐”的过程。每生成一个Token,只需要基于已有的KV Cache做一次前向计算。这个阶段的特点是算力消耗小,但内存带宽消耗大——因为每次要读取完整的KV Cache。
这两个阶段叠加起来,单卡推理的“容量”就被锁死了。显存决定你能不能装下模型和KV Cache,算力和内存带宽决定你每秒能生成多少Token,而推理引擎能不能把多请求的Prefill和Decode混在一起调度,决定了你这张卡在并发上来时会不会被拖死。
1.2 单卡部署的几个硬性天花板
很多人本地跑大模型玩得很开心——用Ollama或者vLLM起个单卡服务,自己问几个问题觉得挺快。但一旦作为后端服务暴露给真实用户,三个瓶颈立刻显现:
显存天花板。模型权重吃掉一部分显存,剩下的空间才是KV Cache的配额。7B模型在FP16下权重约14GB,A100 80G还能留出大量空间做并发;但如果换成72B模型,就算用量化塞进单卡,KV Cache的空间就被压缩到很小,并发一高直接OOM或者疯狂换入换出。
算力天花板。单卡算力是固定的,Decode速度就有物理上限。以7B模型为例,单张A100实测大约能跑到每秒800~1000个Token的总吞吐,看起来不少,但一个长回答就要出500个Token,换算下来单卡同时只能支撑几十个活跃用户,超过就得排队。
单点故障风险。单卡服务挂了等于整个服务挂了,没有降级方案。这在生产环境里是没法接受的。
结论很直接:单卡只适合个人玩、小流量内部工具、或者开发联调。真要做面向多用户的推理服务,多卡、多机是必然选择。而“多卡”怎么组合,直接决定了你后面集群的上限。
2. 从单卡到多卡:三类并行方式怎么选
2.1 张量并行:把一个大模型“拆”到多张卡上
张量并行(Tensor Parallelism)解决的问题是“单卡放不下”。思路很直观:把Transformer里某一层的权重矩阵按行或按列切开,分到多张GPU上,计算时通过卡间通信把部分结果拼起来。
举个例子,一个线性层权重是 4096x4096,切成4份,每张卡各存 4096x1024 的部分。前向计算时,每张卡算出自己那部分结果,再用AllReduce操作汇总。
这里面有个关键约束:每做一次张量并行,都需要跨卡同步一次。Transformer层数深,一次请求要经历几十上百次同步,通信开销非常可观。所以张量并行一般只用在单机多卡内——靠NVLink的高速互联,带宽足够大,通信损耗才压得住。跨机跨交换机做张量并行,性能会惨不忍睹。
实际工程里,TP(张量并行)的规模通常取4或者8,对应一台机器上的卡数。超过8基本不划算。
2.2 流水线并行:把模型的“层”切成几段接力跑
流水线并行(Pipeline Parallelism)是另一种切法:不切权重矩阵,而是按Transformer层数切。第1~16层放在GPU0,第17~32层放GPU1,数据像流水线一样从GPU0流到GPU1。
PP的好处是通信量小——只在层与层的边界传输一次中间激活值,不像TP那样每层都要同步。但它的缺点是容易出现GPU空转:GPU1要等GPU0算完第一批数据才开始干活,早期阶段会有一批GPU在摸鱼。
工程上解决这个问题的手段是micro-batch切分:把一个batch拆成更小的batch,交错地灌进流水线,让各卡尽量并行干活。但就算这样,PP的GPU利用率也普遍低于TP。
所以我的建议是:能用TP不用PP,PP只在模型大到单机放不下时才考虑。比如千亿参数模型,必须跨多台机器部署,TP+PP混合是无奈也最合理的选择。
2.3 数据并行:最朴素的扩容手段
如果模型单卡能放下,只是算力不够,那就用数据并行(Data Parallelism):每张卡存一份完整的模型权重,各跑各的请求,互不干扰。这就是典型的“横向扩容”。
DP是最容易理解的扩容方式,但工程上它有一个隐含要求:前面必须有一个负载均衡器把请求分配开来,否则几份副本之间会出现严重的冷热不均。
这里要特别提醒一个误区:很多人做了数据并行,每张卡各自挂一个服务进程,然后让负载均衡器做轮询——这能跑,但效果很粗糙。为什么?因为各个请求的输入长度、生成长度差异巨大,轮询分发会让某些卡碰上“长文本大户”而忙死,其他卡却空着。更合理的做法是“全对全”的一体化调度,这个后面展开讲大模型推理集群的调度层时再细化。
2.4 并行方式的组合艺术
主流大模型推理集群里的并行配置,通常是这么组合的:
| 并行方式 | 解决问题 | 代价 | 推荐规模 |
|---|---|---|---|
| 张量并行TP | 单卡放不下模型 | 高频通信开销 | 4~8卡,需NVLink |
| 流水线并行PP | 跨机部署超大模型 | 流水线空泡 | 尽量少用 |
| 数据并行DP | 并发不够用 | 需依赖负载均衡调度 | 按需扩 |
| 专家并行EP | MoE模型专家分布 | 复杂调度 | 仅MoE场景 |
对于现在主流的MoE模型(比如Mixtral、DeepSeek系列),还会引入专家并行(Expert Parallelism),把不同的专家网络分散到不同卡上,路由时做All-to-All通信。这个技术非常高效,但调优门槛也高,普通场景不做强制要求。
一句话总结:模型放不下,先加TP;TP扩不动了,考虑PP;并发不够,DP加节点。这个优先级顺序值得刻在脑子里。
3. 千卡推理集群的整体架构设计
3.1 先想清楚集群的四个分层
千卡集群不是几百张卡堆一起那么简单的。我习惯把架构分成四层:接入层、调度层、推理Worker层、存储与权重分发层。每层各司其职,彼此通过接口解耦。
3.2 接入层:流量的第一道关卡
接入层是外部请求进入集群的入口。它不关心你是哪个模型、跑在哪个Worker上,只负责两件事:安全和转发。
安全层面要做认证鉴权、限流。转发层面把请求转发给调度层。这里有一个常见的架构坑:有人图省事,接入层直接把请求发到某个Worker——这样调度层就形同虚设了,无法做到全局最优,也会让后续扩缩容变得很难操作。
所以接入层必须只与调度层通信,不应该知道Worker的存在。所有从外部进来的请求,一律先进全局队列。
3.3 调度层:集群的真正大脑
调度层是整个千卡集群最核心的部分,也是和普通Web集群差异最大的地方。它做的事叫作“请求级调度”:每一个推理请求进入后,调度器决定它去哪一组GPU、什么时候开始执行。
为什么不直接发到Worker就行?因为GPU执行任务是“排队式”的,不是像Web服务器那样接一个请求算一个请求。
想象一下:一组GPU上有4个Worker,每个Worker当前都在处理一个长回答(Decode可能要跑几十秒)。如果调度器愣头青一样把新请求直接扔给某个Worker,这个请求就得在Worker内部排队——而调度器完全不知道它在等,还会继续往这个Worker塞更多请求,最后导致延迟雪崩。
好的调度器必须掌握每个Worker的实时负载状态:当前正在处理的请求数、预估剩余时间、排队长度、KV Cache余量。综合这些信息,才能做出最优决策。
调度策略上,业界主流做法是:
- 最少请求数优先:哪个Worker在跑请求最少,优先排给它
- 基于预估完成时间优先:结合请求长度估算剩余处理时间
- 亲和性调度:同一个会话或同一用户的请求尽量分到同一个Worker,这样能复用其已生成的KV Cache,大幅降低延迟
其中第三点特别重要。我见过一个真实案例:因为没做亲和性调度,同一用户连续追问时,每次请求被分发到不同Worker,KV Cache全部作废,相当于每次追问都要重新把之前的对话历史从头算一遍。对话一长,延迟高到用户直接放弃。做了亲和性调度后,这个场景的首Token延迟降低了60%以上。
3.4 推理Worker层:无状态化的关键设计
推理Worker是实际执行GPU计算的进程,一般来说一个进程占一张或多张GPU,跑着vLLM/SGLang这类推理引擎。
这里有一个架构层面的关键决策:Worker必须无状态化。什么叫无状态?就是它只负责执行推理,不存储用户的会话记录、不维护用户状态的上下文。所有的会话状态都交给调度层管理——调度层知道某个用户上次的KV Cache在哪个Worker上,下次请求就优先调度到那个Worker。
但无状态化之后引出另一个技术问题:调度层是怎么做到“还记得上次在哪”的?答案是调度层维护了一张映射表,记录“用户ID/会话ID → 最近使用的Worker实例”。这张表在调度器进程内可以直接存内存里,最简单也最可靠。
如果做亲和性调度之后,同一个Worker都负载过高,怎么办?折中方案是:只要那个Worker还能扛得住,就硬塞给它;如果扛不住,就牺牲亲和性换整体稳定,允许调度到别的Worker重新计算前缀。什么都不如“服务不挂”重要。
3.5 存储层与权重分发:容易忽略的第四层
千卡集群里还有一个容易被忽略的环节:模型权重从哪来、怎么更新的。
模型权重少则几十GB,多则几百GB。集群启动时不能每个节点都从对象存储慢慢下载——那要等好几十分钟。业界通常有这样的方法:
首启时把权重文件从对象存储拉下来,写入节点的本地NVMe或SATA盘。之后每次启动直接读本地盘,中间只做增量更新。若权重文件在某次更新后损坏了,可以通过校验和去对象存储做校验修复。
还有一个更高级的方案是page-based权重分发:把权重文件切成固定大小的分片,通过P2P的方式在节点之间互相分发(类似BT下载的原理),一台机器拉完,其他机器从它那里拿。几千张卡的集群,几分钟内就能把几百GB的权重传完,比传统的主从分发快一个数量级。
4. 千卡负载均衡的落地细节
4.1 别把Web负载均衡直接照搬过来
很多人一听“负载均衡”,第一反应是Nginx、LVS、F5。但这些传统LB只处理连接和网络包分发,不了解上层推理服务的特性,在千卡推理集群里其实很容易出问题。
问题出在请求重量极度不均匀。同样是“你好,你是谁”,跟“请帮我解析这篇三万字的论文并提炼结论”,计算量完全不是一个量级。轮询和最小连接数这类经典算法,在这种场景下都会失效。
正确的做法是把负载均衡“上移”:不让LB直接去分流量,而是让它把请求交给调度器的全局队列,再由调度器基于各Worker的真实负载状态决定派发到哪张卡。负载均衡的本质不是“均匀分发”,而是“避免堆积”。
4.2 排队长度的隐喻:最好用的调度信号
在大量实践中,我发现一个非常可靠的经验法则:用“Worker排队长度”作为调度信号,几乎总是能超过复杂的预测算法。
原理很好理解:GPU推理任务天然是排队式的,队列越短说明这张卡越空闲,把请求发给它,等待时间最短;队列越长,说明这张卡已经忙得快转不动了,再往里面塞请求就是火上浇油。
调度器可以定期获取各Worker的“当前请求数+排队的请求数”,给每个Worker算一个负载评分,综合评分最低的Worker优先接收新请求。
4.3 会话保持与亲和性:一个被低估的性能利器
前面提过亲和性调度,这里再展开讲一下。对话类大模型的推理有一个独特特性:多次请求之间存在前缀复用。用户发“帮我总结一下这篇文章”,紧接着又追问“再补充一下细节”,后一个请求的Prompt包含前面所有对话历史,但前一轮的KV Cache已经算好了。如果能把第二次请求发到同一个Worker,就可以直接复用缓存,省掉一大段重复计算。
工程实现的要领是:
- 请求进来时带上会话ID或用户ID
- 调度器根据会话ID查表,找到上次使用的Worker
- 如果该Worker健康且负载可接受,就发送给它
- 如果Worker异常或过载,则放回全局队列,让负载最低的Worker接单
这种策略对多轮对话场景简直是性能“外挂”。我们实测过,长对话场景下做了亲和性调度,单请求的首Token延迟能下降50%以上,整机吞吐也能提升30%。
4.4 预热、慢启动与优雅下线
千卡集群的负载均衡还有一个经常被忽略的细节:新加入的Worker需要预热,退出服务的Worker需要优雅排空。
新Worker启动后,如果负载均衡器立刻把大量请求转给它,会出现一个尴尬的现象:模型权重虽然加载到显存了,但CUDA Kernel和KaCache还处于冷状态,第一个请求会异常地慢。你想想,新来的请求排队到它的头上,结果一看半天没出字,用户体验直接拉闸。
所以正规做法是:新Worker刚注册时标记为“预热中”,调度器只给它分配少量请求,随着处理量逐渐提升权重,直到完全放开。
优雅下线方向也一样。如果把一个还在处理请求的Worker直接kill掉,正在排队的请求全部失败。正确的下线流程是:先通知调度器“我要下线了”,调度器立刻停止分发新请求给它,但保留队列中已有的请求,等这些请求全部处理完,Worker再自杀退出。
这个细节,生产环境中真的能帮你少收很多客服投诉。
5. 容量规划与动态伸缩
5.1 从QPS反推GPU数量:来感受一下如何计算
选机器之前,必须先把容量算清楚。推理集群的容量不是看“峰值QPS”,而是看两个更接地气的指标:总生成速率(Token/s)和平均首Token延迟(TTFT)。
举个例子,业务想要支撑100路并发,每路平均每秒生成20个Token,那集群的Total throughput至少要做到100×20 = 2000 Token/s。假设单张A100跑7B模型实测能到800 Token/s,那至少需要3张卡才够。再预留30%的冗余,取4张。
很多人会问,这怎么听着这么简陋?但现实就是这样,很多时候决定你GPU数量的不是并发数,而是总Token吞吐。这也是为什么很多看起来并发不高的小团队,一算发现要买几十张卡——模型大、单卡吞吐低,潮水一来就承压。
5.2 自动伸缩:按队列深度而不是按CPU负载
集群的自动伸缩逻辑和传统微服务有个显著区别——参考指标应该是调度层的全局队列长度,而不是CPU使用率。
GPU推理的CPU占用通常很低,真正繁忙的是算力和显存,而这俩在系统指标里并不直观。队列长度才是实打实的信号:队列越来越长,说明需求超过了集群的消化能力,需要扩容;队列长期空着,说明资源闲置,可以缩容。
伸缩动作也不能一梭子全扩。我习惯分两步:先小步扩容(比如每次加2台),观察队列长度是否下降,如果下降不明显再继续扩。这样能避免“一下子扩了几十台,结果用户量根本没那么大”的浪费。
5.3 冷热分层:把预算花在刀刃上
一个很现实的问题:千卡集群的GPU费用是非常高昂的。我见过不少团队把整个集群都配置成性能最强的卡(比如H100),结果日常利用率只有20%,纯属浪费。
分层方案值得借鉴:用少量H100等高算力卡处理高价值、超低延迟需求(比如实时对话、付费产品);用A100/A800处理常规流量;把低优先级的离线批量推理(比如数据清洗、定时生成)安排到降配节点上,慢一点没关系,只要便宜就好。
再进一步,还能按时间段做缩容:夜间流量低时,把大部分机器缩容回收,只保留少量兜底。这些措施单独看没啥,叠加起来能把集群成本压低30%以上。
6. 常见问题与排查实录
千卡集群的坑,坑坑不一样。我把过去几个月折腾下来的高发问题整理成速查表,后面再挑典型的展开聊。
| 典型症状 | 可能原因 | 排查建议 |
|---|---|---|
| 集群吞吐上不去,但GPU利用率很高 | TP配置过大,通信开销吃掉收益 | 检查AllReduce占比,降低TP规模重测 |
| 某几张卡总有OOM,其他卡很空闲 | 调度器未感知显存余量 | 让Worker定期上报KV Cache余量,调度器考虑显存维度 |
| 新启动的节点处理后延迟高 | 未做预热,Kernel和缓存未就绪 | 注册时标记预热中,渐进分配流量 |
| 长对话场景延迟恶化明显 | 亲和性调度未生效,缓存重复计算 | 核查会话ID传递、调度表TTL |
| 扩缩容后集群抖动、请求大面积超时 | 下线时未优雅排空 | 强制排空队列后才能摘除Worker |
| 权重更新后部分节点仍跑旧版本 | 版本校验没做或分发失败 | 启动时校验权重文件hash,版本不匹配拒绝对外服务 |
6.1 案例一:显存明明够,为什么会OOM?
有次压测时发现个诡异问题:集群总显存才用了60%,但某个Worker频繁OOM。查了很久才发现原因——并不是真的显存爆了,而是单卡上KV Cache预留空间过小。相同显存空间下,KV Cache分配策略是贪心的,长请求会一次性申请一大块连续显存,但分配后释放不彻底,碎片化严重,后面的大请求无连续空间可分配。
解决方案是给推理引擎显式限制单请求的max_seq_len,并开启连续批处理中的显存池化,让KV Cache以小块为单位分配、减少碎片。这属于那种“症状在外面,病根在内部”的经典问题。
6.2 案例二:首Token延迟突然飙高,问题出在慢启动
压测环境刚搭好时,首Token延迟从200ms一下跳到3秒——彼时我一开始以为是模型推理慢,后来发现是当天有台新Worker刚加入集群,它一启动就被调度器平摊到了大量流量,冷启动状态下的性能叠加,导致那一整批请求全被拖累。
做了预热机制之后问题就消失了。这个案例也说明,千卡集群里很多“性能劣化”并不是硬件问题,而是调度策略的缺陷——尤其是对“新节点”的管理不当。
6.3 案例三:NCCL通信超时的背后
有次我们跨机扩展TP规模,想试一下TP=16(跨两台机器),结果一上线就大面积报NCCL超时错误。排查发现问题出在这里——跨机通信走的是RoCE网卡,但系统默认用的是内核态IPoIB,性能和稳定性都跟不上。
这个坑提醒我们:TP规模超过单机后,网络栈必须认真配置,RDMA/RoCE必须显式指定使用用户态驱动,否则结果就是延迟爆炸。跨机TP配置启动前,建议先跑NCCL all_reduce的benchmark,确认带宽在可接受范围内再加流量。
7. 从单卡到千卡,真正难的不是卡的数量
把整个架构梳理完,你会发现一个有点反常识的结论:从单卡到千卡,真正难的从来不是GPU的数量,而是“状态管理”。
单卡上跑模型,KV Cache、并发上下文都在一个进程里,天然一致;一旦拆到千卡,KV Cache散落各处、请求要跨机器调度流转、Worker有生有死,整个系统的复杂度一下就上来了。这时真正决定集群好坏的不是单卡跑得多快,而是调度器能不能准确掌握全集群的状态、做出合理的分配决策。
在我做过的几次集群改造里,最深的一条体会是:先保证调度层拥有足够准确的实时状态,再谈优化策略。很多团队上来就研究复杂的预测算法、强化学习调度,结果连Worker上报负载的链路都不稳定,收上来的数据是脏的——再好的算法也是白搭。
如果你正打算从单卡往集群迁移,我建议的路径是:先花一周把可观测性做好(每个Worker的负载、队列、延迟指标都清晰可见),再动手实现调度层。数据健全之后,很多看似复杂的问题,用最简单的策略就能解决。
这个内容后续还可以扩展的方向不少:比如多模型混部时的KV Cache隔离、基于强化学习的动态调度、离在线混合部署等。但万变不离其宗,状态感知是根,策略优化是叶。根扎稳了,叶子才能长好。