这门课有点离谱,很多题没有唯一客观答案,还不对应视频原文。本人用了一些奇怪的方法才试出答案。

单元测验的答案都是准确的;期末考试看不到分数,答案由大模型生成,仅供参考。

二三联防

  1. 篮球23区域联防通常适用于哪种情况?
    对方投篮能力差或陷入低迷
  2. 在篮球比赛中2-3区域联防战术的核心目的是什么?
    保护篮下,限制对方内线得分
  3. 区域联防战术中,防守队员主要依据什么来判断自己的防守位置?
    球的位置
  4. 篮球2-3区域联防的优点包括哪些?
    压迫感强,给对方造成心理压力
    容易造成对方失误
    限制对方突破和中距离投篮
  5. ​篮球2-3联防的应用场景包括哪些?
    面对拥有身高和体型优势的内线球员时
    比赛关键时刻需要加强防守时

进攻二三联防

  1. 攻2-3联防时,哪种方式最能有效破解上线防守?
    切入并传球给空位球员
  2. 进攻2-3联防时,以下哪项是球员最需要避免的?
    忽视与队友之间的配合
  3. 当后卫发现低位防守队员补防时,应如何应对?
    观察场上形势,选择传球或突破
  4. ​进攻2-3联防时,如何利用对方的防守空隙?
    多传球,制造进攻方人数多于防守方的局面
  5. ​在进攻2-3联防时,以下哪些策略是有效的?
    利用外线投射能力强的球员吸引防守
    保持进攻节奏,避免被防守打乱
    加强内线的篮下进攻

身体素质(上)

  1. 坐姿臂屈伸动作是练习()非常有效的动作之一。
    肱三头肌
  2. 我们在做壶铃甩摆练习时,通过伸髋肌群发力将壶铃甩出,在髋关节完全打开时,壶铃应该甩至与()齐平高度。
    眼睛
  3. 篮球运动专项身体素质训练可以保证运动员掌握有难度的动作技术质量,提高我们的(),培养我们优良的作风和顽强的意志品质,增强篮球比赛的对抗性和观赏性。
    竞技水平
    延长运动寿命
    应变能力
    减少运动创伤
  4. 我们可以通过()动作来进行下肢力量训练。
    哑铃前蹲
    壶铃甩摆
    保加利亚分腿蹲
  5. 我们可以通过()动作来进行核心力量训练。
    仰卧卷腹抛接球
    仰卧卷腹
    坐姿卷腹
    仰卧负重卷腹

半场人盯人防守

  1. 人盯人防守战术中,防守队员主要盯防的是?
    各自负责的进攻球员
  2. ​在人盯人防守中,当对手运球突破时,防守球员应:
    跟随对手,尽量干扰其运球和突破
  3. 在人盯人防守中,防守队员应如何调整自己的防守位置?
    随时注意人、球、对手、篮圈的方位
  4. 在人盯人防守中,当防守无球队员时,防守队员应主要注意什么?
    对手的跑动方向
  5. 人盯人防守的基本要求包括哪些?
    人球兼顾
    盯人为主
    控制对手

进攻半场人盯人防守

  1. 进攻半场人盯人防守战术打法的基础是()之间的进攻配合。
    两三名队员
  2. 进攻半场人盯人防守的方法,应根据全队,特别是()的身体、技术条件情况来确定。
    中锋
  3. ‌进攻半场人盯人时要先确定进攻的(),选择适合队伍的进攻落位。常用的落位阵型有1-2-2、1-3-1、2-3等。
    区域与结构
  4. 进攻半场人盯人防守,常见的战术打法有:
    通过中锋打法
    移动进攻打法
    综合性进攻打法
  5. 进攻半场人盯人防守是由哪些配合来组织全队进攻战术的?
    突分
    策应
    掩护
    传切

身体素质(中)

  1. 三人直线快攻(往返)是一种(),将多种基础练习综合起来进行循环练习。
    高强度间歇训练
  2. ​运动员进行低强度、长时间不间断的训练方法,主要目标是提高()和发展基础有氧代谢能力。
    心肺功能
  3. 有氧耐力和无氧耐力是两种不同的体能素质,主要区别在于()。
    运动方式
    运动时间
    供能方式
    锻炼效果
  4. 下列哪几个项目属于柔韧练习。
    伸展运动
    坐位体前屈
  5. 灵敏素质是指在各种突变条件下,运动员()的能力,是复合素质的综合表现。
    随机应变
    转换动作
    改变体位

