Skip to content

Latest commit

 

History

History
584 lines (419 loc) · 23 KB

File metadata and controls

584 lines (419 loc) · 23 KB

Module 1: 基石算子——逐元素的世界

深度学习的所有复杂操作,最终都可以追溯到对单个数值的加减乘除。就像再宏伟的建筑也是由一块块砖砌成的——本模块就是带你认识每一块"砖"。

📓 配套 Jupyter Notebook: module-01-notebook.ipynb — 40 个 cell,边读边跑,代码验证每一个概念。建议先读本文档建立认知框架,再打开 Notebook 逐 cell 运行。


学习目标

  • 说出 7 种 BinaryOp 及其在 LLM 中的具体应用场景
  • 理解广播 (Broadcasting) 的规则,一眼判断两个张量能否广播
  • 理解梯度与反向传播的基本原理——深度学习为什么能"学习"
  • 画出 ReLU / GELU / Swish 的函数图像,讲出它们的故事
  • 对比 Sigmoid 和 Softmax——一个做"独立判断",一个做"联合决策"
  • 解释五种归一化的核心差异——为什么现代 LLM 集体投奔 RMSNorm
  • 判断一个算子是 memory-bound 还是 compute-bound(背下 roofline 拐点!)
  • 理解 SIMD 为什么能让逐元素算子"一次处理 16 个"

0. 前置基础:梯度与反向传播——深度学习为什么能"学习"

在深入到具体算子之前,必须先理解整个深度学习的"发动机"是怎么转起来的。如果只记住一件事,记住这个:模型通过梯度来学习,梯度告诉每个参数该往哪个方向调整

0.1 什么是"学习"?

深度学习的"学习"其实就是一个不断试错、不断修正的过程:

1. 前向传播:  输入 x → 模型(参数W) → 预测 y_pred
2. 计算损失:  Loss = 衡量(y_pred, 真实值 y_true) — "你错得有多离谱?"
3. 反向传播:  计算 Loss 对每个参数的梯度 — "每个参数对错误负多大责任?"
4. 更新参数:  W_new = W_old − lr × 梯度 — "朝减少错误的方向调一调"
5. 重复 1-4:  直到 Loss 足够小

0.2 什么是梯度?——"站在山上找下山最快的方向"

你站在一座山上(山的高度 = 损失函数的值,越低越好)
你想下山(减少损失)
梯度告诉你:往哪个方向走,下山最快

梯度 > 0: "往这边走高度会增加" → 反方向走!
梯度 < 0: "往这边走高度会降低" → 继续走!
梯度 ≈ 0: "这儿是平的" → 不知道该往哪走(梯度消失!)

数学上:对于函数 f(x₁, x₂, ..., xₙ),梯度是一个向量:

∇f = [∂f/∂x₁, ∂f/∂x₂, ..., ∂f/∂xₙ]

每个分量 ∂f/∂xᵢ 回答一个问题:
  "如果我把 xᵢ 调大 0.0001,f 会变大还是变小?变多少?"

一个具体到不能再具体的例子:

假设 Loss = (预测值 − 3)²,而预测值 = w × 2
即 Loss = (2w − 3)²

∂Loss/∂w = 2(2w−3) × 2 = 4(2w−3)

如果 w=0: Loss=9, 梯度=4(0−3)=−12
  → 梯度为负 → 增大 w 能减小 Loss
  → w = 0 − 0.01×(−12) = 0.12  ← 果然变大了!

如果 w=3: Loss=9, 梯度=4(6−3)=12
  → 梯度为正 → 减小 w 能减小 Loss
  → w = 3 − 0.01×12 = 2.88  ← 果然变小了!

如果 w=1.5: Loss=0, 梯度=4(3−3)=0
  → 梯度为零 → 已经到最低点了!

💡 关键直觉:梯度不告诉你"最小值在哪",只告诉你"现在该往哪走一步"。所以需要反复迭代——每一步都根据当前位置重新计算梯度。

0.3 链式法则——梯度的"接力赛"

神经网络是一层一层叠起来的:y = f₃(f₂(f₁(x)))。最后一层的 Loss 怎么影响第一层的参数?靠链式法则

∂Loss/∂W₁ = ∂Loss/∂y × ∂y/∂h₂ × ∂h₂/∂h₁ × ∂h₁/∂W₁
            └────── 梯度从输出层"接力"传回输入层 ──────┘

