好的,我将扮演一名手写笔记识别专家,对这份《从头开始构建大模型》课程的笔记进行识别、整理和补充。

第一页笔记分析与重构

本页笔记核心内容涵盖了大型模型(LLM)的三个基础构建模块:分词器 (Tokenizer)计算量 (FLOPs) 的估算,以及训练过程中的 内存 (Memory)计算 (Compute) 考量。

1. 分词器 (Tokenizer)

分词是将原始文本字符串转换为模型可以理解的数字序列(Token IDs)的过程。笔记中对比了不同类型的分词策略。

  • 基本概念:

    • 词元 (Word-based): 直接使用词作为基本单位。
      • 优点: 符合人类直觉。
      • 缺点: 词汇表规模 (Vocab size) 会非常庞大,无法处理未登录词 (Out-of-Vocabulary, OOV),且无法体现词根、词缀等关系。笔记中提到 “word - local not given”,可能指基于词的分词无法很好地处理局部或罕见的词汇组合。
    • 字节 (Byte-based): 将文本视为原始的字节流进行切分。
      • 优点: 词汇表规模小(仅256个),没有OOV问题。
      • 缺点: 压缩率低 (compress ratio),生成的序列非常长,增加了模型处理的负担。
  • 字节对编码 (Byte Pair Encoding, BPE) :

    BPE 是一种在词元和字节元之间取得平衡的子词 (subword) 算法,也是现代 LLM(如 GPT 系列)中最主流的分词算法之一。

    • 核心思想: 算法从字符/字节级别开始,迭代地将最常出现的相邻子词对合并成一个新的、更大的子词,并将其加入词汇表。
    • 流程:
      1. 初始化: 将词汇表初始化为所有基本字符(例如 UTF-8 的字节)。
      2. 分词: 将文本切分成字符序列。例如,"hugging" 变为 ['h', 'u', 'g', 'g', 'i', 'n', 'g']
      3. 迭代合并: 在语料库中找出出现频率最高的相邻子词对,将其合并成一个新的子词。例如,如果 'gg' 出现频率最高,就合并成 'gg',词汇表增加 'gg',序列变为 ['h', 'u', 'gg', 'i', 'n', 'g']
      4. 重复: 不断重复步骤3,直到达到预设的词汇表大小或满足停止条件。

2. 计算量 (FLOPs) 估算

FLOPs (Floating Point Operations) 是衡量模型计算复杂度的关键指标。

  • 训练过程中的 FLOPs : 对于 Decoder-only 的 Transformer 模型(如 GPT),一次完整训练(一次前向传播 + 一次反向传播)所需的总计算量有一个广为接受的估算公式:

    FLOPs6×Nparams×NtokensFLOPs≈6×N_{params}×N_{tokens}

    Where :

    • NparamsN_{params}: 模型的参数量。
    • NtokensN_{tokens}: 训练数据的总 Token 数量。
    • 系数 “6” 的来源: 这是一个工程近似值。
      • 前向传播: 大约需要 2×Nparams×Ntokens2×N_{params}×N_{tokens} FLOPs。主要来自于矩阵乘法。
      • 反向传播: 大约需要 4×Nparams×Ntokens4×N_{params}×N_{tokens} FLOPs,通常是前向传播的2倍,因为它不仅要计算梯度,还要处理链式法则。
  • 矩阵乘法 (MatMul) 的 FLOPs :

    矩阵乘法是 Transformer 中最主要的计算开销。对于两个矩阵 X∈RM×K 和 W∈RK×N 相乘,其计算量为:

    FLOPsMatMul2×M×K×N\text{FLOPs}_\text{MatMul}≈2×M×K×N

    • 系数 “2” 是因为每个元素的计算都包含一次乘法和一次加法。