全场紧逼人盯人防守

  1. 全场紧逼人盯人防守时,防守队员应如何利用场地空间?
    分散在场地的各个角落
  2. 全场紧逼人盯人防守时,要求防守球员要有充分的体力与信心,()及其顽强拼搏作风。
    速度快、反应灵敏
  3. 在全场紧逼人盯人防守中,防守球员对持球者的逼抢主要是为了
    制造对手失误
  4. 全场紧逼人盯人防守的优势在于:
    使进攻方难以组织有效的进攻战术
    限制对手的传球和移动
    消耗防守队员体力
    制造对手失误,增加抢断机会
  5. ‍全场紧逼人盯人防守是以个人防守为基础,综合运用()等防守基础配合所组成的全队防守。
    夹击
    关门
    换防
    挤过

进攻全场紧逼人盯人防守

  1. 进攻全场紧逼人盯人防守时,以下哪项策略是有效的?
    减少个人运球,多利用团队配合
  2. 破解全场紧逼人盯人防守时,球员之间应该如何配合?
    保持适当的间距,以便传球和移动
  3. 在进攻全场紧逼人盯人防守时,接应队员积极跑动的正确做法是?
    快速而准确地移动,以制造进攻方人数多于防守方的局面
  4. 在进攻全场紧逼人盯人防守时,接应队员要把握()的配合原则,尽量减少传接球的失误,为全场进攻建立基础。
    横向跑动与纵向传接球
  5. 在进攻全场紧逼人盯人防守时,以下哪些策略是有效的?
    保持球员之间的间距,以便传球和移动
    快速转移球权,避免长时间持球

身体素质(下)

  1. 下列拉伸动作中,主要拉伸大腿后侧肌肉的动作是( )。
    腿后侧拉伸
  2. 做屈肘水平外展动作过程中,要求()肌群要放松,保持收腹挺胸。
    颈部
  3. 柔韧素质是指人()的伸展能力。
    肌肉
    关节活动幅度
    韧带
  4. 柔韧训练可分为()等练习方式。
    主动性
    被动性
    混合性
  5. 做弓箭步转体练习时,前腿屈膝形成前弓步,后腿跟随屈膝90°,同时手臂水平举后,前弓步腿的同侧手臂向后旋,带动()向后转直至达到最大幅度。
    头部
    上身

期末测试

  1. 在2-3联防时,外线防守队员应如何对待突破能力强的对手?
    保持距离,等待协防
  2. 进攻2-3联防时,哪种战术可以制造进攻球员多于防守球员的局面?
    Fast Break(快攻)
  3. 仰卧卷腹训练时,由()发力将上体卷起,同时双臂随身体向前伸,直至指尖能够触及到脚。
    腹部肌群
  4. 全场紧逼人盯人防守战术是与对方在( )展开全面激烈的争夺。
    全场
  5. 在全场紧逼人盯人防守中,如何有效限制对手的传球?
    全体球员收缩防守,减少传球空间
    切断传球路线,迫使对手失误
  6. 有氧耐力通常指运动员长时间进行低强度运动的能力,主要目标是提高()和发展()能力。
    基础有氧代谢
    心肺功能
  7. 进攻2-3联防时,以下哪些注意事项是正确的?
    保持球的移动,避免停滞不前
    外线球员应随时准备接球并投射
    球员之间应保持良好的沟通,协同作战
  8. 以下哪项是人盯人防守的注意要点?
    防守时以人为主,人球兼顾
    防守时应尽量靠近对手,不给其投篮或传球的空间
    防守无球队员时,主要任务是防止对手接球
  9. 进攻半场人盯人时要选择适合队伍的进攻落位,常用的落位阵型有()
    1-3-1
    2-1-2
    1-2-2
  10. 破解全场紧逼人盯人防守时,以下哪些做法有助于提升进攻效率?
    保持球员之间的默契和沟通
    快速而准确地做出进攻决策

由于 Transformer 本质上是位置无关的,因此通过正确设置 positional embedding 和 attention mask,我们可以把多个“序列”合并到一个序列里进行推理。当这些序列具有很长的公共前缀时,这种方法会比 batched inference 更高效。这种方法的具体用途不是本文的重点,此处不做展开。

图片出自 SpecInfer: Accelerating Generative Large Language Model Serving with Tree-based Speculative Inference and Verification

但是,Transformers 库的自带 API 并不支持传递 attention mask 矩阵,而只支持选择是否 mask 掉整个 token(这种 mask 是用来处理 padding 的)。一种常见的解决方法是直接修改模型的代码,比如 REST 的实现。显然,这种方法在需要测试多种模型时会很麻烦。

有没有更通用一点的方法呢?有的!通过阅读源码可以发现,当模型把 2d 的 attention mask(这个参数是从 forward 一路传过来的)转化为 4d 时,如果输入已经是 4d 的话,这个函数会直接返回输入。因此通过这个不在文档里的 API,就可以传递 attention mask 矩阵了。