反向传播 (Backpropagation) 的本质:
  1. 前向: 从输入算到输出,每层记下中间结果(为反向做准备)
  2. 反向: 从输出往回传梯度,每层用链式法则乘上自己的导数(雅可比矩阵)

为什么叫"反向传播"? 因为梯度的计算方向和数据的前向流动方向相反

前向:  x → W₁ → h₁ → W₂ → h₂ → W₃ → y → Loss
反向:  Loss → ∂L/∂y → ∂L/∂W₃ → ∂L/∂h₂ → ∂L/∂W₂ → ∂L/∂h₁ → ∂L/∂W₁
       └─────────────── 梯度往回传 ──────────────────────┘

0.4 为什么这跟激活函数有关?

每层的梯度都要乘上激活函数的导数。导数 = 0 → 梯度在此中断:

反向传播经过 ReLU:
  grad × ReLU'(x)
    ReLU'(−5) = 0   ← 断了!前面的层收不到任何梯度!
    ReLU'(3)  = 1   ← 正常通过

反向传播经过 Swish:
  grad × Swish'(x)
    Swish'(−5) ≈ 0.007 ← 很小,但不为 0 → 信号微弱但没断!
    Swish'(3)  ≈ 0.95  ← 几乎全通过

这就是为什么:

  • ReLU 可能"杀死"神经元:一旦某个神经元对所有输入都输出负值,梯度永远为 0,参数再也无法更新
  • Swish/GELU 更稳定:即使 x<0,也保留微弱梯度,"死"不掉
  • 这也是为什么激活函数的设计如此重要——它不只是"加非线性",它决定了梯度能否顺畅流动

0.5 优化器:梯度指路,优化器决定你"走多快"

SGD (最简单的更新规则):
  W_new = W_old − learning_rate × ∇W
                  └─ 学习率,控制"步子多大"

为什么需要 learning_rate(学习率)?
  梯度只告诉你"当前位置的坡度"——
  你站在半山腰,只看到周围几米的地形,看不到整座山。
  
  学习率太大 → 可能直接跳过最低点 → 来回震荡
  学习率太小 → 走太慢 → 训练到天荒地老
  学习率合适 → 平稳快速收敛

这是深度学习最重要的超参数,没有之一。

1. 数学运算——深度学习世界的"原子"

1.1 七种基本二元运算

两个张量逐元素做运算——这是所有复杂操作的最小可分解单元

操作 公式 在 LLM 中干啥
Add a + b 残差连接("跳过几层直接加过来")、偏置加法
Sub a − b 残差计算("和原来差了多少")
Mul a × b 门控的灵魂——SwiGLU 的 gate×up、注意力权重×Value
Div a / b 归一化中的除法、缩放
Pow a^b RoPE 的频率计算
Max max(a, b) 最大池化
Min min(a, b) 截断、Clip

💡 Mul 是门控机制的基石。从 LSTM 的遗忘门 (f_t ⊙ c_{t-1}) 到 SwiGLU 的 Swish(gate) ⊙ up,所有"选择性放行"的操作都建立在逐元素乘法之上。一个操作数充当门控信号(0~1 范围),另一个充当被控信息。理解了这一点,GLU 家族的所有变体就不神秘了——它们只是在"门控信号的生成方式"上做文章。

1.2 广播 (Broadcasting)——张量的"自动对齐术"

两个张量形状不完全一样时,框架会自动"扩展"较小的张量。这件事你天天在用,但你真的理解规则吗?

规则只有一句话:从最后一个维度向前比较,每个维度要么相等,要么其中一个为 1。

张量 A: [B, C, 1, 1]    ← C 个标量,每个在 (1,1) 的空间位置上
张量 B: [B, 1, H, W]    ← 一张 H×W 的图,通道维度是 1

广播后: [B, C, H, W]    ← 标量被"涂满"整张图,图被"复制"到每个通道

逐维度检查:
  轴0: B vs B → 相等 ✓
  轴1: C vs 1 → 1 扩展为 C ✓  (A 的每个标量被复制 H×W 次)
  轴2: 1 vs H → 1 扩展为 H ✓  (B 的每行被复制 C 次)
  轴3: 1 vs W → 1 扩展为 W ✓

更多实例——练到一眼判断:

