)
上一篇第 14 篇《一次服务很多人》 下一篇第 16 篇《为什么要有自己的权重格式》一句话导读大模型推理的一次生成其实分两段——先一口气把提示词全部读完预填充再一个字一个字往外吐逐词生成。两段的计算形状完全不同慢的几乎全在后面一段。关键词推理引擎入门、大模型推理、预填充prefill、逐词生成decode、KV 缓存KV cache、计算形状第 14 篇讲的是每轮给每个请求各发一个词。可一个请求自己从头走到尾走过哪些步骤这一篇把一次完整生成拆成两段先一口气读完提示词再一个字一个字往外吐。两段的形状不同慢的几乎全在后面一段。① 一句话概念一次生成分两段先全读再逐词吐。② 起点考试答题先通读再续写假设你在考试答题。拿到卷子第一件事是把题目从头到尾读一遍——读的时候你不是读了上半句就停下想下半句而是把整段题一次性看完对整道题的意思同时建立起理解。读完才动手答题。答题是另一回事你是一个字一个字往下写的。每写一个字动作都一样“看前面 → 写一个”。读题和答题的节奏完全不同读题是一大段一起处理答题是一小格接一小格。而且注意答题时你不用重新读题——对题目的理解已经记在脑子里了每写一个字只是把它接上去。所以整场考试就是通读一遍 续写若干字这两段缺一不可先后分明。③ 伪代码先建缓存再逐词往外推一次生成就是这两段的拼接先建缓存再靠缓存逐词往外推。函数 生成(提示词, 权重) - 词序列: 缓存 空 # 记下读过的内容 阶段A 预填充: 对 提示词 的每一个词: 缓存.追加(算出该词 的中间结果) # 一口气读完全部提示词 阶段B 逐词生成: 上一个词 提示词的最后一个词 当 没有遇到结束符: 下一个词 前向(上一个词, 缓存) # 每步只处理一个词 缓存.追加(下一个词 的中间结果) 输出(下一个词) 上一个词 下一个词 返回 输出 # 代价: 阶段A 一次算 n 个词阶段B 每步只算 1 个词共 m 步逐行要点阶段A 预填充提示词是已经拿到手的全部输入可以一次一起处理。它的形状是一大块一次处理 n 个词能并行摊开的活很多所以相对快。缓存两段里各有一处追加这份缓存就是第 9 篇讲的 KV 缓存KV cache——有了它阶段 B 才不必把提示词重读一遍怎么复用、怎么落盘见第 11、12 篇。前向(上一个词, 缓存)阶段 B 每步只喂进一个词形状退化成单行。单行能并行的活太少可权重却还得完整读一遍所以这一步是整段里最慢的。当 没有遇到结束符阶段 B 的长度不固定——可能只吐一个词也可能一直吐到长度上限。最后注释整段生成的耗时几乎全在阶段 B因为它要被重复 m 次而阶段 A 只做一次。两段的成本结构不同这正是进阶篇要把它们拆成两条代码路径的原因。两段的计算形状差异如图 1 所示。图 1预填充 逐词生成两段的计算形状完全不同上方一个宽方块代表阶段 A——一次读完提示词下方一串小格代表阶段 B——每格一个词中间的箭头表示 KV 缓存在两段之间传递、不断追加。④ 纸笔实验必做任务一次生成提示词有 10 个词输出有 5 个词。请标出这 15 个词10 个输入 5 个输出分别属于阶段 A 还是阶段 B各占多少步并指出哪一段更耗时、为什么。阶段 A预填充处理全部 10 个提示词只算1 次一次处理一整块。阶段 B逐词生成每步 1 个词共5 步。预期结果整段共 1 5 6 步10 个输入词全在阶段 A5 个输出词全在阶段 B。更耗时的是阶段 B——它每步处理的活很少却要重复 5 次每次都得把权重完整读一遍阶段 A 处理的词多却只做一次。这解释了为什么输入很长未必是最贵的事输出很长才是。可选 REPL 版# 一次生成拆成两段数一数每段各处理多少词、走多少步提示词10输出5阶段A_处理词数提示词# 一次性处理整块阶段B_步数输出# 每步只处理一个词print(阶段A 处理词数:,阶段A_处理词数)# 10print(阶段B 步数:,阶段B_步数)# 5# 追问阶段B 每步都要读一遍权重 → 步数越多越慢谁在主导总耗时⑤ 进阶锚点进阶锚点本篇概念在《30 天手搓推理引擎》Day 15里被真正实现——15-1 入口架构参数 → 分发serve/自检→ 退出/15-2 prefill 与 decode两条路径为何分开/15-3 单步走一次生成token 循环与 KV 追加。入门版到这里就够了进阶版会多出入口参数如何分发到两条路径、prefill 与 decode 在源码里为什么被拆成两个函数以及逐步断点跟踪一次 token 循环 KV 追加的实测记录引自进阶系列源码与文档口径。想动手 → 仓库https://gitee.com/pei-xiaoguang/kestrel-llm从 Day 1 开始。下篇预告既然生成要读权重那权重到底以什么形式躺在文件里下一篇回答一个更根本的问题为什么这个引擎要有自己的权重格式。