3. 内存与计算考量

  • 模型 FLOPs 利用率 (MFU) :

    这是一个衡量硬件(如 GPU)计算效率的指标。

    MFU=Actual FLOPsTheoretical Peak FLOPsMFU=\frac{\text{Actual FLOPs}}{\text{Theoretical Peak FLOPs}}

    • Actual FLOPs: 模型在实际运行时达到的 TFLOPs/s。
    • Theoretical Peak FLOPs: 硬件供应商标注的理论峰值 TFLOPs/s。
    • MFU 越高,说明对硬件算力的利用越充分。
  • 内存占用 (Memory Footprint):

    模型训练时,数据需要在不同设备间传递。主要的内存开销来自于张量(Tensor)的存储。一个张量占用的内存由其元素数量和数据精度决定。例如,一个使用 FP32(32位浮点数,即4字节)精度的张量,其内存占用为:元素数量 * 4 bytes.

    数据精度 (Precision):

    • FP32 (单精度): 传统的浮点数格式,精度高但计算和内存开销大。
    • FP16 (半精度): 精度较低,但能显著减少内存占用和加速计算。缺点是其表示的数值范围(动态范围)有限,容易出现上溢或下溢。
    • BFloat16 (BF16): 为了弥补 FP16 的不足而设计。它牺牲了一部分尾数精度,以换取和 FP32 一样的指数位。这使得 BF16 拥有与 FP32 几乎相同的动态范围,更适合深度学习训练,能有效避免溢出问题。笔记中提到的 “bfl6 better than fpl6, 增加指数” 正是指这一优势。

好的,我們來詳細解析講義中關於優化器 (Optimizer) 的部分。以下內容完全基於您提供的 lecture_02.py 文件。

優化器的作用 (以 AdaGrad 為例)

優化器的核心作用是根據計算出的梯度來更新模型的參數,目標是讓模型的損失函數 (loss) 越來越小。

最基礎的優化器是隨機梯度下降 (SGD),它的更新規則非常簡單:新參數 = 舊參數 - 學習率 * 梯度。然而,在複雜的訓練過程中,為所有參數使用固定的學習率可能不是最高效的。

講義中以 AdaGrad 為例,展示了更進階的優化器如何工作。AdaGrad 的全稱是 “Adaptive Gradient Algorithm”,它的關鍵特性是為模型中的每一個參數獨立地調整學習率

根據講義中提供的 AdaGrad 程式碼實現,其運作機制如下:

  1. 維護一個「狀態 (state)」AdaGrad 會為每一個參數 p 維護一個名為 g2 的狀態變數。這個 g2 用於累加該參數歷史所有梯度的平方和
  2. 更新狀態:在每一步更新時,它會先取得當前的梯度 grad,將其平方後累加到 g2 中 (g2 += torch.square(grad))。
  3. 自適應更新參數:參數的更新公式為 p.data -= lr * grad / torch.sqrt(g2 + 1e-5)
    • 核心思想:分母中的 sqrt(g2) 會調節實際的學習率。如果一個參數的歷史梯度一直很大,它的 g2 就會很大,從而導致分母變大,實際的學習率就會減小。反之,如果一個參數的歷史梯度很小,它的 g2 就會小,實際學習率就會增大
    • 效果:這使得優化器能夠在梯度較大的維度上更謹慎地更新(防止步子太大邁過頭),在梯度較小的維度上更大膽地更新(鼓勵參數繼續學習)。

講義中也提到了一個優化器的演進層次關係:SGD -> AdaGrad -> RMSProp -> AdamAdam 是目前最常用的優化器之一,它結合了 RMSProp (AdaGrad 的變體) 和動量 (Momentum) 的思想。


笔记补充:一個完整訓練步驟中的記憶體組成

假設我們使用 32 位浮點數 (float32),每個數值佔用 4 bytes。

  1. 模型參數 (Parameters)
    • 這是模型本身的權重和偏置,是模型需要學習的核心。其大小由模型架構決定,在訓練過程中是固定的 (num_parameters)。
  2. 梯度 (Gradients)
    • 在反向傳播 (loss.backward()) 後,會為每一個模型參數計算一個梯度值。因此,梯度的數量和模型參數的數量完全相同 (num_gradients = num_parameters)。這部分記憶體用於暫存梯度,以便優化器後續使用。
  3. 優化器狀態 (Optimizer States)
    • 這是像 AdaGradAdam 這樣的自適應優化器需要的額外記憶體。
    • AdaGrad 為例,它需要為每個參數儲存一個 g2 (歷史梯度平方和),所以它需要和參數一樣多的額外儲存空間 (num_optimizer_states = num_parameters)。
    • (講義註:對於更常用的 Adam 優化器,需要儲存兩份狀態:一階動量和二階動量,所以其狀態記憶體大約是參數量的兩倍。)
  4. 活化值 (Activations)
    • 這是在前向傳播過程中產生的中間計算結果(例如 h1 = x @ w1)。
    • 這些中間結果必須被儲存,因為在反向傳播計算梯度時會被用到(鏈式法則)。
    • 活化值的記憶體大小與批量大小 (batch size)序列長度直接相關,而不僅僅是模型大小。這是訓練大模型時一個非常重要的記憶體瓶頸。

