尧图精选

大模型推理集群架构实战:从单卡瓶颈到千卡负载均衡

🕒 发布时间:2026/10/2 5:20:57 📁 来源:尧图网络
前几天有个朋友私信我说他手上有两张卡想把一个7B模型拆成张量并行结果并发一上去每张卡的显存反而先爆了。我问他为什么非要拆他说“集群嘛总得多机多卡才算数。”这个理解恰恰是大多数人在“大模型推理集群架构设计”这个题目上栽跟头的地方。推理集群不是“机器多”而是每一层的选型都要为吞吐、延迟、成本做过权衡。这篇文章就用一个治理模型的完整过程来聊从单卡推理的瓶颈在哪开始把并行策略一个个选明白再说vLLM这类推理引擎为什么是集群的地基最后拆解千卡规模下负载均衡到底在“均衡”什么以及那些不太容易注意到的工程坑。适合正在做模型推理服务化、准备把本地部署扩展到多卡环境、或者单纯想知道千卡集群为什么这么复杂的工程师参考。1. 先算一笔账单卡推理的瓶颈到底在哪很多人以为单卡推理跑不动是因为“算力不够”其实绝大多数场景先卡住的是显存容量然后才是显存带宽。这两笔账算清楚后面集群怎么设计就有了判断依据。1.1 显存账一个KV Cache就把并发压死了模型权重是静态的加载完占多少就是多少但KV Cache是动态的随着请求数量和上下文长度不断膨胀。7B量级的模型大约28层、hidden_size为3584每个token在推理时需要缓存一份K和一份V占用字节数大概是2K和V× 28层数× 3584hidden_size× 2FP16字节数 401,408 字节约0.4MB/token这个数字看起来不大乘上并发和上下文长度就恐怖了。假设平均上下文长度2000 token同时1000个请求在跑0.4MB × 2000 × 1000 800GB单张H100/A100的显存才80GB连零头都不够。哪怕只有一个请求上下文长度拉满到32K也要12.8GB再开几个并发就直接OOM。长上下文场景下KV Cache对显存的吞噬是指数级的这也是为什么“单卡部署教程”里经常默认很小的上下文长度或很低的并发数——一上生产就原形毕露。1.2 算力账decode阶段是显存带宽的瓶颈模型推理的decode阶段是逐token生成的每一步都要把完整的权重从HBM读一遍。7B模型FP16权重约14GB一张A100的HBM带宽大约2TB/s读一遍要7ms也就是说单序列理论decode上限只有140 tokens/s左右实测通常还要打折。这里有个很容易误解的地方算力峰值其实不是瓶颈。decode阶段每个token大约需要2倍的模型参数量FLOPs也就是14 GFLOPs左右A100的FP16算力在300 TFLOPS以上理论能到每秒2万多个token但显存带宽先把路径堵死了实际跑到100多token/s就顶天了。那为什么批处理能让吞吐涨那么多因为权重搬运是共享的——同一个batch里的所有请求复用同一次权重读取每多一个请求额外成本只有KV Cache读写和对应的计算。所以单卡提升吞吐的核心不是“算得快点”而是“把更多并发塞进同一个batch”而batch大了显存又不够这就回到了第一个账。1.3 “单卡跑通”和“线上能用”之间的鸿沟8B模型单卡是能跑的但并发稍微一多延迟就线性恶化QPS可能几十就封顶27B到32B这个量级的模型单卡只是勉强放下低并发可用高并发基本没戏。很多朋友从“本地部署大模型”开始跑通了一个Demo就以为部署很简单其实“能跑”和“能接业务”中间隔着一条河河的一边是模型权重要求的显存另一边是并发和延迟要求带来的KV Cache容量。从单卡走向多卡真正的驱动力排序应该是显存容量不够了、单副本吞吐不够了、最后才是高可用要求。很多人上来就考虑“万一卡挂了怎么办”那是后面阶段的事前期的核心矛盾是把模型和负载从“一张卡”里解放出来。2. 并行策略选型TP、PP、DP不是训练专属并行方式不是训练才需要推理集群一样要面对“模型放不下”和“吞吐不够”两个问题。三种分法各有适用场景别一上来就全上。2.1 张量并行把一层切成薄片再组回去Tensor ParallelismTP把每一层Transformer的参数按行、列切分到多张卡上Attention的多个head天然可以分散到不同卡前向计算完后通过AllReduce把结果合并回去。它解决的是“单卡放不下一个模型”的问题——比如27B模型FP16权重54GB两张80GB的卡TP2就能跑。但TP的代价是通信。每一层都要做AllReduce批量越大通信量越大所以它对硬件互联极其敏感。实测下来大致是TP2约1.71.9倍单卡性能TP4约3.23.5倍TP8约5.56.5倍越往上收益越明显下降因为通信开销把并行收益吃掉了。工程上有一条铁律TP组内必须有NVLink或同等级别的高速互联。把TP8拆到两台机器、每台4张卡通过万兆网做AllReduce性能大概率比TP4还差这个坑后面专门讲。2.2 流水线并行看着很美推理场景很尴尬Pipeline ParallelismPP把Transformer按层切开卡1算完前14层把中间结果传给卡2再算后14层。训练阶段能用microbatch把流水线气泡填满但推理场景batch通常比较小气泡占比大还要额外增加一次跨机传输的延迟。所以推理集群里PP尽量少用它不是主要手段。只有当模型大到单机8张卡TP8都装不下比如几百B参数才考虑用PP做跨机切层比如4路TP加2路PP的组合。真用到的时候也要把PP的切面放在互联质量最好的位置减少网络传输带来的延迟。2.3 数据并行集群扩展的真正主力Data ParallelismDP最简单每个副本都是完整模型副本之间不需要通信网关把请求分发到不同副本就行。它是推理集群横向扩展的本质——加副本就能加吞吐某个副本挂了其他副本照常服务。DP的代价也很直接每个副本都要一份完整模型显存。如果单个模型连一张卡都放不下DP就无从谈起。所以最典型的组合是“先TP/PP解决单副本容量问题再DP解决多副本扩展问题”。在脑子里的结构图大概是DP在最外层TP在单机内部PP极少数情况跨机三者组合成一个“DP × TP/PP”的矩阵。三种并行方式可以这样对比并行方式解决什么问题通信需求推理场景适用性TP单卡放不下模型极高单机内多用跨机慎用PP模型超大需要跨机高少用仅超大模型DP单副本吞吐不够无集群扩展核心手段3. 推理引擎不是部署工具vLLM为什么是集群的地基并行策略想好了模型也切开了还有一个更大的问题要处理多请求进来你手里的GPU能不能把它们高效地装进batch。这个问题不做引擎层优化再好的负载均衡都是给一个低效引擎打补丁。3.1 连续批处理吞吐量翻几倍的原理传统动态batching的做法是等一个batch里的所有序列都生成完再一起释放显存、换下一批请求。这有个致命问题一个batch里总会有慢请求比如输出特别长的其他快请求即使生成完了也得等它GPU的算力和显存被白白占住。vLLM的Continuous Batching把粒度从“请求”降到“iteration”每一轮decode都动态决定这个batch包含哪些请求某条请求一完成立刻把新请求补进来。打个比方传统batching像自助餐厅一桌人到齐了才开餐连续批处理像流水席吃完就走随时补位。这个改动让吞吐量直接翻几倍同样一块GPU上能承载的并发数大幅增加多副本集群的性价比一下子就出来了。3.2 PagedAttention把KV Cache放进虚拟内存解决了batch调度问题还有显存碎片问题。传统实现里KV Cache必须分配一段连续的显存请求长短不一很容易产生碎片显存利用率只能到六七成。PagedAttention把KV Cache切成分页块来管理不连续也能用就像操作系统的虚拟内存需要哪块就映射哪块。两个机制叠加后一个显著的变化是每个实例的“容量”不再是一个固定的并发数而是一个动态值取决于当前请求的平均上下文长度和模型长度配置。这个特性对负载均衡有决定性的影响——按固定QPS或者固定连接数做均衡在动态容量面前就是刻舟求剑。3.3 vLLM部署的几个关键参数与实测建议vLLM不是唯一的选择TensorRT-LLM、SGLang各有优势但vLLM因为生态和API友好度成了大多数团队起步的默认选项。一个典型的72B模型部署命令长这样vllm serve Qwen/Qwen2.5-72B-Instruct \ --tensor-parallel-size 8 \ --max-model-len 32768 \ --gpu-memory-utilization 0.92 \ --trust-remote-code \ --host 0.0.0.0 \ --port 8000几个参数说下我的经验--tensor-parallel-size必须和实际参与TP的GPU数量一致否则要么启动报错要么性能崩坏。--gpu-memory-utilization一般给0.850.92。给太高后续遇到长上下文时会OOM给太低KV Cache空间不够并发上不去。--max-model-len不是拍脑袋设一个数字它决定了KV Cache的每个分页上限同时多并发下缓存是叠加的。建议先按业务真实上下文长度设定比如线上平均4K、最长8K就设8192不要盲目拉到32K。提示新版本vLLM还可以用--max-num-seqs限制每个实例同时处理的请求数。这个值建议从默认值开始实测P99延迟后再调整别一上来就调大。4. 千卡负载均衡的做法从轮询到预估剩余服务时间副本多了网关出来了标题里的“千卡负载均衡”才算真正开始。这一步要是做不好前面加的一百张卡可能只有五十张在干活另外五十张在排队等长请求结束。4.1 负载均衡到底要均衡什么连接数只是一个粗糙的代理普通HTTP服务的负载均衡看连接数、CPU使用率基本够了因为每个请求的成本差异不大。但大模型推理完全不同一个输出50个token的请求可能几十毫秒就结束一个输出5万token的请求可能占着引擎几分钟两者的资源占用差几个数量级。连接数均分在这种场景下会出大问题因为连接数完全无法反映“这个连接还要占用多少时间”。同样重要的还有KV Cache需求长上下文的请求占用了更多显存直接挤压其他请求的batch容量。所以推理负载均衡的核心目标是三件事最小化平均排队延迟、最小化P99延迟、最大化整体吞吐。三者互相牵扯单看任何一项都会掉进坑里。4.2 网关层需要的能力清单Nginx能做HTTP层转发但面对流式SSE、长度感知调度、实例状态上报、TP组健康检查这些需求还是得有一个专门的服务来做推理网关。能力清单大致包括模型路由一个网关后可能挂了多个模型要能根据请求里的模型名路由到对应的副本组。负载感知每个engine实例周期上报运行中请求数、排队数、KV Cache利用率、平均decode速度。队列管理所有副本都忙时是排队还是拒绝要有明确策略。优雅摘除缩容或故障的副本不再接收新请求但已经在处理的请求要等它结束或超时。流式转发SSE长连接的超时、断开、重连要处理干净。4.3 调度策略对比别迷信“最少连接”不同调度策略的适用场景差异很大用一个表格看比较直观策略核心思路优点典型问题轮询请求轮流分配实现简单长请求会导致严重倾斜最少连接分给活跃请求最少的实例活跃数均衡不知每个连接还剩多久最小排队数分给排队请求最少的实例避免堆积队列短不代表处理快长度感知按输入/输出长度预估耗时适合长短混合需要长度预测能力混合加权队列运行容量综合生产环境常用参数需要调生产环境里我最推荐的是最后一种“混合加权”思路再叠加长度感知。因为它承认了一个事实推理实例的负载是动态的、多维的任何单一指标都会失真。4.4 一个可落地的调度公式与伪代码先给出一个能直接用的启发式估计函数核心思想是估算“如果新请求交给这个实例大概还要等多久”def estimated_load(eng, req): # eng.running_requests 是当前正在处理的请求 # eng.ema_decoding_speed 是该实例最近的平均decode速度tokens/s remaining_tokens sum( r.est_output_len - r.generated_tokens for r in eng.running_requests ) req_tokens estimate_output_len(req) # 用历史P50/P90预测 return eng.queue_len (remaining_tokens req_tokens) / max(eng.ema_decoding_speed, 1e-6) def pick_engine(req, engines): usable [ e for e in engines if e.health ok and e.queue_len e.max_queue ] if not usable: raise Overload(all engines busy, retry later) return min(usable, keylambda e: estimated_load(e, req))estimate_output_len怎么做最土也最有效的办法是按模型维护历史统计按prompt长度分桶记录每个桶里输出长度的P50和P90。调度时可以用P90来估宁可保守一点别把请求发给一个快忙死的实例。更进一步的做法是把请求按“短、中、长”分级网关用多队列隔离短请求优先处理长请求进长队列——这能有效避免一个长输出卡死后面所有短请求。4.5 亲和性、预热与过载保护负载均衡不止是“选一个副本”而已它决定了副本怎么放、怎么上线、怎么保护亲和性同一个TP组的副本必须尽量放在同一台物理机上否则跨机通信会毁掉TP的性能而不同模型的副本则尽量分散避免物理机故障导致多点失效。预热新启动的实例不能马上接流量。权重加载、CUDA kernel编译、缓存预热都需要时间ready探针必须等这些完成。就算ready了建议先放一点低优先级请求把它“暖”起来否则第一个突发流量会打出毛刺。过载保护每个实例设置最大并发数vLLM里就是--max-num-seqs和最大排队深度超过阈值的请求在网关直接拒绝或降级而不是无限排队把链路拖死。5. 千卡规模的工程之坑我踩过的和见别人踩过的架构设计在文档上都是整齐的真正干活的时候一堆细节坑在等着。这几个坑我基本都亲眼见过写出来当个排错手册。5.1 跨机TP加了卡反而变慢的经典案例当时有个团队把TP8拆到两台机器上每台4卡通过万兆网连接跑7B模型时发现延迟比TP4还高。排查起来很有意思nvidia-smi显示每张卡利用率都不高但两台机器之间的网络流量几乎是满载的。原因很直接NVLink单向带宽约600GB/s万兆网只有约1.25GB/s差了500倍。TP每一层都要做AllReduce跨机通信就成了全链路最短的那块木板。这个坑的解法不是“去买更贵的网络”而是重新设计部署结构TP组严格限制在单机内跨机扩展全部用DP副本。单机8卡搞不定的模型才考虑跨机并且要有RDMA级别的网络。5.2 一个长输出请求把P99拖垮有朋友问过一个问题网关明明用的是轮询为什么P99延迟还是烂后来查日志发现有一个18K token的长输出请求占住了实例A轮询依然公平地把短请求平均分到A和其他实例。结果A上的请求排队等这个长请求其他实例明明有空闲短请求的延迟还是被拖垮。这个问题靠“按轮询连接数”是解不了的必须做长度感知调度。把短请求分到不同实例长请求集中在专门的长队列里短请求优先P99立刻下来。实践里效果最好的组合是“长度分级队列预估剩余时间调度”。5.3 TP组的健康检查粒度单卡故障不能只看Pod状态千卡规模下GPU故障不是低概率事件而是每天的常态。某张卡出现间歇性HBM ECC错误时表现很微妙Pod状态是Running但推理偶尔返回乱码或者生成质量明显下降。如果健康检查只看到“进程活着”这个引擎还会继续被路由流量用户那边就是一阵一阵的坏结果。正确做法是把TP组作为健康检查单元组内任何一张卡异常、任何一次AllReduce超时整个TP组摘除自动拉起新副本再重新调度。同时硬件层指标ECC事件、NVLink错误、温度阈值要纳入监控和告警不能等到业务指标异常才被动处理。5.4 网关超时和引擎超时互相打架这个坑特别隐蔽。网关设了3秒超时引擎侧排队用了2秒decode又要1.5秒总共3.5秒。网关先超时了客户端重试重试请求又进了引擎队列整体负载翻倍排队更严重然后继续超时形成重试风暴。解法是统一超时策略网关超时要么大于“引擎排队超时预估decode时间”要么干脆让引擎不设排队超时只设“首token超时”把全局超时交给网关统一管理。重试必须有上限而且要带随机退避避免所有客户端同时重试把系统打垮。5.5 多模型混部时的干扰问题小模型多了以后每个模型单独占用一整套物理机非常浪费自然就会想到混部。但混部有个隐性问题NVLink、PCIe、CPU调度是共享的。一个高并发模型的张量并行通信会干扰同一台物理机上其他模型的推理导致延迟出现毛刺。我的实践是用“干扰预算”的思路管理混部给延迟敏感型模型独占资源比如cpuset隔离、显存上限、速率限制给吞吐型模型设置较低的优先级如果无法隔离干脆不混部宁可浪费一点显存也不牺牲SLA。6. 从单卡到千卡的路线图什么阶段该做什么架构不是一步到位的大多数团队都是从单卡起步被业务逼着一层层往上加。每个阶段有明确的“触发标志”到了再动手没到别硬上。6.1 阶段一单机单卡先把模型价值验证出来最早期阶段QPS需求可能在10以下主要目标是验证模型效果、做内部工具、打通产品逻辑。这个阶段不需要任何集群概念vLLM单卡部署调大--max-num-seqs能用就行。唯一要记住的是别在这一阶段写一堆“架构设计”文档业务还没验证复杂架构是负资产。6.2 阶段二单机多卡把TP用起来触发标志很明确模型单卡放不下或者单卡并发已经明显不够。这时候在单机内做TP8卡一组vLLM一条命令启动前面放一个简单的Nginx或者直连就行。8B模型单机8卡吞吐比单卡提升57倍是正常水平。这个阶段的关键是验证TP组内的互联和性能是否符合预期为后面的跨机打好底。6.3 阶段三多机多副本上推理网关触发标志是单机8卡算力不够了或者业务要求主备容灾。这时开始部署K8s每个模型的TP组作为一组副本多个副本分散到不同物理机。网关层要真正做起来模型路由、负载上报、健康检查、优雅摘除。这一阶段不要追求完美调度公式先把“队列、指标、摘除”链路跑通把异常副本能自动替换做熟练再优化调度策略。6.4 阶段四千卡规模把负载均衡做成系统工程触发标志是多模型混部、多租户隔离、SLA体系都需要精细化调度。这时的负载均衡已经不只是“把请求分到不同卡”而是延迟、成本、可用性之间的实时权衡。需要分层的架构接入层网关做路由和超时管理调度层做长度感知、多级队列、优先级观测层做TTFT/TBT/E2E指标的全面采集。自动扩缩容也是这时候才值得做——根据排队长度或KV Cache水位扩副本配合预热池保证秒级放量。我个人在跑千卡集群时最大的体会是稳定压倒复杂。调度算法先跑最简单的轮询最小排队把观测做起来等日志和数据告诉你“这里有长尾”“那里有倾斜”再逐步上长度感知和预估剩余时间。很多团队一上来就上一个复杂的“智能调度”结果参数没调明白还不如老老实实轮询。如果你也要走这条路记住三个原则并行组内一定要高速互联副本扩容优先用DP一切调度决策都要有观测数据支撑。这三个原则守住了千卡负载均衡就是水到渠成的事而不是玄学。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →