A1 · Basics:把深度学习当实验科学

年份 2026(Fall 2026)—— 课程进行中,后续版本可能调整
分值 不计分(Ungraded)
截止 9/28(周一)· PDF 交到 Gradescope
算力 课程提供(H100 / Modal);自学者建议一块 GPU —— 模型很小(35M 级),但要跑很多次对照实验
配哪几讲 候选(机器按术语匹配,供参考):第2讲 §一、引言:深度学习是一门实验科学;第2讲 §七、实验效率:昂贵实验与廉价代理
官方页面 https://deep-learning-alchemy.github.io/#schedule

做什么

题面来自官方材料(本地原件在 _local/,官方链接见「材料」)。 编号是原件的编号(notebook 是 cell 序号、PDF 是页码),方便跟官方题面对号。 本项目不提供答案(D53)。

第 1 页

Assignment 1: Basics - Empirical Machine Learning(作业 1:基础 - 经验机器学习)

CS 312 · 不计分

Instructions(说明)

本作业不计分,但完成作业将显著帮助你在 quiz 和 tutorial 中获得部分分数。请在课程安排中的截止时间前,向 Gradescope 提交一份 PDF writeup。

对于每道题,你的交付物分两部分:一组你在探究讲义中描述的现象时运行的 experiments,以及一段简短(约一段话)的、对你实验解释的综合。你对实验的描述应足够清楚(通过超参数变化、代码 diff 等某种组合),以至于别人对你运行了什么几乎没有解释空间。记住,你可以在 quiz 中引用你的作业来论证答案,所以你的 writeup 必须让我们能直接核查:你在 quiz 中引用的实验确实存在,并且确实做了你声称它做的事。

在最后一题中,你可以开发一个有趣的 outcome prediction 问题,类似我们的 quiz:你需要提交一段来自我们代码库的简短代码 diff(少于 100 行)和一个预测任务,形式为 multiple choice 或数值预测任务。如果我们选择以任何方式(包括某种修改形式)在实际 quiz 中使用你提交的 quiz 问题,你将获得相当于总成绩 1% 的 bonus points;如果该题很难(我们可能粗略定义为 chance-corrected cross entropy $-\log p_{\text{students correct}}/\log K \ge 0.5$,针对 $k$-way multiple choice),再额外获得 1%。如果你希望有资格获得 bonus,请在 Gradescope 中清楚标注你的题目。

Overview(概览)

Empirical machine learning and our model organism(经验机器学习与我们的 model organism)

在本作业中,你将获得 empirical machine learning 的核心技能(高效运行 experiments、得出稳健结论、调参与调试 models),并成为我们将用作“model organism”的小规模 language model 的专家。贯穿本课程,我们将通过对这个基础模型做小改动来研究 deep learning,因此对这个 baseline 的行为有良好直觉很重要。

我们将围绕 4 个 sections、7 个 core problems 和一个 final optional problem 组织本作业,对应你在做 empirical machine learning 和 deep learning research 时经常问自己的问题。

这些是 fundamental questions,即使只掌握这些问题也会让你成为更有效的研究者。我们认为学习这些直觉和技能的最佳方式是通过 immersion:运行数百个 experiments 并内化结果,因此预计每个 section 需要 2–4 小时 active human time,总共应该有大约 170 次 d8 或更小规模的 model training runs。为帮助你分配时间,我们在每个 experiment subsection 中给出了建议的“number of experiments”。

第 2 页

Our model. 我们课程的核心是一个刻意做小但标准的 language model(课程 repository)。它小到你可以运行许多 experiments,但又足够真实,使得失败、tradeoffs 和 scaling behavior 与大语言模型相似。

Logging. 代码库自然日志到 weights and biases,并且代码库有 hooks 来存储许多有用的统计量(如 gradient norms)供你的 runs 使用。作业后面会要求你查看并理解这些统计量,所以你应确保你的 runs 正确 log 到 w&b,并且不要删除你的统计量。

Objective. 模型按 next-token prediction 训练。给定 tokenized sequence $x_1,\ldots,x_{1024}$,它从 prefix $x_{\le t}$ 预测每个 next token $x_{t+1}$。training loss 是 batch 中所有预测位置的平均 cross-entropy,validation loss 是在 held-out sequences 上的同一量。默认 run 使用 600,000 条 training sequences,即 614.4M training tokens。

Optimization. 标准 recipe 使用 AdamW,$(\beta_1,\beta_2)=(0.9,0.95)$,peak learning rate 0.003,1% linear warmup,以及 linear decay to zero。batch size 为 64 sequences,因此每个 optimizer step 看到 65,536 tokens。Weight decay 为 0.1,embeddings、normalization parameters 和 biases 不参与 decay,gradients 被 clip 到 norm 1.0。Dropout 默认关闭。默认 run 在单张 H100 上训练约 10–15 分钟。

