关键词: LLM 请求生命周期、Prefill、Decode、KV Cache、TTFT、Output Speed、Throughput、Dense、MoE、llama.cpp、vLLM、SGLang、推理系统

如果你现在对这些现象很熟:

  • TTFT 很高,但 Output Speed 看起来不差
  • 同一个模型在 llama.cppvLLM 上表现完全不一样
  • 单用户很顺,一上并发就开始变得很奇怪
  • MoE 明明总参数更大,结果实际吞吐反而比 Dense 更好

那大概率不是你不会看 benchmark,而是你脑子里还缺一个更底层的模型:

一次大模型请求在系统里,到底经历了什么。

这篇文章就想补上这块。

它不打算只解释几个术语,而是想回答 3 个更根本的问题:

  1. 一次请求从 API 入口到最后一个 token 返回,中间到底经过了哪些阶段?
  2. DenseMoE、长上下文、reasoning、多模态,会在哪些地方让这条链路发生分叉?
  3. 为什么你最后看到的 TTFTOutput SpeedThroughput,很多时候并不只是“模型能力”的结果?

如果你把这条链路真正看懂,后面再看任何推理 benchmark,都会比以前顺很多。

一、为什么有必要单独理解“一次请求到底发生了什么”

很多人第一次看大模型推理,脑子里的画面是这样的:

用户发一句话 -> 模型算一下 -> 返回一句话

这个理解当然不能说错,但它太粗了。

一旦你开始做下面这些事情:

  • 本地部署
  • 长上下文压测
  • 比较 llama.cppvLLM
  • 分析 TTFT 为什么很高
  • 研究 DenseMoE 的性能差异
  • 做并发、吞吐、买卡决策

你就会发现,如果脑子里没有“一次请求在系统里实际经历了哪些阶段”的模型,很多现象都会变得很难解释。

比如:

  • 为什么有时首 token 很慢,但后面输出速度很快?
  • 为什么同一个模型单用户很顺,一上并发就掉得厉害?
  • 为什么有的模型参数更多,实际吞吐却更高?
  • 为什么长上下文不只是让 TTFT 变慢,还会拖慢 decode?
  • 为什么同一个模型在 llama.cppvLLM 上表现会差很多?

这些问题的共同根源是:

推理指标只是结果,请求生命周期才是原因。

所以这篇文章不先讲结论,而是先讲一次请求里到底发生了什么。


二、先给一条总流程:一次请求的完整生命周期

如果从真实推理系统角度看,一次请求大致会经过下面这些阶段:

1. 请求进入 API / Gateway
2. 消息拼装与 prompt template 渲染
3. tokenizer 分词
4. 请求进入调度器 / batch 队列
5. Prefill:一次性处理整段输入
6. 建立或扩展 KV Cache
7. Decode 循环:逐 token 生成
8. 采样 / 终止条件判断
9. detokenize
10. 流式返回或一次性返回
11. 记录 usage、latency、日志、监控指标

如果再按“谁在干活”来分,可以粗略拆成三层:

  • 应用层:接请求、做模板、拼上下文、处理工具调用
  • 推理引擎层:排队、batch、KV cache、调度、采样、返回
  • 模型层:真正的 Prefill + Decode

这三层混在一起,就是为什么很多人以为自己在比较模型,其实比的是整条链路。


三、请求进入系统后,第一步并不是“立刻进模型”

1. API / Gateway 入口

不管你走的是:

  • OpenAI 兼容接口
  • 自己封装的 Gateway
  • llama.cpp 自带 server
  • vLLM 的 API server
  • SGLang 的 serve 接口

请求真正进入模型之前,通常都要先过一层 API。

这一层会做一些很基础但很关键的事情:

  • 接收 HTTP 请求
  • 解析 JSON
  • 校验参数
  • 决定模型路由
  • 统一鉴权、限流、日志、错误处理

如果你已经接了多模型路由、fallback、灰度流量,这一层甚至还会先决定:

  • 请求到底发给哪个模型
  • 用哪个端口
  • 走哪个 backend

也就是说,“一次请求到底发生了什么”,第一步其实是系统逻辑,而不是模型逻辑。

2. 消息拼装与 prompt template 渲染

这是很多人最容易忽略的一步。

用户传进来的往往不是最终送给模型的那串 token,而是类似:

  • system prompt
  • user message
  • assistant 历史
  • tool definitions
  • function schema
  • chat template kwargs
  • RAG 检索结果

系统会先把这些内容拼成最终 prompt。

这一阶段会影响很多事情:

  • token 数到底有多少
  • 是不是触发了 thinking 模式
  • 是不是附带了工具定义
  • 上下文长度会不会突然暴涨
  • 为什么“明明用户只说了一句话,模型却吃了上万 token”

很多“模型突然变慢”的问题,根本不是模型本身出了问题,而是模板和上下文拼装层把输入变得太重了

3. tokenizer 分词

接下来系统才会把字符串变成 token。

这一层看起来很朴素,但它直接影响:

  • prompt_tokens
  • 上下文上限是否会超
  • KV cache 规模
  • 成本估算
  • 并发能力