代码大概是这样的:(注意传入的 mask 值应为 -inf/0 而非 0/1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

# Load model
model_name = "Qwen/Qwen2.5-0.5B-Instruct"
model = AutoModelForCausalLM.from_pretrained(model_name, device_map="auto")
tokenizer = AutoTokenizer.from_pretrained(model_name)

# Create test input
message = "You are a are not a"
message_tokenized = tokenizer.encode(message, return_tensors="pt").to(model.device)
assert message_tokenized.shape[-1] == 6

# Create position_ids and attention_mask according to the tree structure
position_ids = torch.Tensor([[0, 1, 2, 1, 2, 2]]).long().to(model.device)
attn_mask_bool = torch.Tensor([[[
[1, 0, 0, 0, 0, 0],
[1, 1, 0, 0, 0, 0],
[1, 1, 1, 0, 0, 0],
[1, 0, 0, 1, 0, 0],
[1, 0, 0, 1, 1, 0],
[1, 0, 0, 1, 0, 1],
]]]).bool()
attn_mask_float = torch.where(attn_mask_bool, 0.0, -torch.inf).to(model.device)
print("position_ids:", position_ids.shape)
print("attn_mask_float:", attn_mask_float.shape)

r'''
树的结构:
- are ----- a
/
You --- - not
\ /
- are ---
\
- a

如果实现正确,两个 "are" 和 "a" 的 logits 应该相同
'''

# Run model
with torch.no_grad():
outputs = model(
message_tokenized,
position_ids=position_ids,
attention_mask=attn_mask_float
)
logits = outputs.logits
print("logits:", logits.shape)
print(f"{torch.allclose(logits[0, 1, :], logits[0, 3, :])=}")
print(f"{torch.allclose(logits[0, 2, :], logits[0, 5, :])=}")

输出为:

1
2
3
4
5
position_ids: torch.Size([1, 6])
attn_mask_float: torch.Size([1, 1, 6, 6])
logits: torch.Size([1, 6, 151936])
torch.allclose(logits[0, 1, :], logits[0, 3, :])=True
torch.allclose(logits[0, 2, :], logits[0, 5, :])=True

这种方法的优点:

  • 对大部分 Transformer 模型即插即用
  • 不需要深入模型的实现细节

这种方法的缺点:

  • 不是官方支持的 API,随时可能失效
  • 不支持 Flash Attention 2

收录了一些博主喜欢的实用工具。本页面不定期更新。

计算

Qalculate!

功能非常全面且设计合理的 PC 端计算器。(高级计算器和编程语言之间并没有明确的区隔;由于它很重视 fuzzy parsing,所以我还是将其算作计算器)

支持的特性非常多,比如大量非初等函数、简单的符号计算、不确定度传播、单位转换等等。详见官方示例

出自 qalculate.github.io

IPython

博主在科学计算方面基本只会 Python 生态,会用 MATLAB 或 Mathematica 的人可以忽略此节。

Python 自带 REPL 的上位。支持保存历史记录、语法高亮以及用·exit 而非 exit() 退出等。其中一部分功能也添加到了 Python 3.13 的新 REPL 中。

IPython 还支持一些拓展语法。博主比较常用的有:

  • 按 Ctrl+R 可以搜索历史记录
  • 函数/类名后加 ? 可以查看其帮助文档
  • Tab 补全可以补全目录/文件名
  • 全局变量 _ 可以访问上一个返回值,Out[i] 可以返回第 i 个语句(块)的返回值

Plotly

比 Matplotlib 更现代的 Python 绘图库。这个库视 DataFrame 为一等公民,而且默认就能生成美观的交互式图表,适合 prototyping。

美中不足的是深度定制样式还是不如 Matplotlib 方便,毕竟后者资料太多了。而且现在有大模型。

网页小工具

Pastebin

无需注册,支持不超过 50MB 的二进制文件,可以直接 POST/GET 的 pastebin。

最朴素的就是最好用的。

Windows 工具

PowerToys

由微软官方开发的一组 Windows 上的奇怪小工具。本人用它把 CapsLock 键改成了 Backspace(因为我打字准度太差了

AutoHotkey

用于操作自动化的脚本语言,可以写一些快捷键什么的。其实拿来写游戏的挂机脚本都可以,就是满屏 ImageSearchClick 啊可维护性太差了。

WizTree

可以非常快速地统计出磁盘占用的树形结构,清理磁盘空间时很有用。

看抽象代数的时候突然想到,群的 Cayley 表和数独一样,都要求每行(列)中的元素互异。那么能不能按照群的规则(即结合律)出一道变种数独题呢?于是简单试了一下。

规则:

  • 题面是一个 \(10 \times 10\) 数表(行和列从 \(0\) 开始计数),元素为 \(0 \sim 9\) 的整数;第 \(0\) 行和第 \(0\) 列的值已经给出
  • 与普通数独相同,每行、每列的元素不能重复;注意没有九宫格的条件
  • \(i\)\(j\) 列的值为 \(x\)\(j\)\(k\) 列的值为 \(y\),则 \(i\)\(y\) 列和 \(x\)\(k\) 列的值相同

TLDR:补全一个以 \(0\) 为单位元的 \(10\) 阶群的乘法表。(提示:\(10\) 阶群只有 \(\mathrm{Z}_{10}\)\(\mathrm{D}_{10}\) 两种)

题 1:(表中横、竖线仅作美化排版用途,无实际含义)

\[ \begin{array} {r|rrr|rrr|rrr} 0 & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 \\ \hline 1 & 0 & & & & & & & & \\ 2 & & & 1 & & & & & & \\ 3 & & 4 & & & & & & & \\ \hline 4 & & & & & & & & & \\ 5 & & & & 2 & & & & & \\ 6 & & & & & & & & & \\ \hline 7 & & & & & & 3 & & & \\ 8 & & & & & 9 & & & & \\ 9 & & & & & & & & & \\ \end{array} \]

解析

表中 \(2 \times 3 \ne 3 \times 2\),说明这个群一定是 \(\mathrm{D}_{10}\). 这里先明确一下记号:\(\mathrm{D}_{10} = \langle r, s \mid r^5 = s^2 = e,\ sr = r^{-1}s \rangle\).

解决题目的关键在于理解 \(\mathrm{D}_{10}\) 的自同构。具体而言,对于任意的 \(n \in \{1, 2, 3, 4\},\ m \in \{0, 1, 2, 3, 4\}\),存在一个自同构,把 \(r^n\) 映射到 \(r\),把 \(r^m s\) 映射到 \(s\).

\(0\) 是单位元,而 \(1 \times 1 = 0\),说明 \(1\) 是“s 型”元素。根据这个群的自同构,我们可以确定,假如本题有解,那么一定存在一个 \(1\) 对应群中元素 \(s\)(下文简写为 \(1 \sim s\))的解。那么我们不妨设 \(1 \sim s\).

\(2 \times 3 = 1\),说明 \(2\)\(3\) 一个是“r 型”,一个是“s 型”。注意到 \(5 \times (3 \times 2) = 2\),因此 \(5\)\(3\) 互为逆元。“s 型”元素的逆元都是它本身,因此 \(3\) 是“r 型”,\(2\) 是“s 型”。

不妨设 \(3 \sim r\),则 \(2 \sim rs\)\(5 \sim r^4\). \(3 \times 2 = 4\),说明 \(4 \sim r^2 s\).

这是目前已确定的元素:

\(0\)\(1\)\(2\)\(3\)\(4\)\(5\)\(6\)\(7\)\(8\)\(9\)
\(e\)\(s\)\(rs\)\(r\)\(r^2 s\)\(r^4\)

还剩下 \(r^2,\, r^3,\, r^3 s,\, r^4s\) 尚未确定。

\(7 \times 6 = 3\),说明 \(6\)\(7\) 要么都是“r 型”,要么都是“s 型”。而 \(r^2 \times r^3 = r^3 \times r^2 = e\),说明 \(6\)\(7\) 只能都是“s 型”,因此 \(6 \sim r^3 s\)\(7 \sim r^4 s\).

\(8 \times 5 = 9\),而 \(8\)\(9\) 只能在 \(r^2,\, r^3\) 中取值,因此 \(8 \sim r^3\)\(9 \sim r^2\).

完整的对应关系如下:

\(0\)\(1\)\(2\)\(3\)\(4\)\(5\)\(6\)\(7\)\(8\)\(9\)
\(e\)\(s\)\(rs\)\(r\)\(r^2 s\)\(r^4\)\(r^3 s\)\(r^4 s\)\(r^3\)\(r^2\)

据此把乘法表补全即可。

答案:

\[ \begin{array} {r|rrr|rrr|rrr} 0 & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 \\ \hline 1 & 0 & 5 & 7 & 8 & 2 & 9 & 3 & 4 & 6 \\ 2 & 3 & 0 & 1 & 5 & 4 & 8 & 9 & 6 & 7 \\ 3 & 2 & 4 & 9 & 6 & 0 & 7 & 1 & 5 & 8 \\ \hline 4 & 9 & 3 & 2 & 0 & 6 & 5 & 8 & 7 & 1 \\ 5 & 7 & 1 & 0 & 2 & 8 & 4 & 6 & 9 & 3 \\ 6 & 8 & 9 & 4 & 3 & 7 & 0 & 5 & 1 & 2 \\ \hline 7 & 5 & 8 & 6 & 9 & 1 & 3 & 0 & 2 & 4 \\ 8 & 6 & 7 & 5 & 1 & 9 & 2 & 4 & 3 & 0 \\ 9 & 4 & 6 & 8 & 7 & 3 & 1 & 2 & 0 & 5 \\ \end{array} \]

题 2:

\[ \begin{array} {r|rrr|rrr|rrr} 0 & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 \\ \hline 1 & & & & & & & & & \\ 2 & & & & & & & & & \\ 3 & & & 0 & & & & & & 4 \\ \hline 4 & & & & & & & & 0 & \\ 5 & & 7 & & & & & & & \\ 6 & & & & & & & & & \\ \hline 7 & & & & & & & & & \\ 8 & & & & & 2 & & & & \\ 9 & & & & 1 & & & & & \\ \end{array} \]

解析

用和题 1 类似的方法,可以发现 \(\mathrm{D}_{10}\) 是不行的。因此,这个群只能是 \(\mathrm{Z}_{10}\). 记 \(\mathrm{Z}_{10} = \langle g \mid g^{10} = e \rangle\).

\(3 \times 3 = 0\),说明 \(3\) 的阶为 \(2\),因此 \(3 \sim g^5\).

考虑从 \(4\) 出发往后推。但我们还不确定 \(4\) 的阶是 \(5\) 还是 \(10\). 先假设 \(4\) 的阶是 \(5\),那么不妨设 \(4 \sim g^2\).

\(3 \times 9 = 4\),说明 \(9 \sim g^7\). \(4 \times 8 = 0\),说明 \(8 \sim g^8\). \(9 \times 4 = 1\),说明 \(1 \sim g^9\).

这是目前已确定的元素:

\(0\)\(1\)\(2\)\(3\)\(4\)\(5\)\(6\)\(7\)\(8\)\(9\)
\(e\)\(g^9\)\(g^5\)\(g^2\)\(g^8\)\(g^7\)

还剩下 \(g,\, g^3,\, g^4,\, g^6\) 尚未确定。

还有两个条件没有用到:\(5 \times 2 = 7\)\(8 \times 5 = 2\). 对 \(8 \times 5 \times 2\) 使用结合律,得到 \(2 \times 2 = 8 \times 7\).

这说明 \(7\)\(g^4\) 或者 \(g^6\). 若 \(7 \sim g^4\),则 \(2 \sim g\)\(2 \sim g^6\);若 \(7 \sim g^6\),则 \(2 \sim g^2\)\(2 \sim g^7\),矛盾。因此,\(7 \sim g^4\).

\(5 \times 2 = 7\),若 \(2 \sim g\),则 \(5 \sim g^3\);若 \(2 \sim g^6\),则 \(5 \sim g^8\),矛盾。因此,\(2 \sim g\)\(5 \sim g^3\)\(6 \sim g^6\).

完整的对应关系如下:

\(0\)\(1\)\(2\)\(3\)\(4\)\(5\)\(6\)\(7\)\(8\)\(9\)
\(e\)\(g^9\)\(g\)\(g^5\)\(g^2\)\(g^3\)\(g^6\)\(g^4\)\(g^8\)\(g^7\)

据此把乘法表补全即可。

答案:

\[ \begin{array} {r|rrr|rrr|rrr} 0 & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 \\ \hline 1 & 8 & 0 & 7 & 2 & 4 & 3 & 5 & 9 & 6 \\ 2 & 0 & 4 & 6 & 5 & 7 & 9 & 3 & 1 & 8 \\ 3 & 7 & 6 & 0 & 9 & 8 & 2 & 1 & 5 & 4 \\ \hline 4 & 2 & 5 & 9 & 7 & 3 & 8 & 6 & 0 & 1 \\ 5 & 4 & 7 & 8 & 3 & 6 & 1 & 9 & 2 & 0 \\ 6 & 3 & 9 & 2 & 8 & 1 & 4 & 0 & 7 & 5 \\ \hline 7 & 5 & 3 & 1 & 6 & 9 & 0 & 8 & 4 & 2 \\ 8 & 9 & 1 & 5 & 0 & 2 & 7 & 4 & 6 & 3 \\ 9 & 6 & 8 & 4 & 1 & 0 & 5 & 2 & 3 & 7 \\ \end{array} \]


以下是作者的一些疑问:

  1. 存不存在只有 5 个已知数且有唯一解的题面?
  2. 有没有比较有效的方法构造这类题目的 \(8 \times 8\) 版本?

本章以 Llama 3 为讨论对象。

2.1 模型设计

Llama 3 的模型架构较为常规,但值得一提的是其模型尺寸的选择。Llama 3 的旗舰版本是 405B 而非常见的 70B,这一选择是经过了充分分析和论证的。

给定总计算量(这直接由预算决定),Llama 团队希望训练出一个尽可能强的模型。盲目增加参数量并不是最优解:增加参数意味着减少训练数据量,而一个欠拟合的大模型未必比一个充分拟合的较小模型更优。DeepMind 团队在 2022 年研究了该问题。这篇文章在固定总计算量的条件下训练了大量不同尺寸的 LLM,并观察到(最小化 loss 意义下的)最优模型尺寸与总计算量之间存在与数据相当吻合的经验关系。Llama 3 沿用了这一方法,但重做了其中的实验。实验得出的数据与原文有差异,但结论是一致的。405B 的选择正是根据这项数据外推得来。

图片出自 Training Compute-Optimal Large Language Models

作者曾经读到过一句话:“工程师是能用最低限度的创新达成目的的人。”Llama 3 正是这样:其成功的最主要原因并非其方法论所含的原创性,而是大量人力、算力资源的投入以及对现有 best practice 的有效整合。这样的工作是否预示着在不久的将来,深度学习领域“单打独斗”的时代将会迎来结束?这是一个引人遐想的问题。

2.2 Pre-training

Llama 3 的预训练分为三个步骤:初始训练、上下文增长和 annealing(冷却?)。

2.2.1 初始训练

训练由一个较短的预热阶段(学习率线性增长)和一个较长的主阶段(学习率余弦衰减)组成。模型初始阶段很不稳定,因此使用更低的学习率和更小的 batch size 以保证梯度在合理范围内,使模型的学习更加可控。

值得一提的是,训练数据不仅在内容上进行了多方面的把控,不同类型数据的配比也是刻意调整优化过的。得益于 scaling law,小模型上的实验结果可以较为可靠地迁移到大模型上,使得对训练数据配比的调优成为可能。展现了深度学习中炼金术的一面

2.2.2 上下文增长

由于 transformer 模型的算力消耗随输入长度是超线性增长的,以超长上下文训练会拖慢训练效率。因此,模型在初始训练阶段以 8K 上下文长度进行训练,训练好之后逐步增长到 128K。

用于监控训练进程的指标是原长度下的表现和新长度下 needle in a haystack 测试的成功率。当模型 100% 完成 needle in a haystack 任务,且在原长度下的表现恢复到训练前水平时,即认为模型适应了新的上下文长度,接下来再进行下一轮上下文增长,直到达到最终的 128K。

2.2.3 冷却

在以上两个阶段后,还会再进行一个较短的冷却阶段(学习率线性衰减)。这一阶段使用精选的高质量数据进行训练。这或许是 LLM 版的“考前冲刺”?

2.3 Post-training

经过 pre-training 后,模型具有了理解语言的能力,但它只学习了文本续写这一项任务。想要让模型顺利地和人类对话,还需要作出一定微调。

2.3.1 Instruction Tuning

首先,需要规定一种用于对话的格式,以便 LLM 知道上文的每一部分都来自于谁(系统/用户/LLM/工具...)。以 Llama 3.1 为例,一个简单的 prompt 如下:(形如 <|begin_of_text|> 的为特殊 token)

1
2
3
4
5
6
7
8
<|begin_of_text|><|start_header_id|>system<|end_header_id|>

Cutting Knowledge Date: December 2023
Today Date: 23 July 2024

You are a helpful assistant<|eot_id|><|start_header_id|>user<|end_header_id|>

What is the capital of France?<|eot_id|><|start_header_id|>assistant<|end_header_id|>

定义了对话格式后,需要在对话数据上微调 LLM。对话数据可能来自于人,也可能由已经 post-training 过的 LLM 生成。

2.3.2 偏好学习

仅仅在格式上让 LLM 适配对话是不够的。互联网上存在大量的低质量内容,也包含很多我们不希望 LLM 去模仿的行为(如恶意言论)。因此,需要让 LLM 学习在人类看来更加“正确”的行为,而非无意识地模仿互联网上的内容。

构建高质量的对话数据集是较为困难的。但是还有一种相对容易的获取人类偏好的方式:让人类比较对同一问题的两个回答孰优孰劣。模型通过某种方式的学习,提高生成较优答案的概率,降低生成较劣答案的概率,从而实现适配人类偏好的效果。

受限于作者的数学水平,本节只概括性地描述偏好学习的理念,不涉及具体细节。

一个典型的 RLHF (Reinforcement Learning from Human Feedback) 流程如下:

  1. 收集偏好数据。一条偏好数据是形如 \(\mathrm{(input,\,output\_win,\,output\_lose)}\) 的三元组。
  2. 根据偏好数据训练一个模型(通常是 LLM 附加一个分类层),用于给 \(\mathrm{(input,\,output)}\) 对打分。训练时最大化打分和偏好数据的吻合程度(给 \(\mathrm{output\_win}\) 高分,给 \(\mathrm{output\_lose}\) 低分)。这个模型被称为 reward model。
  3. 以 reward model 为指导微调模型。这一步最大化模型输出在 reward model 上的得分,同时通过一个惩罚项避免模型偏离太远。由于 reward model 并不可微,这一步无法使用梯度下降法。通常使用强化学习领域的 PPO 算法进行优化。

PPO 过程较为繁琐,需要训练辅助模型和使用相对复杂的优化算法。DPO 通过一些数学上的调整,在保留原本算法的优秀性质的同时不再需要 reward model 和强化学习,而是直接更新模型参数。DPO 的复杂性显著低于 PPO,同时性能接近或更优。

Transformer 模型通常采用三种主要架构:encoder-decoder、encoder-only 和 decoder-only。

1.1 Encoder-decoder

图片出自 Attention Is All You Need

Transformer 原始论文中设计的模型架构就是 encoder-decoder 的。Encoder 通过双向自注意力处理输入,decoder 通过单向自注意力逐个生成输出,encoder 通过互注意力向 decoder 传递信息。

Encoder-decoder 架构起源于机器翻译等输入和输出之间具有一定异质性的任务。对于这种任务,使用一个模型理解输入,另一个模型生成输出是合理的做法。然而,在 LLM 的主要任务(QA、续写、聊天等)中,输入和输出是高度同质的(它们大概率是同一种语言,且在逻辑上相承接),这时使用两个模型分别处理两部分信息不仅没有必要,还会在模型设计中引入额外的复杂性。因此,LLM 架构中 decoder-only 的占比远远高于 encoder-decoder。

1.2 Encoder-only

这类模型的典型代表为 BERT。这一模型是文本理解领域的经典 backbone。

BERT 的预训练是自监督的,包括两个任务。

较为重要的一个是预测被 mask 的 token,一种类似于 denoising autoencoder 的经典预训练方法。具体而言,输入中随机 15% 的 token 会被 mask 掉(80% 概率变为 [MASK],10% 概率变为随机 token,10% 概率不变),而模型的任务就是还原这些 token。通过这个任务,模型可以学到 token 与 token 之间的双向关系,这是 next token prediction 做不到的。

另一个任务是 next sentence prediction,即输入两个语段,预测它们是否出自同一篇文章的相邻片段。这个任务可以加强模型对语段之间关系的理解。

BERT 可以生成用于下游文本理解任务的 deep representation,但不适合用于文本生成。虽然确实存在这样的方法,但其生成过程明显比 decoder-only 模型更复杂。究其原因,BERT 被训练为利用双向信息理解语言,但生成文本的过程本质上是单向的(生成文本时,模型只知道已经生成的 token,但不知道未来的 token)。这导致朴素的生成方式用于 BERT 时效果很差。

1.3 Decoder-only

目前的绝大多数 LLM 都是 decoder-only 语言模型。这类语言模型主要由四部分组成:

  1. Tokenizer:tokenizer 负责在输入文本和 token 序列之间相互转换。token 是语言模型处理信息的最小单位。对于英语输入,token 通常是一个完整单词或较不常用单词的一部分(在 ChatGPT 的 tokenizer 中,一个 token 大概相当于 3/4 个英文单词)。可能的 token 总数大致为 \(10^5\) 量级。
  2. Embedding:embedding 层是一个可学习的表,将每个 token 映射到一个高维向量。经过 tokenizer 和 embedding 层之后,文本输入被转化为一个 \(\mathrm{num\_tokens} \times \mathrm{embed\_dim}\) 的矩阵。
  3. Transformer 网络: 这一部分包含了模型的绝大部分计算量。每个 transformer 块包含一个掩蔽多头自注意力层和一个 feed-forward network(通常是两层神经网络),配有归一化和残差连接。在 LLM 中,整个网络通常由连续数十个规模相同的 transformer 块组成。
  4. LM head:这是模型的输出层,将 transformer 网络的输出转化为下一个 token 的概率分布。推理时,LM head 的输出通过一些采样方法生成出 token 序列,再通过 tokenizer 转换为文本。

以下是 Meta-Llama-3.1-8B-InstructQwen2.5-7B-Instruct 的大致架构,可以发现两者大同小异:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
LlamaForCausalLM(
(model): LlamaModel(
(embed_tokens): Embedding(128256, 4096, padding_idx=128004)
(layers): ModuleList(
(0-31): 32 x LlamaDecoderLayer(
(self_attn): LlamaSdpaAttention(
(q_proj): Linear(in_features=4096, out_features=4096, bias=False)
(k_proj): Linear(in_features=4096, out_features=1024, bias=False)
(v_proj): Linear(in_features=4096, out_features=1024, bias=False)
(o_proj): Linear(in_features=4096, out_features=4096, bias=False)
(rotary_emb): LlamaRotaryEmbedding()
)
(mlp): LlamaMLP(
(gate_proj): Linear(in_features=4096, out_features=14336, bias=False)
(up_proj): Linear(in_features=4096, out_features=14336, bias=False)
(down_proj): Linear(in_features=14336, out_features=4096, bias=False)
(act_fn): SiLU()
)
(input_layernorm): LlamaRMSNorm((4096,), eps=1e-05)
(post_attention_layernorm): LlamaRMSNorm((4096,), eps=1e-05)
)
)
(norm): LlamaRMSNorm((4096,), eps=1e-05)
(rotary_emb): LlamaRotaryEmbedding()
)
(lm_head): Linear(in_features=4096, out_features=128256, bias=False)
)

Qwen2ForCausalLM(
(model): Qwen2Model(
(embed_tokens): Embedding(152064, 3584)
(layers): ModuleList(
(0-27): 28 x Qwen2DecoderLayer(
(self_attn): Qwen2SdpaAttention(
(q_proj): Linear(in_features=3584, out_features=3584, bias=True)
(k_proj): Linear(in_features=3584, out_features=512, bias=True)
(v_proj): Linear(in_features=3584, out_features=512, bias=True)
(o_proj): Linear(in_features=3584, out_features=3584, bias=False)
(rotary_emb): Qwen2RotaryEmbedding()
)
(mlp): Qwen2MLP(
(gate_proj): Linear(in_features=3584, out_features=18944, bias=False)
(up_proj): Linear(in_features=3584, out_features=18944, bias=False)
(down_proj): Linear(in_features=18944, out_features=3584, bias=False)
(act_fn): SiLU()
)
(input_layernorm): Qwen2RMSNorm((3584,), eps=1e-06)
(post_attention_layernorm): Qwen2RMSNorm((3584,), eps=1e-06)
)
)
(norm): Qwen2RMSNorm((3584,), eps=1e-06)
)
(lm_head): Linear(in_features=3584, out_features=152064, bias=False)
)

Decoder-only 语言模型的预训练任务为 next token prediction。具体而言,模型对每个位置预测其 token 种类的概率分布(由于 transformer decoder 使用的是掩蔽自注意力,模型预测某个位置时只能看到它之前的 token),训练目标为最大化正确文本的 log likelihood。推理与其他模型类似,逐个采样即可。

在 decoder-only 语言模型的发展过程中,积累的量变产生了质变。这一点由 GPT 系列的发展可见一斑:

  • GPT-1:模型尺寸约 0.1B,训练语料来自书籍。在完成下游任务时仍然需要微调。
  • GPT-2:模型尺寸约 1.5B,训练语料来自互联网。可以 few-shot 完成下游任务,但通常需要精心设计的 prompt。
  • GPT-3:模型尺寸最大 175B,训练语料来自经过调配的多个数据集。可以 zero-shot 完成一些任务,也能理解一般的自然语言 prompt。

TOC

h3

h4

h5

another h4

LaTeX

\[\int_{0}^{1}\!x^{-x} \mathrm{d}x = \sum_{n=1}^{\infty}n^{-n}\]

\(\int_{0}^{+\infty}\!\sin(x^2) \mathrm{d}x = \sqrt{\frac{\pi}{8}}\)

\[\mathit{mathit} \quad \mathrm{mathrm} \quad \mathcal{mathcal} \quad \mathscr{mathscr} \quad \mathbb{mathbb} \quad \mathfrak{mathfrak}\]

\[\mathit{MATHIT} \quad \mathrm{MATHRM} \quad \mathcal{MATHCAL} \quad \mathscr{MATHSCR} \quad \mathbb{MATHBB} \quad \mathfrak{MATHFRAK}\]

\[\mathit{01234} \quad \mathrm{56789} \quad \mathcal{01234} \quad \mathscr{56789} \quad \mathbb{01234} \quad \mathfrak{56789}\]

\[2.71828\,18284\,59045\,23536\,02874\,71352\,66249\,77572\,47093\,69995\,95749\,66967\,62772\,40766\,30353\,54759\,45713\,82178\,52516\,64274\]

代码块

1
2
3
4
5
6
7
8
9
10
11
12
13
(* take first n elements from list xs *)
let take n xs =
let rec take_impl acc n xs = match (n, xs) with
| (0, _) -> acc
| (_, []) -> acc
| (n, x::xs) -> take_impl (x::acc) (n-1) xs
in List.rev (take_impl [] n xs)

(* drop first n elements from list xs *)
let rec drop n xs = match (n, xs) with
| (_, []) -> []
| (0, xs) -> xs
| (n, _::xs) -> drop (n-1) xs
Copied from lipsum.com
1
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

Mermaid 图

sequenceDiagram
    Alice->>John: Hello John, how are you?
    John-->>Alice: Great!
    Alice-)John: See you later!

折叠

不要点开

Hidden text

0%