EECS498 · Transformer I · 09

From Raw Sequence to Embedding

Transformer 真正开始计算时没有字符串,只有一批整数,以及一张会被 gradient 更新的向量表。这一课把两者之间每一步都走完。

80–100 分钟WI22 A5Token → ID → vector → position

30-second recall

30 秒回忆

你实现了 generate_token_dict、prepocess_input_sequence 和 positional encoding,并用 nn.Embedding 接收 arithmetic data。

  1. token ID 为什么不是语义数字?
  2. embedding table 的一行代表什么?
  3. 为什么没有 position 的 attention 无法可靠区分顺序?

Before We Start · 先诊断记忆

1. 输入 ID shape 为 [32,9],embedding dim 为 64,输出 shape 是?

2. 两句话中的 bank 有同一 token ID。进入第一层前的 token embedding 呢?

3. 哪个对象通常不接收 gradient?

贯穿五课的真实样本:BOS NEGATIVE 30 subtract NEGATIVE 34 EOS → BOS POSITIVE 04 EOS

Problem · 模型到底接收什么?

原始字符串是人类界面;矩阵乘法需要有固定 shape 的数值 tensor。一个看似简单的“把文字变成数字”,实际包含几个不同对象:

character抽象字符,如 负 或 3
Unicode code point字符在标准中的编号
UTF-8 bytes字符被编码后的 1–4 个 bytes
token模型词表所采用的离散单元
token ID该单元在词表中的 row index
embedding从参数表查出的连续向量
contextual representation经过 attention/MLP 后、已经吸收上下文的状态

WI22 toy tokenizer 没有经过 UTF-8/BPE 管线:空格切词后,特殊单词保持整体,数字字符串按 digit 拆开。列出 Unicode 层次只是为了精确区分对象,不是说你当年的代码实现了现代 tokenizer。

Naive solution · 直接把字符串或数字喂进去?

把 30 当作浮点数 30,会暗中规定“30 比 3 大十倍”这一几何关系,也无法表示 BOS。把每个完整表达式分配一个 ID,则新表达式没有可复用结构。你的 digit vocabulary 采用了更小的离散积木:10 个数字和少量操作符可以组合出未见的两位数。

如果每个两位数都是独立 token?

模型不必重新组合十位/个位,但词表会扩大,而且对未列入词表的数字没有自然分解。tokenization 是建模选择,不是无害的预处理细节。

Worked Example · 从原始文本走到 tensor

raw string
"BOS NEGATIVE 30 subtract NEGATIVE 34 EOS"

↓ split + special-token / digit rule

[BOS, NEGATIVE, 3, 0, subtract, NEGATIVE, 3, 4, EOS]

↓ vocabulary lookup

[14, 11, 3, 0, 13, 11, 3, 4, 15]              [K=9]

↓ stack N examples into a batch

token_ids                                         [N,9]

↓ row lookup in E ∈ ℝ^(16×M)

token_embeddings                                  [N,9,M]

↓ add position-dependent vectors P

Transformer input X                               [N,9,M]

注意 vocabulary size V=16 没有成为 activation 的最后一维。ID 11 直接选择参数表 E 的第 11 行;lookup 的结果是长度 M 的向量。

Mechanism · Embedding table 到底是什么?

  1. 参数:E ∈ ℝ^(V×M)。若 V=16、M=64,就有 16×64 个可训练数。
  2. 查表:X[n,i] = E[token_ids[n,i]]。
  3. 重复 token:同一 ID 初次查到同一行;此处尚未读取上下文。
  4. 反向传播:loss 对被访问行产生 gradient;optimizer 更新这些行。
token_ids[N,K] · integer indices · 非 parameter
E[V,M] · float parameter
E[token_ids][N,K,M] · activation
∂L/∂E[V,M] · gradient

embedding 起初不必“有意义”。它只是可学习坐标;训练任务持续推动某些 rows,使它们成为对预测有用的起点。语义是训练结果和后续网络共同形成的,不是 ID 自带属性。

Inline check. 若交换 vocab 中 ID 11 与 3,同时交换 embedding table 的第 11、3 行,其他所有输入 ID 也按新字典编码,模型行为怎样?

Mental model

Token ID 是地址;embedding table 是会学习的记忆表;lookup 得到的是无上下文起点,不是词在这句话中的最终含义。

Your Code · 两个阶段,两种职责

Your code · 2023

win22/A5/transformers.py · prepocess_input_sequence()

View source →
for letter in input_str.split(" "):
    if letter in token_dict:
        out.append(token_dict[letter])
    else:
        for digit in letter:
            out.append(token_dict[digit])