Architecture. 默认模型是 d8,一个 Llama-style decoder-only Transformer,具有 8 个 pre-norm blocks、hidden size 512、8 个 attention heads、rotary position embeddings、RMSNorm、query-key normalization 和 SwiGLU MLP。它使用 4096-token vocabulary、context length 1024、untied input/output embeddings,约有 35M parameters。内置 model family 通过改变 depth 从 d4 到 d18;width、MLP size、head layout、vocabulary size、context length 及相关细节在这个 ladder 中固定,但可通过 custom model configuration 更改。

What to take away from this assignment(从本作业中应带走什么)

我们的 model organism d8 是一种非常特定类型的 deep learning model,无论是在其 setting(language modeling)还是 scale($<35$M parameters)上。d8 的 lessons 远非 universal,我们经常会发现 d8 上的现象与已发表结果不一致。因此,我们必须非常小心从 d8 中能带走什么!

我们要求你掌握 d8,有时甚至记住现象和 losses,因为我们认为对 model organism 的强掌握是有用的,即使现象本身不会迁移。

我们认为你可以而且应该从这些 assignments 和课程中带走两件具体的事:1). 对于你自己的领域,你应该像在本课程中一样努力掌握那里的现象,应用同样的思考过程和技术;2). 当你没有其他信息来源时,你可以把本课程给你的直觉当作非常粗略的 prior。

Running Experiments Well(做好实验)

Empirical machine learning 的核心就是 experimentation 和 experiment design。我们如何花费有限的资源(实现 experiments 和运行它们的 compute),并得到可靠的结论?

我们将练习一些 experiment design 的技能,并培养更好的判断力:如何 downscale experiments(以及知道何时不应信任 downscaled experiments)。做 experiment design 有很多好方法,但我们会先从教学团队的一套有明确观点的 principles 开始引导你。

第 3 页

AI tool use(AI 工具使用)

你可以自由使用任何你选择的 AI assistance。本作业不计分,而要在 quizzes 中取得好成绩,需要你完全理解自己的 experiments 和 results。最后一题鼓励你想出新颖有趣的东西,可能启发教学团队制作新的 quiz question。因此,对于最后一题,尝试与 AI 给出的默认答案不同的东西符合你的最佳利益。

Starter code and instructions(起始代码与说明)

克隆课程 repository,并用 uv sync 安装其 pinned dependencies。在 utils.py 中配置你的 username 和其他 execution environment 细节。每道题的标准 experiment code 记录在 experiments/a1_basics/p*_*.py 中;入口点是 launch_training_jobs(RUNS)。

第 4 页

Assignment 1: Basics - Empirical Machine Learning

Hyperparameters

Hyperparameter(例如 learning rate 或 weight decay)对模型性能有极大影响。下面的 sweep 说明了调参如何改变某项干预看起来能带来的收益。但 hyperparameter 同时也非常难处理 —— 就其本质而言,它们正是我们至今还无法可靠地自动化处理的那些参数;通常还需要人的判断,来决定什么时候(以及哪些)hyperparameter 需要调。

由于我们从语言建模(language modeling)入手,所以存在一些“基础” hyperparameter。下面给出一个较完整的清单,按 hyperparameter 类型分类。

Optimizer

Hyperparameter 默认值 说明
learning_rate 0.003 peak learning rate(峰值学习率)
optimizer_name adamw adamw 或 sgd
beta1 0.9 Adam $\beta_1$(momentum)
beta2 0.95 Adam $\beta_2$
lr_schedule linear linear、cos、constant、wsd(例如 wsd0.2:先 stable,然后在最后 20% 内做 linear decay)
warmup_percent 0.01 线性 warmup,以占总步数的比例表示
weight_decay 0.1 masked:对 embeddings、norms、biases 不做 decay
grad_norm 1.0 梯度裁剪阈值;设为 None 即关闭

Data

Hyperparameter 默认值 说明
num_train_sequences 600,000 训练时长(training horizon);每条序列 1024 tokens
num_epochs 1.0 允许小数;大于 1 表示重复使用数据
batch_size 64 每个 optimizer step 的序列数
context length 1024 由预处理好的 dataset 固定
data_seed 42 训练序列的 shuffle 顺序

Model

Hyperparameter 默认值 说明
depth (d4–d18) d8 内置阶梯:$\text{layers}=\text{depth}$、$\text{hidden}=64\cdot\text{depth}$、$\text{heads}=\text{hidden}/64$、$\text{MLP}=3.5\times\text{hidden}$、$\text{head\_dim}=64$、$\text{vocab\_size}=4096$、context length 1024。自定义 LMConfig 可以覆盖这些选择。
qk_norm true 在 attention 点积之前对 queries 和 keys 做 normalize
tie_word_embeddings false 为 true 时共享输入与输出 embedding 的权重
model_seed 42 初始化时的随机性
dropout 0.0 attention 与 residual 分支共用的 dropout 概率

Systems

