从 CS336 作业理解 Transformer 训练基本功

训练系统的基础不是某个单独概念,而是一组张量形状、内存和数值表示之间的约定。

Activation checkpointing

Checkpointing 常被概括为“时间换空间”。前向时不保存所有中间激活,反向时重新计算一部分,从而减少显存占用。

它不是保存参数矩阵的 checkpoint,而是保存计算图中必要的边界状态。

Linear 与 einsum

线性层可以写成:

... d_in, d_out d_in -> ... d_out

这说明输入最后一维是 d_in,权重提供 d_out 个输出方向,结果最后一维变成 d_out

理解这个 einsum,比记住某个框架 API 更可靠。

权重布局

PyTorch 的 Linear 权重常按 (out_features, in_features) 存。这会让初学者觉得和数学里的 xW 方向相反。

实际实现会根据内存布局和 GEMM 调用做选择。重要的是知道每一维代表输入还是输出,而不是死记矩阵在纸面上的方向。

BF16

BF16 适合 LLM 的关键原因是指数位和 FP32 一样多,动态范围大,但尾数更短。它牺牲精度,保留范围,因此比 FP16 更不容易溢出。

训练基础最终都要回到三个问题:张量形状是什么、内存保存什么、数值格式能不能承受当前计算。

知识补全:形状推理是训练系统的地基

Transformer 训练里的很多 bug 都不是公式错,而是形状理解错。batch、sequence、head、hidden、vocab、expert 这些维度在不同模块中不断重排。

例如 attention 常见形状是:

(batch, seq, hidden)
-> (batch, heads, seq, head_dim)

MLP 则常在 hidden 和 intermediate hidden 之间变换。MoE 又会多出 expert 维度和 token dispatch。

掌握形状推理后,einsum、reshape、transpose、contiguous、shard 都会更容易理解。

数值格式的直觉

FP16 尾数更多但指数范围小,容易溢出。BF16 尾数更少但范围大,因此在大模型中更稳。FP32 通常用于累加、优化器状态或敏感计算。

混合精度训练的核心不是“全都用低精度”,而是把不同计算放在合适精度上。

学习检查清单

读训练代码时,可以逐步标注:

  1. 每个张量的 shape。
  2. 每次 matmul 的输入输出维度。
  3. 哪些激活会被保存到反向。
  4. 哪些地方用了 checkpointing。
  5. 参数、梯度、优化器状态分别是什么 dtype。
  6. 是否存在隐式转置或 contiguous 拷贝。

这比单独背 Transformer 结构更接近工程实践。

Source Log Coverage

The excerpts below are generated from Renyuan_Log.md and preserve the original tables, code fences, ASCII diagrams, commands, links, and explanations with source line numbers.

SourceLinesTitle
2026-04-051551-1554毕业设计推进
2026-04-061555-1561非技术休整
2026-04-121871-1899CS336、MoE 与训练系统概念
2026-04-131900-2002Checkpoint、BF16 与 Linear 权重布局

Source Log: 2026-04-05

Source lines: Renyuan_Log.md:1551-1554


1551 |# 2026-04-05
1552 |
1553 |## 毕设毕设!
1554 |

Source Log: 2026-04-06

Source lines: Renyuan_Log.md:1555-1561