總結來說,在一個訓練步驟中,總的記憶體消耗可以估算為:

Total_memory = 4 * (num_parameters + num_gradients + num_optimizer_states + num_activations)


第二页:模型量化

本页笔记的核心主题是模型量化 (Quantization),这是一种关键的模型压缩与优化技术,旨在降低模型的计算和存储开销。

1. 量化的基本思想

量化的核心是将模型中高精度的数据类型(通常是32位浮点数,FP32)映射到低精度的数据类型(例如,8位整型,INT8)。

  • 目标: 量化模型中的权重 (Weights)激活值 (Activations)
  • 优势:
    • 减少内存占用: INT8 只需 FP32 四分之一的存储空间。
    • 加速计算: 硬件对整型运算的支持通常比浮点运算更快、更节能。
  • 挑战: 将连续的浮点数映射到离散的整数,必然会引入精度损失(量化误差)。量化技术的目标就是最小化这种误差。

2. 均匀量化(Affine Quantization)

笔记中给出了最常用的一种量化方法——均匀仿射量化的核心公式。

Xint=clip(round(XfloatS+Z),qmin,qmax)X_{int}=\text{clip}(\text{round}(\frac{X_{\text{float}}}{S}+Z),q_{min},q_{max})

这个过程可以理解为对一个浮点数区间进行“平移和缩放”,以映射到目标整数区间。

  • Xfloat: 原始的 FP32 值。
  • Xint: 量化后的 INT8 值。
  • S (Scale, 缩放因子): 一个正的浮点数,决定了量化的粒度或精度。它将浮点数的范围映射到整数的范围。
  • Z (Zero-point, 零点): 一个整数,确保原始浮点数中的 0 能够准确地映射到量化后的整数域中的某个值。
  • round(⋅): 将结果四舍五入到最接近的整数。
  • clip(⋅,qmin,qmax): 将结果截断在目标整数类型的范围内(例如,对于 INT8,范围是 [-128, 127])。

3. 量化策略分类

笔记中提到了两种不同的分类维度:

维度一:根据映射方式

  • 均匀量化 (Uniform Quantization): 如上述公式所示,量化步长(由缩放因子 S 决定)在整个数值范围内是恒定的。这是一种简单高效的方法。

    非均匀量化 (Non-uniform Quantization): 量化步长不是固定的,可以根据数据的实际分布进行调整。例如,在数据密集的区域使用更精细的量化步长,在稀疏区域使用较粗的步长。这种方法通常能更好地拟合呈现非均匀分布(如高斯分布)的权重和激活值,从而获得更高的精度,但实现也更复杂。

维度二:根据应用时机

  • 后训练量化 (Post-Training Quantization, PTQ): 这是一种简单快捷的量化方法。它在模型已经完成标准 FP32 训练之后进行。只需少量校准数据集来计算权重和激活值的缩放因子 (S) 和零点 (Z),无需重新训练模型。

  • 量化感知训练 (Quantization-Aware Training, QAT): 这种方法在训练过程中就“模拟”量化操作。具体来说,它在前向传播中模拟量化和反向传播中的舍入误差,让模型在训练时就适应量化带来的精度损失。QAT 通常能达到比 PTQ 更高的模型精度,但代价是需要完整的训练流程和数据,计算成本更高。

此外,笔记开头的

动态量化 (Dynamic Quantization)静态量化 (Static Quantization) 是 PTQ 的两种主要形式:

  • 静态量化: 激活值的缩放因子和零点是通过一个校准数据集提前计算好的,在推理时直接使用。

  • 动态量化: 权重量化是离线的,但激活值的量化是在运行时(on-the-fly)动态计算的。这为不同输入提供了更精确的缩放因子,但带来了额外的计算开销。