同样 10 万中文字符,不同模型的 token 数可能明显不同。

所以很多时候你真正要问的不是:

这个请求有多少字符?

而是:

这个请求在当前 tokenizer 下到底有多少 token?

这也是为什么做容量规划时,字符数只能做粗估,真正有效的是 token 数。


四、真正进模型前,还有一个非常关键的环节:调度器

请求 token 化之后,不一定会马上进模型。

在真实推理引擎里,通常还会经过:

  • 请求排队
  • 调度
  • continuous batching
  • prefix sharing
  • slot / sequence 分配

这一步主要发生在推理引擎层,比如:

  • vLLM
  • SGLang
  • 各种自定义 serving stack

它在做的事情不是“理解内容”,而是“决定这批请求怎么更高效地塞进 GPU”。

这一步为什么重要?

因为很多你最后看到的现象,其实和调度器强相关:

  • 为什么单用户速度很好,一上并发就不均匀
  • 为什么有的请求 TTFT 很低,有的很高
  • 为什么系统总吞吐升高了,但单用户体感变差

换句话说:

用户感觉慢,不一定是模型慢,也可能是调度慢。


五、真正进入模型以后,核心只有两大阶段:Prefill 和 Decode

这是整条链路里最关键的一层。

1. Prefill:先把整段输入“读完”

Prefill 阶段处理的是整段输入 token。

特点是:

  • 整段输入并行计算
  • 计算量大
  • GPU 利用率高
  • 更接近算力密集型

这一阶段结束后,模型才有能力生成第一个 token。

所以:

  • prompt 越长
  • prefill 越慢
  • TTFT 往往越高

很多长上下文场景里,真正拖慢体验的第一元凶,就是 prefill。

2. KV Cache:把历史中间状态存下来

在 prefill 过程中,模型不会每次都从头重新理解所有历史,而是会把中间状态存进 KV Cache

KV Cache 的作用可以理解成:

  • 把已经算过的历史结果存起来
  • 让后续 decode 不用整段重算

它非常关键,因为 decode 阶段的效率,很大程度上取决于:

  • KV cache 有多大
  • 每一步访问它有多贵
  • 多并发时它占了多少显存

这也是为什么:

  • 上下文越长
  • 并发越高
  • decode 往往也会越来越慢

不是因为模型“智商下降了”,而是因为它每次生成新 token 时,要带着更大的历史状态往前走。

3. Decode:一个 token 一个 token 地往外吐

进入 decode 后,请求就变成了一个循环:

读当前上下文状态
-> 算下一个 token 的概率
-> 采样选出一个 token
-> 把新 token 拼回去
-> 更新 KV cache
-> 再生成下一个 token

这一阶段的特点是:

  • 每步计算量相对小
  • 但每步都要访问权重和 KV cache
  • 更容易受显存带宽限制
  • 更容易在高并发下暴露瓶颈

所以:

  • Output Speed
  • TBT
  • 系统级吞吐

这些指标,大多都和 decode 阶段强相关。


六、Dense 和 MoE,到底会让请求生命周期哪里发生分叉

这个问题非常关键。

因为“请求生命周期”不是完全架构无关的。

主干流程都差不多,但在模型内部每一步到底怎么走,和架构关系很大。

1. Dense:每一层都走全量参数

Dense 模型的特点是:

  • 每个 token 经过每一层时,都走完整的 FFN / attention 路径
  • 参数利用方式稳定
  • 计算路径比较固定

它的好处是:

  • 行为更直接
  • 路径更均匀
  • 引擎适配往往更成熟

它的代价是:

  • 每个 token 都要动用全量参数
  • decode 访存压力通常更直接
  • 大模型时显存和带宽压力会更重

2. MoE:每个 token 先路由,再只走部分专家

MoE 模型在生命周期里多了一步:

routing

也就是每个 token 进入某层时,不是直接走完整 FFN,而是先:

  1. 计算 gating / routing 分数
  2. 选出 top-k experts
  3. 只让这个 token 经过部分专家

所以在模型内部,MoE 的单 token 路径更像:

attention
-> routing
-> top-k experts
-> combine
-> 下一层

这会带来几个非常直接的差异:

  • active params 远小于 total params
  • 单 token 实际参与计算的参数更少
  • 某些场景下 decode 吞吐更高
  • 但调度和实现复杂度更高

也就是说:

Dense 和 MoE 的区别,不是“一个更先进”这么简单,而是:

每个 token 在模型内部实际走的路不同。

3. 为什么 MoE 有时参数更多,实际反而更快

这正是因为:

  • Dense:每 token 走全量参数
  • MoE:每 token 只走少量被选中的 experts

所以你会看到一种很反直觉的现象:

  • 总参数更大的 MoE
  • 实际 active params 更小
  • decode 吞吐反而可能比 Dense 更高

但这不代表 MoE 一定全面更优。

因为它还会带来:

  • routing 开销
  • 实现复杂度
  • 引擎兼容性问题
  • 部分场景下更复杂的调度行为

所以在真实系统里,MoE 的优势不是“永远更快”,而是“在某些服务形态下更有机会把大模型做成可部署系统”。