Hyperparameter 默认值 说明
num_micro_batches 1 每个 optimizer step 的梯度累积块数;通常只在 batch size 大于 256 时才需要
deterministic false 在可能的情况下要求 PyTorch 具有确定性行为
precision mp mp、bf16 或 fp32

一个关键任务是 hyperparameter tuning,也就是选出尽可能最好的 hyperparameter 组合。这往往相当复杂,因为 hyperparameter 空间维度很高,而且最优 hyperparameter 会随设定发生微妙变化。很多论文就是因为 baseline hyperparameter 选择上的细微错误而失效的。别让这种事发生在你身上!

有几点关键思路要记住。第一,有些参数必须重新调,而另一些通常设成粗略的默认值即可。第二,当我们重新调某个 hyperparameter 时,我们想测试一个

第 5 页

……“合理的范围”,它有很大机会包含最优点。在很多情况下,这要用到 log 空间上的网格,也就是测试 $\mathrm{base\_param}\times b^{i}$,其中 $b$ 是任意底数,$i$ 是取整数值的搜索范围。你可以在最近的一篇 StepFun 论文里看到可视化示例(见此处)。

(图注:Houyi et al (Step Law) 的 hyperparameter loss landscape 形状图显示,当 learning rate 以 log 单位放置时,landscape 呈现出良好的凸形。)

最后,许多 hyperparameter 之间会相互 co-var(协同变化),你不应该只调其中一个或另一个,而应该把两者放在一起调。下面我们用一些实际例子来看看这些想法到底是什么意思。

Problem 1: Warmup - hyperparameter sweeps and optimization(约 50 次 run)

首先,我们来做一些基础的 hyperparameter optimization。你的目标是:拿标准的 d8 training recipe,建立一个好的心智模型,理解 hyperparameter 如何影响模型性能(以及最优 hyperparameter 之间如何 co-vary)。本题全程采用 compute-matched 设定,所以你必须保持 $\mathrm{num\_params}\times\mathrm{num\_total\_tokens}$ 不变。

第 6 页

在第一份作业中,我们会在每一节里给出一道示例 quiz 题,帮助你了解你被期望能够完成的是哪一类预测任务。Quiz 题分为 3 个难度:easy、medium、hard。easy 几乎不要求你在作业内容之外做泛化(如果你真的照着我们的要求去 run,并且记住 run 的结果,就应该能答对);而 hard 题则更加 out of distribution,我们只预期做过一些探索的同学,能够通过引用自己的作业结果稳定拿到部分分数。

下面是一道“easy”题的示例,它考查你在 part b 中的发现。

Example question

Difficulty: Easy

我们想为 d8 配置找到更好的 hyperparameter。为了这个目标,我们打算把 batch size 提高 2 倍,同时相应调整我们的 LR。这个实验有 3 个可选方案。

--- a/experiments/a1_basics/p1_hyperparameters.py
+++ b/experiments/a1_basics/p1_hyperparameters.py
@@
-RUNS = [
-    TrainConfig(),
-]
+RUNS = [
+    TrainConfig(
+        learning_rate=learning_rate,
+        batch_size=batch_size,
+    )
+    for learning_rate in [0.001, 0.003, 0.009]
+    for batch_size in [64, 128]
+]

(原 PDF 的缩进缺失,已按 Python 语法补回。)

考虑这个网格中的以下三种配置。

请把这三个配置按 terminal loss 从低到高排序,每个框里填一个字母。

Prediction: ___ < ___ < ___

第 7 页

下面是一道更难的示例题,它更着重考查你对 hyperparameter 的理解能否泛化。

Example question

Difficulty: Medium

从标准的 d8 recipe 出发,把 peak learning rate 提高到 0.009,weight decay 提高到 1.0。其他所有设置保持标准,包括 600,000 条序列的训练预算。

--- a/experiments/a1_basics/p1_hyperparameters.py
+++ b/experiments/a1_basics/p1_hyperparameters.py
@@
+RUNS = [
+    TrainConfig(learning_rate=0.009, weight_decay=1.0,
+        lr_schedule=s, warmup_percent=w)
+    for s, w in [("wsd0.2", 0.0), ("wsd0.2", 0.2), ("linear", 0.1)]
+]

(原 PDF 的缩进缺失,已按 Python 语法补回。)

请把 A–C 按 final validation loss 从最低(最好)到最高(最差)排序。

Run lr_schedule warmup_percent
A wsd0.2 0.0
B wsd0.2 0.2
C linear 0.1

Prediction: ___ < ___ < ___

第 8 页

Scaling

可预测的计算回报意味着,scale(在我们的模型规模和数据量上)是运行实验时需要选择的一个特别重要的参数。理解正确的 scale 概念,以及你的模型如何 scaling,对有效的 empirical machine learning 至关重要。

Background

在许多情况下,如果我们正确地选择坐标轴,就会看到性能随 compute 变化的高度规律性趋势。

记录的 d4–d9 pilot 拟合(左图)显示出合理的 scaling。