第三页:Transformer 架构新编(1)

这一页的笔记内容非常丰富,从训练的工程实践细节,深入到了 Transformer 模型的各种架构变体和关键组件的选择,反映了构建和优化现代大模型的深度考量。

1. 训练的工程实践与考量

  • 参数初始化与随机种子 (Parameter Initialization & Random Seed)

    • 为了保证实验的可复现性,需要在代码中固定所有可能的随机源。笔记中列举了三个关键部分:torch.manual_seednumpy.random.seedrandom.seed

    • 一个良好的初始化策略(如 Kaiming He 或 Xavier 初始化)对于训练初期的稳定性至关重要。

  • FLOPs vs. 实际运行时间 (Runtime)

    • 笔记中有一个非常深刻的观察:计算量 (FLOPs) 和实际运行时间并非完全成正比。

    • 案例1: 矩阵乘法虽然占据了模型 99% 的 FLOPs,但其运行时间可能只占 60%,这是因为现代 GPU 对大规模并行计算(如矩阵乘法)做了极致优化,硬件利用率非常高。

    • 案例2: 归一化操作 (Normalization) 的 FLOPs 占比极低(如 0.1%),但其运行时间可能高达 25%。这是因为这类操作涉及大量的逐元素读写和归约 (reduction) 操作,会频繁访问内存,成为访存瓶颈 (memory-bound) 而非计算瓶颈。

  • 训练内存占用估算 (Memory Estimation)

    • 在 FP32 精度下,使用 Adam/AdamW 优化器训练一个模型,其显存占用可以粗略估算。笔记中的公式可以整理为:

      4×(Nparams+Nactivations+Ngradients+Noptimizer_states)显存≈4×(N_\text{params}+N_\text{activations}+N_\text{gradients}+N_\text{optimizer\_states})

      • NparamsN_\text{params}: 模型参数。
      • NactivationsN_\text{activations}: 为反向传播而缓存的激活值(大小与模型、序列长度和批次大小相关)。
      • NgradientsN_\text{gradients}: 与模型参数量相等的梯度。
      • Noptimizer_statesN_\text{optimizer\_states}: 优化器状态。Adam/AdamW 需要为每个参数存储动量 (momentum) 和方差 (variance) 两个状态,因此这部分大小约为 2×NparamsN_\text{params}

2. Transformer 架构的变体与优化 (Transformer Variations)

  • 激活函数 (Activation Function)

    • 现代 LLM 已经超越了传统的 ReLU,普遍采用基于门控线性单元 (Gated Linear Units, GLU) 的变体。

    • SwiGLU 是其中最成功的一种,其公式为 FFN(X,W,V,W2)=(Swish(XW)XV)W2FFN(X,W,V,W_2)=(Swish(XW)⊗XV)W_2,其中 ⊗ 代表逐元素相乘,而 Swish(x)=xsigmoid(x)Swish(x)=x⋅sigmoid(x)。这种门控机制被认为能提供更丰富的表达能力。

    • FFN 维度: 传统 Transformer 的 FFN 隐藏层维度通常是模型维度的4倍 (d_ff=4d_model) 。但使用 GLU 变体后,为保持参数量和计算量大致不变,这个比例通常会调整,例如 Llama 模型中使用的比例是 8/38/3。笔记也鼓励实践者勇于打破常规 (“Be Rule breaker!”)。

  • 归一化层 (Normalization Layer)

    • 位置: 笔记中的图示是 Post-LN 结构,即 [Sublayer] -> Add -> Norm。笔记中提出了一个关键问题:为什么 LayerNorm 不直接作用在残差连接 (Residual Path) 上?答案是,残差连接的核心是提供一条“恒等映射” (identity connection),确保信息可以直接流向更深层;而 LayerNorm 会改变数据的分布,从而“破坏”这种直接的恒等连接。

    • LayerNorm vs. RMSNorm: RMSNormLayerNorm 的一个简化版本,它去掉了重新中心化 (re-centering) 的步骤,即移除了均值计算和偏置项 (bias)。这使得其计算量(FLOPs)大大减少,而在实践中发现其性能与 LayerNorm 相比几乎没有损失,因此在现代 LLM 中被广泛采用。

  • 位置编码 (Positional Embedding)

    • 旋转位置编码 (RoPE) 是目前的主流方案。
    • 其核心思想是,通过对词向量进行“旋转”,将绝对位置信息编码到向量的相位中,从而使得两个 token 在经过注意力计算后的得分,仅与其相对位置和内容相关,而与它们的绝对位置无关。这使得模型能更好地泛化到训练时未见过的更长序列。