✅ 能广播的:
  [3, 1, 4] + [1, 5, 4] → [3, 5, 4]   轴0:3vs1→3 ✓ 轴1:1vs5→5 ✓ 轴2:4vs4→4 ✓
  [2, 3] + [3]           → [2, 3]       从最后对齐: 3vs3→3, 前面2直接保留
  [5, 1] + [1, 6]        → [5, 6]       轴0:5vs1→5 ✓ 轴1:1vs6→6 ✓
  [2, 3, 1] + [1, 1, 5]  → [2, 3, 5]    轴0:2vs1→2 ✓ 轴1:3vs1→3 ✓ 轴2:1vs5→5 ✓

❌ 不能广播的:
  [3, 4] + [2, 4]        → ❌           轴0: 3 vs 2, 都不为1
  [3, 4] + [3, 5]        → ❌           轴1: 4 vs 5, 都不为1
  [2, 3, 4] + [5, 4]     → ❌           从最后对齐: 3 vs 5, 都不为1

💡 记忆技巧: 从右往左逐维对齐,像做竖式加法:
  [2, 3, 1, 5]
       [1, 1, 5]     ← 5=5匹配, 1vs1可扩, 3vs1可扩 → ✅
  
  [2, 3, 4]
     [5, 4]          ← 4=4匹配, 但3vs5都不为1 → ❌

广播在 LLM 中的应用:

场景 1: LayerNorm 的 gamma 参数
  x:          [batch, seq_len, hidden_dim]
  gamma (γ):  [hidden_dim]
  → 自动广播为 [1, 1, hidden_dim] → 每个 hidden 维度的 γ "涂满"整个 batch 和 seq

场景 2: 注意力掩码
  mask:          [seq_len, seq_len]
  → 需要广播到: [batch, num_heads, seq_len, seq_len]
  → 在前两个维度自动扩展,无需手动复制

场景 3: 位置编码
  pos_embed:        [seq_len, hidden_dim]
  token_embed:      [batch, seq_len, hidden_dim]
  → 位置编码在 batch 维自动扩展后相加

💡 在深度学习中,BN 的 γ/β(形状 [C])广播到 [B,C,H,W]、注意力掩码([seq,seq])广播到 [batch,heads,seq,seq]——这些都是广播在默默工作。你不需要手动 expand,框架帮你做了。

1.3 一元运算与归约运算

一元运算(每个元素独立变换):

操作 公式 在哪出现
Exp Softmax 的核心——把任意实数变成正数
Sqrt √x RMSNorm 的均方根
Log ln(x) 交叉熵损失——"惩罚"错误的高置信度预测
Floor/Round ⌊x⌋ / round(x) 量化取整——FP32→INT8 的关键步骤

归约运算(多个值合并为一个):

操作 公式 本质
Sum Σxᵢ Softmax 的分母——把所有 exp 值加起来
Mean Σxᵢ/n GlobalAvgPool——把整张特征图压缩为一个数
Max max(xᵢ) Softmax 的数值稳定技巧——先减 max 再 exp
L2Norm √(Σxᵢ²) 权重衰减——让权重不要太大

💡 归约是"从多到少"的核心操作。在 GPU 上做归约比 CPU 复杂——归约要求线程间通信,GPU 的几千个线程并不擅长互相等待。高效的 GPU 归约用的是树形归约(tree reduction),每轮两两合并,O(log n) 轮完成而非 O(n)。

1.4 MatMul / GEMM / InnerProduct——三位一体的矩阵大师

这是神经网络参数化的核心——80%+ 的计算量都在这里。

MatMul:      C = A × B              最通用的矩阵乘法 [M,K] × [K,N] → [M,N]
GEMM:        C = α·A·B + β·C        加上了缩放和累加——更灵活
InnerProduct: y = Wx + b            GEMM 的特例——封装了权重和偏置

三者的关系: InnerProduct ⊂ GEMM ⊂ MatMul

几乎每一个带可学习参数的操作最终都落到矩阵乘法上——卷积用 im2col 变成 MatMul、Attention 的 QKV 投影是 MatMul、MLP 那三层是 MatMul、最后的 Projection 也是 MatMul。

1.5 EinSum——用一行字符表达任意张量运算

# 矩阵乘法: [M,K] × [K,N] → [M,N]
torch.einsum('ij,jk->ik', A, B)

# Attention Q·K^T: [B,H,Q,D] × [B,H,K,D] → [B,H,Q,K]
torch.einsum('bhqd,bhkd->bhqk', Q, K)

# 逐通道缩放: [B,C,1,1] × [B,C,H,W] → [B,C,H,W]
torch.einsum('bc,bchw->bchw', scale, x)

