研究聚焦大模型推理服务的核心瓶颈:在线系统为何经常不是被权重大小限制,而是被 KV Cache、调度、量化误差和真实流量分布共同限制。方法上,文章把推理拆为 prefill 与 decode 两个阶段,围绕 KV Cache、PagedAttention、连续批处理、量化、投机解码、前缀缓存和压测指标展开机制分析。结论是,推理优化不是追求单次最快,而是在吞吐、首 token 延迟、输出稳定性、显存容量、质量和成本之间建立可观测取舍;上线决策必须基于真实流量分布和业务质量基线。
推理优化;KV Cache;PagedAttention;连续批处理;量化;投机解码;前缀缓存;TTFT
本文的研究问题是:当多个用户以不同输入长度、输出长度和取消行为同时访问模型服务时,系统如何维持可接受的延迟、吞吐和质量。方法上,文章采用瓶颈分解:先区分 prefill 与 decode 的计算特征,再分析缓存内存、调度策略、低精度表示、草稿验证和上下文复用对端到端体验的影响。评估不以单一 tokens/s 为准,而同时观察 TTFT、ITL、p95/p99 延迟、KV Cache 使用率、错误率和业务准确性。
图中显示,推理优化同时发生在计算、内存和调度层。一个常用的 KV Cache 粗略估算是:
其中, 表示 KV Cache 显存占用, 表示层数, 表示 KV 头数, 表示每个头维度, 表示序列长度, 表示活跃序列数, 表示每个缓存元素的字节数,系数 2 分别对应 key 和 value。这个公式解释了为什么长上下文和高并发会迅速吃掉显存,也解释了 GQA、KV Cache 量化和 PagedAttention 的工程价值。
大模型推理优化不是把模型换成更快的版本,也不是把显卡换成更贵的型号。真正的推理优化,是在用户请求、模型结构、显存管理、批处理调度、量化格式、缓存策略和产品体验之间找到可持续的平衡。一个系统能不能稳定服务用户,往往不取决于某次离线跑分有多漂亮,而取决于高峰期长短请求混在一起时,首 token 延迟是否可控,输出是否连续,显存是否还能容纳 KV Cache,错误是否能被定位,成本是否能被解释。
很多团队第一次部署大模型时,习惯先看权重大小和单请求速度。这个视角只覆盖了问题的一小部分。生成式模型在线服务的真实压力来自两件事:第一,用户请求并不是整齐到达、整齐结束的静态批;第二,模型为了逐 token 生成,需要为每个活跃序列保存越来越长的 KV Cache。只要上下文变长、并发上来、输出变多,瓶颈就会从“能不能加载权重”转向“能不能高效管理显存和调度请求”。
推理优化的核心目标也不只是更快。生产系统同时关心吞吐、首 token 延迟、输出 token 间隔、成本、质量、上下文长度、并发公平性、取消请求、长任务隔离、流式稳定性和回滚能力。吞吐提高但 p99 延迟失控,用户会觉得服务不可靠;量化省下显存但结构化输出错误增加,业务会把省下的钱重新花在人工修正上;投机解码在短回答里很快,但在低接受率场景下可能增加系统复杂度。
这篇文章把大模型推理优化拆成五个主线:KV Cache、PagedAttention、连续批处理、量化和投机解码。它们不是孤立技巧,而是一组互相牵制的工程机制。KV Cache 决定长上下文和高并发的显存账;PagedAttention 解决 KV Cache 的碎片和共享问题;连续批处理让真实在线请求保持 GPU 忙碌;量化降低权重、激活或缓存的内存压力;投机解码尝试减少自回归生成的串行等待。理解这五件事,才能真正看懂 vLLM、TensorRT-LLM、Text Generation Inference、SGLang、llama.cpp 等推理栈的价值边界。
大语言模型生成一次回答,通常可以拆成两个阶段。第一个阶段是 prefill,也叫提示词预填充。模型一次性处理用户输入、系统提示词、工具定义、历史对话和检索内容,计算出每一层注意力所需的初始 key 和 value。第二个阶段是 decode。模型从当前上下文开始,每次生成一个或少量新 token,并把新 token 对应的 key 和 value 追加到缓存里,直到遇到停止条件。
Prefill 和 decode 的性能特征差别很大。Prefill 面对的是已经给定的输入 token,矩阵计算可以较好并行,长 prompt 会让这个阶段非常重。Decode 面对的是自回归生成,后一个 token 依赖前一个 token,天然有串行性。即使 GPU 很强,也不能把完整回答一次性算完,除非引入投机解码、多 token 预测或特殊模型结构。普通 decoder-only 模型每生成一个 token,都要读取已有上下文对应的 KV Cache,并执行一轮前向计算。
用户体验里的首 token 延迟主要由排队、tokenization、prefill、调度等待和第一次 decode 决定。用户体验里的流式速度主要由 decode 阶段决定。一个长文档问答请求可能 prefill 很慢,但开始输出后还算平滑;一个短 prompt 长输出请求可能首 token 很快,但后面每个 token 都拖得很久。优化前先拆指标,否则很容易把所有慢都归因到“模型不够快”。
在线服务还要看请求分布。大量真实流量是混合的:有人只问一句话,有人贴一整份合同;有人要一句摘要,有人要几千字报告;有人刚发出请求就取消,有人保持长会话连续追问。静态 benchmark 通常把输入长度、输出长度和批大小固定住,结果更像实验室条件。生产系统必须用真实分布压测,并分别观察短输入短输出、短输入长输出、长输入短输出、长输入长输出和多轮共享前缀这些场景。
Transformer 的自注意力会为每个 token 计算 query、key 和 value。训练时整段序列一次性计算,推理生成时如果每一步都重新计算全部历史 token,成本会随着生成长度急剧上升。KV Cache 的作用就是把已经计算过的 key 和 value 存起来。下一步生成时,模型只需要为新 token 计算新的 query、key、value,再让新 query 与历史 key 做注意力计算。
KV Cache 让自回归生成可用,但也把显存压力从权重扩展到了请求状态。每个活跃请求、每一层、每个 token 都有 key 和 value。上下文越长,KV Cache 越大;并发越高,KV Cache 越大;层数越多、隐藏维度越高、数据类型越宽,KV Cache 越大。一个模型权重能放进显存,并不意味着它能承载生产流量。权重是固定账,KV Cache 是随请求动态增长的账。
粗略估算时,可以把 KV Cache 看成与层数、序列长度、批内序列数、KV 头数、每个头维度和元素字节数成正比。多头注意力保存每个头的 key 和 value,Grouped Query Attention 和 Multi Query Attention 会减少 KV 头数,从而降低缓存占用。很多新模型选择 GQA,不只是为了模型质量和训练稳定,也与推理显存效率有关。
KV Cache 还有一个容易被忽略的特性:它是动态的。用户 A 可能输入 500 token、输出 50 token;用户 B 可能输入 30000 token、输出 2000 token;用户 C 可能中途断开。请求到达和结束时间不同,缓存块持续申请和释放。如果推理引擎用简单连续内存管理,就会出现碎片、预留浪费和无法复用的问题。显存明明还有总量,却找不到足够连续空间,这类问题在长上下文高并发下尤其明显。
KV Cache 对成本的影响也很直接。服务商按 token 收费时,输入 token 和输出 token 价格不同,背后就有 prefill 和 decode 成本差异。自部署时,长上下文会降低同一张 GPU 能容纳的并发序列数,吞吐下降后单位回答成本上升。盲目把 max_model_len 开到模型标称上限,会让每个实例预留更大缓存预算,最终可能牺牲大部分普通短请求的服务效率。
生产系统应该把 KV Cache 当成一等指标,而不是隐藏在显存使用率后面。只看 GPU 显存总占用不够,要看当前运行序列数、等待序列数、KV Cache 使用率、缓存块命中率、prefix cache 命中率、prefill token 吞吐、decode token 吞吐和 OOM 原因。很多线上问题表面是延迟升高,根因是 KV Cache 接近上限后调度器无法再接纳新请求。
PagedAttention 的名字容易让人以为它是一种新的注意力数学公式。更准确地说,它是围绕 KV Cache 的内存管理方式。传统做法倾向于为每个序列分配连续 KV 缓存空间,而 PagedAttention 借鉴操作系统分页思想,把每个请求的 KV Cache 切成固定大小的块,通过块表把逻辑位置映射到物理显存块。这样,一个请求的缓存不必在物理显存中连续存放。
这个设计的价值来自三个方面。第一,减少碎片。不同请求长度不同,块级分配比连续大块分配更容易填满显存空隙。第二,按需增长。请求生成到哪里,缓存块申请到哪里,不必为最大长度一次性预留完整空间。第三,共享前缀。多个请求如果共享相同前缀,对应 KV 块可以复用,尤其适合相同系统提示词、多轮会话、同一长文档多次提问和并行采样。
vLLM 论文把 PagedAttention 与 vLLM serving 系统一起提出,重点指向高吞吐 LLM 服务里的 KV Cache 浪费问题。Hugging Face Text Generation Inference 的概念文档也把 PagedAttention 描述为把 KV Cache 分块并通过查找表访问,从而支持非连续内存和更多推理 batch。这个思想解释了为什么 vLLM 在真实服务场景中经常比朴素 Transformers 推理更稳:它不是只快在单次矩阵乘,而是减少了调度时被缓存碎片卡住的概率。
PagedAttention 也不是免费午餐。块表带来一次间接访问,注意力 kernel 需要适配分块布局。块大小选择会影响内部碎片和访问效率。块太大,短请求浪费增加;块太小,元数据和调度开销增加。不同模型、不同 GPU、不同上下文长度下,最优块大小不一定一样。新论文和系统还在探索虚拟内存式管理、连续虚拟地址、KV offload 和分布式 prefix cache,说明 KV Cache 管理仍是活跃工程方向。
对应用开发者来说,PagedAttention 的正确使用方式不是记住论文术语,而是在部署参数里尊重缓存预算。max_num_seqs、max_num_batched_tokens、gpu_memory_utilization、max_model_len、block size、prefix caching 开关都和 PagedAttention 的效果有关。参数过保守,GPU 吃不满;参数过激进,高峰期 OOM 或排队严重。一次压测不能只看 tokens/s,还要看在不同并发和上下文长度下,缓存块是否稳定回收。
批处理是提高 GPU 利用率的基本手段。传统静态批处理会把一组请求凑成 batch,一起跑完。问题是生成式请求长度差异很大。短请求早就生成完了,却要等长请求继续 decode;新来的请求也要等当前 batch 结束才能进入。对于用户在线对话,这种等待会浪费 GPU,也会拖慢首 token 延迟。
连续批处理,也常被称为 iteration-level scheduling 或 in-flight batching,思路是每个生成步都重新调度 batch。某个请求完成、取消或达到长度上限后,马上释放位置;等待队列里的新请求可以在下一轮加入。Hugging Face Transformers 的连续批处理文档把这个机制描述为每个 generation step 动态重新调度 batch,完成的请求离开,新请求立即进入。TensorRT-LLM 的 in-flight batching、vLLM 的调度器和很多生产推理引擎都围绕同一目标展开:让 GPU 尽可能持续工作。
连续批处理的收益来自真实流量的差异性。短请求不必陪长请求跑完整段;长请求也不必独占实例;新请求能更快进入服务。它尤其适合聊天、客服、代码助手和多租户模型网关。静态批适合离线推理或整齐任务,连续批更适合开放在线服务。两者不是谁永远更好,而是请求到达方式不同。
连续批处理会引入调度策略问题。调度器需要决定谁先 prefill、谁继续 decode、一次接纳多少 token、等待多久凑 batch、长 prompt 是否分块、长输出是否限速、不同租户是否公平。只追求吞吐,短请求可能被长上下文挤压;只追求首 token 延迟,GPU 可能批不起来;只按请求数限流,超长上下文用户会占走大量 KV Cache。生产调度往往要同时按 token、序列数、缓存块、租户优先级和超时时间治理。
连续批处理还会放大 prefill 与 decode 的冲突。Prefill 处理长输入时计算量大,会占用 GPU;decode 需要稳定小步前进,关系到所有正在流式输出的用户。如果一个超长 prompt 直接进入 prefill,可能让已有用户的输出间隔变长。chunked prefill 就是为此出现的工程手段之一:把长输入分块预填充,让 decode 轮次能穿插运行,避免长请求把交互流完全堵住。
良好的连续批处理系统还需要取消请求处理。用户关闭页面、网络断开、上层超时后,如果推理引擎还继续为无效请求生成 token,就会浪费 KV Cache 和 GPU 时间。生产服务应该把客户端取消、网关超时和业务停止信号传到推理层,及时释放缓存块。很多成本浪费不是模型慢,而是系统在认真回答已经没人要的请求。
量化的基本思想是用更低精度表示模型中的数值。推理里常见的是权重量化,也可能包含激活量化和 KV Cache 量化。FP16 或 BF16 权重每个参数约 2 字节,INT8 约 1 字节,INT4 约半字节,FP8 约 1 字节。把权重从半精度压到 8 位或 4 位,可以显著降低显存占用和内存带宽压力,让更大模型跑在更少 GPU 上。
量化的第一条原则是分清对象。权重量化主要减少权重显存;激活量化影响计算中间值;KV Cache 量化减少上下文缓存。很多团队说“模型已经 INT4 了,为什么长上下文还是爆显存”,原因就在这里:权重省下来了,KV Cache 仍可能是 BF16 或 FP16。长上下文高并发场景下,KV Cache 可能比想象中更早成为瓶颈。vLLM 的量化文档把 FP8、INT8、INT4、AWQ、GPTQ、GGUF、BitsAndBytes、量化 KV Cache 等列为不同实现路径,也提醒了硬件兼容性差异。
量化的第二条原则是分清硬件和 kernel。低比特不一定更快。INT4 权重如果没有成熟 kernel,可能只是省显存但吞吐没有明显提升。FP8 在 Hopper、Blackwell 等新架构上更有优势,老卡未必收益相同。GGUF 在本地 CPU、Metal、CUDA 混合场景里很方便,但大规模在线服务更可能选择 vLLM、TensorRT-LLM 或 TGI 的格式和 kernel。格式选择不是信仰问题,而是要看硬件、引擎、模型结构和业务指标。
量化的第三条原则是评测业务质量。通用聊天看起来没问题,不代表数学、代码、工具调用、JSON 输出、中文长文档、专业术语和安全拒答都没问题。量化误差常常在边界任务上出现:数字错一位,引用漏一条,JSON 多一个逗号,工具参数类型不稳定,长上下文中间信息被忽略。对生产系统来说,这些错误比闲聊口吻变化更重要。
KV Cache 量化尤其需要谨慎。Hugging Face Transformers 的缓存文档提醒,量化缓存能降低内存需求,但短上下文且显存足够时可能伤害延迟。vLLM 的量化 KV Cache 文档提供 FP8 KV Cache 路径,并区分 per-tensor 与 per-attention-head 等量化方式。工程上应把它视为“用质量和复杂度换上下文容量与吞吐”的选项,而不是默认开关。
量化上线前最好建立三套基线。第一套是 BF16 或 FP16 质量基线,用来判断模型本身能否完成任务。第二套是量化质量基线,用同一批业务样本比较答案准确率、格式成功率、引用正确率、拒答边界和人工偏好。第三套是性能基线,比较 TTFT、输出 token 间隔、吞吐、显存、缓存使用率和错误率。只有质量损失可接受、性能或容量收益明确,量化才算真正省钱。
自回归生成慢,本质上是因为大模型每次只能稳妥地产生下一个 token。投机解码的思路是找一个更小、更快的 draft model 先猜出多个候选 token,再让目标大模型一次验证这些候选。如果候选被接受,就相当于一次大模型前向推进了多个 token;如果候选不被接受,就回退到大模型给出的修正分布继续生成。Leviathan、Kalman 和 Matias 在 ICML 2023 的论文系统化讨论了这个方向,目标是在保持输出分布一致的前提下提高推理速度。
投机解码能否有效,关键看接受率和额外开销。小模型越接近大模型,候选 token 越容易被接受,但小模型本身也越贵。小模型太弱,猜错很多,大模型验证后只能接受少量 token,额外 draft 开销就会抵消收益。温度、top-p、任务类型、语言、上下文长度和模型家族都会影响接受率。确定性短回答、代码补全、格式固定任务可能更适合;开放创作、高温采样和复杂推理可能收益不稳定。
投机解码还有多种变体。有的使用独立 draft model,有的使用同一模型的轻量头或多 token prediction,有的使用 n-gram 猜测,有的结合 Medusa、EAGLE、MTP 等结构。TensorRT-LLM 的特性列表包含 speculative sampling,vLLM 也提供 speculative decoding 功能。不同实现对模型兼容、显存、并发和缓存管理要求不同,不能只看“最高加速倍数”。
投机解码与连续批处理之间也有张力。一个请求一次验证多个 token,会改变 decode 步长;不同请求接受 token 数不同,会让 batch 内序列推进不均匀;draft model 也要占用 GPU 或 CPU 资源。高并发服务里,单请求 token 间延迟下降,不一定等于整体吞吐稳定提升。投机解码更像精细优化,而不是替代基本调度和 KV Cache 管理。
投机解码上线时要记录接受率、每步草稿长度、实际接受 token 数、回退次数、draft 模型耗时、目标模型验证耗时和端到端延迟。接受率长期偏低时,应该自动降级或关闭。对多租户服务来说,还要避免某些任务类型拖累整体调度。例如代码补全可以开启投机,长文档总结未必开启;低温结构化抽取可以尝试,创意写作不一定值得。
KV Cache 不只可以在一个请求内部复用,也可以在请求之间复用。前缀缓存就是典型例子。如果多个请求共享同一段开头,例如相同系统提示词、相同工具 schema、相同知识库文档或同一会话历史,那么后续请求可以直接复用前缀部分的 KV Cache,跳过重复 prefill。vLLM 的 Automatic Prefix Caching 文档明确说明,新请求如果与已有查询共享相同前缀,可以复用已有 KV Cache,从而跳过共享部分计算。
前缀缓存适合两类场景。第一类是同一长文档多次提问。用户上传年度报告、产品手册或合同后,连续问不同问题,文档内容作为稳定前缀反复出现。第二类是多轮对话。客户端每轮把完整历史传给模型,如果历史前缀稳定,缓存能减少重复处理。对模型网关来说,工具定义、角色说明和固定安全策略也可能成为高频共享前缀。
前缀缓存的限制同样清楚。它只减少 prefill,不减少生成新 token 的 decode 时间。如果回答很长,主要耗时在输出阶段,缓存收益会下降。如果请求前缀不稳定,例如每次把时间戳、随机 ID、动态检索结果放在最前面,缓存命中率会很低。缓存还通常局限在一个推理实例内,负载均衡如果把同一会话打散到不同实例,命中率会下降,除非有 prefix-aware routing 或分布式 KV Cache。
设计 prompt 时要考虑缓存友好。固定内容放前面,动态内容放后面;工具 schema 顺序保持稳定;不要在系统提示词开头塞每次变化的追踪字段;长文档如果需要多次提问,尽量保持原文块顺序不变。很多团队只从语言效果设计 prompt,却忽略了前缀稳定性带来的推理成本差异。
一个在线模型服务不应该先写启动命令,再等用户来适配。正确顺序是先了解产品流量:平均输入长度、p95 输入长度、最大输入长度、平均输出长度、p95 输出长度、并发峰值、会话持续轮数、长文档比例、工具调用比例、取消率、租户优先级和可接受延迟。然后再决定模型大小、上下文上限、最大输出、批处理参数、量化方式和缓存策略。
如果大多数请求是短问答,长上下文上限就不该无节制打开。可以把普通交互池设成较短上下文和较低 TTFT,另设长文档池处理重任务。如果大多数请求共享固定系统提示词和工具 schema,就应该提高前缀缓存命中率。如果输出很长,投机解码、流式节奏和取消请求处理更关键。如果输入很长但输出短,chunked prefill、prefix cache 和上下文裁剪更重要。
模型网关要按 token 而不是按请求理解负载。一个 100 token 问答和一个 100000 token 文档分析不能算同一次请求。限流也不应只有 QPS,还要包含输入 token、输出 token、并发序列、KV Cache 预算和排队时间。否则少数长上下文用户会挤压所有短交互用户,最后表现为全站慢。
部署参数要有业务含义。max_model_len 是允许用户携带多少上下文,不是模型卡片上写多少就开多少。max_num_seqs 是同一实例能同时承载多少活跃序列,不是越大越好。max_num_batched_tokens 控制 prefill 批内 token 上限,影响吞吐和首 token 延迟。gpu_memory_utilization 留多大余量,关系到 OOM 风险和缓存容量。量化格式、KV dtype、prefix caching、speculative decoding 都应该绑定评测结果。
推理优化最常见的错误,是只跑性能压测,不跑质量回放。量化、投机解码、上下文裁剪、检索压缩和系统提示词改写都会影响答案。即使算法承诺输出分布一致,具体工程实现、采样参数和 tokenizer 组合也可能带来差异。生产验收至少要包含业务样本、长上下文样本、结构化输出样本、工具调用样本、安全边界样本和多轮会话样本。
压测也不要只看平均值。TTFT 要看 p50、p95、p99;输出 token 间隔要看抖动;端到端延迟要按输入长度和输出长度分桶;错误率要区分 OOM、超时、取消、上下文超限、网关断流和模型输出非法。GPU 指标要看利用率、显存、KV Cache 使用、批大小、队列长度和缓存命中率。没有这些拆分,就很难判断瓶颈在 prefill、decode、调度、网络还是上层业务。
压测样本要接近真实。反复发送同一个短 prompt 会高估缓存收益,也会低估长尾延迟。只测单用户会低估 KV Cache 压力。只测最大上下文会低估普通流量体验。更好的做法是用真实日志脱敏后构造混合 workload,保留输入长度、输出长度、会话轮数和到达时间分布,再在候选配置之间比较。
评估成本时要算有效结果。一个模型实例每秒输出 token 很高,但如果答案质量下降导致用户重试,单位有效回答成本可能更高。一个量化版本显存省了 40%,但 JSON 成功率下降 5%,对自动化工作流可能不可接受。一个投机解码配置让平均速度提高,却让 p99 不稳定,对客服场景可能得不偿失。生产优化不是追单项最高,而是让业务指标整体更好。
第一个误区是把上下文窗口当成有效阅读能力。模型支持 128K token,不代表它能均匀、稳定、低成本地使用 128K token。长上下文会增加 prefill 成本和 KV Cache 压力,也可能引入“中间信息不易被使用”的问题。真正的长文档应用需要检索、重排、分段摘要、引用校验和位置设计,而不是把所有内容塞进去。
第二个误区是把量化当成无损压缩。权重低比特化、激活量化和 KV Cache 量化都会改变数值行为。影响可能很小,也可能集中出现在业务最敏感的地方。没有业务评测的量化,只是把风险推迟到用户侧。
第三个误区是把 PagedAttention 当成无限显存。PagedAttention 能减少浪费,提高缓存管理效率,但不能改变 KV Cache 随上下文和并发增长的物理事实。显存预算不够时,仍然需要限制上下文、降低并发、使用 GQA/MQA 模型、启用 KV 量化、增加 GPU 或拆分任务池。
第四个误区是把连续批处理当成无脑增吞吐。批处理提高 GPU 利用率,但等待窗口、长短请求混排和 prefill/decode 争用会影响用户体验。调度器需要业务目标,否则吞吐提升可能伴随交互延迟恶化。
第五个误区是把投机解码当成所有模型都能 2 倍加速。投机收益取决于 draft 模型质量、目标模型验证成本、采样策略和请求类型。接受率低时,系统可能更复杂但不更快。应该按任务开关,而不是全局迷信。
第一步,建立基线。用目标模型的标准精度跑真实业务评测和混合流量压测,记录质量、TTFT、输出速度、吞吐、显存和错误类型。没有基线就谈不上优化,因为你不知道任何改动是收益还是损失。
第二步,控制上下文和输出。给不同产品入口设置合理的最大输入、最大输出和超时。短问答、长文档、批处理、代码生成和后台报告不要混在同一个资源池里。先避免明显浪费,再谈高级优化。
第三步,启用合适的推理引擎。高并发在线生成优先考虑支持 PagedAttention、连续批处理、OpenAI 兼容服务、指标暴露和多 GPU 的引擎。单机本地、边缘设备或低成本场景可以考虑 llama.cpp 与 GGUF。NVIDIA 高端 GPU 上的严肃生产部署可以评估 TensorRT-LLM。选择依据是业务和团队能力,不是社区热度。
第四步,调缓存。对稳定系统提示词、工具 schema、长文档和会话历史设计前缀稳定性。观察 prefix cache 命中率和 prefill 时间变化。命中率低时,不要急着怪引擎,先检查 prompt 是否每次变化、负载均衡是否打散、文档拼接顺序是否稳定。
第五步,尝试量化。先权重量化,再根据长上下文压力评估 KV Cache 量化。每个版本都必须跑同一套质量回放和性能压测。不要把“能启动”当成“能上线”。
第六步,尝试投机解码。选择接受率高、输出较长、质量边界清楚的任务做灰度。持续观察接受率和 p99。收益不明显时及时关闭,避免复杂度留在系统里。
第七步,建立可观测性和回滚。任何推理优化都要能按模型、租户、入口、上下文长度和配置版本拆指标。出现质量回退、错误率上升或延迟长尾时,可以迅速切回基线版本。
如果权重放不下,先看权重量化、张量并行、更小模型或更大显存。不要误以为 PagedAttention 能让权重变小。
如果长上下文并发爆显存,先看上下文限制、KV Cache 量化、GQA/MQA 模型、PagedAttention 参数、prefix cache、任务拆池和更多实例。
如果首 token 慢,先拆排队时间和 prefill 时间。可能需要减少输入、chunked prefill、前缀缓存、缩短等待窗口、交互池隔离或提高 prefill 吞吐。
如果流式输出慢,先看 decode 吞吐、batch 形态、KV Cache 带宽、采样设置、投机解码和输出长度限制。
如果 GPU 利用率低,先看连续批处理、请求到达分布、batch token 上限、等待窗口和是否过度隔离资源。
如果质量下降,先回到标准精度和原始上下文,逐项排查量化、采样、裁剪、检索、prompt 改写和模型版本。
理解原理后,再看推理引擎会清楚很多。vLLM 的强项是高吞吐在线服务、PagedAttention、连续批处理、前缀缓存、OpenAI 兼容接口和活跃模型适配。它适合做统一模型服务入口,尤其适合请求长度不一、并发变化大、需要快速接入开放权重模型的团队。使用 vLLM 时,重点不是追最新参数,而是把模型支持、量化格式、上下文上限、缓存策略和服务指标一起验证。
TensorRT-LLM 更偏 NVIDIA 深度优化路线。它把模型构建、kernel、并行、量化、in-flight batching、paged KV cache 和 speculative sampling 放在同一个高性能框架里考虑。优势是能充分利用 NVIDIA 新硬件特性,适合对吞吐和延迟要求很高、硬件栈统一、愿意投入构建和调优成本的团队。代价是工程门槛更高,模型转换、engine 构建、版本兼容和部署流程都要管理好。
Text Generation Inference 更贴近 Hugging Face 生态。模型来源、部署接口、容器化、指标和常见服务特性比较完整,适合已经围绕 Hugging Face 模型仓库和推理服务工作的团队。它的价值不只是跑模型,而是把模型服务化、流式输出、批处理、监控和常见部署形态做成相对标准的入口。对很多中等规模团队来说,少踩集成坑比追某个极限 benchmark 更重要。
SGLang 强调结构化生成、RadixAttention、前缀复用和面向 LLM 程序的高效运行。它适合有复杂 prompting、工具调用、结构化输出、树状搜索或多轮共享前缀的场景。很多 Agent 和复杂推理应用不是单次聊天,而是由多个模型调用组成。此时,运行时能不能复用前缀、能不能组织结构化生成、能不能减少重复 prefill,会直接影响整体成本。
llama.cpp 的重点在本地和边缘推理。GGUF、CPU、Metal、CUDA、Vulkan、多种量化格式和广泛模型社区,让它成为个人电脑、Mac、低成本服务器和离线场景的常用选择。它不一定是高并发在线服务的首选,但非常适合本地知识助手、离线工具、隐私敏感小规模部署、原型验证和社区分发。对很多产品来说,本地能跑和云上能跑一样重要,因为部署边界会影响数据安全和用户信任。
选择引擎时,不要只问“哪个最快”。更好的问题是:它是否稳定支持目标模型;是否支持所需上下文长度;是否支持当前硬件;是否有合适量化格式;是否暴露 token 级指标;是否能处理取消请求;是否支持流式输出;是否方便灰度和回滚;是否和现有网关、鉴权、日志、监控体系兼容。一个单机压测最快的引擎,如果生产运维不可控,最后仍可能是错误选择。
同一个团队也可以同时使用多个引擎。云上高并发聊天用 vLLM,本地桌面助手用 llama.cpp,NVIDIA 生产集群的固定模型用 TensorRT-LLM,复杂 Agent 后端尝试 SGLang。这不是架构混乱,而是让工具服务于任务。但前提是上层要有统一模型网关和评测体系,否则多引擎会变成多套不可比较的黑盒。
推理优化经常被理解成底层工程,但上下文组织同样会决定性能。一个 prompt 多几千 token,不只是多花输入费用,也会增加 prefill 时间和 KV Cache 占用。系统提示词重复、工具 schema 冗长、历史对话无限追加、检索片段无去重,都会把推理压力推高。很多时候,最便宜的推理优化是删掉无用上下文。
上下文应该按稳定性排序。长期不变的系统规则、工具 schema 和固定文档前缀适合放在开头,方便前缀缓存命中。每次变化的用户问题、时间、临时参数和本轮检索结果放在后面。很多系统把动态字段放在最前面,例如请求编号、当前时间、调试标记,结果破坏了前缀匹配。缓存不命中时,推理引擎只能重复计算相同内容。
上下文还应该按任务相关性裁剪。长窗口给了更多空间,但不代表应该把所有材料都放进去。用户问一个条款定义,就优先给相关条款、上下级章节和引用来源;用户要做全文总结,再给完整结构和分段摘要。把整份文档无差别塞入模型,会让 prefill 变慢、KV Cache 变大,也会让重要证据被噪声淹没。
历史对话要做状态化,而不是机械拼接。多轮聊天里,旧需求、已解决问题、用户更正和模型错误都会留下。如果每轮都把原始历史完整发送,模型既要理解当前任务,又要从历史垃圾里识别过期信息。更好的做法是维护当前目标、已确认事实、未解决约束、用户偏好和最近少量原文。这样既省 token,也减少答非所问。
工具调用也要压缩。数据库查询结果、搜索结果、代码执行日志和业务 API 返回值,往往包含大量模型不需要的字段。工具层应该把结果整理成面向任务的事实、异常、候选动作和证据链接,而不是把原始 JSON 全量塞入上下文。对推理服务来说,少一千个无用 token,就是少一段 prefill、少一段缓存、少一次误读风险。
因此,推理优化团队和应用团队不能分开工作。底层工程师可以提供 PagedAttention、连续批处理、量化和缓存,但应用侧如果不断制造混乱上下文,服务仍会慢、贵、不稳定。生产级大模型系统的成本账,最终是模型、引擎和上下文共同决定的。
任何推理优化都应该可回滚。换量化格式、打开 KV Cache 量化、调整 batch 参数、启用投机解码、改变上下文上限、升级推理引擎,都可能改变质量和延迟。生产系统不能把这些动作当作一次性改配置,而要当作版本发布。每个版本都应该记录模型权重、tokenizer、chat template、推理引擎、启动参数、量化方法、采样参数和评测结果。
灰度时要避免只看整体指标。新配置可能让平均吞吐提高,却让长文档用户失败;可能让短问答更快,却让结构化输出错误上升;可能让显存占用降低,却让中文专业任务质量下降。指标应该按入口、模型、租户、语言、输入长度、输出长度和任务类型拆分。只有这样,才能知道优化到底帮了谁、伤了谁。
回滚路径要提前准备。模型服务常见问题包括 OOM、输出格式漂移、工具调用失败、p99 延迟升高、流式断开、显存碎片、缓存命中异常和新版 engine bug。上线前就应该知道如何切回旧镜像、旧模型、旧参数或旧路由。没有回滚的优化,本质上是在生产环境赌博。
版本治理还要覆盖客户端。很多模型问题不是服务端单独造成的,而是客户端 prompt、工具 schema、采样参数和停止词一起变化。一次看似底层的引擎升级,如果同时改了上层 prompt,就无法判断收益来源。严肃的生产评测应尽量一次只变一个关键因素,或在记录里明确所有变化。
最终,推理优化应该像数据库索引优化一样被管理:有基线,有实验,有指标,有灰度,有回滚,有长期观察。只有这样,团队才不会在每次引擎升级、模型发布和硬件变化时重新摸黑。
推理优化最后必须回到指标,否则很容易变成技术名词堆砌。KV Cache 对应的指标,是缓存占用、缓存块利用率、上下文长度分布、活跃序列数和 OOM 类型。PagedAttention 对应的指标,是同样显存下能承载多少并发、碎片是否减少、长短请求混合时是否还能稳定调度。连续批处理对应的指标,是 GPU 利用率、队列等待、batch 内 token 数、TTFT 和输出抖动。量化对应的指标,是显存、吞吐、质量、格式成功率和业务错误率。投机解码对应的指标,是接受率、每步接受 token 数、draft 开销和端到端延迟。
这些指标要和用户体验连起来。TTFT 过高,用户觉得系统没有响应;输出 token 间隔抖动,用户觉得回答卡顿;上下文超限,用户觉得系统不理解完整资料;引用错误,用户觉得模型不可信;成本过高,产品无法规模化。底层优化如果不能改善这些体验,就只是工程自嗨。
指标还要有分桶。所有请求混在一起看平均值,会掩盖真问题。短问答、长文档、代码生成、结构化抽取、工具调用和后台批处理应该分别看。输入 500 token 和输入 50000 token 的请求不该放在同一个延迟平均里。输出 50 token 和输出 5000 token 的请求,也不该用同一个成功标准。
生产团队最好给每个模型入口建立一张小表:目标用户是谁,最大上下文多少,推荐输出多少,p95 TTFT 目标是多少,p95 输出速度目标是多少,允许失败类型是什么,超限时如何提示,何时走异步,何时切小模型,何时切强模型。这样,推理优化就从“调参数”变成了“维护服务承诺”。
大模型推理优化的难点,不在于知道更多名词,而在于把名词放回真实系统里。KV Cache 告诉我们,生成式服务的显存账会随用户上下文和并发增长;PagedAttention 告诉我们,缓存管理决定了高并发能不能稳定;连续批处理告诉我们,在线请求需要动态调度;量化告诉我们,省显存要用质量评测付款;投机解码告诉我们,自回归瓶颈可以被工程性缓解,但不能脱离任务接受率。
成熟的推理系统不会只追求某个榜单上的 tokens/s。它会用真实请求分布设计资源池,用 token 级指标观察服务,用业务评测约束优化,用缓存和调度减少浪费,用量化和投机解码做有证据的加速。最终目标不是让模型在实验室里显得更快,而是让用户在高峰期也能得到稳定、准确、成本可控的回答。
当团队能把每次优化都对应到明确用户体验、资源账本和质量回归时,推理服务才真正进入可持续迭代状态。