3. 其他关键超参数

模型“长宽比”: 指模型的深度 (层数) 与宽度 (模型维度 d_model) 的比例,笔记建议这个比例可以在 1:100 左右作为参考。

  • 注意力头维度 (head dim): 每个注意力头的维度也是一个重要的设计选择。
  • 词汇表大小 (vocab size): 词汇表大小会影响模型的嵌入层大小和最终输出层的计算。
  • 权重衰减 (Weight Decay): 一种有效的正则化技术,用于防止过拟合,提高训练的鲁棒性。

第四页:Transformer(2)和 MOE(1)

第四页的笔记分为左右两个部分。左侧主要探讨了标准 Transformer 架构中的一些关键优化技术,特别是在注意力和推理效率方面。右侧则引入了目前非常流行的高效架构——专家混合网络 (MoE)。

1. 标准 Transformer 架构的优化

  • Softmax 稳定性优化

    • 在计算注意力分数时,Softmax 函数对非常大的输入值可能会产生数值溢出。一个标准的数值稳定技巧是 z-loss : 这是一个辅助损失项,用于惩罚输出层 Softmax 的 log-normalizer log(Z(x)) 过大。通过将这个值拉向 0,可以有效提升训练的稳定性 。
  • QK-Norm

    • 在注意力计算中,QKT 的点积结果可能会变得非常大,导致梯度不稳定或消失,尤其是在模型层数很深或使用低精度训练时。

    • QK-Norm 是一种在前向传播中稳定注意力的技术。其思想是在 Q 和 K 进行矩阵相乘之前,分别对它们进行归一化(通常使用 LayerNorm 或 RMSNorm)。这能有效控制输出的方差,防止数值爆炸。

  • 推理效率优化:GQA & MQA

    • 背景: 在自回归生成(解码)新 token 时,模型需要缓存(Cache)之前所有 token 的键(Key)和值(Value)矩阵,这被称为 K/V Cache。随着序列长度增加,K/V Cache 会占用巨大的显存,成为推理的主要瓶颈。
    • 计算强度 (Arithmetic Intensity, AI): 这个指标定义为 浮点运算次数(FLOPs) / 内存访问字节数(Bytes)。在生成 token 时,注意力计算需要从显存中读取庞大的 K/V Cache,但执行的计算量相对较少,因此这是一个典型的 访存密集型(或称计算强度低)操作。
    • 解决方案:
      • 多查询注意力 (Multi-Query Attention, MQA): 所有注意力头共享同一份 K 和 V 矩阵,极大地减小了 K/V Cache 的大小。
      • 分组查询注意力 (Grouped-Query Attention, GQA): 这是 MQA 和标准多头注意力(MHA)的折中方案。它将查询头(Q heads)分成若干组,组内的 Q 头共享一份 K 和 V 矩阵。GQA 在大幅减少 K/V Cache 的同时,相比 MQA 能更好地保留模型性能。

2. 专家混合网络 (Mixture of Experts, MoE)

MoE 是一种旨在用更少的计算量(FLOPs)来激活和利用更大模型参数的架构,其性能通常优于同等计算量的稠密模型 (Dense Model) .

image-20250928153008200

  • 核心思想
    • MoE 模型将传统 Transformer 中的前馈网络层(FFN)替换为一个“路由-专家”结构 121212。
    • 这个结构包含:
      1. 选择层/路由器 (Select Layer / Router):负责为每个输入 token 决定应该由哪些专家来处理。
      2. 多个并行的专家网络 (Experts):每个专家本身就是一个 FFN。
    • 对于每个 token,路由器仅激活少数几个(例如 Top-k)专家进行计算,其他专家则不激活状态。
  • 优势 (Advantages)
    • 参数量 vs. 计算量: MoE 的关键优势在于它解耦了模型的总参数量和单次前向传播的计算量。你可以在保持计算量(FLOPs)不变的情况下,极大地增加模型的总参数量(知识容量)。
    • 性能提升: 在相同的计算预算下,MoE 模型因为拥有更多的总参数,通常能获得比稠密模型更好的性能。
  • 劣势 (Disadvantages)
    • 通信开销: 在多 GPU/多节点训练中,路由器需要将不同的 token 发送到存储在不同设备上的专家处,这个过程(All-to-All communication)会引入显著的通信延迟。
    • 内存开销: 尽管每次只有少数专家被激活,但在训练或服务时,所有专家的参数都必须加载到内存/显存中,这带来了额外的内存开销。

