开源大模型推理引擎现状及常见推理优化方法
我跪了!同样模型同样显卡,性能差20%?开源推理引擎背后的真相,我踩了3年的坑才搞明白!
前几天,一朋友甩给我张截图,问我:“兄弟,这数据靠谱吗?”
我一看,Artificial Analysis上的排行榜,vLLM跑到了第一,Output Speed 230 TPS!TTFT在10K输入时不到一秒!
好家伙。
我心里先骂了句“真他奶奶的快”,然后翻了翻旧账——去年这时候,同样的场景,vLLM还被人吐槽“花架子”呢。怎么这么快就翻身了?
作为踩坑老人,我决定亲自跑一遍。vLLM 0.6.3,SGLang 0.3.0,同一个模型(一个397B参数的MoE模型),同一张显卡。
结果?
性能差异20%以上!
你说这事魔不魔幻?
说到这儿,你可能觉得我要开始讲技术原理了。别急,我先说个场景,你感受一下那种抓狂。
从2023年底开始,我断断续续维护过几个推理服务。你根本想象不到,我踩过的坑能绕海淀黄庄三圈。每次看到性能瓶颈,以为找到病因了,折腾三天三夜,最后发现是错的。
更崩溃的是,有时候你明明找到了优化点,改了一行代码,整个推理链路就崩了。
那种感觉,就像你亲手从一堆乱麻里抽出一条线,结果把整团线全扯散了。
今天聊的,就是我那些年拍大腿的教训,全是从实操里摸出来的,不端着,不装逼。
主流引擎的“真面目”,谁在裸泳,谁在冲浪?
先说vLLM。
这玩意我从0.4升到0.6,每个版本都看着它成长。你猜什么感受?
它越来越知道自己要干什么了。
PagedAttention、Continuous Batching这些已经是标配,真正让它起飞的是V1架构的KV cache管理。它理清了block table、slot mapping这些东西,使得prefix cache能做到“零开销”——就是复用缓存不用重新计算,多轮对话直接起飞。
不信?你翻翻vLLM的源码,调度器那一套逻辑从V0重构成V1,复杂了不少,但吞吐确实上来了!
前两天DigitalOcean公布的Blackwell Ultra测试我特意看了:某个模型的attention fuse、某个模型的EAGLE3 draft model、某个模型的linear attention fusion,改动全合入主仓了,不是私有分支!
意味着什么?
开源引擎在先进优化上已经能追平私有方案,甚至跑得更快!
SGLang呢?另一个让我“眼前一亮”的项目。
RadixAttention这个设计太聪明了。它把KV cache塞进前缀树里,用LRU做垃圾回收。多轮对话里,相同的prompt直接复用,不用重新算。
我拿那个397B的模型做了个对话bot测试,你猜怎么着?
同样的请求模式下,SGLang的首Token时延比vLLM低40%!因为它缓存命中率高太多。
但SGLang也有个毛病:模型支持还没vLLM全。遇到偏门模型,你得自己去写custom layer。对新手来说,这就是道天堑。
至于tgi……
哎。
作为和vLLM同时期的开拓者,huggingface背景,天时地利,开局一把好牌打成这样,真的可惜。现在两个月才发一版,感觉都快放弃了。
最逗的是什么?tgi说自己用了PagedAttention,我一看源码——只是在decode阶段调了个同名kernel!说的和做的,两码事。
队伍不给力啊。
lmdeploy也挺有意思,我试过少量实验,稳定性可以,但社区热度不如前两者。Mooncake的PD分离架构我还没在生产中用,但看过架构图:Prefill和Decode物理分离,各自独立扩缩容。很像通信里的CUPS分离,思路很硬。
我踩过的那些坑,能写本“推理优化血泪史”
回头看,推理优化里真正称得上“妙招”的并不多。
大多数时间,你都在和三个魔鬼较劲:显存、调度、Kernel Launch。
我按重要性排了五个方向,写下来给后来人参考。每一个,都是我拍过大腿的。
第一,把计算访存比拉高。
说白了就是让GPU每次搬数据进来,能多算点东西。最直接的方法是扩大batch size,但显存不够怎么办?给KV Cache做量化(INT8/FP8),或者给权重做量化(bfloat16不够就换int4)。这些技术,目标只有一个:把batch撑大。
我测过,B=1到B=16之间,吞吐差距百倍都有可能!
但batch不是无限大——到256往后,显存瓶颈就换成了计算瓶颈,增幅就平了。想继续提,得换思路。
说到这儿,投机解码是个方向:小模型快速生成多个token,大模型一次验证。但这在并发高时不划不来——本来batch就大,模型利用率也高,乘个接受率α<1,吞吐可能反而降。
我试过小模型带大模型:低并发(B≤4)能提50%,高并发(B≥64)基本零收益。
所以你看,很多“看上去很美”的技术,放到真实场景里,都得重新盘一遍。
第二,Kernel优化——真正的硬仗。
FlashAttention用tiling把attention算子的计算访存比拉高一截。vLLM V1对某些MoE模型做的linear attention fusion,把几个kernel合并,减少launch overhead,prefill阶段少了几百次kernel launch。
别小看这个数字。几百次,对于延迟敏感的服务,就是天壤之别。
MoE领域,Nvidia那篇通信优化让我印象深刻——MoE的all-to-all通信经常卡30%时间,RingReduce和局部性调度能拉回来。30%啊!你想想,你的GPU在30%的时间里都在干等。
第三,硬件利用率——让GPU别闲着。
通信计算重叠、CUDAGraph消除Python侧launch开销、异步调度……这些vLLM和SGLang已经做了很多。
但我专门跑过一次profile trace,发现有些服务的时间线上,40%都是空的!原来是host在等GPU返回sync point。
40%!你的GPU有一小半时间都在摸鱼!
那个gLLM的提交记录说,repetition penalty原本每步都重建全历史mask,从所有seq的token重建一个[batch, vocab] mask,输出越长越贵。改成worker-local persistent pool后,每步只增量scatter新token,工作量从每次O(total_len)变成O(batch)加少量新输入。
这种优化看着小,decode跑百万步,就是天差地别。
还有那个calc_input hidden states的pipeline,原来在CPU和GPU之间反复同步hidden states,很多位置编码和slot mapping是冗余复制。vLLM最新commit改成预分配大buffer,每步只copy有效数据,原来额外的一块CPU端内存就这么省下来了。
别小看这个。
prefill时间从15秒降到9秒,就是这些小东西堆起来的。
第四,调度策略不对,再强的kernel也白搭。
continuous batching让新请求随时插队,旧请求退出不阻塞。但prefill和decode混跑问题很大:prefill吃算力,decode吃显存且访存敏感,大batch下互相干扰。
所以PD分离成了热点——Mooncake把prefill节点和decode节点物理分离,各自独立扩缩容;vLLM V1也做了类PD的逻辑分离。
负载均衡也重要:vLLM的DPLB解决不同dp rank间attention负载不均,SGLang的EPLB处理MoE场景下expert访问不均。
我踩过那个坑:不同rank之间差距半小时!一个rank完事了,另一个还在干等。
所有的优化努力,到调度这关没过了,就是打水漂。
我的个人判断:少看PPT,多看代码
做了两年推理优化,我发现调试一个服务最花时间的不是模型逻辑。
而是显存碎片、调度等待、隐式同步——这三个鬼!
调试工具首选NVIDIA Nsight Systems:看timeline上的间隙,如果是host等待GPU,就去查synchronization;如果是GPU空转但SM没跑满,就去查batch size和kernel效率。
选引擎这事,我建议看场景。
假如你是简单对话、长上下文,SGLang的RadixAttention更强;假如你需要支持的模型更广、社区工具更多,vLLM现在是第一选择。tgi除非huggingface大力出奇迹,否则我不推荐新项目用了。
小优化策略,我总结一句口诀:
尽量增量而不是全量,能融合就别分开;缓存比重建好,异步总比等待强。
跑过profile trace后再对照这句话,每个空档都能找出几处可优化的点。
往后看,PD分离会成为标配,投机解码会变成自动适配——先看负载,后端自动切算法。缓存体系也会更复杂:全球部署的LMCache跨实例KV共享已经在路上了。
但核心还是那句话:让GPU忙起来,别让它等你。
说到这儿,我劝同行:少读PPT,多读代码。
vLLM的调度器、SGLang的radix tree、Mooncake的传输协议——认真啃一遍,比刷十篇综述都管用。
我啃过一阵,虽然头发少了点,但每个bug都能一眼看出是哪层出问题。
做这个行业的,既要看得远,也要蹲得下来。
我还会接着记笔记,踩下一个坑。
你呢?
能拆的代码别留着等明天,能跑的实验别等着看PPT。
毕竟,性能这个东西,不会凭空掉下来。它只会在你踩了足够多的坑之后,满脸不耐烦地跟你碰个杯,说一句:来,喝了这杯,咱继续干!
读者评论 3