在许多情况下,这可以通过幂律很好地建模($L = \left(\frac{1}{C}\right)^\alpha + \epsilon$,其中 $C$ 是 scaling 轴,$\alpha$ 是斜率,$\epsilon$ 是不可约误差),不过一般而言,我们会预期它呈 S 形(sigmoidal),随着 scale 趋于无穷,从随机猜测 / 初始化插值到不可约误差。

在这种参数化拟合下,我们可以预测计算规模远大于训练运行的模型行为。我们可以把 testing problem 看作:给定固定的 scaling recipe(即当我们改变 x 轴时如何改变模型),做出这些预测;而把 engineering problem 看作:我们如何定义 scaling recipe 本身。

事实证明,这两个问题都很难,我们不能把 scaling 视为理所当然。有很多 scaling 趋势误导我们的情况,尤其是当我们没有谨慎设计 scaling recipe 时。

即使我们的 scaling law 拟合看起来不错,跨许多个数量级的外推也可能相当棘手;而且事实证明,scaling law 的准确性既是一个技术上有挑战性的问题,也是一个经济上极其重要的问题。下面是一些说明这件事为何可能很棘手的例子。

在 “Scaling Laws for Neural Language Models” 中,Kaplan 等人发现了一个高精度的 scaling law(见左图),描述模型性能如何依赖于 FLOPs 和模型规模。然而后来,在 “Training Compute-Optimal Large Language Models” 中,Hoffmann 等人发现了这些 scaling 外推中的细微问题,并表明相对于 Kaplan 等人的估计,小得多的模型才是 compute optimal。

第 9 页

Kaplan 等人原始 scaling laws 与 Hoffmann 等人的 “Chinchilla” laws 之间的 scaling law 分歧。

作为一个更受控的近期例子,Marin 项目训练开放语言模型。在最近的一篇博客文章(https://openathena.ai/blog/delphi/)中,Marin 研究人员讨论了他们的失败 scaling 外推:他们发现,即使到某个点为止拟合近乎完美,也可能由于训练不稳定性而开始迅速发散。

Marin 项目的失败 scaling law。scaling law 在 $10^{21}$ 之前看起来近乎完美,但之后很快就急剧发散。

预测依赖 scale 的性能

让我们先为比较两种方法的 scaling 趋势时可能发生的 3 种不同情况建立直觉。

  1. 线性、固定斜率:两种方法共享同一斜率,因此(在对数空间中)收益与 scale 无关。
  2. 线性、变化斜率:两种方法随 scale 变化,但以一种可预测的、共享的线性方式变化,使得交叉点的估计成为可能。
  3. 非线性:两种方法可能看起来是线性的,但存在非线性偏差,使得跨多个数量级的预测变得困难。

Problem 2:预测 scaling law 的可靠性(50 runs)

在这个问题中,我们将固定 scaling recipe,在 d4 到 d9 的 scale 之间训练模型,并让宽度随深度一起变化(所有整数深度,包括 d5 或 d9 这样的奇数深度,都可在代码库中使用;见上方的超参数表)。

第 10 页

(a) 作为起点,构建 4 张跨 d4 到 d9 scale 的 scaling law 图,测试 d8 模型的以下变体:1). 其他超参数设为 d8 的 baseline model;2). constant LR schedule;3). linear LR schedule,dropout = 0.2;4). linear LR schedule,learning rate=0.03。在训练 d8 和 d9 之前,先预注册你的 scaling 预测,然后运行 d8 和 d9。对于一直延伸到 d20 的远距离 scale,你最好的预测是什么?(20 runs)

文件:experiments/a1_basics/p2_scaling_law_reliability.py

RUNS = []
seen_configs = set()
for depth in range(4, 10):
    for learning_rate, lr_schedule, dropout in [
        (0.003, "linear", 0.0),
        (0.003, "constant", 0.0),
        (0.003, "linear", 0.2),
        (0.03, "linear", 0.0),
    ]:
        config_key = (depth, learning_rate, lr_schedule, dropout)
        if config_key in seen_configs:
            continue
        seen_configs.add(config_key)
        RUNS.append(
            TrainConfig(
                model_config=depth_model_config(depth),
                learning_rate=learning_rate,
                lr_schedule=lr_schedule,
                dropout=dropout,
            )
        )

(b) 我们现在想寻找能够改变斜率、但不破坏有用 scaling 趋势的干预。探索我们第 1 节中的超参数集合,并同时考虑模型规模 scaling 和数据 scaling。找出 scaling law 斜率发生变化、但仍处于线性区域的新情况。(10 runs)

(c) 最后,让我们彻底打破 scaling law。在我们的超参数空间内寻找会导致强烈偏离幂律函数形式的干预(可能是激进的做法)。哪些类型的干预似乎真的会打破 scaling laws?这发生在什么 compute regime 和 scaling 轴下?(20 runs)