规则:消去重复字母 → 保留未出现字母。'ij,jk->ik' 中 j 重复了→消去,i 和 k 保留→输出 ik。


2. 激活函数——给网络注入"灵魂"

2.1 为什么要非线性?

如果没有激活函数,无论多少层,从输入到输出永远只是一个线性变换——W_n(...(W_1·x)) = W_effective·x。100 层 = 1 层。激活函数的非线性打破了这种"层数的幻象"。

反向传播角度(联系 0.4 节):激活函数的导数 = 梯度的"中继器"。ReLU 的中继器在 x<0 时直接断电;Swish/GELU 的中继器永远有微弱信号通过。这就是"死亡 ReLU"的根源——好的激活函数必须在大部分输入范围上有非零梯度。

2.2 进化路线

ReLU (2012):        max(0, x)                         ← 硬截断, 计算最快, 但有死亡区
  ↓
LeakyReLU (2015):   x>0 ? x : 0.01x                   ← "给负值区开一条小缝"
  ↓
GELU (2018):        x·Φ(x)                            ← "概率门控", BERT/GPT 标准
  ↓
Swish/SiLU (2017):  x·σ(x)                            ← "自门控", LLaMA/Qwen 标准
  ↓
SwiGLU (2022):      Swish(xW₁) ⊙ (xW₂)               ← "显式门控 MLP", 现代 LLM 标配

2.3 核心激活函数详解

ReLU——简单粗暴但有效

ReLU(x) = max(0, x)
导数:     x>0 → 1; x≤0 → 0

优点: 一个 max 操作搞定,快到不能再快
缺点: x≤0 时梯度为 0 → 神经元可能"永久死亡"

直觉: 像一个只有"开"和"关"两档的电源开关。
      x>0: 通电,信息畅通
      x≤0: 断电,信息归零,梯度也归零

GELU——"概率门控"

GELU(x) = x · Φ(x)   (Φ = 标准正态 CDF,近似: 0.5x(1+tanh(√(2/π)(x+0.044715x³))))

直觉: 不像 ReLU 的问"x>0?(是/否)",
     GELU 问的是"x>0 的概率有多大?"

     x=−2: Φ(−2)≈0.02 → GELU≈−0.04  "基本确定是错的,几乎全关"
     x=0:  Φ(0)=0.5   → GELU=0       "不确定,对半分"
     x=2:  Φ(2)≈0.98  → GELU≈1.96   "很确定是对的,几乎全过"
     
     这是一种"概率门控"——模型根据对输入的"信心"来决定放行多少。

Swish/SiLU——现代 LLM 标配

Swish(x) = x · σ(x) = x / (1 + e⁻ˣ)

直觉: x 同时扮演"信息"和"自己的门控信号"两个角色
  x=5:   σ(5)≈0.99 → Swish≈4.95  "有话说,大声说"
  x=0.5: σ(0.5)≈0.62 → Swish≈0.31 "不太确定,小声点"
  x=−3:  σ(−3)≈0.05 → Swish≈−0.14 "不对,基本闭嘴"

与 GELU 几乎完全等价(β=1 时),但计算更简单——Sigmoid 比正态 CDF 好算。
代表: LLaMA, Qwen, Mistral——所有主流开源 LLM。

SwiGLU——门控 FFN 的新范式

SwiGLU(x) = Swish(x·W_gate) ⊙ (x·W_up) · W_down

为什么中间维度是 3072 (3×1024) 而非 4096 (4×1024)?
  标准 ReLU-FFN: W1(1024×4096) + W2(4096×1024) = 2×4=8 个"参数块"
  SwiGLU:        gate(1024×3072) + up(1024×3072) + down(3072×1024) = 3×3=9 块
  → 总参数量接近,但 SwiGLU 多了一层门控 → 精度显著更好

直觉: gate 分支产生"放行多少"的软门控,up 分支携带信息本身
      gate⊙up = "对 up 的每个维度,根据置信度选择性表达"

Sigmoid vs Softmax——别再搞混了

对比 Sigmoid Softmax
决策方式 逐元素独立判断 整个向量联合决策
多分类 各类独立(可以有多个高概率) 互斥选择(概率和=1,只能选一个)
典型场景 二分类、LSTM 门控 多分类、注意力权重
输出范围 每个值独立在 (0,1) 所有值加起来 = 1

💡 一句话:Sigmoid 是"这图里有猫吗?有狗吗?"(可以同时 yes)。Softmax 是"这图是猫还是狗还是鸟?"(只能选一个)。

