我做模型批处理吞吐量对比时,最先做的事是把口径统一。口径不统一,后面的GPU对比、框架对比都会变成数字游戏。我的压测报告里会写清楚tokens/s、requests/s、samples/s和有效吞吐量,也会注明输入输出长度、并发度、精度和硬件配置。业务同学看容量,算法同学看瓶颈,运维同学看稳定性,这三层视角要在一份报告里对得上。
1.1 吞吐量定义:tokens/s、requests/s、samples/s与有效吞吐量
tokens/s是我做大模型压测时最常用的吞吐指标。它表示单位时间内处理或生成的token数量,压测报告里我会分开写“输出tokens/s”和“总tokens/s”。输入长度很长时,总tokens/s会好看一些,用户真正感知到的生成速度要看输出tokens/s。这个细节不写清楚,模型批处理吞吐量对比很容易失真。
requests/s更贴近业务容量,适合看每秒能接多少请求。samples/s适合分类、嵌入这类固定样本任务,一次请求可能包含多个样本。我会用同一份业务负载把三个指标换算到同一口径,再做横向比较。数字好看不等于系统好用,指标背后的请求结构要摊开看。
有效吞吐量是我最看重的口径。它只统计满足SLO的成功请求,把超时、失败、截断、排队过久的请求剔除掉。业务同学关心有效吞吐量,运维同学关心失败率和排队时长,算法同学关心总吞吐上限。我在报告里会把总吞吐和有效吞吐并排放,避免拿实验室峰值去承诺线上容量。
1.2 批处理关键参数:batch size、max batch tokens、动态批处理与连续批处理
batch size是单次并行处理的请求数或样本数,max batch tokens是单次批处理的token预算。短Prompt场景里batch size很直观,长上下文场景里我更盯max batch tokens。token预算设得太松,显存容易被打爆;设得太紧,GPU利用率上不去。我一般会做一轮参数扫描,把显存占用、吞吐和P99延迟放在一起看。
动态批处理按请求到达节奏组批,适合流量有波动的服务。连续批处理在解码步持续调度,不等整批请求全部结束,交互式服务里它的吞吐提升很明显。我在同一张卡上对比过两种模式,连续批处理对长短请求混跑的帮助更大,调度器实现质量也会拉开差距。
批处理参数不是越大越好。batch size拉高会带来更长排队时间和更高显存压力,max batch tokens拉高会放大长Prompt的尾部风险。我的做法是先定SLO,再找吞吐拐点。拐点之前的参数组合更稳,拐点之后的数字再漂亮也不适合直接上生产。
1.3 延迟与吞吐权衡:TTFT、TPOT、P99延迟与SLO约束
TTFT是首token延迟,用户发出请求到看见第一个字的时间。TPOT是每个输出token的平均时间,长回答场景里它直接影响阅读体验。P99延迟看尾部请求,线上投诉经常来自这批慢请求。我做模型批处理吞吐量对比时会同时记录TTFT、TPOT和P99,不只看平均延迟。
吞吐和延迟是一对需要摆在一起看的指标。并发度往上走,吞吐通常往上走,延迟也会往上走。我的做法是画吞吐-延迟曲线,把SLO线画上去,找曲线和SLO线的交点。交点附近的参数组合更有参考价值,有效吞吐量也在这个位置更有意义。
在线对话业务更在意TTFT和P99,离线批推理更在意总吞吐和成本,长文档处理更在意TPOT和显存余量。同一个模型在不同业务里的“好吞吐”不是同一个数。我会按业务场景分别定SLO,再把压测结果放进同一张对比表里。
1.4 评测环境与复现要素:模型规模、输入输出长度、并发度、精度与硬件配置
评测环境要写全。模型规模、输入输出长度分布、并发度、精度、GPU型号、显存容量、驱动版本、CUDA版本、推理框架版本,这些信息缺一项,复现就容易跑偏。我的报告里会放一张环境表,别人拿着这张表能搭出接近的测试环境。
warmup要做足,数据分布要贴近真实业务,随机种子和采样参数要固定。我吃过亏,冷启动阶段的数字混进平均值,吞吐看起来低了一截。压测脚本的请求间隔、超时设置、重试策略也要记录,这些设置会改变排队行为和失败率。
报告结构我会分成环境、负载、指标、结论四块。环境写清楚硬件和软件,负载写清楚请求结构,指标写清楚总吞吐和有效吞吐,结论写清楚适用边界。业务同学能看懂容量,算法同学能看懂瓶颈,运维同学能看懂风险,这份模型批处理吞吐量对比报告才算合格。
GPU型号这件事,我踩过的坑最多。同一份压测脚本,同一版推理框架,换个卡,吞吐数字能差出一倍甚至更多。我后来养成一个习惯,看任何一份吞吐报告,先找GPU型号那一栏,找不到就不往下看。模型批处理吞吐量对比,卡是地基,地基不稳,上面搭什么都是虚的。
我这一章的对比结论都来自同一套方法:固定模型、固定输入输出长度分布、固定并发梯度,扫batch size,记录有效吞吐和P99。数字我会给区间,不会给一个精确到小数点的“标准答案”。不同框架、不同驱动版本、不同CUDA版本,结果都会漂。区间能表达量级,精确数字只会误导人。
2.1 数据中心GPU对比:A100、H100、L40S、A10等型号的批处理吞吐量差异
H100在我手上的连续批处理场景里,基本是A100的1.6到2倍。7B模型、FP16、短Prompt短输出,这个倍数比较稳。70B模型、长上下文,倍数会往1.5靠,甚至更低一些。原因不复杂,H100的HBM3带宽到3.35TB/s,A100是2TB/s左右,decode阶段是典型的访存密集,带宽给多少,吞吐吃多少。H100的FP8支持是另一个变量,精度降下去,吞吐能再抬一截,这个后面单说。
A100 80G是我心里的“基准卡”。我做任何对比,都先把它跑一遍,后面的数字都跟它比。A100的优势是显存容量和生态成熟度,80G能塞下的batch size和KV cache,24G卡想都别想。它的短板是没有FP8原生支持,跑FP16的时候功耗和吞吐的性价比不算最优。我在A100上跑70B的GQA模型,TP=4,8K上下文,能撑住的并发请求数比很多人预估的低,KV cache吃显存吃得厉害。
L40S这张卡有意思。48G显存,GDDR6,带宽864GB/s,纸面带宽只有A100的四成多。我一开始对它期望不高,实测下来,短Prompt、FP8、连续批处理的组合里,它能跑到A100的六到八成。它的算力不弱,FP8吞吐给得足,显存也够用。长上下文场景它就露怯了,带宽瓶颈明显,TPOT会拉长。我给它定位是“中等并发、成本敏感、精度可以降”的在线服务。
A10是另一条线。24G显存,600GB/s带宽,单卡跑7B FP16勉强够,跑13B就得量化。它的吞吐在我的测试里大概是A100的四分之一到三分之一。A10的价值不在吞吐,在密度和功耗,一张A100的功耗预算能塞好几张A10,做轻量并发或者多租户隔离,它有自己的位置。我见过拿A10跑70B的方案,靠量化加多卡,能跑,吞吐很难看。
这几张卡的差距,我一般会拆成两句话讲给业务同学听:显存决定你能不能上这个模型、能不能开大batch,带宽决定你每个token吐得多快。两张卡显存一样、带宽差一倍,吞吐大概率也差将近一倍。算力在prefill阶段有用,decode阶段更多看带宽。这个区分不搞明白,选型就容易选错。
2.2 消费级与边缘GPU对比:RTX 4090、RTX 3090、L4等适用边界
RTX 4090是我做小规模验证时最常用的卡。24G显存,GDDR6X带宽超过1TB/s,这个带宽比A10高一截,单请求decode速度不差。7B模型FP16,batch size小的时候,4090的表现会让人误以为它能干数据中心的活。batch size往上拉,显存先满,然后是无NVLink带来的多卡瓶颈。它的定位很清楚:单机、小并发、研发验证、私有化轻量部署。
RTX 3090比4090老一代,带宽936GB/s,24G显存,没有FP8。跑INT8量化模型还行,跑FP16的13B模型就比较紧。我在3090上做过7B的INT4部署,短对话场景能撑住个位数到十几的并发,再往上P99就难看了。3090现在的价值是存量设备利用,新采购我基本不推它,能效和生态都落后了。
L4是边缘场景里我很喜欢的一张卡。24G显存,带宽只有300GB/s,功耗70W左右,半高半长,能塞进很多机箱。它的吞吐不高,7B FP16的批量吞吐大概是A100的六分之一到五分之一。它的好处是稳定、省电、被动散热方案多,边缘节点、门店、工厂这类地方,供电和散热条件差,L4能落地。拿它跑高并发在线服务,不合适,别硬上。
消费级卡和数据中心卡的差距,很多时候不在峰值算力,在几个“软”指标上:ECC显存、虚拟化支持、驱动的长期维护、多卡互联、散热设计的持续负载能力。4090跑十分钟压测的数字很好看,跑十小时持续负载,频率会掉,风扇会吵,机箱温度会飘。我做生产选型,会专门跑一轮长稳测试,短时峰值参考价值有限。
还有一个边界是模型规模。24G卡跑7B FP16大概刚好,13B FP16就放不下,要量化或者分片。量化会改变吞吐曲线,INT4的7B在4090上很香,INT4的70B在4090上要靠多卡加offload,吞吐会掉到不可用的程度。我一般会画一张“显存-模型规模-精度”的可用性表,先筛掉跑不动的组合,再谈吞吐。
2.3 显存容量、显存带宽与GPU互联对批处理吞吐量的影响
显存容量对批处理吞吐量的影响,最直接的体现是KV cache。我算过一笔账:70B模型、GQA 8个KV头、head_dim 128、80层、FP16的KV cache,每个token大概320KB。8K上下文,一个请求就是2.5GB左右。A100 80G扣掉模型权重,能给的KV空间有限,batch size是被显存卡死的,不是被算力卡死的。这个账不算清楚,压测的时候batch开到一半就OOM,白忙。
带宽的影响在decode阶段最明显。自回归生成每吐一个token,都要把模型权重读一遍(或者读当前层),带宽就是天花板。H100的3.35TB/s和A10的600GB/s,差五倍多,单请求的TPOT差距也在这个量级附近。batch size开大之后,权重读取被多个请求分摊,带宽的边际收益会变化,这个拐点每张卡都不一样,得实测。
互联这事很多人忽略。单卡场景无所谓,多卡张量并行的时候,PCIe和NVLink的差距就出来了。TP=8跑70B,每层都要做all-reduce,PCIe Gen4 x16的带宽和NVLink差一个数量级,通信开销会把扩展效率吃掉一大块。我在PCIe机器上跑TP=8,扩展效率经常只有五成多,NVLink机器能到七八成。这个差距在batch小的时候更明显,通信占比更高。
显存带宽和互联还会互相放大。卡间通信慢,你就得把batch开大来摊薄通信开销,batch开大又吃显存,显存不够就得减并发。这是一个连环套。我做多卡方案的时候,会先把单卡的吞吐拐点找到,再决定要不要多卡、用几张、TP还是副本。顺序反了,方案就拧巴。
2.4 多卡并行与张量并行下的批处理吞吐量扩展性与效率损耗
多卡并行的扩展性,我分两种看。数据并行,也就是起多个模型副本,扩展效率高,接近线性,前提是显存够、请求调度能分摊均匀。张量并行,把单层切开,扩展效率低一些,通信开销躲不掉。我压测过的组合里,单机8卡NVLink跑TP=8,7B模型短序列,扩展效率大概七成到八成五;PCIe机器跑同样的配置,五成到六成五。这些数字跟batch size强相关,batch越小,效率越差。
TP的甜点位置我的经验是TP=2到TP=4。TP=8的通信量上去了,单请求延迟能降,总吞吐的提升不一定划算。70B这种单卡放不下的模型,TP=4是常见选择,四张80G卡,显存够KV,通信还在可接受范围。百B级模型往上,TP=8加流水线并行,调优空间就很大了,调度和并行策略配不好,八张卡的吞吐可能只比四张卡高两三成。
副本扩展看起来简单,也有坑。多个副本共享一张卡的时候,显存要分,batch size要分,吞吐不一定线性涨。多个副本跑在不同卡上,前面的负载均衡做不好,会出现一张卡排队、一张卡闲着的情况。我在线上见过副本数加了,有效吞吐没动,查下来是请求分发策略按连接数分,没按在途token数分。这种问题压测不容易暴露,得上真实流量。
效率损耗我不只看总吞吐,还看单位token成本和P99。TP=8把总吞吐拉高了,P99可能也拉高了,通信抖动会传到尾延迟上。副本方案P99更稳,总吞吐可能低一点。在线服务我偏向副本,离线批推理我偏向TP加高batch。这个选择没有标准答案,得看SLO卡在哪。
最后说一句我对“扩展性”的态度。厂商标称的线性扩展曲线,我基本不信。我自己跑,从1卡到2卡、2卡到4卡、4卡到8卡,一段一段测,每段的效率损耗记下来。模型批处理吞吐量对比做到这个颗粒度,选型的时候心里才有底,也不会被一张漂亮的PPT带偏。
同一个模型,同一张卡,换一个推理框架,吞吐数字能翻倍。这话不是夸张,是我过去一年在A100和H100上反复压测得出的结论。框架这层“软件栈”对批处理吞吐量的影响,有时候比从A10换到A100还大。我见过不少团队,花大价钱买了H100,结果用了一个调得半死的默认配置,吞吐跑出来的数字跟A100差不多,白瞎了硬件预算。
我做框架对比的方法很土:同一台机器,同一个模型权重,同一套输入输出长度分布,同一组并发梯度,挨个框架扫batch size,记录有效吞吐、TTFT、TPOT和P99。每换一个框架,配置项至少要花半天到一天来摸,不能拿默认配置糊弄自己。这篇写的数字都是这么来的。
3.1 主流推理框架概览:vLLM、TensorRT-LLM、SGLang、TGI、llama.cpp等
vLLM是我做对比时的“基准框架”。它开源、生态好、模型接得快,PagedAttention和连续批处理是它的招牌。我在A100上跑7B FP16的短对话场景,vLLM的batch size能开到128以上,有效吞吐稳在几千tokens/s量级。它的优势不在单请求极致延迟,在并发吞吐量,PagedAttention把KV cache碎片压得很低,显存利用率高。缺点是配置项多,gpu_memory_utilization、max_num_seqs、max_model_len这些参数配不好,吞吐能差出三四成。
TensorRT-LLM走的是另一条路,NVIDIA官方亲儿子。它要用TensorRT编译引擎,对模型做图优化,kernel融合做得深。我测下来,同样的H100、同样的7B FP16,TRT-LLM在低延迟高吞吐区间的综合表现比vLLM好一截,尤其是P99。代价是部署流程重,模型要先转换再编译,换精度要重新编,JIT编译那一下等你几分钟是常事。还有个坑,它的in-flight batching和vLLM的连续批处理是一个思路,但调度器的可调参数少,你想精细控制抢占策略,能动的旋钮不多。
SGLang是这两年冒得很快的框架,核心卖点是RadixAttention,做前缀缓存共享。长Prompt、系统提示词固定的场景,它的吞吐优势明显。我实测过RAG负载,同一个系统提示词加不同文档,SGLang的TTFT比vLLM低不少。它的调度器也挺聪明,支持跳跃式前向,多轮对话场景有优势。生态比vLLM差一些,新模型适配慢半步,有些算子还在补。
TGI是HuggingFace家的,老牌选手。我之前用它跑BLOOM和Llama 2,稳,但吞吐上限比vLLM低一些。它的连续批处理和token流式做得规整,观测指标齐全,Prometheus格式直接吐。新版本追上来不少,跟vLLM的差距在缩小。它的定位我理解是“稳字当头”,不是跑分冠军,是省心之选。
llama.cpp是异类。它主打CPU和消费级GPU,GGUF格式,量化选项多。我在M2 Max和RTX 4090上跑过7B Q4_K_M,单用户场景的decode速度很不错。它的并发能力和连续批处理跟vLLM不在一个量级,但私有化轻量部署、边缘场景、Mac开发机验证,它很好用。llama.cpp的server模式支持连续批处理,吞吐比单请求模式强,跟数据中心框架比还是差着量级。
这五个框架我排个序:追求高并发吞吐,vLLM和SGLang是第一梯队,TRT-LLM在极致延迟场景能插一脚;追求部署省心,TGI;追求边缘和轻量,llama.cpp。没有哪个框架全场景通吃,我见过团队死磕一个框架然后在特定场景被竞争对手用另一个框架压制。
3.2 连续批处理、PagedAttention与请求调度机制的吞吐差异
连续批处理这事,是推理框架吞吐差距的核心分水岭。老式静态批处理,一批请求全进来,等最长的那个生成完,整批释放,短请求白等了。连续批处理把请求队列拆成token级调度,一个请求生成完一个token,位置空出来,新请求立刻补进去。这个机制让GPU利用率从“波动大”变成“接近满”,吞吐提升是结构性的,不是调参能追上的。
vLLM的PagedAttention是另一个关键。传统KV cache分配要预留最大长度的连续显存,80%的显存浪费在预留和碎片上。PagedAttention把KV cache切成固定大小的block,按需分页,显存利用率能拉到90%以上。显存利用率高了,能塞的batch size就大,吞吐自然高。我做过对比,同一个A100 80G,开PagedAttention和不开,7B模型的max batch能差三倍,吞吐差两倍多。这个机制后来被SGLang、TGI、TRT-LLM都学了去,现在成了标配。
调度机制的差异体现在抢占和公平性上。高并发压测的时候,请求排队是必然的,调度器怎么选下一个请求、怎么处理超长请求的抢占,直接影响有效吞吐。vLLM默认的FCFS调度简单,长请求堵路的时候,后面的短请求干等。SGLang的调度器考虑cache复用,命中前缀的请求优先。TRT-LLM在batch token预算耗尽时才做调度决策,粒度粗一些。这些差异在综合负载下能拉开10%到20%的吞吐差。
我踩过的一个坑是max_num_seqs设小了。vLLM里这个参数限制同时在跑的序列数,设成32和设成256,吞吐能差一倍多。框架不会自动帮你找到最优值,它受显存、模型长度、请求分布共同影响。我的做法是从小往大扫,盯住P99和OOM,找到吞吐拐点再回调10%。这个动作每个生产部署都得做一遍,没有通用答案。
连续批处理也有副作用。它把GPU利用率拉满了,尾延迟会变难看。一个超长请求混在一堆短请求里,短请求的P99会因为频繁抢占和重算被拉高。我见过在线服务开了连续批处理,吞吐涨了60%,P99从800ms涨到2.3s,SLO直接破了。这时候就得在调度策略上做文章,限制长请求的并发、给短请求优先级、或者干脆把长短请求分池。
3.3 不同模型架构与量化精度下的框架吞吐表现
稠密模型和MoE模型在框架里的吞吐曲线差别很大。稠密模型每个token都要激活全部参数,计算量固定,batch size和吞吐基本线性相关到拐点。MoE模型每个token只激活部分专家,计算量随路由变化,框架的调度器要处理专家负载不均的问题。我测过Mixtral 8x7B在vLLM上的表现,batch小的时候吞吐跟同等激活量的稠密模型接近,batch开大之后,专家并行和all-to-all通信开始拖后腿,吞吐增长速度明显放缓。
GQA和MHA架构对框架的KV cache管理影响很直接。GQA的KV头少,70B GQA模型的KV cache比MHA小好几倍,同样显存能塞的batch size大得多。vLLM和SGLang对GQA的支持都很好,PagedAttention的block分配天然适配。我拿70B GQA在A100 80G上跑TP=4,batch size能到32以上;换成MHA的同等模型,batch size直接腰斩。这个差距在长上下文场景更夸张。
量化精度的影响要分框架看。TRT-LLM对FP8的支持最深,H100上FP8的吞吐比FP16高40%到60%,精度损失在多数任务上可接受。vLLM的FP8也在快速成熟,我测的7B模型FP8对比FP16,吞吐提升30%左右,P99改善更明显。INT8在vLLM和TGI上都有支持,但kernel效率参差,有些模型INT8跑出来比FP16还慢,得实测。AWQ和GPTQ这类weight-only量化,vLLM支持得最全,4bit量化下7B模型在4090上能跑出不错的吞吐,但70B模型4bit量化后框架的kernel选择会窄很多。
llama.cpp的量化体系是另一套逻辑。GGUF的Q4_K_M、Q5_K_M这些格式,在CPU和消费级GPU上效率很高,混合精度量化做得细。我拿Q4_K_M的7B在4090上跑,单请求decode速度跟FP16的vLLM差不多,并发一上来就露馅了,llama.cpp的连续批处理调度器简单,batch大了之后token吞吐上不去。它的强项是单用户低延迟和低显存占用,不是高并发。
有个反直觉的发现:同一个模型,同一个精度,不同框架跑出来的实际计算精度可能不同。TRT-LLM的FP8会做per-tensor scaling,vLLM的FP8有per-token和per-tensor两种模式,选错了精度掉得厉害。我做精度敏感任务的时候,会拿一批标准样本跑一遍输出一致性,不能只看吞吐数字漂亮就上。
3.4 框架部署复杂度、生态兼容性与吞吐稳定性对比
部署复杂度我按“从拿到权重到服务跑起来”的时间算。vLLM最快,pip装完,一行命令拉起OpenAI兼容接口,半小时能出活。TGI也快,Docker镜像一拉,环境变量配好就转。SGLang跟vLLM接近,稍微多几步。TRT-LLM最重,模型转换、引擎编译、精度校准,一套流程走完半天到一天,换模型重来一遍。llama.cpp编译简单,量化模型下载就能跑,但要做成生产级API服务,得自己包一层。
生态兼容性这事,模型支持广度是硬指标。vLLM的模型库更新最快,新模型发布后几天就有支持,HuggingFace格式直接吃。TGI紧随其后。SGLang稍微慢一点,主流模型没问题,冷门架构要等。TRT-LLM的模型支持受限于TensorRT的算子覆盖,新架构适配周期长,有些模型要等NVIDIA官方出支持。llama.cpp的GGUF转换生态很活跃,主流量化模型都能找到现成的。
OpenAI API兼容性现在成了标配,vLLM、SGLang、TGI、llama.cpp的server都提供兼容接口,业务代码迁移成本低。TRT-LLM也提供了,但一些高级参数比如logprobs、function calling的支持完整度参差。我做框架切换评估的时候,会拿业务侧的真实请求打一遍,看返回结构、流式格式、错误码是否一致,光看文档不够。
吞吐稳定性是生产环境最看重的。我做过72小时长稳压测,vLLM在持续负载下吞吐波动大概5%以内,显存碎片控制得好,没有明显衰减。TRT-LLM的引擎编译后运行很稳,波动最小,但触发重编译的场景(比如变长输入超出profile范围)会有抖动。SGLang的RadixAttention在缓存命中率波动时,吞吐会有可见起伏。TGI稳,llama.cpp在长稳压测里偶发内存增长,得盯着。
框架的社区活跃度我一般看两个指标:GitHub issue的响应速度和版本发布频率。vLLM和SGLang的迭代速度很快,几乎每周都有新版本,修bug快但也可能引入回归,生产环境我会锁版本,不追最新。TRT-LLM跟着TensorRT的节奏走,版本周期长一些,稳定性好。TGI和llama.cpp的节奏介于中间。我选框架的时候,会看这个框架背后有没有持续的工程投入,半路弃坑的框架见过不止一个,迁移成本很高。
最后说一句选型的心里话。框架对比不能只看跑分,得看你的团队能不能hold住。一个需要深度调参才能发挥性能的框架,团队里没人懂,跑出来的数字还不如一个默认配置友好的框架。我见过用TRT-LLM跑出比vLLM还低的吞吐的案例,查下来是引擎编译的profile没覆盖实际输入长度,一直在走fallback路径。框架是工具,工具趁手比工具先进重要。
模型这个东西,在批处理吞吐量这件事上,从来不是“参数越大越吃亏”这么简单粗暴。我测过太多次,同一张卡,同一个框架,换一个模型,吞吐曲线能长得完全不一样。有的模型batch一开大就线性往上冲,有的模型batch开到64就顶天了,再往上加纯属浪费显存。这一章我按模型类型、参数规模、输入输出长度分布和精度四个维度来拆,都是实打实压出来的。
4.1 稠密模型与MoE模型的批处理吞吐量特征
稠密模型是我压测里最“听话”的一类。每个token过一遍全部参数,计算量固定,batch size加上去,GPU利用率跟着往上走,吞吐基本是线性爬到某个拐点再放缓。7B稠密模型在A100 80G上,vLLM开到batch 128以上才开始看到边际递减。这个曲线好预测,容量规划也好做,你知道每多一个batch能多榨出多少tokens/s。
MoE模型就完全是另一回事了。我拿Mixtral 8x7B在vLLM上压的时候,batch小的时候它表现跟同等激活参数量的稠密模型差不多,看着挺美。batch一开过32,吞吐增长就明显钝化了。查下来是两个事:专家负载不均,有些专家被路由到的次数远高于平均值,这些专家成了瓶颈,其他专家在等;all-to-all通信在多卡部署下开始吃带宽,计算和通信重叠没做好,GPU有空洞。
我后来做MoE的吞吐评估,不再只看总参数量,我盯激活参数量和专家并行度。8x7B实际每个token激活13B左右,但它的吞吐曲线拐点比13B稠密模型来得早。这个认知很关键,很多团队照搬稠密模型的batch调参经验去配MoE,吞吐跑不到预期还找不到原因。MoE的甜点区更窄,调参窗口更小,这是它的基因决定的,不是框架能完全抹平的。
还有个细节,MoE模型在连续批处理下的表现跟请求的领域分布有关。数学推理和代码生成的专家激活模式差异很大,混在一起跑,专家缓存的局部性变差,吞吐会比单一领域负载低一截。我做生产容量规划的时候,会把MoE模型的batch上限比稠密模型下调20%到30%,留出专家路由波动的余量。
4.2 不同参数规模模型对比:7B、13B、70B与百B级模型
7B模型是吞吐量的“甜点区”。单卡A100 80G,FP16,vLLM,batch能开到128到256,有效吞吐轻松上到几千tokens/s。它的KV cache小,显存压力轻,框架调度灵活度高。我见过用7B做在线客服的团队,单张A100撑住几百并发,P99还能压在SLO以内。7B的吞吐瓶颈在框架调度效率,不在模型本身。
13B模型开始出现分水岭。单卡A100跑FP16,batch上限掉到64左右,吞吐比7B低40%上下。这个降幅不是线性的,因为KV cache翻倍了,显存里能塞的并发序列数受限。13B模型的一个好处是它对量化更友好,INT8或FP8之后,吞吐能拉回接近7B FP16的水平,精度损失在多数任务上可控。我一般建议13B配FP8跑生产,性价比比硬上FP16高。
70B模型是另一个世界。单卡跑不动,得TP=4或TP=8。我在A100 80G×4上跑Llama 3 70B FP16,vLLM的batch size撑到32就得小心了,再大P99会抖得厉害。TP并行本身有通信开销,4卡之间的all-reduce吃掉一部分吞吐增益。我实测4卡TP的吞吐大概是一卡理论值的2.8到3.2倍,不是线性4倍。70B GQA架构帮了大忙,KV cache比MHA小好几倍,同样显存能多塞一倍的batch。
百B级模型我压过的最大的一个是Llama 3.1 405B,8卡H100 TP=8。这个量级谈“批处理吞吐量”得先看互联。NVLink带宽够不够,TP组的通信重叠做没做好,直接决定吞吐是能跑出理论值的60%还是80%。batch size在这个量级不是你想开多大就开多大,得看序列长度和显存放不放得下KV cache。我测下来百B级模型的吞吐优化重心从“调batch”变成了“调并行策略和通信效率”,这是质变不是量变。
参数规模往上走,吞吐的边际成本不是等比例涨的。7B到13B,吞吐掉四成;13B到70B,吞吐掉七到八成;70B到百B级,吞吐再掉一半以上,还得搭上多卡互联的复杂度。我做选型的时候会问团队一个问题:你的业务真的需要百B级的推理质量吗?很多时候70B加好的RAG和微调,业务效果够用,吞吐成本和延迟表现好得多。
4.3 输入输出长度分布影响:短Prompt短输出、长上下文、RAG与生成任务
短Prompt短输出是吞吐量最友好的负载。输入100 token,输出50 token,连续批处理的调度器吃得最舒服,请求周转快,KV cache释放及时,GPU几乎不空转。我拿7B模型在A100上跑这类负载,batch开到256,有效吞吐能冲到6000 tokens/s以上。在线对话的多数轮次就是这个形态,框架调好了吞吐很漂亮。
长上下文是吞吐的“黑洞”。输入32K、输出1K这种负载,Prefill阶段的计算量爆炸,TTFT拉得很长。PagedAttention对长上下文的显存管理有帮助,但Prefill的算力消耗跑不掉。我测过70B模型32K输入的TTFT,A100×4上单请求要好几秒,batch根本开不大。长上下文场景的吞吐优化得从架构下手:分块Prefill、chunked prefill、前缀缓存,能用的都用上,TTFT和吞吐之间还得做取舍。
RAG负载有个特点,系统提示词和检索模板是固定的,只有检索到的文档片段在变。这个结构天然适合前缀缓存。SGLang的RadixAttention在这种负载下优势明显,我实测同一个RAG模板加不同文档,SGLang的TTFT比vLLM低30%到40%,吞吐高20%左右。vLLM的prefix caching也在进步,差距在缩小。RAG场景选框架的时候,前缀缓存命中率是个硬指标,值得单独压一轮。
纯生成任务,比如文章续写、代码补全,输出长度远大于输入,decode阶段主导吞吐。这类负载对TPOT敏感,对batch size的容忍度高。我跑7B模型做长文生成,batch开到192,TPOT还能稳在50ms以内。生成任务的吞吐上限受KV cache容量限制比受算力限制更明显,显存带宽是瓶颈。H100的HBM3带宽在这种负载下比A100优势大,我测出来差距能到1.6倍以上。
混合负载是最难伺候的。线上真实流量里短对话、长文档、RAG问答混在一起,调度器要做的事很复杂。我见过一个部署,短请求被长请求堵住,P99从600ms飙到4s,吞吐数字看着不差但SLO崩了。解法要么是长短请求分池部署,要么是调度器做优先级和抢占。vLLM的调度策略调优空间大,但得花时间摸。SGLang的调度器对混合负载的友好度高一些,自动处理的逻辑更聪明。
4.4 量化与推理精度影响:FP16、BF16、INT8、FP8、AWQ与GPTQ
FP16是基准线,所有对比都拿它当参照。A100和H100上FP16的kernel成熟度最高,精度损失可忽略,吞吐数字也是各框架调优的默认目标。我做压测第一步永远是跑FP16拿基准,后面所有量化的增益都跟这个数比。
BF16跟FP16在吞吐上基本持平,A100和H100的Tensor Core对两者支持一样好。差别在数值稳定性,BF16的动态范围大,大模型长序列生成时不容易溢出。我跑70B以上模型默认用BF16,省心。有些框架对BF16的kernel调优不如FP16细致,我遇到过BF16吞吐比FP16低5%的情况,测一下就知道,别假设。
FP8是H100上的重头戏。TRT-LLM对FP8的支持最深,per-tensor scaling做得好,H100上FP8对比FP16吞吐提升40%到60%,70B模型的P99延迟改善更明显。vLLM的FP8也在快速成熟,7B模型上我测到30%左右的吞吐增益。FP8的精度损失要看任务,分类和抽取类任务基本无感,复杂推理和数学任务得做输出一致性验证。我一般拿业务样本跑一遍对比,掉点超过阈值就不上。
INT8的情况复杂一些。vLLM和TGI都支持,但kernel效率参差。我测过同一个7B模型,INT8在vLLM上比FP16快20%,在另一个框架上反而慢了10%。INT8的量化校准方式也影响大,per-channel和per-tensor的选择对精度和速度都有影响。我的经验是INT8适合对延迟敏感、对精度容忍度高的场景,上之前必须实测,不能看论文数字拍板。
AWQ和GPTQ是weight-only量化,4bit为主。这类量化对显存占用的压缩最狠,7B模型4bit之后显存只要4GB出头,4090单卡就能跑得挺舒服。vLLM对这两种格式支持最全,AWQ的kernel效率一般比GPTQ好一些。吞吐上,4bit量化在decode阶段有优势,显存带宽压力小,batch能开大。但Prefill阶段的计算量没减,长Prompt场景的TTFT改善有限。我拿AWQ 4bit的7B在4090上跑短对话,吞吐能到FP16在A100上的六成左右,单位成本算下来很划算。
量化的坑我说两个。一个是精度掉点不是线性的,4bit到3bit可能断崖式下跌,别贪。另一个是框架对量化格式的支持深度不一样,同一个AWQ模型在vLLM上跑得好,换SGLang可能kernel没适配好,吞吐反而不如FP16。我做量化选型的时候,先锁框架再选量化格式,顺序反了容易白忙活。
量化那一节说完,其实已经踩到这一章的门口了。我这些年压测下来最大的感受是:硬件决定天花板,框架决定地板,真正落到生产上能跑出多少吞吐,全在参数调优那几下手上。同一个模型、同一张卡、同一个框架,调和不调,吞吐能差一倍。这一章我把压箱底的操作细节和选型思路摊开讲。
5.1 批处理参数调优:最大批大小、token预算、并发队列与抢占策略
最大批大小这个参数,几乎所有框架都有,但多数人只知道往上加。我见过太多团队把 max_num_seqs 开到256然后发现P99抖成筛子。问题出在哪?batch size不是孤立的,它跟你的序列长度分布绑死在一起。64个并发请求,每个请求输出2048 token,和64个请求每个输出128 token,显存占用差了一个数量级,调度压力完全不是一回事。
我现在调batch size的方法是反着来:先定SLO,TTFT压多少、TPOT卡多少、P99不能超过多少,然后从SLO倒推batch上限。拿7B模型举例,A100 80G,vLLM,SLO要求TPOT中位数50ms、P99不超120ms。我把batch从64开始往上试,128还行,192开始TPOT的P99就顶到110ms了,256直接破线。甜点区在128到192之间,再往上加吞吐收益抵不过SLO违规的风险。
token预算这个维度比batch size更细。vLLM和SGLang都有 max_num_batched_tokens 这类参数,控制一轮调度里所有请求的token总量上限。短请求为主的场景,这个值可以开大,让调度器一轮多塞请求,吞吐冲得高。长Prompt场景反过来,token预算开太大,一轮里混进一个32K的请求,其他请求全被卡在排队,TTFT全线崩。我跑长上下文负载的时候,会把token预算控制得比较保守,同时开chunked prefill,让长请求分块穿插进去。
并发队列和抢占策略是调度器的灵魂。vLLM的默认调度是FCFS,先来先服务,简单但不够聪明。线上混合负载的时候,短请求很容易被前面的大请求堵住。vLLM支持调 scheduler_delay_factor 和抢占相关的参数,让调度器在显存逼近上限时主动换出部分请求的KV cache。这个动作有代价,被抢占的请求重新调度要重算Prefill,吞吐数字会掉,但P99能救回来。我做过对比,开抢占策略之后有效吞吐掉了8%左右,P99从2.3s压到700ms以内,SLO达成率从82%拉到97%。这个账我算得过来。
SGLang的调度器在混合负载下更省心,它的RadixAttention和自动抢占逻辑默认配置就比较合理,调参负担轻。我见过一些团队直接上SGLang跑RAG,几乎没怎么调参数,吞吐和延迟表现就很体面。vLLM调优空间更大但得花时间摸,适合有专门推理优化人力的团队。
还有个实操细节,max_num_seqs 和 max_num_batched_tokens 这两个参数在vLLM里是联动的,你单改一个有时候看不出效果。我一般两个一起调,跑一轮压测换一组,做个小网格搜索,三四组就能找到甜点。别嫌烦,这半小时的调参能省掉后面几个月的容量焦虑。
5.2 面向GPU型号与推理框架的组合选型矩阵
选型这件事我从来不信“万能组合”,A100加vLLM就是标准答案这种话说出来是不负责任的。我的经验是按三个维度做矩阵:GPU型号、推理框架、业务负载形态。三个维度交叉,答案自己会浮出来。
数据中心卡里面,A100 80G是万金油,FP16成熟度高,vLLM和TRT-LLM都吃得很透。7B到70B模型它都能扛,75B以上得上多卡。H100的优势在FP8和HBM3带宽,TRT-LLM加FP8的组合在H100上跑70B模型,吞吐比A100 FP16高出一大截,我实测差距能到2倍以上。H100上vLLM的FP8也在追,但kernel成熟度暂时还赶不上TRT-LLM。预算充足、模型规模大、吞吐压力重的团队,H100配TRT-LLM是当前最能打的组合,前提是你团队有人能啃TRT-LLM的部署复杂度。
L40S和L4是另一条路。L40S没有NVLink,多卡TP效率一般,单卡48G显存跑7B到13B的FP16或者AWQ量化很合适。我拿L40S配vLLM跑13B FP8,batch开到64,吞吐能到A100 FP16的六七成,单位成本算下来有优势。L4功耗低、显存24G,适合边缘侧或者轻量推理,跑7B AWQ 4bit在在线客服场景够用,吞吐别指望太高,胜在部署密度大。
消费级卡我重点说RTX 4090。24G显存,跑7B FP16有点紧,AWQ 4bit就很舒服。我拿4090配vLLM跑7B AWQ,batch开32到48,短对话场景有效吞吐能到FP16在A100上的一半多一点。它的瓶颈在显存带宽和没有NVLink,多卡并行不划算。但对中小团队来说,4090的性价比在低并发场景下是无敌的。RTX 3090的情况接近,显存同样24G,算力弱一截,二手价格更低,跑量化模型做离线批推理很实惠。
框架和GPU的搭配上,我的默认建议是这样:NVIDIA数据中心卡优先试vLLM,想榨干H100性能就上TRT-LLM;RAG和长上下文占比高的负载优先SGLang;边缘和消费卡用llama.cpp或者vLLM的量化路径;TGI适合已经有HuggingFace生态绑定的团队,部署简单但吞吐调优空间相对小一些。
这个矩阵不是静态的,框架迭代太快了。我半年前做的结论,现在可能得打个补丁。我的习惯是每季度挑两三个候选组合重跑一轮基准,不重跑就等着被旧结论坑。
5.3 吞吐量基准测试方案、监控指标与结果分析方法
压测这件事,方法比工具重要。我见过团队拿个 ab 或者 wrk 改一改就去压推理服务,出来的数字完全没法用,因为HTTP层的压测工具模拟不了真实LLM负载的输入输出长度分布和流式返回特征。
我自己的压测方案分三层。第一层是固定负载的纯净基准:固定输入长度、固定输出长度、固定并发度,比如输入512输出256、并发32,跑五分钟取稳定段的数据。这一层用来做框架和硬件的横向对比,变量控制得最干净。第二层是分布负载:按真实业务的输入输出长度分布采样,用真实的Prompt模板,跑混合并发。这一层出来的数字才接近生产。第三层是压力测试:并发逐级往上推,找到吞吐拐点和SLO破线点,绘制吞吐-延迟曲线。
工具上我用vLLM自带的benchmark脚本打底,改造成读业务分布采样。SGLang有类似的工具。关键是指标要采全:吞吐量要同时记tokens/s和requests/s,延迟要拆TTFT、TPOT、E2E的P50/P95/P99,显存要盯峰值和KV cache利用率,GPU利用率要记sm occupancy和显存带宽利用率。只报一个吞吐数字的压测报告,我认为是不合格的。
结果分析有个坑我得提醒。压测跑出来的吞吐数字,如果是流式接口,要区分“服务端生成吞吐”和“客户端接收吞吐”。网络传输、流式分块大小、客户端处理速度都会影响后者。我一般以服务端日志里的token计数为准,客户端数字做参考。
监控指标在生产上要跟压测口径对齐。我推的监控面板必含这几项:请求排队时长、Prefill队列深度、Decode队列深度、KV cache使用率、被抢占请求比例、每请求token数分布。这几个指标一摆,吞吐为什么掉了、延迟为什么抖了,基本能定位到原因。我遇到过一次P99突然从500ms跳到1.8s,查监控发现KV cache使用率到了96%,调度器频繁抢占,把抢占比例曲线拉出来一看就明白了,加了一张卡就解决了。
结果分析我有个笨办法但很管用:每次压测结果都存成结构化数据,跑一个对比脚本,跟上次的基线做diff。框架升级、参数调整、GPU驱动更新,任何变动前后都跑一次diff。吞吐提升了还是退步了,延迟改善了还是恶化了,一目了然。凭记忆做对比容易自己骗自己。
5.4 成本效率评估:每token成本、每请求成本与SLO达成率
吞吐量高不等于成本低,这个道理说起来简单,做容量规划的时候还是有人掉坑里。我评估成本效率必算三个指标:每百万token成本、每千请求成本、SLO达成率。三个一起看,单看一个都会误导。
每token成本的计算口径要统一。我用的是“有效吞吐”做分母,就是实际达成的tokens/s乘上SLO达成率。一台A100服务器按云上按需价格算大概每小时二三十块,有效吞吐跑到3000 tokens/s,摊下来每百万token成本在二三十块这个量级。H100贵一倍多,但FP8加TRT-LLM的有效吞吐能到A100的两倍以上,每token成本反而可能更低。这个账要按有效吞吐算,按理论峰值算出来的成本是自欺欺人。
每请求成本更适合面向C端的产品做定价参考。短对话请求输入50输出50,一个请求100 token,按每百万token成本折算,单请求成本低到几乎可以忽略。长文档处理一个请求几万token,成本就上来了。我帮一个做合同审核的团队算过,单请求平均输入8000输出500,他们用70B模型跑,每请求成本比用7B模型高一个数量级。后来他们把任务拆了,抽取和分类用7B,复杂推理才路由到70B,整体成本砍掉六成多,效果几乎没差。
SLO达成率是成本效率的分母,这个指标不达标,前面算的成本全是虚的。我见过一个部署,吞吐压到理论峰值的九成,单token成本看着很漂亮,但P99延迟破线,SLO达成率只有七成。实际有效成本比一个保守配置、SLO达成率99%的部署高出三成。为了冲吞吐把延迟底线丢了,这个买卖不划算。
还有个隐性成本得算进去:运维复杂度。TRT-LLM的吞吐表现确实好,但部署和调优的人力成本比vLLM高不少,模型转换、engine构建、版本升级都更折腾。团队如果没有专职推理优化的人,上TRT-LLM的隐性成本可能把硬件上的成本优势吃掉。我选型的时候会把“团队能不能hold住”作为一个硬指标列进去。
GPU利用率的尾巴也要看。离线批推理场景可以容忍延迟,把batch开到显存极限,利用率拉到八九成,每token成本能压到最低。在线场景为了保SLO,GPU利用率通常在五六成,这是为延迟付的溢价。两种场景的成本目标不一样,不能拿同一套数字去比。
5.5 典型业务场景选型建议:在线对话、离线批推理与长文档处理
在线对话是我经手最多的场景。短Prompt短输出为主,偶尔掺杂长消息和多轮上下文。这个场景的第一优先级是TTFT和P99,吞吐是第二位的。我的默认推荐是vLLM或SGLang配7B到13B模型,数据中心卡用A100或L40S,量化上FP8或者AWQ 4bit看精度要求。batch size别贪大,128左右,把KV cache使用率控制在80%以下,留出突发流量的余量。有RAG环节的加前缀缓存,SGLang在这一点上的默认表现更省心。多轮对话场景要特别注意KV cache的复用策略,vLLM的prefix caching开起来,多轮对话的TTFT能降三到四成。
离线批推理完全是另一套逻辑。没有实时用户等着,延迟不敏感,吞吐和成本就是全部。这个场景我推荐把batch往死里开,token预算拉满,抢占策略关掉或者设得很宽松。框架上vLLM配量化模型,用消费级卡或者上一代数据中心卡跑,成本能压到极低。我有个客户做数据标注和语料生成,用4090配7B AWQ跑离线批处理,单位token成本比在线部署低一个数量级。离线场景还可以考虑把多个请求拼成超长序列一次前向,nanoflow或者类似的打包方案能把GPU利用率推到九成以上。调度上按输入长度分桶,同桶的请求一起批,避免长短混跑互相拖累。
长文档处理最考验架构。输入动辄几万token,Prefill是瓶颈。我推荐SGLang打底,RadixAttention对长文档的分块处理和前缀复用有优势。chunked prefill必开,把长Prefill切成小块跟其他请求的Decode穿插跑,避免一个长请求把整个batch卡住。TTFT要跟业务方对齐预期,32K输入在A100×4上跑70B模型,TTFT压到3秒以内就算不错了,硬压到1秒代价太大。模型选择上GQA架构优先,KV cache小,能多塞并发。长文档场景我通常会建议单独部署一个池子,跟在线对话隔离,两边互不干扰。
三个场景往深了说还能继续拆,比如在线对话里再分高优和低优通道,离线批推理里再分小模型预处理和大模型精处理。选型的颗粒度跟着业务复杂度走,别为了省事搞一个大一统部署,吞吐和成本两头不讨好。我的经验是场景隔离带来的额外运维开销,远小于混部导致的SLO波动和调参扯皮。
写到这章收尾,回头看整本书的路子其实就一句话:吞吐量是调出来的,不是买出来的。硬件和框架给了你舞台,参数调优和场景匹配才是真的功夫。每压一轮测、每调一组参数,你对自己系统的理解就深一层。这套东西没有一劳永逸的答案,模型在变、框架在变、硬件在变,保持压测的习惯,保持对数字的怀疑,比记住任何一个具体结论都值钱。