我们现在已经看到了全部 3 种情况的例子,因此应当认识到:scaling laws 确实经常出现,但我们不应把它们视为理所当然。在 quiz 中,我们将评估你识别哪些情况预期会 scaling、哪些不会的能力。与任何 scaling 预测一样,我们会提供一张初步的 pilot scaling 图,并问你是否信任朴素的 scaling 投影。

示例题

Difficulty: Hard

Note: 当前版本的代码库不容易支持上下文长度随 scale 变化,因此我们不提供 diff,也不希望你亲自运行这个实验(即使你有算力)。把这个题目看作一个示例,用来说明我们希望你能够完成哪类泛化。

第 11 页

我们比较一个固定上下文的 recipe 与一个上下文随深度增长的 recipe。两者都使用相同的 39.3216M-token 训练数据,重复 15 次,即 589.824M training tokens。两者都使用 LR 0.003、linear decay、1% warmup,以及每个 optimizer step 61,440 tokens。Gradient accumulation 用于保持 global batch size 固定,validation 始终使用相同的 1024-token 序列。Pilot 深度为 d4/d5/d6;held-out endpoint 为 d10。

以下测得的 pilots 用 $L = a C^{-\alpha} + \varepsilon$ 拟合,其中 $C$ 是相对于相同 token horizon 下 d8 的 $6ND$ body-compute proxy:

图元素 内容
横轴 Depth(width = $64 \times$ depth):4, 5, 6, 8, 10
纵轴 Validation loss:3.50, 3.75, 4.00, 4.25, 4.50, 4.75
图例 Context 1024;Context grows with depth

Pilot runs 在 d4、d5 和 d6。虚线将拟合曲线外推到 d10。

拟合曲线预测 d10 gap(scaled 减去 fixed context)为 +0.0550 nats。作为额外背景,你可以查看 d6 run 的 validation loss 曲线。

图元素 内容
横轴 Epoch:0, 2, 4, 6, 8, 10, 12, 14
纵轴 Validation loss:3.4, 3.5, 3.6, 3.7, 3.8, 3.9
图例 Fixed context;Scaled context

d6 pilots 的 validation 曲线。

我们现在在 d10 运行两个 recipes。预测 validation-loss gap $\Delta = L_{\text{scaled}} - L_{\text{fixed}}$(单位为 nats),并解释你是否信任 pilot 外推。

第 12 页

Engineering scaling(工程化缩放)

最后,我们用一点说明性讨论来结束本节:什么是“scaling recipe”(缩放配方)。到目前为止,我们假设已经有一族在计算规模上不同的模型,并测试它们是否遵循 scaling law。但是,我们应该如何沿着 scaling ladder 选择这些模型?这就是寻找 scaling recipe 的问题。

作为热身,让我们先写下一个非常基础的 naive scaling recipe。以标准 d8 run($C=1$)为锚点,它让四个量同时随 compute multiplier $C$ 变化:tokens 按 $\sqrt{C}$ 增长,depth 和 width 各自按 $C^{1/6}$ 增长(保持该 ladder 固定的 aspect ratio,因此参数量 $N \propto \text{depth} \cdot \text{width}^2$ 按 $\sqrt{C}$ 增长),而 peak learning rate 按 $C^{-1/6}$ 缩小。这是一个朴素的 “Chinchilla” 风格 recipe:它在 parameters 和 tokens 之间均匀分配 compute,同时让 learning rate 随 width 反向缩放。具体来说:

Quantity Scaling $C = 1/64$ $C = 1$ $C = 64$
model depth $\propto C^{1/6}$ d4 d8 d16
width $64 \cdot \text{depth}$ 256 512 1024
training tokens $\propto \sqrt{C}$ 76.8M 614.4M 4915.2M
peak learning rate $\propto C^{-1/6}$ 0.006 0.003 0.0015

当然,你可以提出任何你喜欢的 recipe,但有一个重要约束:你的 recipe 必须满足 FLOPs 计算等式

$$\text{FLOPs} \approx 6 N D,$$

$$N \approx 14.5 \cdot \text{depth} \cdot \text{width}^2,$$

其中 $D$ 是 training tokens 的数量,$N$ 是 non-embedding parameter count(attention 每层 $4\,\text{width}^2$,加上 gated MLP 的 $3 \times 3.5\,\text{width}^2$;在 d8 时得到 $N \approx 30.4\text{M}$,与参数表一致)。因子 6 计算的是每个 parameter 每个 token 的前向和反向矩阵乘法。

如果你想要这个等式的推导,应该看看 CS336 的 assignments / lectures,它们详细讲了这些想法。对于任何深度神经网络,你都可以遵循类似过程,推导出作为超参数函数的 FLOPs 成本。注意,任何 scaling law 上真正的 x 轴很可能应该是训练模型的真实成本(通常以美元计),但为了本次作业,我们会忽略这种复杂性。后续作业中我们会反复回到 scaling recipe 这个想法,因为我们正在为不同 optimizer 和 architectures 找出合适的 scaling recipe。

