大三上 大模型系统与工程 第二次课笔记 20250912

一、介绍大模型(如 GPT、LLaMA、Gemini 等)的推理流程。

可以把这个过程想象成让一个博学但“慢思考”的巨人完成一项任务。它的知识已经全部学好了(存储在模型的权重参数中),推理就是它运用这些知识进行“思考”和“输出”的过程。

整个推理流程可以清晰地分为三个核心阶段,下图概括了其工作流:

flowchart TD A[输入文本<br>“法国的首都是”] --> B(编码与预处理) subgraph B [阶段一:编码与预处理] B1[Tokenization<br>分割为Token] B2[嵌入查找<br>转换为向量] B3[位置编码<br>注入顺序信息] end B --> C(前向传播与计算) subgraph C [阶段二:核心计算] C1[输入向量] C2[通过多层Transformer<br>进行计算] C3[产生输出向量<br>即下一个Token的概率分布] end C --> D{采样与决策} subgraph D [阶段三:采样与解码] D1[根据概率分布<br>选择下一个Token] D2[贪婪采样<br>Greedy Sampling] D3[随机采样<br>Stochastic Sampling] D4[将新Token加回输入<br>继续循环] end D2 -- 确定性高<br>选择最高概率 --> D1 D3 -- 随机性强<br>按权重随机选择 --> D1 D4 --> C1 D --> E[最终输出文本<br>“法国的首都是巴黎”]

阶段一:编码与预处理(理解输入)

模型接收的是一段文本(称为 Prompt,如“法国的首都是”),但它无法直接理解文字,需要转换成它能处理的数字形式。

  1. 分词(Tokenization)

    • 将输入文本拆分成模型能识别的基本单元,称为 Token。Token 不一定是一个完整的单词,可能是子词或单个字符。
    • 例如,“法国的首都是” 可能会被分词为 ["法国", "的", "首都", "是"]
    • 每个 Token 都会被映射到模型词汇表中的一个唯一 ID。
  2. 嵌入查找(Embedding Lookup)

    • 模型有一个巨大的“字典”(嵌入矩阵),存储着每个 Token ID 对应的向量(一组高维数字,通常有数百或数千个维度)。
    • 这个过程就是将每个 Token ID 转换成一个有意义的数学向量。这个向量旨在捕获该 Token 的语义信息。
  3. 位置编码(Positional Encoding)

    • 语言中单词的顺序至关重要。但 Transformer 模型本身没有顺序概念。
    • 位置编码是一种技术,它会将每个 Token 在句子中的位置信息(是第几个词)也编码成一个向量,然后加到该 Token 的嵌入向量上。
    • 这样,模型就能知道“法国”在“的”前面了。

至此,输入的文本已经变成了一组包含了语义和位置信息的数字向量, ready for processing.


阶段二:核心计算(前向传播)

这是模型“思考”的核心环节。预处理后的向量序列会被送入模型的Transformer架构中,进行一系列复杂的数学运算。

  1. 层层处理

    • 模型由数十个甚至上百个 Transformer 层(或称为块)堆叠而成。
    • 每一层都包含两个核心组件:
      • 自注意力机制(Self-Attention):让序列中的每个 Token 都能与其他所有 Token 进行“交互”和“关注”。例如,在处理“首都”这个词时,模型会格外关注“法国”这个词,从而建立起它们之间的强烈联系。
      • 前馈神经网络(Feed-Forward Network):对每个 Token 的表示进行独立的、非线性的变换,进一步提取特征。
    • 向量序列会依次通过每一层,每一层都会对其稍作修正和增强,使其包含更多上下文信息。
  2. 输出预测

    • 经过所有层的处理后,我们会得到序列中最后一个位置的输出向量(对应“是”这个 Token)。
    • 这个输出向量被送入一个语言模型头(通常是一个线性层 + Softmax 函数),转换为一个概率分布
    • 这个概率分布覆盖了模型整个词汇表,表示模型认为下一个最可能出现的 Token 是哪个
    • 例如,词汇表中“巴黎”对应的概率可能是 65%,“伦敦”是 5%,“罗马”是 3%…… 其他所有词共享剩下的概率。

阶段三:采样与解码(生成输出)

现在模型得到了一个概率分布,我们需要决定下一个词到底是什么。

  1. 采样策略(Sampling Strategy)

    • 贪婪采样(Greedy Sampling):总是选择概率最高的那个 Token(如“巴黎”)。这种方法简单高效,但容易生成重复、枯燥的文本。
    • 随机采样(Stochastic Sampling):根据概率分布进行随机选择。概率高的词被选中的机会大,但也不排除选择概率稍低的词。这能增加输出的多样性和创造性。
      • 温度(Temperature):一个关键参数,用于控制随机性。高温(>1)会让概率分布更平缓,选择更随机;低温(<1)会让概率分布更尖锐,更倾向于最高概率的词。
  2. 自回归生成(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 阶段,因为它是整体性能的瓶颈。

posted @ 2025-09-12 14:13  陆舟LandBoat  阅读(20)  评论(0)    收藏  举报