模型为什么不直接读字符串
神经网络擅长处理定长数值张量,而文本是长度不一的字符序列。Tokenizer把字符串按词表规则变成一串离散单元;它决定长度、成本和模型看见的边界。 先把文字切成 token,再把每个 token 映射到整数 ID;embedding 表用 ID 取出一行连续向量。
这条流水线解决三个问题:有限词表怎样覆盖开放语言、字符串怎样变成数值、语义相近的片段怎样在可学习空间里表达。
Token 不是单词
一个 token 可能是完整英文词、词根、标点、空格模式、汉字或字节片段。切分取决于 tokenizer 的词表与算法,所以“1,000 个汉字等于 1,000 token”并不可靠。相同文本在不同模型上可能得到不同长度;API 费用和上下文占用应按目标模型的 tokenizer 或服务端 usage 计算。
Grok-1 仓库包含 tokenizer.model,runners.py 用 sentencepiece.SentencePieceProcessor 加载它。README 写出词表大小 131,072;run.py 则配置 vocab_size=128 * 1024、pad_token=0、eos_token=2。
ID 只是索引,Embedding 才进入计算
假设词表大小为 ,隐藏维度为 ,embedding 矩阵可以写成:
是可用 token 数; 是每个 token 的向量宽度; 是整数 ID; 是查到的第 维向量。ID 42 和 43 在数学上没有“更接近”的含义,但训练后两行 embedding 可以在某些方向上表达相近用法。
Grok-1 的 、。单看输入 embedding 就有约 亿个标量。实际参数存储还取决于数据类型、量化和分片。
位置从哪里来
如果只给 embedding,模型看见的是一组向量,不知道顺序。Grok-1 在 Attention 的 query 和 key 上应用RoPE用随位置旋转向量的方式注入相对位置信息。 。model.py 的 RotaryEmbedding 计算不同频率的正余弦相位,MultiHeadAttention 对 key_heads 与 query_heads 调用它。
这比“给每个位置加一个编号”更巧妙:两个位置的相对差会影响 query 与 key 的内积。但公开代码的 base_exponent=10000 与 8,192 长度,只能描述 Grok-1;当前长上下文模型如何扩展位置表示未公开。
输出为何又回到词表
经过 64 层后,每个位置仍是一条 维表示。语言模型头把它投影为 个 logits,再经 softmax 得到候选概率。Grok-1 的 InOutEmbed.decode() 用同一张 embedding 矩阵的转置做投影,这叫 tied embeddings:
是最终隐藏状态, 把它与每个 token 向量比较, 是尚未归一化的 131,072 个分数。共享输入与输出矩阵减少参数,也让“理解 token”与“生成 token”的空间保持联系。
优势、代价与边界
| 设计 | 优势 | 代价 / 限制 |
|---|---|---|
| 子词 tokenizer | 有限词表覆盖新词与多语言 | 切分影响成本;罕见文本可能变长 |
| 大词表 | 常见片段更可能一个 token 表达 | embedding 与输出投影更大 |
| 高维 embedding | 表达更多特征方向 | 显存、通信和矩阵计算增加 |
| 权重绑定 | 省参数、统一空间 | 输入与输出被约束为同一基底 |
本章检查点
字符串先被 tokenizer 变成 ID,ID 再从 embedding 表取向量。tokenizer 是模型协议的一部分:若换掉它,原有权重所理解的 ID 含义也一起被打乱。