第 3 章 · 词嵌入(Embeddings)

解释一个整数 token id 如何变成高维向量,以及为什么向量相近意味着 token 相关。

Transformer 需要每个 token 的连续表示,这样才能表达相似度,梯度也才能流动。

7 分钟
前向传播分词嵌入查表× 24 层最终 RMSNormLM head采样
术语表 · 7 个术语
embedding
token id 被映射到的那个向量——嵌入矩阵的一行,网络其余部分就在它上面做计算。
embedding matrix
形状为 [vocab_size, hidden_dim]。对 Qwen3.5-0.8B 来说约为 248,320 × 1,024——约 254M 个参数。
hidden_dim
每个 token 的向量在模型中流动时的宽度。Qwen3.5-0.8B 为 1,024。
tied embeddings
让输入嵌入矩阵与输出 lm_head 共享,使同一个向量空间既负责读取提示词,也负责写出下一个 token 的 logit。
PCA
主成分分析——把高维点投影到方差最大的两个方向上,从而画出 2D 图。
cosine similarity
两个向量的点积除以各自的范数。衡量方向的一致程度而忽略长度;接近 1 表示非常相似。
semantic direction
像 E(woman) − E(man) 这样的向量差,近似编码某一个特征(性别、单复数、所属国家),可以加到其他嵌入上或从中减去。

词嵌入(Embeddings):把 token 变成向量

分词给了模型一串整数——每个 token 一个 id。但整数不携带模型能计算的含义:id 1234 并不比 1235 更大、更小,或在任何有用的意义上更相似。下一步是把每个 id 映射成网络其余部分能做数学运算的连续向量。这个向量就是该 token 的嵌入(embedding)。

为什么是向量?

连续的表示让模型能够表达相似的“程度”。核心想法是:向量就是空间中的一个方向,含义相近的词最终会指向相近的方向——于是“相关”变成了模型可以用夹角度量的东西。著名的(虽然有点理想化的)word2vec 例子——king − man + woman ≈ queen——表明学到的嵌入甚至能把“关系”捕捉成可以做算术的方向。现代 LLM 的嵌入结构比 word2vec 的纠缠得多(一个模型要跨多种语言和代码风格服务几十种任务),所以这种干净的类比算术变弱了——但底层思想没变:向量相近意味着 token 相关,而 Transformer 各层的整个前向传播,做的就是以符合上下文的方式挪动这些向量。

这里的“相近”指的是指向相同的方向,而不是“尺子量出来的距离短”。在把真实的 1024 维向量投影成图片之前,先在一个可以上手拖动的平面 2-D 玩具里建立核心直觉:含义住在向量的方向里,两个向量之间的夹角度量它们有多相关。

所有度量只用到两小块数学,而且都能写在一行里。点积就是“对应位置相乘,再加起来”:对 a = [3, 4]b = [4, 3]a·b = 3×4 + 4×3 = 24范数 ‖a‖ 是向量的长度:‖a‖ = √(3² + 4²) = 5,同样 ‖b‖ = 5。余弦相似度只是把长度除掉之后的点积:cos = 24 / (5 × 5) = 0.96——这两个向量几乎指向同一个方向。这一个配方——点积除以范数——就是本章里所有的相似度数字。

方向 = 含义,夹角 = 相似度
拖动青色箭头,或使用滑块
50°catdogcar你的词
把“你的词”与谁比较

拖动长度,看下方的 cos θ 纹丝不动——长度不会改变夹角。

夹角 θ
50°
cos θ (相似度)
0.64
≈ 同方向 → 相关
|你的词| = 1.10 · |cat| = 0.92——长度不同,而 cos θ 只看它们之间的夹角

把探针转到与 cat 对齐 → cos θ → 1(“相关”)。转到直角 → cos θ → 0(“不相关”)。指向相反方向 → cos θ → −1(“相反”)。长度从不进入余弦——这正是嵌入相似度用夹角、而不用直线(欧氏)距离的原因。

