大三上 大模型系统与工程 第二次课笔记 20250912
一、介绍大模型(如 GPT、LLaMA、Gemini 等)的推理流程。
可以把这个过程想象成让一个博学但“慢思考”的巨人完成一项任务。它的知识已经全部学好了(存储在模型的权重参数中),推理就是它运用这些知识进行“思考”和“输出”的过程。
整个推理流程可以清晰地分为三个核心阶段,下图概括了其工作流:
阶段一:编码与预处理(理解输入)
模型接收的是一段文本(称为 Prompt,如“法国的首都是”),但它无法直接理解文字,需要转换成它能处理的数字形式。
-
分词(Tokenization):
- 将输入文本拆分成模型能识别的基本单元,称为 Token。Token 不一定是一个完整的单词,可能是子词或单个字符。
- 例如,“法国的首都是” 可能会被分词为
["法国", "的", "首都", "是"]。 - 每个 Token 都会被映射到模型词汇表中的一个唯一 ID。
-
嵌入查找(Embedding Lookup):
- 模型有一个巨大的“字典”(嵌入矩阵),存储着每个 Token ID 对应的向量(一组高维数字,通常有数百或数千个维度)。
- 这个过程就是将每个 Token ID 转换成一个有意义的数学向量。这个向量旨在捕获该 Token 的语义信息。
-
位置编码(Positional Encoding):
- 语言中单词的顺序至关重要。但 Transformer 模型本身没有顺序概念。
- 位置编码是一种技术,它会将每个 Token 在句子中的位置信息(是第几个词)也编码成一个向量,然后加到该 Token 的嵌入向量上。
- 这样,模型就能知道“法国”在“的”前面了。
至此,输入的文本已经变成了一组包含了语义和位置信息的数字向量, ready for processing.
阶段二:核心计算(前向传播)
这是模型“思考”的核心环节。预处理后的向量序列会被送入模型的Transformer架构中,进行一系列复杂的数学运算。
-
层层处理:
- 模型由数十个甚至上百个 Transformer 层(或称为块)堆叠而成。
- 每一层都包含两个核心组件:
- 自注意力机制(Self-Attention):让序列中的每个 Token 都能与其他所有 Token 进行“交互”和“关注”。例如,在处理“首都”这个词时,模型会格外关注“法国”这个词,从而建立起它们之间的强烈联系。
- 前馈神经网络(Feed-Forward Network):对每个 Token 的表示进行独立的、非线性的变换,进一步提取特征。
- 向量序列会依次通过每一层,每一层都会对其稍作修正和增强,使其包含更多上下文信息。
-
输出预测:
- 经过所有层的处理后,我们会得到序列中最后一个位置的输出向量(对应“是”这个 Token)。
- 这个输出向量被送入一个语言模型头(通常是一个线性层 + Softmax 函数),转换为一个概率分布。
- 这个概率分布覆盖了模型整个词汇表,表示模型认为下一个最可能出现的 Token 是哪个。
- 例如,词汇表中“巴黎”对应的概率可能是 65%,“伦敦”是 5%,“罗马”是 3%…… 其他所有词共享剩下的概率。
阶段三:采样与解码(生成输出)
现在模型得到了一个概率分布,我们需要决定下一个词到底是什么。
-
采样策略(Sampling Strategy):
- 贪婪采样(Greedy Sampling):总是选择概率最高的那个 Token(如“巴黎”)。这种方法简单高效,但容易生成重复、枯燥的文本。
- 随机采样(Stochastic Sampling):根据概率分布进行随机选择。概率高的词被选中的机会大,但也不排除选择概率稍低的词。这能增加输出的多样性和创造性。
- 温度(Temperature):一个关键参数,用于控制随机性。高温(>1)会让概率分布更平缓,选择更随机;低温(<1)会让概率分布更尖锐,更倾向于最高概率的词。
-
自回归生成(Auto-Regressive Generation):
- 将上一步生成的 Token(如“巴黎”)追加到原始输入序列的末尾,形成新的输入:“法国的首都是巴黎”。
- 将这个新的序列再次送入模型,重复阶段二的计算,预测下一个 Token(例如,可能是“。”、“
”结束符或另一个词)。 - 如此循环往复,模型就像一个“逐词预测器”,不断生成下一个词,直到生成一个表示结束的特殊 Token 或达到最大长度限制。
总结与特点
| 特性 | 说明 |
|---|---|
| 自回归(Autoregressive) | 核心特点。输出是逐个 Token 生成的,每次生成都依赖于之前所有的输入和已生成的内容。 |
| 计算密集型 | 推理过程需要巨大的计算量(主要是矩阵运算),尤其生成长文本时。这也是推理速度比训练慢得多的重要原因之一。 |
| 内存带宽受限 | 很多时候推理速度的瓶颈不在于计算速度,而在于从内存中加载模型巨大参数的速度。 |
| 确定性/随机性 | 贪婪搜索下,同样输入总是得到同样输出。引入随机采样后,每次输出可能不同,更具创造性。 |
详细解释如上。
二、大模型的哪个阶段的资源使用量大一些?decode还是encode?
这是一个非常棒的问题,它触及了大模型推理效率的核心。
简单直接的回答是:在绝大多数情况下,Decode(解码)阶段的资源使用量远大于 Encode(编码)阶段。
下面我们来详细解释为什么。
1. Encode 阶段(编码,Prefill 阶段 又名 Encode 阶段)
- 任务:处理用户输入的整个提示(Prompt)。模型会一次性读入所有的输入 tokens(词元),并利用自注意力机制(Self-Attention)为整个输入序列生成一个综合的、上下文丰富的表示。
- 计算特点:
- 一次性计算:输入序列是固定的,模型可以并行地对所有 tokens 进行计算,效率很高。
- 计算量相对固定:计算量只与输入提示的长度有关。一旦处理完成,就为 Decode 阶段准备好了所有必要的上下文信息(即 Key-Value 缓存,简称 KV Cache)。
2. Decode 阶段(解码)
- 任务:逐个 token 地生成输出。这是一个自回归(Auto-Regressive)的过程。生成下一个 token 时,模型会依赖于之前已经生成的所有 tokens。
- 计算特点:
- 串行循环:无法并行化。生成一个 100 个 token 的回答,就需要循环执行 100 次 Decode 步骤。
- 计算量累积巨大:虽然单次 Decode 步骤的计算量可能小于一次完整的 Encode,但成百上千次的循环累积起来,总计算量会变得非常庞大。
- 内存带宽瓶颈:每次 Decode 步骤都需要加载整个模型的参数和不断增大的 KV Cache,这个过程对内存带宽的压力极大, often成为主要瓶颈,而不是纯粹的计算能力(FLOPs)。
资源使用对比(表格)
| 资源类型 | Encode 阶段 | Decode 阶段 | 说明 |
|---|---|---|---|
| 时间消耗 | 较低 | 极高 | Encode 是一次性并行计算。Decode 是成百上千次的串行循环,总时间占绝对大头。 |
| 计算量 (FLOPs) | 与输入长度平方成正比 | 单步少,但总步数多,总FLOPs通常更大 | 输出很长时,Decode 的总计算量会远超 Encode。 |
| 内存占用 (KV Cache) | 一次性生成,占用固定空间 | 持续增长 | Decode 每生成一个 token,KV Cache 就需要为这个新 token 分配空间,内存占用线性增长,是主要瓶颈之一。 |
| 内存带宽 | 压力一次 | 持续高压 | 每次 Decode 都要为单个 token 重读几乎所有模型参数和KV Cache,对带宽需求极高。 |
一个生动的比喻
你可以把这两个阶段想象成:
- Encode 阶段(Prefill 阶段又名 Encode 阶段):像是打包行李。你把所有要带的东西(输入提示)一次性地、高效地整理好放进箱子(生成上下文表示)。
- Decode 阶段:像是一边走路一边从箱子里拿东西。你每走一步(生成一个词),都要停下来打开箱子,看看里面有什么,然后只拿出下一件你需要的东西。走的步数越多(回答越长),这个过程的总时间和精力消耗就越大。
总结
- Encode 阶段:“短平快”,一次性投入,为生成答案做好准备。
- Decode 阶段:“持久战”,是推理过程中资源消耗的绝对主体,尤其是时间和内存带宽。生成的回答越长,Decode 阶段的开销就越压倒性地大于 Encode 阶段。
因此,当前大模型推理优化的核心技术(如 KV Cache、FlashAttention、连续批处理等)几乎全部都是围绕着如何优化 Decode 阶段的巨大开销而设计的。
三、一次prefill和一次decode对比呢?谁的workload比较大?
这是一个非常精准的问题,直击模型推理的细节。答案是:
单次操作(One Operation)的对比中,Prefill 的 workload 远大于单次 Decode。
这里的“一次”指的是:处理整个输入序列的 Prefill 调用 vs. 生成一个 token 的 Decode 调用。
下面我们从几个维度来详细解释为什么:
1. 计算量 (FLOPs)
- Prefill (一次调用):需要为输入序列中的所有 token 计算注意力。其核心计算——自注意力机制的计算复杂度与序列长度的平方(O(n²))成正比。这意味着输入长度增加一倍,计算量会增加约四倍。这是一个非常沉重的计算过程。
- Decode (一次调用):只需要计算当前新 token 与所有之前 token(包括Prefill的输入和已生成的所有输出)的注意力。这是一个增量式的过程,单步计算量远小于处理整个序列的 Prefill。
结论:一次 Prefill 调用所进行的数学运算总量(FLOPs)远远大于单次 Decode 调用。
2. 并行度
- Prefill:可以高度并行化。因为所有输入 token 都是已知的,GPU 可以同时处理它们,极大地利用其数千个计算核心,因此单位时间内的计算吞吐量非常高。
- Decode:本质上是串行的。生成下一个 token 必须严格依赖上一个 token 的结果,无法并行。GPU 的很多计算核心在此时处于闲置状态,主要瓶颈变成了内存带宽。
结论:虽然 Prefill 总计算量大,但因为它能“多人同时干活”(并行),所以完成得很快。而 Decode 每次只能“一个人干活”(串行)。
3. 内存访问
- Prefill:是计算密集型(Compute-bound)。它的速度主要受限于 GPU 能多快地进行数学计算。
- Decode:是内存密集型(Memory-bound)。它的速度主要受限于 GPU 从显存中读取模型权重和 KV Cache 的速度(内存带宽)。单次操作需要移动的数据量很大,但进行的计算相对简单。
一个生动的比喻
想象一个大型讲座:
- Prefill 就像是所有听众一次性同时向演讲者提问。演讲者(GPU)需要集中精力,动用全部脑力(计算核心)来一次性理解并记住所有问题(计算并缓存 KV)。这个过程强度很大,但一次就结束了。
- Decode 就像是演讲者逐个回答每个问题。回答单个问题(生成一个 token)所需的脑力消耗远小于刚才同时理解所有问题。但问题是,他必须回答完一个才能回答下一个,而且每回答一个新问题,他都需要回忆一下之前的所有问题和答案(读取整个 KV Cache)。
虽然回答单个问题很轻松,但要回答成百上千个问题,总的“回答时间”会远远超过最初“听问题的时间”。
总结对比表
| 特性 | 一次 Prefill (处理整个输入) | 一次 Decode (生成一个token) |
|---|---|---|
| Workload 大小 | 非常巨大 | 相对很小 |
| 计算类型 | 计算密集型 | 内存密集型 |
| 并行能力 | 高,可充分利用GPU算力 | 低,本质串行,大量核心闲置 |
| 主要瓶颈 | GPU 的计算能力 (FLOPs) | GPU 的内存带宽 (GB/s) |
| 耗时 | 相对较短(因为并行) | 相对较长(因为串行且内存受限) |
最终结论:
单次操作之间,Prefill 的 workload 更大。但因为在推理过程中,Prefill 只发生一次,而 Decode 会发生成百上千次,所以整个生成过程的总 workload 和耗时通常由 Decode 阶段主导。
这个区别也解释了为什么优化推理性能的技术(如 KV Cache 量化、FlashAttention、连续批处理等)主要都针对 Decode 阶段,因为它是整体性能的瓶颈。

浙公网安备 33010602011771号