从 Token 到 Transformer · 03 / 10
Tokenizer 把文本切开之后,模型仍然没有“读懂”任何东西。它拿到的只是一串来自词表的编号。真正进入 Transformer 计算之前,这些编号还要经过一次查表,被替换成高维向量。
这条链路看似简单,却集中着大模型入门最常见的三个误解:ID 不是语义,embedding 不是上下文理解,向量也不是把编号直接拿去计算。
先跟着一条输入走
假设某个 tokenizer 把一句话切成四个 token:
text["今天", "去", "上", "海"]
词表会为它们提供整数索引:
text[8142, 392, 1187, 506]
这些数字只是地址。如果把词表重新排序,“今天”完全可以变成 42,模型的语义不会因此改变;只要 embedding 表和输出层使用同一套映射即可。
运行时通常还会构造批次维度和辅助字段:
textinput_ids shape: [batch, sequence] attention_mask shape: [batch, sequence]
attention_mask 常用来区分真实 token 与 padding。它不是 Attention 的权重,而是告诉后续计算哪些位置应该参与,哪些位置需要被屏蔽。
ID 如何变成 Embedding
模型内部有一张可训练的 embedding 矩阵。若词表大小为 V,隐藏维度为 D,它的形状就是 [V, D]。
每个 token ID 会选中其中一行:
textembedding = embedding_table[token_id]
这更接近数据库查表,而不是拿 ID 参与乘法。长度为 N 的序列查完后,会得到形状为 [N, D] 的向量序列;加入 batch 后则是 [B, N, D]。
ID 8142 与 ID 8143 数值相邻,并不意味着对应 token 相似。相似性如果存在,来自两行向量在训练中形成的几何关系,而不是编号距离。
位置从哪里进来
仅有 token embedding 还不够。Self-attention 本身不会天然知道某个向量排在第一个还是第四个,因此模型还要注入位置信息。原始 Transformer 使用正弦、余弦位置编码;现代模型也常见可学习位置、相对位置和旋转位置编码等方案。
概念上,可以把输入理解为:
text初始表示 = token 信息 + 位置信息
具体模型未必真的以简单相加完成所有位置处理,但目标一致:同一个 token 出现在不同位置时,后续层必须能够区分。
初始 Embedding 还不是“当前词义”
同一个 token 从 embedding 表查出的初始向量通常相同。比如“苹果”出现在水果语境和公司语境时,入口向量并不会先替你完成消歧。
上下文差异是在 Transformer 层中逐步写入的。Self-attention 让每个位置汇聚其他位置的信息,FFN 再对当前表示做非线性变换。经过一层层更新后,同一个初始 token 会形成不同的 hidden state。
因此,下面三个概念要分开:
| 概念 | 作用 | 是否包含当前上下文 |
|---|---|---|
| Token ID | 定位词表项 | 否 |
| Input embedding | 提供可训练的初始表示 | 通常还没有 |
| Hidden state | 记录经过多层上下文计算后的表示 | 是 |
把 hidden state 也统称为“词向量”虽然方便,却会掩盖模型真正发生变化的地方。
输出端又如何回到 Token
模型最后会把当前位置的 hidden state 投影到词表大小的空间,得到每个候选 token 的 logits。Softmax 把它们转换为概率分布,解码策略再选择下一个 token。
不少语言模型会让输入 embedding 矩阵与输出投影共享参数,这叫 weight tying。无论是否共享,完整闭环都是:
texttoken ID -> 初始向量 -> 多层 hidden state -> 词表 logits -> 下一个 token ID
模型生成文字,本质上是在这个闭环里重复预测、追加和再计算。
小结
Token 是切分单位,ID 是词表地址,embedding 是从参数表查出的初始向量,hidden state 才是上下文不断改写后的内部表示。
理解这四层,后面的 Attention 就不再像在“对文字做魔法”,而是对一组带有位置和上下文的向量进行可训练的信息交换。