仅为示意——这些是手工摆放的 2-D 教学箭头,不是真实嵌入。Qwen3.5-0.8B 的向量住在 1,024 维空间里;这张平面图只为建立直觉。

嵌入表

嵌入查询是一次矩阵乘法(或者等价地,一次按行取数):给定整数 id,取嵌入矩阵的第 id 行。矩阵的形状是 [vocab_size, hidden_dim]。对 Qwen3.5-0.8B 来说,光这一张表就有约 248,320 × 1,024 ≈ 254 million 个参数(一个参数就是一个学到的数字;整个模型一共约 853M 个)——约占整个模型的三分之一。

值得停下来想一想:这 2.54 亿个数字一开始全都是随机噪声。没有人坐下来把 tiger 安排在 lion 旁边、画一条性别轴,或者把 king − man + woman ≈ queen 接好线。本章里你会去拨弄的每一个聚类、每一条语义方向、每一个类比,都是在海量文本上拟合同一个目标——预测下一个 token——所涌现出来的副产品。这套几何结构是训练留下的化石,而不是谁手工搭起来的字典。

下面把这次查询画成可以逐步播放的图。一个 token id 进来,一行向量出去——中间什么也没有计算:

嵌入查询——一个 id,一行
0129,0579,0589,0599,0609,061248,319嵌入表 · 248,320 行 × 1,024 列行号·cat · id 9059

分词器递给模型一个整数:9059(token "·cat")。嵌入表只是一个很高的矩阵——248,320 行,每个词表条目一行。

这 8 个浮点数是示意性替身——右侧的实时演示会从已加载的模型里取出真实的 1,024 维行向量。机制本身才是课程重点:输入 token id,输出表中的一行。

现代 LLM(包括 Qwen3.5)常常把输入嵌入与输出“反嵌入”绑定(tie)——后者就是把最终隐藏状态变回词表 logits 的 lm_head,我们会在 LM head 一章细讲;现在你只需把它读作“给下一个 token 打分的那一层”。绑定这两个矩阵既省参数,也迫使这套表示在两个方向上都好用:读取提示词的那个向量空间,同时也要写出下一个 token 的 logit。右侧的小部件画的正是这同一个共享矩阵里的行。

Qwen3.5 的嵌入矩阵有多大?嵌入绑定又省下了多少?
params = vocab_size × hidden_dim
vocab_size248,320
hidden_dim1024

PCA:通往 1024 维空间的一扇 2D 窗户

想象给一座 3-D 雕塑拍照:你会把它转到特征铺得最开的角度,好看清结构。PCA 就是为 1024 维嵌入空间挑出那个“最佳机位”——而且和照片一样,它把纵深压扁了,所以屏幕上的距离只是粗略的参考,不是精确的尺子。

我们看不见 1024 个维度,所以要投影。主成分分析找出数据中方差最大的方向,并投影到前两个(或前三个)方向上。结果是线性压扁所能做到的最好程度:投影后的点保留了一张 2D 图所能承载的最多原始分布信息。

问题在于“尽可能多”仍然很少。1024 维点云的前两个主成分通常只解释总方差的百分之几——其余都在被丢掉的方向里。所以 PCA 很适合发现聚类(在嵌入空间中总体方向相近的 token 会落在一起),但两根轴装不下 1024 根轴的几何。

你应该看到什么

点击 Load embeddings。小部件会把来自六个类别(动物、数字、颜色、国家、动词、食物)的约 110 个词分词,通过模型 worker 查出每个词第一个 token 的嵌入,并在你的浏览器里运行 PCA。每个词只用它的第一个子词 token 来画,所以被切成多块的词在图上只由开头那一块代表。可以期待看到:

  • 按类别聚类。动物挨着动物,数字挨着数字,依此类推。尽管只是 1024 维空间的一个 2D 小切片,聚类通常仍然清晰。
  • 聚类内部的细结构。数字常常排成大致的梯度。颜色分成暖色/冷色两个子区域。食物把咸味和甜味分开。
  • 看起来“懂语义”的最近邻。点任何一个点:完整 hidden-dim 空间里的 top-5 近邻会被高亮,它们通常是同类词,再加上几个出人意料的跨类别“擦边球”。

