localaiforum
登录
指南 › 基础知识

预填充与解码:生成是如何运行的

按下回车,生成以物理特性完全不同的两个阶段运行。预填充(prefill)用一次并行传播处理整个提示词——计算密集、每词元很快,但你要等它全部完成才看到第一个字。解码(decode)随后一次一个词元地吐出答案——每一步都几乎要把全部模型权重从内存重新读一遍,才能产出一个词元。“第一个词慢、之后流式输出”不是你环境的怪癖;它就是这两个阶段。

阶段一:预填充——提示词

  • 全部提示词词元同时过模型:大型并行矩阵乘法,正是 GPU 为之而生的东西。利用率高;每秒词元高。
  • 你感受到的是首词元时间(TTFT):按下回车后的沉默。
  • 这次传播还把每个提示词词元的键/值向量填进 KV 缓存——解码将要读取的草稿区。
  • 提示词越长,TTFT 线性越长:大型系统提示词、RAG 文档和聊天历史,除非引擎缓存前缀,否则每次都要预填充。

阶段二:解码——答案

  • 每次前向传播产出一个词元。那次传播需要每个权重;产出一个词元意味着从显存流式读取整个模型。
  • 上限是内存带宽而非算力:大致 decode tok/s ≈ 有效带宽 ÷ 权重字节数。8 GB 权重配 ~400 GB/s 有效带宽的卡 ≈ 50 tok/s 上限。
  • 这就是量化能加速解码的原因:每权重比特更少意味着每步要流式读取的字节更少(参见 速度)。

为什么不对称:预填充每次权重读取处理数千词元(巨大矩阵、算术强度高——算力瓶颈),解码恰好处理一个(为一枚词元取回每一字节权重——带宽瓶颈)。同样的权重,相反的瓶颈。

total_time ≈ prompt_tokens / prefill_tok_s  +  output_tokens / decode_tok_s

实例

prompt:  1,000 tokens    prefill 2,000 tok/s → 0.5 s   (TTFT)
output:    300 tokens    decode    40 tok/s → 7.5 s
total ≈ 8.0 s — 94% 的耗时在解码
  • 前半秒处理了 1,000 个词元;接下来的 7.5 秒产出 300 个。同样的模型、同样的 GPU,50 倍差距——不同的阶段,不是不同的努力。
  • 40 tok/s 大约是每秒 160 个英语字符(≈4 字符/词元):比任何人打字都快,所以流式读起来很舒服(词元)。
  • 同样 40 tok/s 的韩语大约是 40–60 字符/秒:词元速率相同,每词元文本更少。
  • 输出长度是解码侧的旋钮:max_tokens 限制生成运行多久,停止序列让它提前结束。两者都线性改变耗时(引擎参数)。
  • 流式改变的是感知,不是物理:你看着解码发生而不是等它完成,速度还是那个 40 tok/s。

实例二:RAG 场景

prompt: 12,000 tokens (system + 20 retrieved chunks)  prefill 1,500 tok/s → 8.0 s
output:   500 tokens                                    decode    40 tok/s → 12.5 s
total ≈ 20.5 s — 仅 TTFT 就占等待的 39%

这就是检索密集型本地部署的形态:肥大的提示词意味着模型在打字前“思考”数秒。解法,按杠杆率排序:

  • 少检索——前 3–5 个分块通常胜过前 20 个(RAG)。
  • 保持系统提示词稳定,让引擎能复用缓存的前缀。
  • 修剪历史:旧轮次可以摘要,而不是全量重放。

KV 缓存:不断长大的草稿区

没有缓存的话,生成第 N 个词元要从头重算全部 N−1 个更早词元的注意力——每一步重做相同的工作,总代价随长度二次增长。实际的做法:

  1. 预填充为所有提示词词元计算键和值,并存储一次。
  2. 每个解码步只计算新词元的查询/键/值,把它的 K/V 追加进缓存。
  3. 新词元的注意力就是它的查询与所有已存键的点积。
  4. 解码一个词元的完整流程:嵌入、逐层运行并读取缓存、投影到词表、采样——追加它的 K/V,重复。

现在每个解码步对上下文长度是线性的(读缓存)而不是二次的(全部重算)。代价是缓存本身,它随每个词元增长:

KV bytes ≈ 2 × layers × kv_heads × head_dim × bytes_per_value × context

8B 级模型(32 层、8 个 KV 头、头维度 128、FP16):
  2 × 32 × 8 × 128 × 2 B ≈ 128 KiB per token
  → ~2.6 GB at 20k context, ~17 GB at 128k context

这个缓存与权重争夺显存——长上下文聊天会让短提示词下运行良好的机器崩溃(KV 缓存、显存计算)。

批处理改变经济学

读 8 GB 权重只为一枚词元很浪费;同样的读取可以同时服务多个序列。批处理 8 个请求,一次权重流产出 8 个词元——吞吐量近乎线性攀升,直到算力饱和。这就是服务背后的全部诀窍(服务 API、并发)。独自在笔记本上,你为每个词元支付全价;服务器把它摊销到同时在线的用户头上。

读懂仪表

仪表 阶段 主要驱动因素
TTFT 预填充 提示词长度、算力、前缀缓存命中
提示词评估 tok/s 预填充 算力、批大小
生成 tok/s 解码 内存带宽、权重字节数、批次
吞吐量(请求/秒) 两者 批大小、前缀缓存复用、并行槽位
上下文内存 两者 每词元 KV 字节、缓存量化

调优前先测量:每个严肃的引擎都会按请求记录提示词评估和生成速度。一分钟的日志就能告诉你哪个阶段吃掉了延迟;靠猜不行。

调优跟随阶段:更短的提示词和前缀缓存解决 TTFT;量化和更小的权重解决解码(引擎参数、故障排查)。

相关: 速度 · KV 缓存 · 并发 · 词元