附录 C — 交互实验索引
全书 15 个交互实验,都在 experiments/ 下,双击就能打开。
不联网、不需要服务器、不需要装任何东西——每个页面是一个自包含的 HTML, 数据已经嵌在页面里。
C.1 怎么用
三种打开方式:
- 直接双击
experiments/exp-1-1.html - 从正文点链接(HTML 版每一节都有入口)
- 在 PDF 里看静态快照(
assets/exp-static/,每个实验一张)
每个实验页都有三个部分:
- 控件(滑块、按钮)——改参数
- 画布——实时重算
- 结论框——告诉你”该看什么”
页面里的数字全是现算的,不是预先录好的动画。 所以你可以把参数拖到书里没写过的位置,看它怎么变。
PDF 装不下交互。
每个实验在 PDF 里配一张快照,选的是”最能说明问题的那一帧”, 不是初始帧。比如实验 6-2 的快照是 b=0.10、输出已经塌缩到 54% 的那一帧。
想看完整的,回到 HTML 版。
C.2 总表
| 编号 | 章 | 一句话 | 主要控件 | 数据来源 |
|---|---|---|---|---|
| 1-1 | 1 | 缩放因子与 softmax 饱和 | d_k、位置数、种子 | 现算 |
| 1-2 | 1 | 真实模型学到的注意力 | 层、头、是否缩放 | 预计算(42 万参数模型) |
| 1-3 | 1 | 改 Q,K,V,看注意力怎么变 | 九个数字、五个预设 | 现算(3 token) |
| 3-1 | 3 | 温度、top-k、top-p | 步数、\tau、k、p、顺序 | 真实模型分布 |
| 3-2 | 3 | 先截断还是先调温 | \tau、p、步数 | 真实模型分布 |
| 3-3 | 3 | KV 缓存省下了什么 | 生成长度、提示词长度 | 现算 |
| 4-1 | 4 | 逐层逐头看注意力图 | 层、头 | 真实权重 + 随机对照 |
| 4-2 | 4 | 消融实验室 | 点头屏蔽 | 预计算消融 |
| 5-1 | 5 | 前向过程 | t | 现算(700 点) |
| 5-2 | 5 | 反向过程:加不加噪声 | t、并排开关 | 预计算轨迹 |
| 5-3 | 5 | 噪声调度 | 调度、T、\beta 端点 | 现算 |
| 6-1 | 6 | 采样器对比 | 采样器、步数 | 现算(1D 高斯) |
| 6-2 | 6 | 偏差诊断 | 偏差 b、步数、\eta | 现算 |
| 7-1 | 7 | CFG 引导强度 | 类别、w | 预计算(每 w 600 点) |
| 8-1 | 8 | 绑定实验室 | 编码器、类别 | 预计算(每类 300 点) |
15 个实验里,7 个是”现算”、8 个是”预计算”。
区别在数据量:现算的页面只需要几十个参数; 预计算的需要真实模型的采样结果(几十万个浮点数),只能离线算好再嵌进去。
C.3 逐个说明
C.3.1 实验 1-1 缩放因子与 softmax 饱和
拖 d_k 从 4 到 512。
看三件事同时发生:logits 的分布变宽、softmax 变尖、梯度(最大概率处的斜率)塌向 0。
结论:\sqrt{d_k} 不是”让数值好看”,是”让 softmax 留在还能学的位置”。 d_k 小时看不出差别——这就是正文案例里”小模型上没事”的原因。
C.3.2 实验 1-2 真实模型学到的注意力
一个真实的 2 层 4 头模型(425,984 参数),在真实唐诗上训练后的注意力。
可以切层、切头,也可以切到”不缩放”的那个模型做对照。
看什么:正常的模型有清晰的局部模式(盯前一位、盯句首); 不缩放的模型注意力摊平,几乎没有结构。
数据来源:教师机离线算好,页面只做回放。
C.3.3 实验 1-3 改 Q,K,V,看注意力怎么变
3 个 token、2 维,九个数字全部可改。
五个预设:正文那个例子、只让自己可见、所有位置一样、制造一个尖锐分布、随机。
结论:注意力前向传播没有黑箱——改一个数,打分、softmax、输出立刻跟着变。 你可以亲手制造”注意力退化成平均”的那个 bug。
C.3.4 实验 3-1 采样实验室
四个滑块:生成到第几步、温度 \tau、top-k、top-p。
还有一个关键的开关:先截断后调温 / 先调温后截断。
结论:同样一组 (\tau,p),只换顺序,候选集能差几十倍。 这不是理论上的差别,是你会不会采到那个词。
C.3.5 实验 3-2 顺序陷阱
3-1 的”顺序”那一个维度,单独放大。
固定 \tau 和 p,只换先后,看候选集怎么变。
结论:top_p 和 temperature 不是可交换的操作。 很多实现出错就出在这个顺序上。
C.3.6 实验 3-3 KV 缓存省下了什么
每一格代表”某一步里某个位置被重新算了一次”。
拖生成长度 N 和提示词长度 P,看格子怎么被涂满。
结论:没有缓存时,第 i 步要重算前 i 个位置,总量是 O(N^2); 有缓存时是 O(N)。 提示词越长,省得越多。
C.3.7 实验 4-1 逐层逐头看注意力图
左边是训练好的模型,右边是随机初始化的同结构模型。
切层(0/1)、切头(H0–H3)。
结论:随机模型也会画出”看起来有结构”的图。 没有对照,你分不清”学到了”和”初始化碰巧”。
C.3.8 实验 4-2 消融实验室
点一个头把它屏蔽掉,看验证集 loss 和生成文本怎么变。
结论:有些头一屏蔽 loss 就涨,有些头屏蔽了几乎没变化。 “这个头很重要”必须用消融证明,不能看注意力图猜。
C.3.9 实验 5-1 前向过程实验室
700 个点,同一个 x_0、同一串噪声,拖 t 从 0 到 1000。
结论:双月分布逐渐溶成标准正态。 注意 t=300 时形状已经看不出来,但 \bar\alpha_t 还有 0.40—— “看起来是噪声”和”数学上是噪声”不是一回事。
C.3.10 实验 5-2 反向过程实验室
同一个模型、同一批初始噪声,只改”反向每一步加不加 \sigma_t z“。
并排回放两条轨迹。
结论:加噪声的轨迹每一步都在抖,不加的是一条平滑曲线。 两条轨迹的终点分布相同,路径完全不同—— 这就是 DDPM 和 DDIM 的关系。
C.3.11 实验 5-3 噪声调度实验室
改调度类型(线性/余弦)、步数 T、\beta 的起点和终点。
看 \bar\alpha_t 和 log-SNR 的形状。
红色区域是”信号已被破坏殆尽”的区间。
结论:线性调度在高噪声端浪费了很多步; 余弦调度把步子更多分给”信号还在”的区间。
C.3.12 实验 6-1 采样器对比实验室
同一个解析问题(1D 高斯 + 完美去噪器),比较 DDPM、DDIM、Heun。
拖步数,看精度随 NFE 怎么变。
结论:同样的 NFE 下,二阶的 Heun 明显好于一阶的 DDIM, DDIM 好于 DDPM。 “步数少”不等于”便宜”——要统一到 NFE。
C.3.13 实验 6-2 偏差诊断实验室
给完美去噪器注入可控偏差 b,看 DDIM 会不会发散。
结论和直觉相反:均匀偏差不会让 DDIM 发散,只会让输出塌缩到目标的 1-b 倍。 真正让 DDIM 崩的,是”误差随 t 变化”或”模型把误差放大”。
这一条推翻了正文初稿的一个假设——实验做出来才发现。
C.3.14 实验 7-1 CFG 引导强度实验室
选一个类(外月/内月),拖 w 从 0 到 40。
看两件事同时发生:类内越来越纯,样本越来越少、越来越散。
结论:准确率在 w=3 就饱和,之后它不再告诉你任何事; 标准差和越界率继续恶化。 “准确率”是一个会骗人的指标。
C.3.15 实验 8-1 绑定实验室
选编码器(均值池化/交叉注意力)、选类别(RR/RB/BR/BB)。
每个点是一个生成样本,画在”左点 y × 右点 y“平面上。
结论:均值池化在 RB 和 BR 上给出镜像对称的两团,各占一半—— 因为它拿到的条件向量完全相同。 交叉注意力只落在目标象限。
同一个词袋、两种顺序、同一个分布。
这不是”生成质量差”,是”条件里没有信息”。 看到镜像对称的两团,就是绑定失败的指纹。
C.4 三条推荐路线
C.4.1 六十分钟:只看”会骗人的指标”
| 时间 | 实验 | 得到什么 |
|---|---|---|
| 0–15 | 7-1 | 准确率饱和,标准差爆炸 |
| 15–30 | 8-1 | 0.55 不是”一半对”,是零信息 |
| 30–45 | 6-2 | 均匀偏差不会发散,只会塌缩 |
| 45–60 | 4-1 | 随机模型也有”结构” |
四个实验的共同点:都推翻了”看一个数就够”的直觉。
C.4.2 半天:按章走一遍
上午:1-1 → 1-3 → 3-1 → 3-2 → 3-3(上篇的机制)
下午:5-1 → 5-2 → 5-3 → 6-1 → 7-1(下篇的机制)
每个 20–30 分钟,够你把参数拖到书里没写过的位置。
C.4.3 一学期:把实验当作业的起点
每个实验页都可以改。 把”只读”变成”改写”,是这份材料真正的用法—— 附录 D 的作业就是按这个思路设计的。
C.5 自己改一个实验
每个实验页的结构都一样:
<h1>实验 X-Y 标题</h1>
<p class="sub">一句话说明</p>
<div class="row">
<div class="ctl"> ← 控件
<label>参数</label>
<input type="range" ...>
</div>
<canvas id="cv"></canvas> ← 画布
</div>
<div class="task">动手:...</div>
<script>
const D = {...}; ← 数据
function draw() {...} ← 重绘
</script>改三处就能做出一个新实验:
- 换数据:改
const D = {...} - 换算公式:改
draw()里的计算 - 换结论:改
.task里那段话
PDF 里的快照不会自动更新。
改完 HTML,要跑:
python3 qa/snap_experiments.py exp-8-1它用无头浏览器截图,按 qa/snap_experiments.py 里 SPECS 的尺寸和 URL 参数。 如果新实验的”最能说明问题的那一帧”不是默认帧, 在 SPECS 里给它加一个 URL 参数。
还有一个隐藏要求:页面加载完成后要设 document.body.dataset.qaReady = "1"。 这是给自动检查用的——没有它,截图脚本不知道什么时候算”画完了”。