token_dict 决定离散映射;这段代码不含可训练 tensor,也不会收到 gradient。

Your code · 2023

win22/A5/transformers.py · Transformer

View source →
self.emb_layer = nn.Embedding(
    num_embeddings=vocab_len,
    embedding_dim=emb_dim,
)
q_emb = self.emb_layer(ques_b)
q_emb_inp = q_emb + ques_pos

ques_b [N,K] 查表后成为 q_emb [N,K,M]。相加要求 ques_pos 可 broadcast 到同一 shape;最后一维 M 没变,后续 block 因而仍使用统一接口。

Why Position? · Attention 本身没有“第几个”

考虑 cat eats fish 与 fish eats cat。若输入只包含三张 token cards,自注意力会随 cards 的排列同步重排输出——它是 permutation-equivariant,但没有独立顺序来源。模型知道集合里有 cat/eats/fish,却不能把“第一个名词”和“第三个名词”的角色固定到位置上。

位置注入做的是:

X_i = E[token_i] + P_i

E[token_i] : 这个离散符号是什么
P_i        : 它处在序列的哪个位置
X_i        : 后续 Q/K/V projections 可以同时利用两类信号

P_i 不是噪声。它是系统性的、随位置变化的 signal。attention 可以学习“读取前一个位置”“区分十位与个位”,因为 WQ/WK 能从混合 representation 中利用位置方向。

如果所有 Pi 完全相同?

每个 token 都得到同一个偏移,位置之间仍不可区分;相当于给所有 cards 贴同一张“位置”标签。

Misconception diagnosis

“相同 token 加 position 后就有了上下文”仍不准确。position 让位置可辨;真正依赖其他 tokens 的 contextual representation,要等 attention 把别处的 values 混进来。

Deep Dive · sinusoidal、learned position、RoPE 改变了什么?

Sinusoidal absolute encoding 用确定函数生成 P;learned absolute embedding 把 P 也当参数;RoPE 不直接把一张 P 加到 X,而是在 Q/K feature pairs 上按位置旋转,使 dot product 显式包含相对位置信息。它们都在解决“内容匹配如何感知顺序/距离”,接口不同,问题相同。

Historical implementation note:repository 的 sinusoidal denominator 使用了会把指数取整的表达式;saved Notebook check 与该历史实现一致。今天调试时应先打印多个 dimension 的频率,确认它们没有意外相同,而不是默默把历史代码改写成标准版本。

谁被训练?什么时候改变?

对象是否 parameter何时改变
toy split / vocabulary mapping否人工固定
token IDs否随输入变化
embedding table E是optimizer step
lookup activation X否(中间值)随输入与 E 变化
contextual hidden state否(中间值)随整句与模型参数变化

Deep Checks · 不只认名词

V=16、M=64、batch=[32,9]。参数与 activation 各是什么 shape?

E=[16,64],lookup activation [32,9,64]。V 属于表的行数,不留在每个 token vector 中。

同一 digit 3 出现两次:哪一步相同,哪一步可能不同?

基础 row lookup 相同;位置相加后不同;经过 attention 后还会因读取的邻居不同而进一步分化。

为什么不能对 token IDs 直接做普通大小关系运算?

ID 是可任意置换的类别地址。把 11 减 3 没有稳定语义;可学习几何位于 embedding rows 中。

若把 sequence 整体重排且不提供 position,self-attention 输出会怎样?

输出按同样 permutation 重排;网络没有一个外部信号判断哪个排列是原句顺序。

Transfer Question

一个模型用 byte-level tokenizer。字符 é 可能成为两个 UTF-8 bytes,对应两个 token IDs。它进入 Transformer 时是一条“字符”还是两个 positions?embedding table 的行数由什么决定?

核对思路

若 tokenizer 保留为两个 byte tokens,就是两个 positions;表的行数由 tokenizer vocabulary 决定,而不是 Unicode 字符总数或句子长度。

闭卷解释

  1. 不使用“向量化”这个模糊词,解释 token ID 如何变成 embedding。
  2. 为什么 embedding 是 parameter,而 tokenizer rule 通常不是?
  3. 用 cat eats fish 解释 position signal 的必要性。
  4. 为什么 embedding 不等于 contextual representation?

Then I implemented: token dictionary → digit split → nn.Embedding → position add.

Now I understand: tokenizer 固定离散地址,embedding 学习每个地址的连续起点,position 提供顺序坐标;上下文要到下一课的信息路由后才出现。

Connection · 下一课为什么必然出现?

现在每个位置已有 [M] 向量,但 slept 的向量仍没有数值上包含 animal 的信息。下一课要构造一个输入相关的矩阵,让位置 i 真的读取位置 j。

Based on your implementation