Stochasticity(随机性)

当我们在小规模上运行实验时,我们常常只看到自己所做的干预带来的很小改进或变化。这些变化什么时候显著,什么时候只是噪声?Run-to-run variation 是机器学习中的常态,有些设置(比如 reinforcement learning)比其他设置严重得多。每当我们开始在一个领域工作时,都应该对从 noise floor 到显著差异之间 loss 或 accuracy 的数量级差异有良好的直觉。

在标准的深度神经网络训练运行中,variability 的来源有哪些?有三个(取决于你怎么数,也许有四个)variability 来源。

第 13 页

Chaotic training 本身不是随机性的来源,但它是随机性的主要贡献者,因为它会把任何已有的、原本很小的随机性来源(比如 hardware nondeterminism)放大成大的、宏观可观察的随机性来源。

你应该对上述每个来源(以及它们的总和)对模型 loss 和 training trajectory 的影响有多大有一种直觉。理解这一点不仅重要,因为它能让你判断你的方法何时带来了改进,也能帮助你识别那些你本来不期望仅凭偶然就会看到的异常训练现象。

作为热身,让我们先测量手头所有的 variation 来源。

Problem 3: Measuring variation(测量变异,25 runs)

取 d8 baseline,并加入 instrumentation,使上面前 3 个 stochasticity 成分分别单独变化或共同变化。对于 hardware nondeterminism,至少在两个不同机器(H100/A100)上运行代码,以可靠地诱发 nondeterminism 效应。为了测试固定 GPU 上的 reproducibility,下面的代码启用 PyTorch determinism,并定义两个相同的 reference runs。

文件:experiments/a1_basics/p3_measuring_variation.py

RUNS = [
    TrainConfig(
        deterministic=True,
        run_name_suffix="deterministic-reference-1",
    ),
    TrainConfig(
        deterministic=True,
        run_name_suffix="deterministic-reference-2",
    ),
]

(a) 首先,让所有 3 个自然 variation 来源都变化,观察 terminal losses 的分布以及 train loss curve 的 variability。loss variation 的大致数量级是多少?loss 分布的形状是什么?哪些因素会改变 training loss curve?(3 runs)

(b) 分别单独隔离这 3 个 variation 来源,看看当你分别变化每一个时,最终得到 full variation 的哪一部分。根据你的观察,你能得出什么结论?(6-12 runs)

(c) 探索 hyperparameter variations。某些 hyperparameter 的选择会显著改变 variability 吗?对于能力更强(更大/训练更久)且 loss 更低的模型,run-to-run variation 会如何变化?(10-16 runs)

这些实验应该让你对应该预期多少随机性有感觉。现在我们将探索第四个“variation 来源”:随机性在训练过程中如何放大。Chaotic training dynamics 是一组更深层的现象,我们之后会在 optimizer module 中回到它。不过现在,我们会做一些 micro-perturbations,看看它们如何在训练中放大。

第 14 页

Problem 4: Amplification of randomness(随机性的放大,15 runs)

我们希望追踪几乎无穷小的随机性来源在训练过程中如何放大。这个起始比较会编辑第一个 shuffled sequence 中的一个 token:在 training data 被加载并 shuffled 之后,row 0 的 token position 100 被改成 token ID 17。训练代码会断言这个 token 原本不是 17。

文件:experiments/a1_basics/p4_amplification.py

RUNS = [
    TrainConfig(deterministic=True),
    TrainConfig(deterministic=True, perturb_one_token=True),
]

(a) 在训练过程中探索改变单个数据点(添加、删除等),同时固定所有随机性来源。你在 terminal training loss 中得到多少 variation?(1-5 runs)

(b) 继续固定所有标准随机性来源。追踪 loss variation 随时间的变化,并理解在时刻 $T$ 的 perturbation 幅度,以及我们在 $T$ 之后走的步数,二者分别如何影响 run-to-run variation。(10-14 runs)

现在你应该能相当自如地判断,在我们的标准 d8 设置中,0.001 或 0.0001 的 loss 差异是否显著。一道 quiz 题可能会测试我们直觉的组合。

Example question(示例问题)

Difficulty: Medium

在成对的 model/data seeds 4 到 13 上训练标准 d8 recipe。用 (A) global batch size 16 代替 64,以及 (B) peak LR 0.009 代替 0.003,分别独立重复这个 ten-seed 实验。假设全部 30 runs 使用同一块 GPU。

哪一种独立改动会使 final validation loss 的 sample standard deviation 超过标准 recipe 的两倍?

  1. 只有更小的 batch。
  2. 只有更高的 learning rate。
  3. 两个改动都是。
  4. 两个改动都不是。

每个 sample SD 都使用以下公式在十个 final losses 上计算:

$$\sqrt{\frac{\sum_{i=1}^{n}(x_i-\mu)^2}{n-1}}.$$