七、除了 Dense 和 MoE,还有哪些架构差异会影响请求生命周期

1. GQA / MQA / KV heads 数量

这部分直接影响:

  • KV cache 大小
  • decode 访存压力
  • 长上下文和并发能力

同样的上下文长度下,KV heads 更少的模型,往往更容易在长上下文下保持可部署性。

2. Long-context 机制

比如:

  • sliding window
  • hybrid attention
  • 各种 rope / sparse / chunk tricks

这些机制会影响:

  • prefill 怎么算
  • decode 时历史范围怎么访问
  • 长上下文下的算力和带宽压力

所以“支持 128K”从来不只是一个 marketing 数字,它背后意味着模型内部访问历史状态的方式已经变了。

3. Reasoning 模型

Reasoning 模型最重要的变化不一定是结构本身,而是输出形态:

  • 它可能先输出 thinking token
  • 再输出真正可见答案

这会让请求生命周期里多出一个非常关键的体验分叉:

  • TTFT 很快
  • TTFAT 很慢

所以评估 reasoning 模型时,如果你不把“第一个 token”与“第一个答案 token”区分开,很多结论都会错。

4. 多模态模型

多模态请求就更不是“文本 token 进,文本 token 出”这么简单了。

它通常还会多出:

  • 图像编码器
  • 投影层
  • 跨模态对齐

也就是说,请求生命周期会变成:

图像 / 文本输入
-> 视觉编码
-> 投影到语言空间
-> 与文本 token 拼接
-> 再进入语言模型主干

这时如果你还用“纯文本模型”的心智去理解 TTFT、吞吐、显存,你就很容易误判。


八、为什么同一个模型在不同引擎里表现会差很多

这是另一条经常被忽略的分叉线。

很多人以为自己在比模型,其实比的是:

  • llama.cpp
  • vLLM
  • SGLang
  • 某个自定义 gateway

不同引擎在请求生命周期里介入的地方主要包括:

  • 调度器
  • continuous batching
  • prefix cache / prefix sharing
  • KV cache 管理
  • streaming 实现
  • sampling 实现
  • 内存布局和 kernel 优化

所以同一个模型:

  • llama.cpp 上也许更适合本地单机、GGUF、固定服务
  • vLLM 上也许更适合多并发 serverless / API 服务
  • SGLang 上也许更适合 Agent、多轮、共享长前缀场景

这也是为什么你最后看到的:

  • TTFT
  • Output Speed
  • Throughput
  • Concurrent Users @ SLO

很多时候不仅是模型决定的,也是引擎决定的。


九、把整条链路重新映射回那些你熟悉的指标

到这里,很多指标应该已经可以重新“挂回去”了。

和输入阶段更相关的

  • TTFT
  • Prefill Speed
  • 长 prompt 下的 E2E

和输出阶段更相关的

  • TBT
  • Output Speed
  • Decode Speed

和系统容量更相关的

  • Throughput
  • QPS
  • Concurrent Users @ SLO

和成本决策更相关的

  • 每美元 Throughput
  • Users / GPU @ SLO
  • GPU / User @ SLO

这时你会发现,很多指标其实不是孤立的定义,而是这条生命周期上不同位置的投影。


十、最容易犯的错误,其实是把“模型问题”和“系统问题”混在一起

当你开始真正理解一次请求的生命周期以后,一个最大的收获就是:

你会更容易区分:

  • 到底是模型本身慢
  • 还是 prompt 拼装太重
  • 还是 tokenizer 让 token 数暴涨
  • 还是调度器排队太久
  • 还是 KV cache 太大把 decode 拖慢了
  • 还是 serving engine 本身没有把这类模型服务好

也就是说:

同样一句“这个模型很慢”,背后其实可能是五六种完全不同的原因。

而只有当你把整条请求链拆开,才知道问题该归因到哪里。


十一、最后一句总结

如果只留一句最重要的话,那就是:

一次大模型请求,不是“输入一句话、模型算一下、输出一句话”这么简单。

它是一条完整的系统链路:

  • API
  • prompt 拼装
  • tokenizer
  • 调度器
  • prefill
  • KV cache
  • decode
  • sampling
  • detokenize
  • streaming 返回

DenseMoE、长上下文、reasoning、多模态,只是在这条主链路上,让某些环节的代价和形态发生了变化。

理解这一点之后,你再回头看:

  • TTFT
  • Output Speed
  • Throughput
  • Concurrent Users @ SLO

这些指标才不会只是名词,而会变成一套真正能指导部署、压测和买卡决策的判断框架。

换句话说,这篇文章真正想解决的,不是“再解释几个术语”,而是帮你把下面这件事想清楚:

当你说“这个模型快”或者“这个系统慢”的时候,你到底在描述哪一段链路,哪一个瓶颈,哪一种成本。

只要这个问题想清楚了,很多原来混在一起的判断就会自动分层:

  • 是模型问题,还是系统问题
  • Prefill 慢,还是 Decode
  • 是单用户体验问题,还是系统容量问题
  • 是 benchmark 好看,还是服务真的可上线

参考资料

更多推荐