最近一直在折腾大模型推理集群的架构设计,从最开始一张加速卡跑 Qwen3 27B 的“勉强能用”,到现在逐步把服务扩展到上千卡规模,中间踩过的坑比想象中多得多。很多人对大模型推理的第一反应是“堆卡就行了”,但真正把从单卡推理到千卡负载均衡这条路走一遍就会明白:单卡拼的是显存与带宽,集群拼的是调度与网络,两者完全是两套打法。
这篇文章把我的实践路线、架构选型、负载均衡策略、实测数据和踩坑排查记录都整理出来。如果你正在做私有化大模型部署、想把单点推理服务扩展成高可靠集群、或者遇到“上了多卡吞吐却不涨”的问题,这份内容应该能给你一个可参考的框架。先交代一下试验环境:我这边的主力加速卡是 K100AI,模型跑的是 Qwen3 27B,推理引擎用社区开源框架再自己做二次开发,压测工具是一个几十行的 Python 脚本。下面的数据都来自这套环境,不同硬件会有差异,但方法论是通用的。
1. 先搞清楚单卡推理到底卡在哪里
1.1 Prefill 与 Decode 是两个性能物种
大模型推理和传统深度学习推理最大的区别在于它分成两个特性完全相反的阶段。第一个阶段叫 Prefill,就是把用户输入的一整段 Prompt 一次性前向计算完,生成第一个 token。第二个阶段叫 Decode,模型每次只生成一个 token,然后把新 token 拼到已有序列里,再继续下一步。Prefill 阶段是典型的高强度矩阵乘,计算密度极高,GPU 的算力利用率可以拉得很高;Decode 阶段则基本被显存带宽钉死,因为每一步都要把全部模型权重从显存里读一遍。你可以把 Prefill 想象成一辆满载的货运列车,一次发一批;Decode 就像出租车,一次只跑一位乘客,你在车上等多久全看司机和路况。
这个区别直接决定了单卡推理的优化方向。如果 Prompt 特别长,瓶颈集中在 Prefill 的算力和显存占用;如果对话场景是“一问一答,每轮只回几十个 token”,瓶颈几乎都在 Decode 的显存带宽上。我遇到过不少团队拿单卡跑 27B 模型,看到 GPU 利用率接近 100% 却抱怨“每秒只有几十个 token”,其实这不是卡出了问题,而是 Decode 阶段本来就该这样:模型权重总量固定,每生成一个 token 都要全量过一遍,相当于几十 GB 的数据要在一个 token 的生成周期内从显存读出来。估算一下,如果带宽在 2TB/s 左右,模型参数量 27B、BF16 权重约 54GB,理论极限也不过几十 token/s。在量化成 INT4 之后权重缩小到十几 GB,单卡才能跑到 40 到 60 token/s 的实用水平。
这里想强调一个新手常犯的错误:不要拿“模型参数量”直接去算算力需求,推理阶段真正吃的是“权重访存量 × 生成 token 数”。很多人用 FP16 跑 70B 模型,发现特别慢,不是因为算力不够,而是每一轮 Decode 都要把 140GB 权重从显存里捞一遍,这种模型不量化不上多卡并行,单卡体验基本没法用。
1.2 显存账本:权重、KV Cache 与中间激活
单卡能不能跑起一个模型,光看“参数能不能塞进显存”是不够的。实际运行时显存要分三块:权重、KV Cache、中间激活。权重占多少很好算:参数量 × 每个参数字节数。比如 Qwen3 27B,BF16 精度下是 27 × 10⁹ × 2 字节,大概 54GB,INT4 量化后大约 13.5 到 14GB。KV Cache 是 Decode 阶段为了减少重复计算而缓存的历史注意力键值,它的大小和“当前并发的请求数 × 上下文长度 × 层数 × 注意力头维度”都相关。我通常按每个 token 大约占 1 到 3MB 来粗估(不同模型结构差异很大),假设一张 80GB 显存的卡留出 60GB 给权重和系统开销,剩下 20GB 给 KV Cache,那也只能容纳 1 到 2 万 token 的并发上下文。中间激活则是前向计算过程中的临时张量,在长 Prompt 的 Prefill 阶段会瞬间冲得很高,这个最容易被忽略。
所以单卡部署 27B 模型,第一件事不是调优,而是先做加减法:量化权重、限制并发数、控制上下文长度。三者之间是一个跷跷板。我实践下来的经验是,优先级从高到低依次是:量化、并发数限制、上下文管理。量化解决权重占用的“大头”,并发数限制解决 KV Cache 的“动态峰值”,上下文管理解决长对话带来的“时间炸弹”。否则一个用户的超长对话就能把整张卡的显存占满,其他请求全部排队。
1.3 单卡性能指标:TTFT、TPOT 与吞吐
跑大模型推理,最常用的三个性能指标是 TTFT、TPOT 和吞吐。TTFT 是“从发出请求到收到第一个 token”的耗时,这个值直接决定用户“卡不卡”的第一印象;TPOT 是生成每个 token 的平均耗时,决定了整体生成速度;吞吐则是系统每秒能处理多少 token,往往用所有并发请求生成 token 的总和除以时间得到。这三个指标在 Prefill 和 Decode 阶段的表现完全不同,一定要分开看。
我用 K100AI 单卡跑 Qwen3 27B(INT4 量化,上下文 4K,也就是一些人习惯写的 qwen3.8:27b 这个 27B 参数版本)做过一轮基准测试,得到的典型数据是:TTFT 在 1.5 到 2.5 秒之间(取决于 Prompt 长度),TPOT 大约 20 到 30 毫秒,也就是生成速度约 40 到 50 token/s。这个水平听起来不惊艳,但用于几百人规模的同时在线对话场景是可以接受的,前提是每张卡的并发数不能太高。单卡同时处理 8 个请求,TPOT 会劣化到 60 到 80 毫秒,因为 KV Cache 的抢占和计算资源竞争都在加剧。所以单卡推理的适用边界很清晰:内部试用、小规模私有化、对峰值压力不敏感的场景。一旦要把同一套服务暴露给几千上万人,就必须开始考虑集群。
2. 从一张卡到一个集群:AI 算力集群的构成
2.1 推理节点的标准配置
先聊节点,再聊机群。单个推理节点不是只插一块 GPU 就行,它要承担的任务比你想象的多:模型参数加载、请求的前端处理、Tokenizer、上下文管理、推理引擎调度、网络通讯。CPU 需要足够强的核数和内存带宽来处理成百上千的并发连接,否则 GPU 还没忙,CPU 先成了瓶颈。网卡更是关键,如果跨机通信要走张量并行,单一千兆网口根本不够用;主流方案是 RoCE 或 InfiniBand,搭配 25G/100G 端口。
我现在用的推理节点是 8 卡 K100AI 的整机,CPU 配 96 核以上、内存 512GB 起步,系统盘用 NVMe SSD。这个配置看起来“浪费”,但实际操作下来是必要的:内存要用来做模型权重的高速缓存和 KV Cache 的 host 侧备份;CPU 多核用来跑请求调度和引擎外的并发逻辑;机内互联拓扑决定了多卡之间能否高效协同。很多朋友在自建集群时为了省钱用 4 卡机器,后面发现做张量并行扩展很别扭,8 卡节点是一个比较均衡的选择。
2.2 集群的关键角色:网络、存储、调度与网关
把一个集群拼起来,除了计算节点还有四类关键角色。第一是网络,分为机内互连和机间互连。机内通常走 NVLink 或类似的高带宽总线,机间走 RoCE 或 InfiniBand,两者之间是数据搬运速度的质变。第二是存储,承担模型权重、Prompt 模板、日志和 Cache 的读写。模型文件动辄上百 GB,最好放到分布式文件系统或者对象存储里,节点启动时并行拉取,不要每次前台上传。第三是调度器,负责把“拉起多少个推理实例”“分配到哪些节点”这类部署层面的决策做掉;第四是服务网关,它是用户请求的第一站,负责做服务发现、负载均衡、健康检查和过载保护,在集群架构里网关的可靠性甚至比 GPU 节点本身还重要。
我见过一个比较“小而美”的集群结构:一个 16 台 8 卡节点的小集群,上面跑 4 个模型服务副本,每个模型服务占 2 个节点,统一由一个负载均衡器接入。这个规模不需要搞复杂的调度系统,一个轻量编排工具加一个可靠的网关就能跑得很顺。但当节点数到几十上百,模型实例到几百个之后,手动管理就完全不可行了,这时候需要引入全局的资源视图,让负载均衡器知道每个后端“现在忙不忙、还剩多少显存、队列多长”,并把请求动态地导给最空闲的那一个。
2.3 模型从入库到出结果的完整路径
我习惯把一条请求的完整路径画出来,帮助团队对齐架构理解。模型权重先上传到对象存储,推理节点启动时把权重拉进本地内存并加载到显存;加载完成后引擎向网关注册自己的服务端点,声明模型名、版本、并发上限。用户请求到达网关后,网关根据后端健康状态与负载指标挑一个实例;实例把请求放进引擎的任务队列,引擎做连续批处理,将多个请求拼进同一个 step 里并行计算;Decode 完成后结果逐 token 通过流式接口回给网关,再由网关返回用户。
这条路径看起来简单,但每一环都可能成为瓶颈。最常见的是节点加载模型时网络打满导致其他服务启动缓慢;其次是网关本身没有健康检查,后端已经 OOM 了还在持续分发请求;再就是引擎侧的任务队列没有长度上限,一旦请求积压,每个请求的延迟都会线性恶化。这些坑后面我会用专门一节来展开。
3. 并行三件套与连续批处理:多卡真正干活的底层逻辑
3.1 数据并行:最简单的扩容方式
所谓数据并行,就是把同一份模型权重复制到多张卡上,每张卡独立处理不同的请求。这是把单卡推理扩成集群最朴素、最可靠的方式:假设单卡吞吐 40 token/s,部署 8 张卡做数据并行,理论上就有 320 token/s。而且每个副本之间互不依赖,某张卡挂了,网关把流量切走就行,故障域非常干净。
但数据并行有个明显的资源浪费:每张卡都要放一份完整权重。27B 模型 INT4 也要 14GB,8 卡就是 112GB,只是为了存同一份参数。同时 KV Cache 是各副本独立的,无法跨副本共享,所以并发大请求时显存压力是线性的。数据并行适合“单卡放得下模型、但单卡吞吐不够”的场景,这也是大模型推理集群里用得最多的并行范式。在做容量规划时,简单乘法就能估算:总吞吐等于数据并行副本数乘以单副本吞吐。对于千卡集群,我的习惯是先保证数据并行作为主扩展维度,把副本数推上去,除非单卡放不下,否则不要轻易引入其他并行。
3.2 张量并行:单卡放不下模型时的拆解手法
当模型权重大到一张卡放不下,比如 70B、数百 B 参数的时候,数据并行就不够用了,得把“一张卡的模型”拆成“多张卡各拿一部分”。张量并行就是把单个 Transformer 层的参数矩阵横向切分到多张卡,每张卡算一部分,算完再通过 allreduce 合并结果。这样权重总容量翻倍,单次计算量也由多卡分摊。
但代价是通信量非常可观。Transformer 层每次前向都要做一到两次跨卡同步,通信延迟直接叠加到每一步 Decode 上。这也是为什么张量并行通常限制在单机内部:机内 NVLink 延迟低、带宽高,跨机走 RoCE 之后每一步多出若干毫秒的同步开销,性能会明显劣化。我的经验是,张量并行尽量在单节点完成,TP=4 或 TP=8 是常见配置;跨节点的张量并行只有在实在没有更大单卡节点时才值得考虑,而且需要把网络质量调到最好。
3.3 流水线并行与量化:最后两道补充手段
流水线并行按 Transformer 层将模型切成几段,由不同卡分别负责不同层,数据像流水线一样依次流过。它省显存,但会产生“气泡”,也就是某些卡在等上游计算时空闲。推理场景里流水线并行的调度开销很难赚回收益,我基本只在训练场景才重点考虑它。推理侧更实用的是量化辅助:用 AWQ、GPTQ 等算法把权重从 BF16 压到 INT4 或 INT8,让数据并行能覆盖更大的模型,或者让单卡有更多显存留给 KV Cache。量化是有精度损失的,但实测下来 27B 级别模型在 INT4 下回答质量下降可控,对于大部分业务可以接受。
3.4 连续批处理:负载均衡真正落地的地方
聊完并行策略,还必须聊一个推理引擎内部的机制——连续批处理。它为什么这么重要?因为大模型的请求长短极端不均匀:一个请求可能 Prompt 只有 20 字,另一个 Prompt 有 2000 字,生成长度也可能天差地别。传统的静态批处理会把一个 batch 里所有请求当成一个整体,必须等最慢的请求结束才一起收工,资源浪费极多;连续批处理则像一个动态拼车系统,每完成一个请求就把它移出去,同时把新请求插进来,这样 GPU 每时每刻都有活干,批的“内容”在持续流动。
推理引擎的调度器要做三件事:给新请求分配 KV Cache 空间、在显存不足时挑选可抢占请求并把它踢出、以及决定每一步哪些请求可以继续解码。我之前为了把这些问题彻底搞懂,专门读过一个精简版推理引擎的源码,叫 Nano 这个学习工程,它把动态调度、KV Cache 管理和批处理逻辑都做了最小化实现,非常适合当教材。自己读一遍调度器的代码,再去看生产级引擎的文档,理解速度会快很多。网关层的负载均衡,说到底就是为引擎侧这种细粒度的批处理服务:均衡度高,引擎才能保持较高的 batch 利用率;均衡度差,就会出现有的卡在排队,有的卡在空转。
4. 千卡负载均衡:从网关调度到全局 Batch
4.1 为什么“轮询”在千卡集群里不好使
第一反应做负载均衡,很多人直接上 Nginx 轮询,每个请求按顺序发给后端。这在“每个请求耗时均匀”的传统 Web 服务里挺好用,但大模型推理请求完全不符合这个假设。请求 A 只需要 20 秒生成 200 个 token,请求 B 可能要 3 分钟生成 1200 个 token,两者占用的显存、算力、时间完全不同。如果轮询把两个长请求都打到同一张卡,另一张卡虽然空闲,用户却感觉系统“卡死了”。
所以在推理集群里,负载均衡要从“连接级别”升级到“负载感知级别”。网关需要知道每个后端实例的实时状态:当前正在处理的请求数、队列长度、显存余量、平均 TPOT。加权轮询时权重要动态变化,权重大的节点被分到更多请求,权重小的被分到更少,而不是傻傻平均。另一个常用策略是最少连接的变体,把小请求优先送到队列短的节点,把长请求送到显存余量大的节点,这样可以让各节点的 batch 填充率都保持在大致相近的水平。
4.2 分层网关架构:从四层接入到七层分发
千卡规模下,负载均衡本身也必须分层,不能只靠一台机器扛。我采用的架构分两层:第一层是四层负载均衡,只做 IP 和端口的流量接入,用来扛住海量 TCP 连接;第二层是七层分发网关,负责解析 HTTP 和流式请求,按模型名、请求优先级、后端负载做智能路由。四层只管把流量送进网关集群,七层才真正做“把请求分给谁”的决策。
分层的核心原因是性能和隔离。四层负载均衡转发效率极高,可以把成千上万的并发连接均匀地分给一组七层网关;七层网关数量可以横向扩展,某个网关挂了,四层把它摘掉就行。否则如果所有请求都压在一台 Nginx 或自研接入服务上,它既要处理 TLS 又要解析请求又要做健康检查,很快会在高并发下成为热点。我们线上结构大概是这样的:外部流量到四层集群,再到七层网关,最后到推理节点。网关要维护一个全局后端列表,定期对后端做健康检查,发一个最小推理请求,如果后端连续多次超时或返回错误,立刻摘除,同时触发新的实例扩容。健康检查间隔不能太短,否则请求量大会形成健康风暴,我一般设置在 5 到 10 秒。
4.3 全局 Batch 与跨节点协作
负载均衡分到节点后,还没结束。千卡集群最高效的方式是把所有空闲的计算资源放进一个全局资源池,引擎调度器能看到整个池子的实时负载,并把请求派到最合适的一组卡上。比如同样跑 Qwen3 27B,集群里可以有 10 个数据并行副本,每个副本内部又用 4 张卡做张量并行。网关只负责把请求分到副本层面,副本内部的调度器再做连续批处理。这里有个实践原则:数据并行副本之间不要共享显存,也不要共享 KV Cache 状态,这样才能做到故障隔离。
按这个结构算一下千卡规模的容量:假设一个节点 8 卡,每 4 张卡组成一个张量并行副本,单节点跑 2 个副本;那么 1000 卡可以组织成 250 个副本。如果每个副本实际吞吐是 40 token/s,理想情况下集群总吞吐可以到 10000 token/s,也就是 1 秒能生成 1 万 token。但真实环境里会被网关转发、网络跨机、批处理碎粒度、慢节点长尾等因素打折,可能只能跑到理论值的 60% 到 80%。这个打折比例就是架构设计要盯的 KPI。
5. 实测数据与性能推算:别靠感觉扩卡
5.1 压测前先拿单卡数据当基线
我所有集群调优都从单卡开始。先压出单卡的极限数据,再分析瓶颈,最后决定要不要扩。压测工具不需要多复杂,我用 aiohttp 写的一个几十行 Python 脚本,同时发起 N 个请求,每个请求生成固定长度的文本,最后统计成功率、TTFT、平均 TPOT 和总吞吐。代码大概是这样的:
import asyncio import time import aiohttp async def send_one(session, url, prompt, task_id): payload = {"prompt": prompt, "max_tokens": 128} t0 = time.time() async with session.post(url, json=payload) as resp: out = await resp.json() latency = time.time() - t0 tokens = out.get("usage", {}).get("completion_tokens", 128) print(f"{task_id}: total {latency:.2f}s, {tokens} tokens") return tokens, latency async def main(): concurrency = 8 url = "http://gateway:8000/v1/completions" prompt = "请写一段800字的短文。" async with aiohttp.ClientSession() as session: tasks = [send_one(session, url, prompt, i) for i in range(concurrency)] results = await asyncio.gather(*tasks) total_tokens = sum(r[0] for r in results) wall_time = max(r[1] for r in results) print(f"系统吞吐约 {total_tokens / wall_time:.2f} token/s") if __name__ == "__main__": asyncio.run(main())注意这个脚本只是估算系统吞吐,并不精确,但足够用来对比不同配置下的相对差异。实际压测时要注意:并发数的增加不是线性收益。我拿 K100AI 单卡跑 Qwen3 27B 测了一组数据,INT4、上下文 4K:单请求时 TPOT 约 22ms,并发 4 个请求时 TPOT 恶化到 35ms,并发 8 个时到了 70ms,总吞吐从 45 token/s 涨到 110 token/s 就不再涨了。这说明单卡在 4 到 8 并发之间的性价比最好,超过 8 并发后单个请求的延迟会快速劣化。这个“甜点位”就是后续集群扩容量时最关键的输入参数。
5.2 从单卡到千卡的扩张推算
扩容逻辑并不复杂。假定每个副本(4 卡 TP)稳定吞吐 40 token/s,网关给它分配的最大并发是 8;单节点 8 卡能跑 2 个副本,所以单节点吞吐约 80 token/s。要支撑 10000 token/s 的集群吞吐,需要 250 个副本,也就是 1000 卡。这个数字是不是正好印证了千卡负载均衡?但注意,这是理想值,现实里模型版本更新、显存碎片、请求不均匀都会吃掉一部分容量。
更合理的做法是先用数据推演压测目标,再小规模验证,最后大规模展开。我一般建议按目标吞吐的 1.25 倍来规划副本数,多出来的 25% 用来吸收节点故障和流量毛刺。钱要花在通信和调度软件上,而不是盲目加卡。很多集群瓶颈根本不是 GPU 算力不够,而是网关、网络、引擎配置三座大山压住了吞吐。
5.3 压测怎么读数据:不止看平均
压测里最容易被误导的是“平均延时”。大模型服务是长尾分布的重灾区,P50 看起来 800ms,P99 可能已经 15 秒。所以每次压测结果必须记录四个值:P50 TTFT、P95 TTFT、P50 TPOT、P95 TPOT,另外加一个错误率和超时请求数。如果 P95 比 P50 高出好几倍,说明负载均衡或批处理策略有问题,通常是有少数副本被挤爆了。这时候不能继续加并发,而是要去查网关的分发是否均匀,以及引擎队列里是不是积压了长请求。
我常用的一个排查小技巧是:在压测脚本里给每个请求打上“模型版本 + 请求序号 + 节点标识”,后端回传时带上节点 ID,最后在日志里按节点分组统计。如果某几个节点的平均 TPOT 明显高于其他节点,99% 是负载不均衡,还有 1% 是这几个节点上磁盘或网络出了问题。这个办法简单粗暴,但能把问题从全局迅速定位到具体节点。
6. 高频问题与排查实录:踩过的坑都在这
6.1 五个高频问题速查
| 现象 | 可能原因 | 排查手段 |
|---|---|---|
| GPU 利用率 100% 但吞吐只有几十 token/s | Decode 阶段受显存带宽限制,属正常现象 | 看 TPOT 是否稳定,压测并发是否过高 |
| 多卡集群加了新节点,吞吐不涨反降 | 网关没感知到新节点,或健康检查把它摘了 | 检查网关后端列表,观察日志里新节点是否有请求 |
| 显存 OOM,但系统还有大量内存 | KV Cache 上限配置过高,或并发请求超出显存预算 | 调低引擎 KV Cache 预留值,限制单节点并发数 |
| 某个节点 TPOT 突然变成几百毫秒 | 该节点网络丢包,或张量并行跨机性能劣化 | 查 RoCE 丢包计数,看机内互联流量,必要时把 TP 组限制在单机 |
| 网关 CPU 打满但 GPU 闲着 | 网关做了太多格式转换、编解码 | 升级网关,或合并转发请求,减少 TLS 开销 |
6.2 慢节点与长尾效应
千卡场景下最棘手的问题之一就是慢节点。一批 100 个副本里只要有一个节点的 TPOT 是别人的 5 倍,大量请求就会在它那里被拖住,而网关可能还在不停地把请求发给它。如果健康检查只探测服务是否活着,不探测服务是否变慢,这个问题极难发现。我的做法是在网关侧做慢实例摘除:当某节点的滚动 TPOT 连续超过阈值,比如 300ms,立刻把它的权重调低到 20%,并触发一次实例重启或扩容提示。这个过程要做得很克制,不然健康检查本身也会引发流量抖动。
6.3 网络层排查与跨节点优化
跨节点通信是千卡集群里最容易被低估的部分。张量并行组内是强同步通信,一次 allreduce 的延时会直接加到每次 Decode 上。如果 TP 组分布在两个机架,机架交换机的转发延迟和带宽竞争都会拖慢整体节奏。我在部署时有一条硬规则:TP 组不跨交换机,数据并行副本则尽量打散到不同机架和电源域,这样单点故障不会带走整条链路。机内则要把多卡拓扑和网卡的 NUMA 亲和性调好,最忌讳的是拿到一台 8 卡机器不做拓扑检查就开跑,结果 GPU 和网卡在不同 NUMA 域,网络跨了远跳。
6.4 调优顺序建议
如果你们集群的吞吐不达标,建议按下面顺序排查,不要一上来就调模型参数量。第一步看网关:分发是否均匀、健康检查是否正常、有没有把流量全打到同一组。第二步看节点:每台 GPU 的利用率分布、显存剩余、队列长度。第三步看网络:跨机流量、丢包、时延抖动。第四步才动引擎参数:KV Cache 上限、连续批处理最大 batch、并发请求数。按这个顺序,绝大多数问题都能在 20 分钟内定位到根因。我自己在线上的经验是,超过一半的“吞吐不达标”问题都出在前两步,而不是引擎或模型本身。
最后分享一个我的体会。千卡推理集群不是单卡直接乘以 1000,而是一个调度系统、通信系统和资源管理系统的整体工程。真正让我觉得值回票价的,不是最后那几千 token/s 的数字,而是把一个复杂的系统拆成单卡基线、并行策略、负载均衡、可观测性这些独立模块后,每个模块都能被单独验证和调优。这套“先单卡压个底,再分层查瓶颈”的方法,比堆任何花哨架构都可靠得多。如果你们也是从单卡起步,建议把每一步的基线数据都记下来,以后无论扩容还是排查,这些数据都是最值钱的资产。