2.4 实战选哪个?

场景 推荐 原因
CNN 分类 ReLU 简单有效,计算快
Transformer (BERT系) GELU 平滑门控,训练稳定
现代 LLM MLP SwiGLU 门控+平滑,当前最佳
移动端部署 HardSwish 无 exp 运算,省计算
LSTM/GRU 门控 Sigmoid 输出天然在 (0,1)

3. 归一化——让训练不再"震荡"

3.1 问题:Internal Covariate Shift

训练深层网络时,前面层的参数一更新,后面层接收到的输入分布就变了——就像射击训练中靶子在不停移动,射手需要不断重新适应。这导致:学习率要更小(慢)、初始化要更谨慎(烦)、深层容易梯度消失(痛)。

归一化相当于把靶子钉死在同一个位置——每层输入被拉到均值 0、方差 1 附近。

3.2 五种归一化——一张图看懂

             N (batch)    C (channel)   H,W (spatial)   适用场景

BatchNorm:  ──────────   ✗ 做归一化    ✗ 做归一化       CNN (大batch)
LayerNorm:  ✗ 每个独立   ───────────  ───────────      Transformer
RMSNorm:    ✗ 每个独立   ───────────  ───────────      现代 LLM
GroupNorm:  ✗ 每个独立   分组内做      ───────────      扩散模型/小batch
InstanceN:  ✗ 每个独立   每通道独立    ───────────      风格迁移

3.3 BatchNorm——CNN 时代王者

训练: y = (x − μ_batch) / √(σ²_batch + ε) · γ + β
推理: y = (x − running_mean) / √(running_var + ε) · γ + β
      ↑ 推理时用训练累积的统计量,不依赖 batch

致命弱点: batch size 小了统计量不准 → 效果差
         Transformer 通常 batch 不大 → BatchNorm 不适用

推理时的魔法:Conv-BN 融合

BN 在推理时退化为线性函数(μ 和 σ 是常数):

BN(x) = γ·(x−μ)/√(σ²+ε) + β = (γ/√(σ²+ε))·x + (β − γ·μ/√(σ²+ε))
     = α·x + b'   ← 一个简单的线性变换!

融合进卷积: W' = α·W,   b_new = α·b + b'
→ 模型加载时一次性算好,推理时完全零 BN 开销

3.4 RMSNorm——现代 LLM 的共同选择

LayerNorm: y = (x − mean(x)) / √(var(x) + ε) · γ + β

RMSNorm:   y = x / √(mean(x²) + ε) · γ

精简了什么?
  - 去掉均值减法 (x−mean) → 省一次归约
  - 去掉偏置项 β → 省一半参数

代价?精度损失 < 0.1%,几乎可以忽略。

为什么 LLaMA/Qwen/Mistral 全选 RMSNorm?
  28 层模型: 每层省 1 次归约 + 1 次减法 + 1 次加法 + 一半参数
  → 累计省 ~30% 归一化计算量 + 50% 归一化参数量
  → 在万亿 token 训练中,这省的时间和算力是天文数字

4. 硬件执行模型——为什么"看起来快"和"实际快"是两回事

4.1 一个反直觉的事实

为什么 [1024,1024] 的 MatMul (8.6G FLOPs) 可能比 [1024,1024] 的 Add (1M FLOPs) 快?

Add:
  计算: 1M 次加法 ≈ 0.001ms
  内存: 读 4MB + 写 4MB ≈ 0.1ms
  → 99% 时间在等内存!瓶颈在带宽。

MatMul:
  计算: 8.6G FLOPs ≈ 8ms
  内存: 读 16MB + 写 4MB ≈ 0.3ms
  → 96% 时间在计算。瓶颈在算力。

结论: 算得慢不可怕,等得慢才可怕。
      许多"轻量"算子(Add/ReLU/Norm)反而是最慢的——
      因为它们几乎不做计算,纯在搬数据。

4.2 Roofline Model——判断瓶颈的神器

                    算力天花板 (TFLOPS)
                    ┃
     计算密集区      ┃        ← MatMul, 大Conv, Prefill
         ╲          ┃
          ╲         ┃
  ─────────╲────────┃──────  ← 内存带宽天花板
             ╲       ┃
   内存密集区  ╲      ┃        ← Add, ReLU, RMSNorm, Decode
                ╲    ┃
                 ────┸────→ 计算强度 (FLOPs/Byte)

计算强度 = 总FLOPs / 总内存访问字节数