--- a/experiments/a1_basics/p3_measuring_variation.py
+++ b/experiments/a1_basics/p3_measuring_variation.py
@@
+RUNS = [
+    TrainConfig(model_seed=seed, data_seed=seed, **changes)
+    for label, changes in [
+        ("standard", {}),
+        ("batch16", {"batch_size": 16}),
+        ("lr009", {"learning_rate": 0.009}),
+    ]
+    for seed in range(4, 14)
+]

第 15 页

在你的 writeup 中解释你的推理。

调试与修复你的模型

现代 machine learning 的挑战之一是:很难知道模型为什么不能正常工作,而修复模型更像一门玄学。模型可能在训练过程中突然发散,或者 loss 在长时间优化后进入平台期。发生这些情况时,我们必须有办法查看模型内部,以更好地了解正在发生什么。

诊断问题

我们有哪些工具可以更好地了解 neural network 内部发生了什么?

完整理解如何 debug 一个模型超出了本课程的范围(事实上,即使对专家来说,这也不是一个已经解决的问题)。不过,诊断的第一步是能够知道什么是正常的。为此,我们会跟踪许多关于训练的常见统计量,以努力理解模型中的“正常训练”看起来是什么样。

Problem 5: Loss curve augury(5 runs)

最基础且最有用的诊断项之一就是 training loss curve。loss curve 的精确形状,无论是在宏观层面(它如何平滑地上升和下降)还是微观层面(loss 的局部波动以及尖峰),都能告诉我们不少关于一次训练运行健康状况的信息。

(a)

查看你那些改变基本 optimizer 超参数组件(learning rate、通过 beta1 控制的 momentum、batch size、learning rate schedule)的 hyperparameter optimization runs,感受一下哪些因素会改变宏观 loss-curve 形状,以及如何改变。learning curve 通常会遵循某些可预测的形状吗?(0-5 runs)

(b)

现在我们已经理解了 loss curve 的宏观结构,接下来探索微观结构。哪些 hyperparameters 会增加或降低 loss curve 下降过程中的“平滑度”?你可以从显然的 batch size 开始,但这是唯一的一个吗?(0 runs)

(c)

回顾你在 Weights & Biases(W&B)中的所有旧 runs。过去是否有任何 runs 显著改变了 loss curve 的形状(无论是宏观还是微观)?尝试建立一个好的心智模型:哪些因素会强烈影响这些形状,以及好的训练与坏的训练分别是什么样子。(0 runs)

与本任务相关的一道 quiz 题可能如下所示。

第 16 页

示例问题

Difficulty: Easy

你是一次 training run 的 babysitter,正在监控你朋友的 d8 training run loss curve。过了一会儿,你看到以下 loss curve。

(图:High learning-rate loss curve。First 25% of the run; left: raw losses and a 51-step mean; right: an unsmoothed zoom。左图横轴为 Optimizer step(0–2000),纵轴为 Train loss(3–9),标题为 First quarter;右图横轴为 Optimizer step(0–800),纵轴为 Train loss(4–9),标题为 Early zoom。)

(a)

基于这条 loss curve,猜一下这次 training run 的 run configuration 是什么。

这里有 4 个选项,它们相对于 d8 的 diff 定义了各自的配置。

该 patch 定义了全部四个候选 runs。

--- a/experiments/a1_basics/p5_loss_curve_augury.py
+++ b/experiments/a1_basics/p5_loss_curve_augury.py
@@
-RUNS = [
-    TrainConfig(),
-]
+RUNS = [
+    TrainConfig(),
+    TrainConfig(batch_size=256),
+    TrainConfig(learning_rate=0.0003),
+    TrainConfig(learning_rate=0.009),
+]

(b)

现在你已经猜了 run configuration,试着猜一下最终 loss,看看相对于标准 d8 loss,这是否实际上会是一次好的 training run。使用相对于标准 run 的 validation-loss gap(2.926):

第 17 页

除了 loss curve 之外,我们还可以查看更高级的统计量,例如可以检查的 gradient statistics 和 activations。既然我们已经理解了改变模型训练动态的基本杠杆,就可以结合 parameters、activation 和 gradient norms 来检查它们。

对这些统计量进行仔细检查和推理,一直是 deep learning 的基础。Activation tracking 对一些最早的 deep learning 发现起到了关键作用,例如 Xavier initialization;而对 gradient norm magnitudes 的推理,则是开发 transformer 中 pre-norm layer norm 放置方式的关键。

Xavier initialization 和 Pre-norm 都是通过思考内部统计量而发展出来的。

更一般地说,activations 让我们更细粒度地了解 forward pass 中发生了什么,gradients 让我们很好地了解 backward pass,而 parameter statistics 可以告诉我们 invariances 和 training dynamics。所有这些都能比只看 loss curves 更好地揭示模型行为。

Problem 6: Activations and gradient norms(0-5 runs)