为什么用余弦,而不是欧氏距离?

余弦相似度度量两个向量之间的夹角,而夹角正是嵌入空间中编码语义方向的东西。欧氏距离度量完整的向量差,把方向和长度混在了一起。训练得到的向量长度取决于优化动态,并没有语义含义——两个同义词的范数可以差很远,方向却几乎相同。这就是余弦成为嵌入相似度标准的原因。有些检索系统会在预先归一化的向量上用 L2,那在数学上是等价的。

散点图用来定向,不用来测量

散点图里你看到的一切都受一条前提约束:它把 1024 维向量投影到仅仅两根轴上,丢掉了 1022 个维度的结构。屏幕上看着很近的两个点,在真实空间里可能离得很远,反之亦然。颜色分组依然会聚在一起(方差最大的几个方向往往能抓住它们),但距离会骗人——所以永远不要从图上读相似度。这正是最近邻浏览器在完整的 1024 维空间、而不是投影后的 2D 坐标里计算余弦相似度的原因。把散点图当成找聚类的导航图,而不是尺子。

余弦相似度,并排看

下面的面板完全跳过投影:每根条形给出该模型家族的一个大致余弦相似度——右侧的实时散点图用的才是真实嵌入。条形的排序就是要教的结论——完全相同 > 同义词 > 相关 > 反义词 > 跨语言 > 不相关。

余弦相似度 · 精选词对
示意性大致数值 · Qwen3.5-0.8B 风格
catcat
1.00
完全相同的 token——健全性检查。向量与自身的余弦恰好是 1。
cat·cat
0.72
同一个词,带不带前导空格是两个 token。很接近,但不相同——它们确实是两个 id。
kingqueen
0.65
同一语义角色下的类同义词对。
ParisFrance
0.58
类别不同(城市、国家),但在训练文本中紧密相关。
hotcold
0.48
反义词常常近得出人意料——它们出现在相似的语法槽位里。
dogperro
0.22
跨语言:概念相同,表面形式迥异。重叠很弱。
catstapler
0.18
不相关的普通名词。低但不为零——在模型眼里它们都只是名词。
xyzthrombosis
0.05
罕见且不相关。在嵌入空间中接近正交。

数值仅为示意;不同模型构建之间的实测可能相差约 ±0.1。要教的结论是*排序*:完全相同 > 同义词 > 相关 > 反义词 > 跨语言 > 不相关。

方向承载含义

到目前为止,我们一直把相似度当作两个完整向量之间的一个数字。但这个空间还有更细的结构:嵌入之间的可以表现得像特征。如果 E(woman) − E(man) 大致捕捉了“性别轴”,那么把这个差加到别的向量上,就应该让它沿同一条轴移动——这正是经典的 word2vec 把戏:E(king) − E(man) + E(woman) 应该落在 queen附近。减掉“男性”,加上“女性”,“王室”这部分会一路跟着过来。

在 2026 年的 LLM 嵌入矩阵里,这一招还灵吗?我们在这个 checkpoint 上实际测了一遍,诚实的回答是“灵,但要打个星号”。queen 本来就是 king 的第 4 名原始近邻——排在大小写和复数变体 KingKing(无前导空格)、kings 之后——所以这个算术并没有凭空召唤出 queen。它真正做到的是把 queen 提升到第 1 名,越过所有 king 变体:这个方向把“男性”成分剥掉了足够多,让女性对应词胜出。同一招还能推广:E(Paris) − E(France) + E(Japan)Tokyo 排到第 1——而且由于这个 248,320 个 token 的词表是多语言的,东京日本会和 OsakaKyoto 出现在同一份 top-10 里。