A100 roofline 拐点: ~150 FLOPs/Byte
  < 150: memory-bound → 优化布局/融合/压缩
  > 150: compute-bound → 优化算法/更大batch/TensorCore

Qwen3 decode GEMM (batch=1): ~0.5 FLOPs/Byte → 严重 memory-bound!
Qwen3 prefill GEMM (batch=512): ~150 FLOPs/Byte → compute-bound!

4.3 SIMD——"一次打包处理 16 个"

标量: for i in range(N): c[i] = a[i] + b[i]
SIMD: c[0:16] = a[0:16] + b[0:16]  ← 一条指令同时处理 16 个数!

不同架构的 SIMD 宽度:
  ARM NEON:     4 个 FP32/指令
  x86 AVX2:     8 个 FP32/指令
  x86 AVX-512:  16 个 FP32/指令
  CUDA warp:    32 线程同时执行

ncnn 的做法: 每个算子写 4 个版本
  - 通用 C++ (fallback,任何 CPU 都能跑)
  - ARM NEON (手机,4路并行)
  - x86 AVX2  (PC,8路并行)
  - Vulkan shader (跨平台 GPU)

4.4 内存层级——为什么"把数据放近一点"如此重要

CPU 内存层级 (从近到远,从快到慢):
  L1 Cache:   ~1ns,   32KB/core    — 寄存器级速度
  L2 Cache:   ~4ns,   256KB/core   — 核心专属
  L3 Cache:   ~12ns,  16MB/共享    — 所有核心共享
  DDR RAM:    ~100ns, 16GB+        — 主内存
  SSD:        ~100μs, 512GB+       — 磁盘(慢 10 万倍!)

关键数据:
  从 RAM 读一个数的时间 ≈ 做 100 次 FP32 乘法的时间
  从 SSD 读一个数的时间 ≈ 做 100,000 次 FP32 乘法的时间

  → "少搬数据"比"少做计算"更重要(对于 memory-bound 算子)
  → Tiling(分块)的本质:让数据在更快的层级中被反复使用

5. 优化器与学习率调度(训练视角)

5.1 从 SGD 到 AdamW

SGD:      W = W − lr·∇W                          (最简单)
Momentum: v = μ·v + ∇W; W = W − lr·v             (累积历史梯度,减少震荡)
Adam:     m = β₁·m + (1−β₁)·∇W                   (一阶矩估计)
          v = β₂·v + (1−β₂)·∇W²                  (二阶矩估计)
          W = W − lr·m̂/(√v̂+ε)                    (自适应学习率)
AdamW:    同 Adam,但权重衰减从梯度中解耦           (LLM 训练标准)

5.2 学习率调度

现代 LLM 训练标配: Warmup + Cosine

lr
 ^        ╱╲
 │       ╱  ╲           ← 余弦曲线
 │      ╱    ╲
 │     ╱      ╲
 │    ╱        ╲
 └───╱──────────╲───→ steps
    warmup  cosine decay

Warmup (前 2000~5000 steps):
  线性从小 lr 增长 → 防止初期梯度爆炸

Cosine Decay:
  按余弦曲线平滑降到 lr_min → 精细收敛

动手练习 & Jupyter 实战指引

打开 module-01-notebook.ipynb,按以下路线边读边跑:

练习 内容 Notebook Cell 验证目标
1. 手算梯度 Loss=(wx-3)^2 5步 SGD §4 (Cell 25-27) w 收敛到 1.5
2. 广播正反例 [3,1,4]+[1,5,4] §1.2 (Cell 8-11) 读懂每个 shape 变化
3. 激活函数 6条曲线 + 负值区对比 §2 (Cell 14-16) ReLU=0 vs Swish=-0.14
4. 手写归一化 BN/LN/RMSNorm vs PyTorch §3 (Cell 22-23) 误差 < 1e-6
5. 死亡 ReLU 全负输入,ReLU vs Swish §4.1 (Cell 29) ReLU DEAD, Swish alive
6. Roofline Q投影 GEMM, B=1~512 §5 (Cell 31-32) B=1 memory-bound
7. 优化器对比 SGD/Momentum/Adam 路径图 §6 (Cell 34) Adam 最快
8. BN 融合推导 Conv+BN 融合手算 纸笔练习 W'=αW, b_new=αb+β'

建议顺序:先读本文档建立认知 → 开 Notebook 跑对应 cell → 改参数观察变化 → 回文档巩固。


下一模块: Module 2: 构建算子