每次在技术群里看到"怎么让模型推理更快"这个问题,十有八九的回答会指向三件事:量化、剪枝、蒸馏。这三板斧确实有效,但它们都落在同一个维度上——改权重。可最近这一年我复现了几轮推理优化项目,结论有点反直觉:0行权重改动,首字延迟硬是降了77%。首字延迟也就是TTFT(Time To First Token),它正在取代简单的"每秒生成多少token"这类老指标,成为推理性能优化里最值得盯的标尺。
这个现象背后的逻辑其实很清晰:大模型推理的开销早就不是"权重计算"一项了,显存布局、调度策略、缓存管理、算子实现这些"权重之外"的环节,每一处都可能在悄悄吃掉你的延迟。2026年,推理提速的主战场已经从模型层移到了执行层。
这篇文章我会把这块掰开揉碎讲清楚。先说说首字延迟为什么这么重要,再拆一下权重之外到底有哪些提速空间,然后专门聊一个最近特别热门的话题——llama.cpp把权重offload到内存到底算不算"动权重",最后把我复现"77%下降"的完整过程和数据贴出来,再把过程中踩过的坑一并交代。这篇文章适合正在做推理服务部署、或者手上有模型但不知道怎么继续压延迟的团队参考。
1. 首字延迟的生死线:为什么大家突然都盯上TTFT
1.1 按下回车到第一个字蹦出来,中间发生了什么
TTFT的定义并不复杂:从用户把prompt发送到服务端,到模型返回第一个token(可以粗略理解为第一个字或第一个词)所经历的时间。但它背后对应的工作量比大多数人想象的要重得多。
一次请求进来,服务端要做的事情包括:输入文本的token化、prompt的逐层前向计算(也就是prefill阶段,这个阶段要把用户输入的所有token并行算一遍)、算完的同时把中间生成的KV Cache(键值缓存)写入显存,然后才是decode阶段生成第一个token。
也就是说,TTFT的绝大部分时间其实花在"读完并理解你的输入"上面,而不是"开始说话"上面。输入越长、模型越大,prefill阶段的计算量和显存占用就越高,TTFT自然水涨船高。
这里有个很多人容易忽略的细节:prefill阶段是典型的计算密集型(compute-bound),而decode阶段是典型的访存密集型(memory-bound)。两者对硬件资源的诉求完全不同,这也是后面为什么要做调度拆分的前提。你如果不把这两个阶段分开看,就很容易出现"调了半天参,TTFT纹丝不动"的情况。
1.2 输入变长时,TTFT的恶化是线性的吗
不是线性,是接近线性的增长,而且在大模型场景下,Attention部分的计算量随序列长度增长得尤其明显。
我这边压测过一组数据:一个中等规模的7B开源模型,在同样一张A100 80GB上,输入512 token时TTFT大概0.6秒,输入2048 token时涨到1.9秒,输入8192 token时就到了6秒以上。换成更大的模型,这个恶化幅度还会放大。
这就带来一个很现实的问题:如果你的业务场景是短query(比如搜索引擎式问答),TTFT可能还能接受;但如果是长文档问答、多轮对话带历史上下文、代码补全带整个仓库代码,输入轻松上万token,TTFT立刻变成体验瓶颈。
还有一个容易踩的坑:很多人只看"生成速度"(TPS或TPOT),觉得生成一顿猛如虎就够了。但实际交互中,用户体验最敏感的是"它什么时候开始有反应"。3秒的TTFT会让用户觉得卡,5秒以上就会觉得服务挂了。所以2026年各家推理框架(vLLM、TensorRT-LLM、SGLang这些)版本更新的重点,几乎都在针对prefill和首token输出做文章,这一点本身就说明问题了。
另外提醒一句:压测的时候别把TTFT和网络往返时间(RTT)混在一起算。我之前遇到过团队把客户端到服务器的网络延迟也算进TTFT里,结果排障排了半天,以为是推理框架慢,最后发现是负载均衡器的超时配置有问题。测TTFT的脚本要端到端计时没问题,但分析归因时一定要把网络、排队、显存搬运这些环节拆开。
2. 权重之外,2026年推理加速的四个主战场
既然权重没动,那加速从哪里来?我把它拆成四个层面,这四个层面就是我后面做优化实验时的主攻方向。
2.1 KV Cache的显存管理:从"整块预留"到"页式分配"
先说一下KV Cache是什么。模型在生成每个token时,都要反复引用之前所有token的注意力信息,为了不每次都重复计算,框架会把每层的Key和Value缓存起来,这就是KV Cache。它的大小随输入长度和并发请求数线性增长,在大上下文场景下,它占的显存比模型权重本身还大。
传统做法(比如Transformers库的默认实现)是给每个请求预分配一块完整的KV空间,不管实际用不用得完。这会导致大量显存碎片和浪费,并发一高就OOM,或者被迫把batch压小,吞吐和延迟一起变差。
vLLM的PagedAttention这类方案,本质上是把显存管理做成"按页分配",类似于操作系统的虚拟内存机制。请求来的时候只分配实际需要的页,用完了释放,利用率能从四成提到九成以上。这个改动对TTFT的影响主要有两个方向:一是显存不再是瓶颈,可以开更大的batch吸收并发;二是prefill阶段不会因为临时找不到连续显存而触发等待。
另外还有一个KV Cache量化的问题。注意,这个量化的对象是KV Cache而不是模型权重,它属于"缓存层优化",权重文件一个字节都没变。把KV Cache从FP16压缩到INT8,显存占用直接减半,又能多塞不少请求。业界对KV Cache量化的讨论这两年明显升温,原因就是它跟权重量化走的是两条完全不同的路。
想估算KV Cache占多少显存的话,可以套这个公式:2 × 层数 × 注意力头数 × 头维度 × 序列长度 × batch大小 × 精度字节数。拿7B模型举例,假设层数32、头数32、头维度128,序列长度2048,batch 8,FP16精度,粗算下来大概是8×2048×32×128×2字节,再乘个层数和batch系数,实际要几个GB。所以KV Cache在显存里是实实在在的大户,省它一省,效果立竿见影。
2.2 投机解码:让一个小模型先跑,大模型只当裁判
投机解码(Speculative Decoding)的思路很有意思:先用一个很小的草稿模型快速生成好几个候选token,再让大模型一次性验证这些token是否合理。如果草稿模型猜对了,大模型一次前向就确认了多个token的产出,decode阶段的瓶颈就被绕开了。
这个方法对TTFT本身帮助不大,因为它主要优化的是"生成后续token"的速率,而不是"第一个token"的等待时间。但它对整个请求的总时延(包括排队、prefill、decode全部环节)有明显改善。
我在实践中的体会是:投机解码更像是一个"吞吐放大镜",如果后端已经有了一定并发,它能让你用同样的显存服务更多请求。但它不是解决TTFT燃眉之急的手段,很多团队把宝全押在投机解码上,结果发现首字延迟还是高,方向就搞偏了。
选草稿模型的时候有个经验:草稿模型最好和主模型用的是同一个tokenizer,词表也得对齐,否则候选token的映射会多一层转换,收益被吃掉一大块。另外草稿模型的接受率不是越高越好,要结合生成长度一起看,项目里可以先跑一小批数据,统计平均接受长度,再决定草稿模型的规模和采样温度。
2.3 Prefill和Decode解耦:把"一口气干完"改成"流水线分工"
前面说过,prefill是计算密集型,decode是访存密集型。如果两者混在一个服务进程里抢资源,计算密集的任务会阻塞访存密集的任务,典型表现就是:某个请求正在做大段输入的prefill时,其他在生成中的请求全都跟着变慢。
prefill/decode解耦的核心思路是把这两个阶段拆成独立的执行单元,甚至放到不同的GPU实例上。prefill节点只负责快速读完输入、生成好KV Cache,然后把KV交给decode节点继续;decode节点只负责不断产出token。这样每个节点都能用自己的资源做最擅长的事。
在单机单卡的场景下,"解耦"主要体现在时间片调度上,比如给prefill预留高优先级、控制单次prefill的batch大小,避免单个大请求独占GPU太久。SGLang的RadixAttention、TensorRT-LLM的disaggregated serving方向,其实都是在做这件事。
实测下来,prefill/decode解耦对长输入的TTFT改善非常明显,因为它直接压缩了"大请求排队抢资源"的时间。我后面复盘里TTFT下降最猛的一轮,就是靠的这个方向。
2.4 引擎和算子:同样的权重,换一个"跑法"差距就很大
最后一个层面往往最容易被忽视:同一个模型权重,在不同的推理引擎里跑,延时差一倍都很正常。
原因在于底层算子实现和调度策略的差异。举个例子:Transformer里的Attention计算,朴素实现是把Q、K、V三个矩阵分别算好再组合,而FlashAttention类算子通过IO重排,把多次访问显存变成更少次数的高效读写,单这一步就能带来可观的prefill加速。CUDA Graph的作用则是把一串GPU kernel启动的开销提前录制好,运行时不再需要CPU反复下发指令,对短请求的延迟抖动抑制特别明显。
这些优化有一个共同特点:权重文件从头到尾没变,模型结构没变,但同一张显卡上的执行效率完全是两码事。我见过一个团队把Transformers默认后端换成vLLM后,什么都没调,TTFT先降了30%,当时群里所有人都愣住了。
为什么很多人守着旧引擎不换?无非是怕踩坑、怕兼容性出问题。但到了2026年,主流推理框架的成熟度已经很高,OpenAI兼容接口、动态批处理、量化格式支持都基本齐了。与其守着旧管线抠权重,不如花一个迭代周期把引擎层的版本和配置更新一遍,这个投入产出比通常比继续压权重高得多。
3. llama.cpp把权重offload到内存,到底算不算"动权重"
3.1 offload的本质是"搬家",不是"改内容"
最近"llama.cpp offload到内存"这个话题在社区里讨论得很凶,有不少人疑惑:把权重从显存搬到内存,这算不算权重改动?
我的判断很明确:不算。offload改变的是权重数据存放在哪一层存储介质,权重文件本身一个字节都没有变。它更像搬家,老房子里的家具一样不少,只是换了个地方摆放。
为什么要这么干?因为不是所有人的显卡都有80GB显存。对一个13B甚至70B的模型,显存放不下全部权重时,传统做法是硬塞,结果频繁触发CPU-GPU之间的搬运,整机卡成PPT。而llama.cpp这类框架的做法是让权重常驻系统内存(RAM),GPU只保留正在计算的那部分层,算完了再换。这利用了现代CPU的高内存带宽和llama.cpp高效的CPU算子,让"显存不够"的机器也能跑起来。
我在本地一台只有24GB显存的机器上跑过13B模型,不开offload基本是死路一条,开了offload之后虽然整体速度比不上全显存,但至少能稳定对话。对于很多个人开发者和原型验证阶段来说,这个"跑起来"比"跑得飞快"重要得多。
3.2 offload和TTFT之间,隔着一堵"内存带宽"的墙
很多人以为offload到内存会让速度慢到不可用,但实测不是这么回事。关键在于,decode阶段瓶颈是访存带宽,而DDR5多通道内存的实际带宽已经能到200GB/s以上,配合小模型足够喂饱CPU算力。
但对TTFT来说,offload的影响就要小心了。prefill阶段需要快速旁路大量权重和中间结果,如果权重在内存里、需要反复搬运,TTFT会明显恶化。我自己的经验是:小模型(7B级别以下)offload到内存跑,TTFT损失在可接受范围内;而大模型(30B以上)offload后,prefill阶段会成为灾难,首字延迟可能从1秒多飙到十几秒。
不同存储介质的带宽差距,是理解offload瓶颈的关键:
| 存储介质 | 典型带宽 | 对prefill的适配度 | 对decode的适配度 |
|---|---|---|---|
| 显存(HBM) | 1.5~3TB/s | 优秀 | 优秀 |
| 系统内存(DDR5) | 50~200GB/s | 偏弱 | 基本够用 |
| 系统内存(DDR4) | 20~50GB/s | 弱 | 瓶颈明显 |
| NVMe SSD | 3~7GB/s | 不可用 | 不可用 |
所以offload更适合"本地跑个小模型图个方便"的场景,它解决的核心问题是"能不能跑",而不是"怎么跑得更快"。真要压TTFT,优先考虑的还得是显存够不够、KV Cache管理好不好、prefill调度顺不顺,跟权重内容没关系。
3.3 视觉模型的"权重下载潮"背后,藏着同一个道理
热搜里"DINOv3权重下载""YOLOv8预训练权重下载""SAM3权重下载"这些词条都很热,但真正做过部署的人会有体会:拿回权重只是第一步,部署端的优化空间一点不比模型本身小。
拿YOLOv8来说,同样的onnx导出权重,用不同的TensorRT版本做算子融合、动态shape、半精度推理,端到端延迟能差出一倍;而NMS阈值、预处理方式(letterbox判断、归一化)反而经常成为瓶颈。SAM这类分割模型也一样,图像预处理的耗时在整条链路里占比极高,你再怎么优化权重,预处理不重写,端到端延迟也压不下去。
所以说,无论是LLM还是视觉模型,"权重之外"的优化正在成为普遍共识。这也是我把这个标题拿出来写的原因:2026年的推理提速,关键词已经不是"更强的模型",而是"更聪明的运行方式"。
4. 0行权重改动,77%首字延迟下降:我的完整复盘
4.1 基线与压测方法
为了避免"体感优化",我把这次实验做成了可复现的对比测试。先说环境:两张A100 80GB(NVLink连接),模型用的是一款7B级别的开源对话模型,FP16权重原封不动。推理框架从原始的HuggingFace Transformers生成式接口起步,这是很多人"开箱即用"的默认状态,基线就定在这。
压测方法是自己写的一个几分钟的脚本:构造一组不同长度的输入(512、1024、2048、4096 token),并发数设为8,循环发送请求,记录每个请求从发出到收到第一个token的时间间隔,取P50作为TTFT参考值,同时记录P95。
基线数据如下:输入2048 token时,TTFT中位数2.8秒;P95到了4.1秒。这个数据其实已经能感受到"卡"了。接下来四轮优化,权重一次没动。
4.2 四轮优化的实测效果
第一轮:把后端从Transformers切换到vLLM,启用PagedAttention。这一步什么都没调,TTFT从2.8秒降到1.9秒,降幅约32%。原因很直接:显存利用率上去了,预分配浪费少了,prefill阶段不再因为显存碎片被迫等待。
顺便说一下为什么Transformers默认管线慢:它对每个请求都是"来一个处理一个",动态图和静态优化的比例失衡,GPU算力利用率经常只有两到三成。换成vLLM这类框架之后,算子被预先优化、显存分配被重写,等于给同样的权重换了一双合脚的跑鞋。
第二轮:打开连续批处理(continuous batching)和前缀缓存(prefix caching)。连续批处理让新请求不用等当前batch跑完就能插入,前缀缓存则让重复的system prompt和公共上下文复用KV。这一轮TTFT降到1.2秒,降幅约37%。特别是前缀缓存,在多轮对话场景下效果比我想的还要猛。
第三轮:开启CUDA Graph,同时把Attention实现换成FlashAttention类算子。TTFT从1.2秒降到0.9秒,降幅约25%。这一轮的收益主要来自kernel启动开销减少和显存访问效率提升,很直观地体现了"同一个权重、不同引擎、不同算子"的差距。
第四轮:做KV Cache INT8量化并调优显存分配策略。TTFT从0.9秒降到0.65秒,降幅约28%。这轮有个细节:KV Cache量化初期效果反复横跳,后来排查发现是量化粒度太粗导致部分请求精度损失、触发重复采样,把粒度调到按层量化后稳定下来。
四轮合计:从2.8秒到0.65秒,TTFT下降了76.8%,约等于77%。整个过程中模型权重文件一直是原始FP16,一行没改。
我把这四轮数据整理成了一张表,方便对照:
| 轮次 | 优化动作 | TTFT(P50) | 单轮降幅 | 累计降幅 |
|---|---|---|---|---|
| 基线 | Transformers默认 | 2.80s | — | — |
| 第一轮 | 切换vLLM,PagedAttention | 1.90s | 32% | 32% |
| 第二轮 | 连续批处理+前缀缓存 | 1.20s | 37% | 57% |
| 第三轮 | CUDA Graph+FlashAttention | 0.90s | 25% | 68% |
| 第四轮 | KV Cache INT8量化 | 0.65s | 28% | 77% |
4.3 为什么零权重改动还能有这么大收益
一个很多人没意识到的点:权重层面的优化(量化、剪枝、蒸馏)在2024到2025年已经被大量工程化落地了,各开源模型的部署默认配置几乎都吃到了这波红利。但执行层(框架、调度、缓存、算子)的优化,普及度还远远不够,大量生产环境还在用最原始的Transformers管线跑,性能账面上的浪费可能高达百分之六七十。
换个说法:模型权重决定的是推理速度的理论上限,执行层决定的是你实际能拿到这个上限的多少。很多团队卡在"权重不能再压缩了"的死胡同里,其实是把这两个概念混为一谈了。做一次执行层的体检和升级,往往比继续压权重性价比高得多。
5. 权重外优化的坑与边界:什么情况下你拿不到这77%
5.1 超长上下文的prefill墙
如果你把压测场景换成128K上下文,前面那套优化组合的效果会大打折扣。原因在于,当输入token数达到数万甚至十几万时,prefill阶段的Attention计算复杂度会爆炸式上升,KV Cache的管理再高效,也架不住计算量本身的量级上去了,TTFT照样一路走高。
这个场景下的解法是另一套思路:稀疏注意力、滑窗注意力、IO优化算子、或者干脆把大上下文拆成检索分块处理。如果你遇到的是这类业务,直接套用上面的四轮优化会失望,需要单独设计方案。
怎么判断自己要不要走长上下文优化这条线?就看一个数据:业务里超过10%的请求输入token数大于8192,如果是,那你的TTFT优化重点就该放在Attention实现和上下文压缩上,而不是一味堆KV缓存和调度。
5.2 高并发时,TTFT和吞吐是跷跷板
为了把TTFT压到极致,如果无脑给每个请求分配最高优先级,并发一上来,整卡算力会被快速抢占,吞吐量直线崩塌,最后所有请求一起变慢,P95 TTFT反而恶化。
实战中一定要做容量规划和排队策略:max_num_seqs限制单批最大请求数、空闲等待时间(idle timeout)设置、长请求和短请求的分队列处理,这些参数比模型层的东西更影响服务稳定性。我的建议是,把TTFT和吞吐两个指标同时打点采样,观察它们的联动关系,而不是只盯着一个数。
举一个我踩过的例子:某次为了把P50 TTFT从0.8秒压到0.6秒,我把batch上限调小了一半,结果P95直接飙到3秒多,因为短请求倒是快了,长请求全在排队。后来改成"短请求走低延迟队列、长请求走吞吐队列"的双队列方案,P50和P95才同时稳定下来。
5.3 "显存省了"不等于"延迟降了"
优化KV Cache、开启量化之后,你会发现显存占用确实降下来了,但某些请求的TTFT反而变差。这个现象我遇到过好几次,原因基本都是量化带来的精度损失,让模型生成了低置信度token,进而触发重新采样或重复推理。
所以每次做完"省显存"类型的优化,一定要用真实业务prompt集做回归测试,不能只看显存数字好看。我一般会同时记录三个指标:显存占用、TTFT分布、生成质量(用采样概率或人工抽查),三者对齐才能确认优化真的成功了。
另外还要提醒一句:不要为了压TTFT而把并发粒度调得太极端。极端小的batch确实让单个请求跑得快,但系统整体吞吐低,排队反而拖累所有请求。这个平衡点没有一个通用公式,要在你自己的负载特征下反复压测。
最后分享一个我这两年的习惯:每个推理服务上线前,我都会写一份固定参数的压测脚本存在仓库里(prompt长度、并发数、采样参数全部固定),每次改动任何配置都跑一遍,把TTFT和吞吐的P50/P95记录成一份趋势表。别小看这个动作,很多"优化完反而变慢"的问题,都是靠着这种对比才能第一时间抓出来。权重之外的优化空间还很大,前提是你得有一把稳定的尺子去量它。