第五页:MOE(2)

第五页笔记详细探讨了 MoE 模型的实现细节,从其并行计算的优势,到训练中的核心难题——负载均衡,再到具体的路由机制和损失函数设计。

1. MoE 的并行计算模式

  • 笔记首先指出了 MoE 的一个关键优势:它天然支持 专家并行 (Expert Parallelism) 。可以将不同的专家(Experts)分布在不同的计算设备(如多个 GPU)上。当一个 token 被路由时,它的数据会被发送到对应的设备上进行计算,然后再将结果返回
  • 这种模式在多节点(Multi-node)集群上优势明显,但也带来了复杂的设备间通信问题 (Intra is complex)

2. MoE 的训练挑战

  • 路由器的训练是启发式的: MoE 的核心挑战在于如何有效地训练路由器(Router/Selection Layer。路由决策本质上是离散的,难以使用基于梯度的标准方法进行优化。
  • 不稳定性: 路由器的训练过程有时会不稳定。笔记中提到,一些看似直观的方法,如强化学习(RL),虽然符合决策制定的思想,但实际效果不佳。而随机探索等方法效果也比较一般。
  • 负载不均衡 (Load Imbalance): 这是 MoE 训练中最核心的问题。路由器在训练初期会倾向于将 token 发送给少数几个表现稍好的“明星专家” (celebrity experts) 。这会导致这些专家被过度训练,而其他专家则因缺乏训练数据而“饿死”,最终导致模型容量被浪费,训练不均衡。

3. 路由机制 (Routing Mechanism)

笔记中提到了两种概念上的路由模式:

  1. Token 选择专家 (Token-chooses-expert): 这是最主流的方式。路由器为每个 token 计算其与所有专家的匹配分数,然后选择分数最高的前 K 个(Top-K)专家来处理该 token 。
  2. 专家选择 Token (Expert-chooses-token): 一种对偶的思路。每个专家根据自身“特长”,从一批 token 中选择它最需要处理的前 K 个。

4. 负载均衡损失 (Load Balancing Loss)

为了解决不均衡问题,研究者们设计了一种 启发式的辅助损失函数 (Heuristic Loss balancing)

  • 路由器的工作原理:
    • 对于每个 token (t),路由器会计算一个与所有专家 (i) 的匹配分数(logits),然后通过 Softmax 函数得到一个概率分布 Si,t 15。这个概率可以理解为该 token 与各个专家的匹配程度 16。
  • 辅助损失函数:
    • 笔记中给出的公式是 Loss=αMfiPiLoss=αM⋅∑fi⋅Pi,其中 α 是一个超参数,M 是专家数量。
    • fi: 代表在该批次(batch)中,被分配给第 i 个专家的 token 比例(fraction of tokens。
    • Pi: 代表在该批次中,所有 token 对第 i 个专家的平均路由概率(average router probability。
  • 工作机制:
    • 该损失函数的目标是同时最小化 fi 和 Pi 的乘积。
    • 如果一个专家被频繁使用(fi 很高),那么损失函数会通过梯度下降来压低路由器分配给它的概率(Pi),从而形成一种“负反馈”。
    • 这种机制会鼓励路由器将 token 更均匀地分配给所有专家,从而实现负载均衡。

5. 门控与输出合成

  • token 被路由到 Top-K 个专家后,每个专家会独立进行计算(如一个 FFN 网络)。
  • 最终的输出是这些专家输出的加权和。权重就是路由器计算出的 Softmax 概率值(gating value。

6. 其他高级 MoE 概念

  • 共享专家 (Shared Expert): 对于一些通用且总是被需要的功能,可以设置一些被所有 token 共享的专家。
  • 细粒度专家 (Fine-grained Expert): 可以将每个专家的规模做小,但增加专家的总数量,以实现更细粒度的知识划分。

第六页:底层优化

第六页笔记的主题转向了底层硬件和算法优化,具体分为两个部分:左侧介绍了 GPU 和 CUDA 的基本架构,右侧则详细阐述了革命性的优化算法——FlashAttention。

1. GPU 与 CUDA 编程模型

要充分发挥大模型的潜力,必须理解其运行的硬件基础。

  • GPU 硬件层级 (GPU Hierarchy):

    • GPU: 整个图形处理单元。
    • 流式多处理器 (Streaming Multiprocessors, SMs): 一个 GPU 包含多个 SM。可以将 SM 理解为 GPU 内部独立的核心,负责执行计算任务(“jobs”)。
    • 流式处理器 (Streaming Processors, SPs): 每个 SM 内部又包含多个 SP。SP 是最基本的执行单元,负责并行地执行具体的“线程” (threads)。
  • CUDA 工作负载层级:

    CUDA 是 NVIDIA 的并行计算平台和编程模型,它将计算任务组织成以下层级:

    • Blocks: 线程(Threads)被组织成块(Blocks)。一个 Block 内的线程可以相互协作(例如通过高速的共享内存)。每个 Block 会被调度到一个 SM 上执行。
    • Warps: 块(Blocks)中的线程又被组织成线程束(Warps),通常一个 Warp 包含32个线程。
    • Threads: 最基本的工作单元。在同一个 Warp 中的线程会以“锁步” (lockstep) 的方式执行相同的指令,但处理不同的数据,这就是所谓的 SIMT (Single Instruction, Multiple Threads) 模型。
  • 影响 GPU 效率的关键问题:

    • 计算与访存的鸿沟: 现代 GPU 的计算速度(Compute)增长远快于内存带宽(Memory)的增长。这导致许多操作的瓶颈不在于计算本身,而在于等待数据从内存中读取或写入,即所谓的“访存墙”。
    • 线程束发散 (Warp Divergence): 在 SIMT 模型下,如果一个 Warp 内的线程执行了条件分支(如 if-else),导致不同线程需要执行不同的指令,这种并行执行的模式就会被打破,从而降低效率。
    • 优化策略: 为了克服这些瓶颈,笔记中提到了几种关键技术:
      • 内存访问优化: 通过内存合并(Memory coalescing,让一个 Warp 内的线程访问连续的内存地址)和分块(Tiling,将大块数据切分后载入高速的共享内存)来提升访存效率。
      • 减少内存 I/O: 通过算子融合(Operation Fusion,将多个连续操作合并成一个,避免中间结果写回内存)、重计算(Recompute,用计算换取访存)和使用更低的数据精度(Lower precisions)来减少对内存的依赖。

2. FlashAttention

FlashAttention 是一种在硬件层面深度优化注意力机制的算法,其核心目标是解决标准注意力计算中的访存瓶颈。核心思想: 将标准注意力计算中需要多次读写全局内存(HBM)的“离线” Softmax,转变为一个单遍完成的“在线” Softmax。

  • 实现方法:
    1. 分块 (Tiling): 将 Q,K,V 矩阵从速度较慢的 HBM(主显存)中分块读入到速度极快的片上 SRAM(共享内存)中。
    2. 在线 Softmax (Online Softmax): 在 SRAM 内部,对一小块 Q 和 K 计算注意力得分后,不将完整的中间结果(QKT 矩阵)写回 HBM,而是在片上直接进行 Softmax 的归一化计算 16。它通过一种巧妙的流式算法,在处理完所有 K 的分块后,得到与标准 Softmax 完全相同的最终结果。
  • 优势:
    • 计算过程(包括 Softmax)在高速的 SRAM 中完成,极大地减少了与 HBM 的数据交换次数。
    • 这使得原本是访存密集型的注意力计算,转变成了计算密集型,从而能更充分地利用 GPU 强大的计算能力,实现显著的加速并减少显存占用。

这是对全部六页笔记的分析。内容涵盖了从分词、计算量估算、模型量化、Transformer 架构变体、MoE 模型,到最终的底层硬件优化,构成了一套完整的大模型构建知识体系。