1555 |# 2026-04-06
1556 |
1557 |## 拯救计划很好看
1558 |我觉得可以和星际穿越媲美。
1559 |太空的浪漫很纯粹。
1560 |[Bilibili细节解析](https://www.bilibili.com/video/BV1oSQZBRE8j/?spm_id_from=333.337.search-card.all.click)
1561 |

Source Log: 2026-04-12

Source lines: Renyuan_Log.md:1871-1899


1871 |# 2026-04-12
1872 |
1873 |## 知识学习
1874 |
1875 |#### assignment1-basics/cs336_basics/trainer/utils.py
1876 |
1877 |#### assignment1-basics/cs336_basics/model/modules.py
1878 |
1879 |#### einsum()
1880 |
1881 |#### 常见显卡
1882 |
1883 |#### LLM参数量估算
1884 |
1885 |#### MoE模型
1886 |
1887 |#### PPO/GRPO/DPO
1888 |
1889 |```
1890 |# 定义前向传播:给定输入 x,输出线性变换后的结果。
1891 |def forward(self, x: torch.Tensor) -> torch.Tensor:
1892 |    # 用 einsum 实现矩阵乘法。
1893 |    # 这里的含义是:
1894 |    # 输入 x 的最后一维是 d_in,
1895 |    # 权重 weight 的形状是 (d_out, d_in),
1896 |    # 输出的最后一维就变成 d_out。
1897 |    return einsum(x, self.weight, '... d_in,  d_out d_in -> ... d_out')
1898 |```
1899 |

Source Log: 2026-04-13

Source lines: Renyuan_Log.md:1900-2002


1900 |# 2026-04-13
1901 |
1902 |## 实践
1903 |
1904 |1. Semantic Router 的流程
1905 |
1906 |这版实现入口在 llmrouter/src/router/semantic_router_strategy.py 和 llmrouter/src/evaluate/run_evaluation.py。
1907 |
1908 |**完整流程**
1909 |```
1910 |1. 从训练集读取已标注样本。
1911 |   代码会把 /tangboyan/llmrouter/data/unified/train.jsonl 和 /tangboyan/llmrouter/results/labels/routing_labels.jsonl 对齐,只保留 unified train 里的 query。对应 load_train_labeled_queries()。
1912 |2. 把每条训练样本变成 semantic text。
1913 |   默认就是 query 本身;如果开了 semantic-text-fields,会把 dataset/subject/difficulty 这类 metadata 也拼进去。对应 build_semantic_text()。
1914 |3. 按路由目标组织成 route。
1915 |   当前支持两种模式:
1916 |    - tiered:5 路分类,直接建 1.5b / 3b / 7b / 14b / 32b 五个 route。
1917 |    - 32b-override:不是五路平权,而是“默认强模型 + 若干小模型 override”。例如 14B 默认,7B override。对应 prepare_route_training_records()。
1918 |4. 用预训练 encoder 建索引。
1919 |   每个 route 里放一批 utterances,semantic-router 用 HuggingFaceEncoder 编码后建立向量索引。对应 build_routes() 和 build_semantic_router_from_train_records()。
1920 |5. 推理时对测试 query 编码并检索。
1921 |   对测试 query 用同一个 encoder 编码,检索 top-k 相似 utterances,然后按 route 聚合分数。对应 score_routes_for_vector()。
1922 |6. 决策。
1923 |    - 如果是普通 tiered 且没调阈值:直接取 router 返回的最佳 route。
1924 |    - 如果开了 threshold tuning:按阈值判断,没过阈值就 fallback 到默认大模型。
1925 |    - 如果是 32b-override:必须走 threshold 逻辑,否则代码直接报错。对应 choose_route_with_thresholds() 和 run_semantic_router_inference()。
1926 |7. 评测。
1927 |   路由结果不会真实再调模型,而是去查已经离线跑好的 benchmark 结果,看被路由到的模型在该题上是否答对,然后统计 accuracy / cost / latency。对应 simulate_strategy() 和 compute_all_metrics()。
1928 |```
1929 |
1930 |2. 测试用了什么数据集?
1931 |测试集是 unified_test,入口写在 llmrouter/src/evaluate/run_evaluation.py。
1932 |具体是:
1933 |- 测试切分文件:/tangboyan/llmrouter/data/unified/test.jsonl
1934 |- 训练切分文件:/tangboyan/llmrouter/data/unified/train.jsonl
1935 |- 评测时会读取 5 个模型在各数据集上的 benchmark 结果,再筛出 unified_test 里的 query
1936 |- 当前这套 v2_5tier 评测覆盖的数据集,从结果里看是:
1937 |    - agieval
1938 |    - ceval
1939 |    - cmath
1940 |    - cmmlu
1941 |    - gsm8k
1942 |    - logiqa2
1943 |当前统一测试集规模是 6449 条。
1944 |
1945 |3. Semantic Router 需要训练吗?
1946 |结论:不需要像 classifier 那样做参数训练。
1947 |- classifier:要训练一个新模型
1948 |- semantic-router:不训练新分类器参数,只是“拿预训练 embedding 模型 + 训练集样本建语义路由索引”
1949 |
1950 |**semantic-router**
1951 |```
1952 |- 需要一套已标注的训练样本,用来构建 route utterances
1953 |- 需要一个预训练 encoder,例如你现在用过的:
1954 |    - sentence-transformers/all-MiniLM-L6-v2
1955 |    - sentence-transformers/all-mpnet-base-v2
1956 |- 可选地需要做一次阈值调优,但这不是模型训练,只是用训练集里再切一小块验证集做搜索
1957 |```
1958 |
1959 |现在这版代码里,threshold tuning 也只是:
1960 |- 切一部分 unified_train
1961 |- 搜索阈值
1962 |- 选 accuracy/cost 最优点
1963 |不是 gradient finetune。
1964 |
1965 |## 知识学习
1966 |
1967 |#### 为什么不直接存“参数矩阵的转置”?
1968 |
1969 |Y = X x W^T
1970 |
1971 |你可能会问:既然都要转置,为什么不直接把 self.weight 定义成 (in_features, out_features)?
1972 |
1973 |答案是:为了计算效率(和历史习惯)。
1974 |逻辑直观:在 (out, in) 的存储方式下,weight[0](矩阵的第一行)直接对应于第一个输出神经元的所有权重。这在逻辑上非常清晰。
1975 |算子优化:底层硬件(如 NVIDIA GPU)在执行 Linear 算子时,针对这种存储方式做了深度优化。
1976 |
1977 |#### 常见数据类型详解
1978 |
1979 |通过浮点数的三个组成部分来理解它们:
1980 |符号位(Sign)、指数位(Exponent,决定范围)和尾数位(Fraction/Mantissa,决定精度)。
1981 |
1982 |#### FP32 (Full Precision / Single Precision)
1983 |结构: 1位符号,8位指数,23位尾数。
1984 |特点: 精度极高,数值范围广。
1985 |LLM 中的角色: 曾经是标准。但在如今的 LLM 训练中,它通常只作为“主权重(Master Weights)”存在,用来在优化器更新时保持微小的梯度变化。
1986 |
1987 |#### FP16 (Half Precision)
1988 |结构: 1位符号,5位指数,10位尾数。
1989 |优点: 内存占用减半,计算速度极快。
1990 |缺点: 数值范围窄(最大约 65504)。在训练 LLM 时,极易产生“梯度溢出(Overflow)”或“下溢(Underflow)”,导致训练崩溃。
1991 |对策: 需要使用混合精度训练(Mixed Precision Training)和损失缩放(Loss Scaling)。
1992 |
1993 |#### BF16 (Brain Floating Point 16) —— LLM 的宠儿
1994 |结构: **1**位符号,**8**位指数,**7**位尾数。
1995 |特点: 它是 Google 为了深度学习专门设计的。它的指数位与 FP32 一样长。
1996 |为什么好用: 它的精度(尾数)虽然不如 FP16,但它的数值范围(Range)和 FP32 完全一样。
1997 |意义: 在训练 LLM 时,你不需要担心梯度溢出,不需要搞复杂的 Loss Scaling。目前主流的大模型(Llama 3, GPT-4 等)基本都采用 BF16 进行预训练。
1998 |
1999 |#### einsum()
2000 |通过 einsum,即使输入是一个高维张量(例如 x 的形状是 (batch_size, L, d_model)),我们仍然可以通过 广播 规则来进行矩阵乘法(在这种情况下,广播会自动应用到批次维度和其他维度)。
2001 |所以,即使 x 不是二维矩阵,einsum 也能处理高维张量并正确地进行矩阵运算,保证维度匹配。
2002 |