1. 从单卡到千卡:先搞清楚我们要解决什么问题
先说个真实场景。很多人第一次接触大模型推理,是从单卡跑Qwen、Llama这类开源模型开始的。一张卡,装个vLLM或者TGI,起个服务,接口调通,感觉“推理也没多难嘛”。但一旦业务量上来,并发请求一多,单卡立刻就被打穿。这时候你自然想到加机器,于是从1张卡变成4张卡、8张卡,再往后就是几十台机器、上百张卡。问题就从“怎么跑通模型”变成了“怎么让这一堆卡协同工作,还能稳定扛住流量”。
这篇内容就是围绕这个演进过程来写的:先拆解单卡推理的瓶颈,再讲多卡、多机的集群架构设计,最后落到千卡规模下的负载均衡和调度策略。适合三类人看:刚入门想做推理服务优化的工程师、已经在用多卡但遇到性能瓶颈的团队、以及准备设计大规模推理平台的后端同学。我会尽量用实际踩过的坑来讲,不堆概念。
这里先给一个整体认知:大模型推理集群和训练集群的设计逻辑完全不同。训练集群追求的是“把算力榨干”,batch size可以怼得很大,容忍一定的延迟;推理集群追求的是“在延迟稳定的前提下把吞吐拉满”,而且要处理动态变化的请求。这个本质差异决定了后面所有架构选择。
2. 单卡推理的瓶颈拆解:一张卡到底能扛多少
2.1 单卡推理的极限在哪里
先拿最常见的配置说:一张NVIDIA H100(80GB显存)跑Qwen3 8B或者27B这种规模的模型。很多人以为瓶颈在GPU算力,实际测下来会发现,算力往往不是最先触顶的。负载均衡的关键往往在于推理引擎的调度能力和显存管理。
以H100跑Qwen3 8B为例,实测下来,在vLLM默认配置下,单卡能做到的吞吐大概是:
| 指标 | 参考值 |
|---|---|
| 输入长度(平均) | 512 tokens |
| 输出长度(平均) | 256 tokens |
| 并发请求数 | 16-32 |
| 吞吐量 | 800-1500 tokens/s |
| 单请求延迟(P95) | 1.5-3秒 |
这组数据会随模型、量化方式、显存大小浮动,但规律是一致的:显存容量决定了你能同时塞多少请求,计算吞吐决定了你每秒能吐多少token,两者必须匹配。
有个很容易忽略的点是KV Cache。以8B模型为例,模型权重本身可能只需要16GB显存(FP16),但KV Cache会随着并发数线性增长。假设每个请求预留4096 tokens的KV Cache空间,一个请求大约吃掉几十MB显存,并发32路时就要额外占用好几个GB。如果并发继续往上加,显存先爆,服务直接OOM,算力再强也没用。
2.2 单卡推理的几个典型瓶颈点
实际压测下来,单卡推理最先出现的瓶颈按出现频率排序是:
第一,显存容量触顶。模型权重 + KV Cache + 激活值,三者加起来超过显存总量,服务直接崩。这个最无解,只能换更大显存卡或者做量化。
第二,调度开销。默认的连续批处理(continuous batching)如果实现得不好,请求排队时间会很长。实测下来,vLLM的调度器在并发超过某个阈值后,调度延迟会明显上升,表现为P95延迟突然拉高。
第三,GPU利用率不均衡。这个在单卡上不太明显,在多卡并行时才是大问题——后面我会专门讲。
第四,输入输出长度波动。真实业务里,有的请求输入只有几十个token,有的输入是几千token的长文档。如果按最大长度预留资源,显存浪费严重;如果不预留,又可能触发OOM。
单卡阶段能做的事其实有限:调大batch size、开continuous batching、做模型量化(FP8甚至INT4)、用paged attention减少碎片。这些优化做完,单卡Qwen3 8B大概能从几百tokens/s提升到1500 tokens/s左右。但再往上就真到头了。
2.3 单卡到多卡:直觉方案为什么不对
很多人第一步想到的是:一张卡跑不完,那就把模型切到多张卡上,用张量并行(tensor parallelism)。这个思路本身没错,但要注意,张量并行是“用通信换显存”,GPU之间的通信开销会吃掉一部分算力。模型越小、单卡显存越够用,张量并行带来的收益就越低,甚至可能变负。
举例说明。Qwen3 8B在单张H100上本来跑得好好的,你非要用2卡张量并行,结果每卡只占一半显存,但每次前向计算都要跨卡同步两次,通信延迟直接拖慢整体速度。实测下来,8B模型2卡张量并行的吞吐反而不如单卡,延迟还会高一截。这时候正确的选择是数据并行(把请求分发到多张卡上,每张卡独立跑完整模型),而不是张量并行。
所以单卡到多卡的第一个决策点在于:模型适合用张量并行切分,还是适合用数据并行扩展?判断标准很简单——单卡显存能不能装下模型权重加上合理规模的KV Cache。装得下,就优先数据并行;装不下,才考虑张量并行。这也是为什么很多生产环境里,7B-14B级别的模型用数据并行,70B级别的模型才必须上张量并行。
3. 多卡推理的集群架构:两种扩展方式的取舍
3.1 数据并行 vs. 张量并行:什么时候选哪个
数据并行(DP)的思路是:每个GPU上都放一份完整模型,请求按一定策略分发到不同的GPU上,各跑各的。它的好处是扩展性好、没有通信瓶颈,坏处是显存浪费——每张卡都要放一份完整的模型权重。
张量并行(TP)的思路是:一个模型切成多份,分别放在多张卡上,一次前向计算需要多卡协同完成。它的好处是能跑单卡装不下的超大模型,坏处是通信开销随并行度增长,通常建议TP大小不超过8(NVLink带宽范围内)。
我的建议是:能DP就别TP,除非模型真的放不下。原因很直接——DP架构下加机器就是线性扩展,每加一张卡就多一份吞吐;TP架构下加卡,性能增长是边际递减的,而且对网络带宽的要求极高。
实际项目中,7B、8B、13B级别的模型,在A100/H100上DP就够了。70B级别,至少需要2卡或4卡TP。而如果做MoE模型(比如Qwen3 30B-A3B这种),因为激活参数少,TP的收益会被通信开销抵消一部分,需要实测调TP大小。
3.2 多机部署的组网需求
多卡部署还只是“单机多卡”,一旦规模到几十张卡,就要跨机器组网了。推理集群对网络的要求比训练集群宽松一些,但也不能随便弄。
关键判断是:机器之间的通信频率。DP模式下的多机通信主要是请求分发和结果汇总,频率低、数据量小,万兆以太网基本够用;TP模式下的多机通信是每层都要做AllReduce,频率极高,必须走InfiniBand或者至少RoCE(RDMA over Converged Ethernet),否则通信延迟会直接吃掉模型并行带来的收益。
具体的组网方案可以这样参考:
- 纯DP集群,单机8卡,机器间万兆网即可,但要注意前端负载均衡的带宽。
- 2机TP=16的70B推理,机器间必须上IB网络,否则性能约等于不可用。
- 混合方案:单机内部用NVLink做TP,机器之间做DP,这是目前最主流的千卡集群布局方式。
这里有个经验值:TP通信的数据量大约等于激活值大小乘以TP并行度。70B模型,hidden size通常是8192,一层Transformer的激活值就有几十MB,每层两次AllReduce,几十层下来通信量非常可观。所以跨机TP一定要谨慎,能塞进单机8卡的就不要跨机。
3.3 推理引擎选型:vLLM、TGI、TensorRT-LLM怎么选
集群架构定下来之后,推理引擎就是承载一切的基础。现在主流就三个:vLLM、HuggingFace TGI、TensorRT-LLM(以及基于它做的Triton Inference Server)。
我的选择逻辑是这样:
先用vLLM做默认选项。理由很简单:生态最成熟、支持模型最多、社区活跃、API兼容OpenAI格式,落地最快。我实测vLLM的continuous batching和paged attention在长上下文章节里的表现,是三个引擎里最稳的。
TGI的优势是在HuggingFace生态内集成度最高,部署最简单的模型,做一些diffusers类的多模态推理更方便。但性能上限和vLLM相比没有明显优势,并发控制不如vLLM灵活。
TensorRT-LLM是性能天花板。同样模型、同样硬件,TensorRT-LLM的吞吐通常比vLLM再高20%-40%。代价是编译时间长、模型切换成本高、动态shape处理麻烦。如果你做的是“固定几个模型长期服务”的生产环境,值得上;如果模型迭代频繁,建议先用vLLM扛着。
还有个容易被忽略的点:引擎和集群调度器的配合。vLLM原生支持张量并行和流水线并行,但数据并行场景下,多个vLLM实例之间没有自动负载均衡,需要自己在前面挂一层路由。这点后面讲千卡调度时会详细说。
4. 千卡规模的负载均衡:从“能用”到“好用”的关键设计
4.1 千卡集群的基本拓扑
到了千卡规模,集群就不再是“一堆机器”了,而是要有清晰的逻辑分层。我常用的一套分层是:
- 接入层:负责统一入口,处理认证、限流、路由。
- 调度层:负责把请求分配到具体的推理实例,核心是负载均衡策略。
- 推理层:实际跑模型的GPU实例,通常以“实例组”(每组若干张卡)为单位暴露服务。
- 存储层:模型权重、KV Cache的offload、日志和监控数据。
千卡部署时,最忌讳的是把集群当成一个大的“GPU池子”,所有请求随机扔给任何一台机器。正确的做法是把实例按照模型、规格、性能分成不同的池子,每个池子独立扩缩容。
举个例子。你有1000张H100,可能这样划分:
- 池A:200张卡,跑Qwen3 8B,TP=1,DP=200,承载大部分在线对话流量。
- 池B:400张卡,跑70B模型,TP=4,组内100个实例,承载高复杂度推理。
- 池C:200张卡,跑多模态模型,TP=2,承载图片输入类请求。
- 池D:200张卡,作为弹性缓冲池,任何池子流量突增时都能临时接管。
这种池化设计的核心好处是:隔离故障域。池A出了问题不会拖垮池B;模型升级也只是在池内滚动发布,不影响其他池子。
4.2 负载均衡策略:不只是“轮询”那么简单
很多人以为负载均衡就是请求轮流分发,实际在千卡推理场景里,简单的轮询(Round Robin)是不够的,因为每个推理实例的“负载”不是一个简单的计数,而是动态变化的。
推理实例的负载取决于三个因素:当前正在处理的请求数、每个请求的剩余输出长度、以及GPU的实际利用率。同样一个实例,可能刚接了一个输出1000 tokens的长请求,也可能刚完成一批短请求。按请求数轮询,会把长请求集中打到同一个实例上,造成局部热点。
在实践中,我推荐两层的负载均衡设计:
第一层,全局调度器根据“实例当前活跃请求数 + 预估剩余token数”做加权路由。这个信息可以从vLLM暴露的/metrics接口实时获取,不需要额外的agent。
第二层,实例内部靠vLLM自己的continuous batching调度,保证同一实例上的请求尽可能均匀地被GPU处理。
两层之间有一个配合点:全局调度器下发请求时要“慢半拍”,不要一次性把队列塞满。给推理实例预留一点缓冲空间,让它在处理完当前批量时能立刻接上新的请求,而不是一直在排队。
关于“等开销负载均衡”,简单解释一下:不是让每个实例处理的请求数完全一样,而是让每个实例的GPU忙闲程度尽量一致。忙闲不均是推理集群最大的性能杀手——同一个集群里,一张卡被打满、另一张卡在空转,整集群的吞吐上不去,还可能因为热点实例超时造成连锁失败。
4.3 请求级调度策略:排队、优先级和抢占
千卡规模的推理集群,流量绝不是均匀的。高峰期可能几十倍于低谷期。这时候调度策略要从“分配”转向“排队和优先级管理”。
我对生产系统的建议是:
- 给每个请求打上优先级标签(在线交互 > 异步任务 > 批量评测)。
- 优先级高的请求可以“插队”,但要有超时保护——不能无限抢占资源,否则低优先级任务会饿死。
- 队列长度要设上限,超过上限直接拒绝(返回503),不要无限排队拖垮系统。
这里有一个关键的工程细节:在线推理和离线批处理的资源池要分开。把离线任务(比如批量评测、数据标注)和在线对话放在同一个池子里,离线任务虽然优先级低,但它也会占用显存和算力,一旦并发量大,在线请求的延迟就会飙高。我的做法是:在线池和离线池物理隔离,离线池有空闲时,让在线池的请求“借用”资源,但高峰期禁止借用反向。
请求级调度还要注意超时设置。推理请求和普通HTTP请求不同,一个大模型生成几百个token可能需要好几秒,网关的超时时间不能设得太短。一般我会把超时设成“模型配置的最大生成时间 + 一定余量”,而不是传统的3秒5秒。否则你会发现,长输出请求全被网关掐断,报错率看起来很高,但实际上是超时造成的假象。
4.4 实例生命周期管理:扩容、缩容和滚动更新
千卡集群的另一个核心问题是实例管理。推理实例和传统无状态服务不一样——加载一个70B模型进显存,可能需要几十秒甚至几分钟。如果频繁扩缩容,资源浪费和启动时延会把整个集群拖垮。
所以生产环境的推理实例管理,我强烈建议“池子预热 + 慢扩快缩”的策略:
- 每个模型池维护一个最小实例数,保证任何时刻都有“热的”实例在跑,新扩容的实例在后台预热。
- 扩容时,新实例先把模型加载好、健康检查通过,再开始接流量,不要一加入集群就立刻分发请求给它。
- 缩容时,先把实例置为“排空”状态(不再接收新请求),等已有请求都结束后再释放资源。
滚动更新同理。更新模型版本或者引擎版本时,一次只替换一个实例,并且分批次验证。不要同时更新超过池子25%的实例,否则一旦新版本有问题,整个池子的可用容量暴跌。
这些经验看着朴素,但真到千卡规模,哪天不小心把全部实例同时重启,那画面太美不敢看。集群故障多半不是设备问题,而是管理操作失误。
5. 核心环节实操:从部署到压测的完整流程
5.1 环境准备与配置清单
先列一份我常用的配置清单,基于NVIDIA H100集群,每台机器8卡:
| 项目 | 配置 |
|---|---|
| GPU | NVIDIA H100 80GB x8 |
| CPU | 64核以上,建议96核 |
| 内存 | 512GB以上 |
| 系统盘 | 1TB NVMe SSD |
| 数据盘 | 4TB NVMe SSD,用于模型权重缓存 |
| 机间网络 | InfiniBand 200Gbps(TP跨机必需) |
| 机内网络 | NVLink + PCIe Gen5 |
| 推理引擎 | vLLM 0.6+ |
| 调度器 | 自研路由层 + Kubernetes + 自定义调度器 |
模型权重建议预先下载到本地数据盘,不要每次启动都从对象存储拉——70B模型权重有140GB左右,从网络拉一次可能好几分钟,这时间够加载好几次了。
5.2 单卡部署vLLM的完整步骤
先以单卡部署Qwen3 8B为例,把基线流程跑通,再扩展。首先安装vLLM:
pip install vllm然后写一个启动脚本,核心参数如下:
python -m vllm.entrypoints.openai.api_server \ --model /data/models/Qwen3-8B \ --served-model-name qwen3-8b \ --tensor-parallel-size 1 \ --max-num-seqs 32 \ --max-model-len 8192 \ --gpu-memory-utilization 0.9 \ --port 8000这里的几个参数值得解释一下:
max-num-seqs:同时最多处理多少个请求。设太大容易显存OOM,设太小浪费算力。8B模型在H100上,32左右是比较安全的起步值,可以逐步往上调。max-model-len:最大上下文长度,影响KV Cache预留。设8192不会让模型真的用到8192,但会按8192预留显存预算。gpu-memory-utilization:GPU显存利用率上限,默认0.9,即预留10%给CUDA context和碎片。实测0.92-0.95也能稳定运行,但0.9最保险。
启动后先用简单的curl验证:
curl http://localhost:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{"model": "qwen3-8b", "messages": [{"role": "user", "content": "你好"}], "max_tokens": 100}'能返回结果说明服务起来了。这时候可以再用压测工具测单卡吞吐,推荐用vllm-benchmarks或者简单的并发脚本。
5.3 多机部署与Kubernetes配置示例
单机部署跑通后,多机集群我推荐基于Kubernetes管理。原因很简单:千卡规模的手动管理不现实,而K8s即便有各种槽点,它的声明式管理、滚动更新、故障自愈能力在推理场景里够用。
先写一个Deployment配置示例(省略了namespace等常规字段):
apiVersion: apps/v1 kind: Deployment metadata: name: qwen3-8b-dp labels: app: qwen3-8b spec: replicas: 8 template: metadata: labels: app: qwen3-8b spec: containers: - name: vllm image: vllm/vllm-openai:latest command: ["python", "-m", "vllm.entrypoints.openai.api_server"] args: - --model=/data/models/Qwen3-8B - --served-model-name=qwen3-8b - --tensor-parallel-size=1 - --max-num-seqs=32 - --max-model-len=8192 - --gpu-memory-utilization=0.9 - --port=8000 env: - name: CUDA_VISIBLE_DEVICES value: "0,1,2,3,4,5,6,7" resources: limits: nvidia.com/gpu: 8 volumeMounts: - name: model-storage mountPath: /data/models volumes: - name: model-storage hostPath: path: /mnt/models这个配置跑起来后,每个Pod会占用一张节点上的8张卡,各自独立跑数据并行。然后需要给Pod加一个Service,做集群内负载均衡:
apiVersion: v1 kind: Service metadata: name: qwen3-8b-service spec: selector: app: qwen3-8b ports: - port: 8000 targetPort: 8000 type: ClusterIP这个Service默认是轮询负载均衡。之前说过,单纯的轮询不够,所以生产环境我会再加一层自定义调度器,根据/metrics的数据做加权分发,而不是直接用Service自带的负载均衡。
5.4 压测方法与调优记录
部署完成必须压测,否则集群到底能扛多少负载完全是玄学。压测我用的是Locust写并发脚本,模拟真实请求序列:
import random import time from locust import HttpUser, task, between class InferenceUser(HttpUser): wait_time = between(0.5, 2) @task def chat_completion(self): prompt_len = random.randint(100, 2000) prompt = "测试文本" * prompt_len max_tokens = random.randint(50, 500) self.client.post("/v1/chat/completions", json={ "model": "qwen3-8b", "messages": [{"role": "user", "content": prompt}], "max_tokens": max_tokens })压测时我重点看四个指标:
- 吞吐量(tokens/s):整体每秒生成的token数。
- P90/P95延迟:大多数请求的完成时间,比平均延迟更有参考意义。
- 拒绝率:因为超时或OOM被掐断的请求比例。
- GPU利用率分布:是否所有卡都忙得均匀。
第一轮压测通常会发现几个问题:某个节点网络带宽成了瓶颈、某个实例GPU利用率偏低、或者某个pod的内存被打满。这时候就要微调参数,最常用的调优手段是:
- 调整
max-num-seqs,观察显存余量变化。 - 调整负载均衡权重,把请求倾斜到GPU用量低的实例。
- 对超长请求做超时保护,防止个别慢请求拖死整个实例。
实测下来,一个8卡节点跑Qwen3 8B,单实例压测到40并发左右,P95延迟开始明显上升。这时候可以判断当前配置下的合理负载水位,并在前端限流器上按这个水位设置阈值,别让流量无限制地涌进来。
6. 常见问题与排查技巧实录
6.1 典型故障现象与解决思路
千卡推理集群的故障,最常见的有这么几类:
**故障一:个别GPU利用率100%,其他GPU只有30%。**这个大概率是请求分配不均匀,热点请求集中在某几个实例上。排查思路是看调度器的路由日志,确认是不是轮询策略把长请求连续打到了同一个实例。解决办法是把负载均衡策略从“按请求数”改成“按活跃token数”。
**故障二:压测时吞吐上不去,GPU利用率整体不到50%。**这种情况多半是瓶颈在CPU或网络,不在GPU。先看CPU占用,如果CPU吃满,说明tokenize/调度开销太大,需要加大max-num-seqs让GPU更忙;如果CPU正常,再看网络流量,跨机TP场景下通信量过大会让GPU花时间等数据。
**故障三:服务正常,但P95延迟突然飙到10秒以上。**这个最常见的原因是长尾请求——某个请求的输出长度特别长,一直占着batch slot,导致其他请求排队时间变长。解决思路是给max_tokens设上限,或者干脆把超长请求路由到专门的长文本实例组。
这几个问题看着简单,但千卡规模下的排查比单机复杂得多。单机可以nvidia-smi盯着看,千卡规模必须依赖监控系统。我的配置是Grafana + Prometheus采集每个实例的GPU利用率、显存、队列长度、请求延迟,告警阈值设在利用率不均超过30%或者P95延迟超过目标值的时候触发报警。没有这套监控,千卡规模下的运维就是盲人摸象。
6.2 负载均衡的隐藏坑:实例容量差异
这是我在实际运维中踩过最深的一个坑。千卡集群里的实例,即使型号完全一样,实际性能也有差异。因素很多:H100的芯片体质差异、散热条件不同导致的降频、同一台机器上不同PCIe槽位的带宽差异、甚至同一张卡上不同NVLink端口的速率差异。
如果负载均衡不考虑这些,按“理论容量”均摊流量,实际表现就是:配置完全相同的两个实例,一个P95延迟400ms,另一个P95延迟900ms。表面上看是负载均衡的锅,其实是硬件差异叠加了负载分配偏差。
我的解决办法是在调度层引入“动态权重”机制。每个实例定期上报自己的“最近N分钟的P95延迟”和“GPU平均利用率”,调度器根据这些指标实时调整权重。延迟高的实例分到的请求自然少一些,延迟低的实例多吃一点流量。这比静态权重靠谱得多,因为它是根据真实表现自适应调整的。
6.3 推理集群的常见误区速查
最后整理一个我常跟团队强调的误区清单:
| 误区 | 实际情况 |
|---|---|
| 总觉得GPU算力不够,猛加卡 | 很多时候瓶颈在调度、网络或显存,加卡不解决根本问题 |
| 用轮询做负载均衡就够了 | 推理请求的动态性决定了必须考虑tokens分布,不是简单计数 |
| 一张卡OOM就加张量并行 | 小模型先考虑数据并行,TP未必带来收益 |
| 长请求和短请求混在一起跑 | 会导致长尾延迟,最好做队列分离或池子分离 |
| 压测只看平均延迟 | 推理场景必须看P95/P99,平均延迟掩盖问题 |
| 模型的batch size和训练一样能怼大 | 推理受延迟约束,batch不是越大越好 |
这些误区的根源,是把训练集群的经验直接套到推理集群上。两者的目标和约束完全不同,架构设计也必须分开考虑。
7. 写在最后:我踩过坑后的几点体会
这个项目做下来,我最深的体会是:大模型推理集群的架构设计,本质上是个资源管理问题——管理GPU算力、显存、网络带宽、请求队列这四类资源,让它们在一个动态变化的流量环境下保持平衡。具体到落地,有几点想分享给你的经验。
第一,不要一上来就追求“千卡”。先从单卡把模型跑顺、把延迟和吞吐摸清楚,再扩展到4卡、8卡,每一步都要有压测数据支撑。直接拍脑袋上1000张卡,出了问题连排查的方向都没有。
第二,负载均衡是大集群的灵魂,但要分层做:全局调度器管实例间分配,推理引擎管实例内调度,两层配合才能既稳又高效。很多时候你以为是引擎不行,其实是上层分发出了问题。
第三,监控和可观测性在集群规模面前不是“加分项”而是“必需品”。在单卡时代你用nvidia-smi就能排查,但千卡规模下没有监控数据,一个故障可能要人肉排查几个小时。我知道搭建监控系统比较枯燥,但它会在故障时救你于水火。
第四,也是最重要的一条:测试环境一定要模拟生产流量。很多集群故障不是硬件问题,而是“压测环境是压测环境、生产环境是生产环境”造成的。请求长度分布、并发模型、超时配置,都必须在真正上线前用真实流量回放验证一遍。我见过太多团队在压测环境跑得好好的,一上生产就崩,根源就在这。
这整套架构不是一成不变的,模型在变、推理引擎在变、硬件也在变。但核心的资源分层和管理思路是稳定的——把算力、显存、网络这些资源管好,把请求合理地分配上去,无论底层怎么换,这个框架都能用。最后祝你们的集群上线顺利,少踩我踩过的那些坑。