现在让我们跟踪不同 transformer 组件上的 activations 以及 gradient norms。与 loss 的情况不同,这是一个更加细粒度、高维的对象,你在探索应跟踪和测量哪些类型的统计量方面有显著更多的自由。我们会先给你一些基本建议,然后让你探索更丰富的空间。

(a)

取一次标准 d8 run 训练轨迹的开头、结尾和中间。可视化该时间点的 parameter、activation 和 gradient norm 的统计量。你会想查看 RMS norm(默认会被记录),但对于你可能进行的任何 runs,你也可能想查看其他统计量,例如 max 或 quantiles。在不同层的各种 transformer 操作(MLP、Attention)的开始和结束处这样做。建立一个心智模型:activations 和 gradients 如何在训练轨迹和深度上演变。(0 runs - 这些统计量默认会记录在 W&B 中)

(b)

回顾你过去的实验,查看你的 W&B logs。你应该能够查看不同层上已有的 training statistics。你的各种干预如何改变这些统计量?(0 runs)。

(c)

我们希望你现在能够操纵这些 gradient 和 activation 值。从第一部分中挑选几个核心统计量(例如 gradient/activations 的 l2 norm),并弄清楚如何用 hyperparameters 操纵它们。你能让 parameter、gradient 或 activation 统计量整体变大或变小吗?只在训练开始或结束时呢?(0-5 runs)

第 18 页

这些指标让我们能够识别异常的 training 情况。在本作业和 quiz 中,我们尚未引入那些会以复杂而微妙的方式破坏模型的 architecture 和 optimizer 变体。不过,我们仍然可以考你一些基本的破坏模型方式,你需要通过 diagnostics 来识别它们。

示例问题

Difficulty: Easy

给定以下第 1、3、6 层的 activation statistics traces。

层从 1 开始编号;该统计量是同一组八条 validation sequences 上 post-block residual stream 的 RMS,在固定 training steps 上采样。

(图:Residual-stream RMS;横轴为 Optimizer step(0–8000),纵轴为 $10^0$ 到 $10^6$;曲线为 Layer 1、Layer 3、Layer 6。)

你需要判断这次 training run 是来自一次健康的 run(例如标准 d8 run),还是来自一组完全疯狂的 hyperparameter 设置,后者由以下 diff 给出。

这种干预不是仅修改 TrainConfig 的变化。它通过编辑 experiment 文件来实现:添加一个自定义 metric logger,在选定的层上注册临时的 forward hooks。

--- a/experiments/a1_basics/p6_activation_gradient_norms.py
+++ b/experiments/a1_basics/p6_activation_gradient_norms.py
@@
+from metric_logging import AFTER_EVAL
+import torch
+
+def log_activation_rms(ctx):
+    input_ids = next(iter(ctx.val_batches["val"]))[:8]
+    input_ids = input_ids.to(next(ctx.model.parameters()).device)
+    stats, handles = {}, []
+    for layer_idx in [0, 2, 5]:
+        def hook(module, inputs, output, layer_idx=layer_idx):
+            stats[f"layer_{layer_idx + 1}_residual_rms"] = (
+                output.detach().float().square().mean().sqrt()
+            )
+        handles.append(ctx.model.model.layers[layer_idx].register_forward_hook(hook))
+    was_training = ctx.model.training
+    ctx.model.eval()
+    try:

第 19 页

with torch.no_grad():
    ctx.model(input_ids=input_ids)
finally:
    for handle in handles:
        handle.remove()
    if was_training:
        ctx.model.train()
    return stats

# Add MetricLogger(event=AFTER_EVAL, fn=log_activation_rms) to each run.

(原 PDF 的缩进缺失,此处已按语法补回;这段代码接续自上一页,try: 出现在上一页。)

在下面两个选项中选一个 —— d8 或 alternative。

Optional exploration

为了更广泛地探索本作业中的主题,请自己构造一个预测问题。这最后一个练习与 quiz 题目没有对应关系,但可以为 quiz 带来 bonus points。

Problem 7: Your own prediction problem (5-10 runs)

到这里你已经把本作业中的各种现象探索得比较透彻,也可能发现了意料之外或令人惊讶的现象。我们希望借助你的洞见,帮助做出更好、更有意思的 quiz。

在你的探索中请包含以下内容:

(a) 一份题目描述(question writeup),形式类似于本作业中的样例题目。答案应当可以基于某段代码的执行结果被客观地检验(理想情况下是选择题或数值预测)。

(b) 一份 code diff,以及能得出该题答案的可执行代码(例如打印出 loss)。code diff 应当尽量紧凑(<100 行代码)。

目标是出一道公平而新颖的题目:它的结果应当可以借助本作业的内容推理出来,同时考察超出所给样例的洞见。课程工作人员可能会把有意思的题目用在未来的 quiz 中。

材料

自查

这份作业和书的关系

配第 2 讲《基础:超参数、缩放律与随机性》、第 3–4 讲《优化 1》。 书里给了概念与推导,这份作业把它们变成“你亲手跑出来的曲线”。