不是每条方向都这么上镜。复数方向 E(cats) − E(cat) 确实存在——复数名词与它的点积一致为正,单数徘徊在 0 附近——但数值非常微弱,理想中的“one < two < three”递增趋势几乎看不出来。而且这些方向本身也只是大致一致:woman − man queen − king 的余弦只有 0.292,远谈不上平行。这并不意外——这个矩阵是为“帮助预测下一个 token”训练的(输入/输出权重绑定,身兼两职),不是为了通过类比测验。线性的特征方向只是副产品,被这一个矩阵必须编码的其他一切弄花了。

嵌入空间中的方向
基于 Qwen3.5-0.8B 实测 · 原始 embed_tokens.weight
类比算术
E(king)E(man)+E(woman)→ 与结果向量最近的 token
  1. #1·queen0.578
  2. #2queen0.493
  3. #3·kings0.487
  4. #4·KING0.469
  5. #5·princess0.466
  6. #6Queen0.465
  7. #7King0.457
  8. #8國王0.433
  9. #9·Queen0.424
  10. #10·wanita0.423

诚实核查:·queen 本来就是 king 的第 4 名原始近邻——排在大小写/复数变体 ·KingKing(无前导空格)、·kings 之后。− man + woman 真正做到的,是把 queen 提升到第 1 名,越过所有 king 变体。点上面的开关对比一下。

复数方向——真实存在,但很微弱

plur = E(cats) − E(cat),再与其他词做点积(余弦)。复数名词的结果一致为正,单数接近 0 或为负——但数值都非常小。

单数复数
dog-0.05dogs+0.13
house0.00houses+0.07
car-0.10cars+0.07
book-0.02books+0.06
tree0.00trees+0.09
idea+0.02ideas+0.04

数词 one → five(理想中的“复数程度递增”趋势——几乎看不出来):

one-0.04
two-0.02
three0.00
four0.00
five+0.02

所有数字均由该 checkpoint 的 embed_tokens.weight([248,320 × 1,024])离线算出:进入任何 Transformer 层之前的原始输入嵌入,在完整词表上用 fp32 计算余弦,近邻列表排除了输入词本身。这种算术是 近似的,不是精确的——这个矩阵是为“预测下一个 token”训练的(且输入/输出权重绑定),不是为类比训练的。一个症状:cosine(woman − man, queen − king) = 0.292,两条“性别方向”只是大致平行而已。

为什么需要这么多维度?

如果只在 3D 里做这件事,几乎所有概念都会撞在一起。“动物”、“国家”、“颜色”、“动词”、“句法角色”、“语域”、“语言”以及另外十几条含义轴,根本没有空间在一个三轴空间里独立变化。Qwen3.5-0.8B 使用 1,024 个嵌入维度,给了模型相当宽裕的“房间”——每个独立特征都可以占据自己的方向,而不挤到别人。

接下来(自注意力)我们将看到这些向量在 Transformer 中是如何真正移动的——在位置之间搬运信息的那个操作,正是它让 "the cat sat on the mat" 的含义不同于 "the mat sat on the cat"。

工程要点
  • 嵌入矩阵约占一个小模型参数量的三分之一——它不是免费的查找表。
  • PCA 适合发现聚类,但它的距离会骗人,所以相似度要在完整的 hidden_dim 空间里算。
  • 输入/输出嵌入绑定迫使一个矩阵身兼两职,这会约束其几何结构能编码的内容。
动手练习

点击 Load embeddings,然后点一个动物,比如 'tiger'。再用输入框添加自定义词 'banana'。它的 top-5 最近邻列表偏向哪个已有类别?为什么?

随堂测验
1. 模型为什么把 token 嵌入成向量,而不是直接把整数 id 往后传?
2. 演示为什么在完整的 1024 维空间里找最近邻,而不是用 2D 的 PCA 坐标?
3. Qwen3.5 把输入嵌入与输出 lm_head “绑定”(tie)是什么意思?
4. 在 Qwen3.5 的原始嵌入矩阵里,king − man + woman 实际证明了什么?

动手试试

互动演示加载中……