从一条直线到大模型输出一个token(三):隐藏层与嵌入

建议先看:从一条直线到大模型输出一个token(二):分词与token表

上一篇末尾,我们把"今天西安的天气怎么样?"变成了一串干巴巴的整数: [ 5237 ,   23872 ,   301 ,   16652 ,   19602 ,   1148 ] [5237,\ 23872,\ 301,\ 16652,\ 19602,\ 1148] [5237, 23872, 301, 16652, 19602, 1148]。这一篇解决一个问题:这些编号没有任何语义,模型怎么"读懂"它们?

1. 先看全局:大模型是一条流水线

在钻进细节之前,先把大模型的整体轮廓画出来。剥掉所有花哨的概念,一个大模型就是一条四段流水线,如图 3-1 所示。

大模型整体流水线

图3-1 大模型整体流水线:本篇位置(第一站:嵌入层)

  • 第一段:token 数组(上一篇的产出,6 个整数)
  • 第二段:嵌入层,把 6 个整数变成 6×4096 的数字矩阵(本篇主角)
  • 第三段:几十层 Transformer,逐层加工语义(第 4~9 篇逐层拆)
  • 第四段:输出层,把最后一层的结果变成 12.8 万个词的概率(第 10 篇)

不同 Transformer 层之间的处理方式,一句话概括:上一层的输出矩阵,就是下一层的输入矩阵。数据从头流到尾,形状基本不变(6×4096 进,6×4096 出),但每一层都在悄悄改写矩阵里的数字——离输出越近,数字里编码的语义越"深"。

这一篇只讲第一站:嵌入层。

2. 模型的宽度:一个 token 用多少个数字表示

2.1 直觉:字用笔画描述,token 用什么描述

一个汉字可以用笔画描述("鑫"有 24 画,全是金),一个 token 呢?

模型的世界里,一个 token 的全部信息就是一串数字。问题是:用多少个数字

  • 用 1 个数:只能表达"重要/不重要"这种一维信息,太穷了
  • 用 10 个数:能表达 10 个独立的特征,勉强
  • 用 4096 个数:每个数字都是一个独立维度的特征,够用

这个数字就是模型宽度(d_model),当前主流大模型清一色 4096:LLaMA-3-8B、GLM-4、DeepSeek-V3(系列基线)都是。宽度越大的模型,单个 token 能携带的语义信息越丰富——这也是"大模型"大的含义之一。

2.2 为什么不能直接用 token id

有个绕不开的问题:为什么不直接把 token id(比如 5237)当输入,非要变成 4096 个数字?

因为 id 只是个编号,编号本身没有语义。5236 和 5237 相差 1,5237 和 5238 也相差 1,但它们对应的词可能一个是",“一个是"天气”——编号的距离和语义的距离毫无关系。更麻烦的是,id 的数值大小还会干扰矩阵运算(下一篇会看到,模型会误以为 5238 比 5237"更重要")。

2.3 蠢办法:one-hot 编码

真要硬用编号,标准的做法是 one-hot:把每个 token 表示成一个 129,280 维的向量(词表多大就多少维),只有自己那个位置是 1,其他全是 0。“今天”(id=5237)就是第 5237 位是 1 的超长向量。

这办法有两个致命伤,如图 3-2 所示。

one-hot对比Embedding

图3-2 为何需要嵌入层:one-hot 的两个致命伤 vs 稠密向量

  1. 任何两个词的距离都一样远。one-hot 向量之间两两垂直:「今天」和「天气」的距离,等于「今天」和「?」的距离。语义信息为零。
  2. 维度爆炸。一个 token 要占 129,280 个数字的存储,6 个 token 就是 6×129,280——而其中 99.997% 都是 0。

2.4 聪明办法:稠密向量(隐藏层登场的时刻)

解决方案:给每个 token 配 4096 个训练出来的实数,让每个数字都承载一点语义信息。这就是稠密向量(dense vector),也叫词向量(word embedding)。

回头看第一篇的"升维"思想:一条直线(1 维)不够用就升到平面(2 维)、升到高维。这里一模一样——1 个编号不够表达语义,就升到 4096 维,让每一维都贡献一点语义。4096 维的空间里,「今天」和「天气」可以靠得很近,「今天」和「?」可以离得远——距离有了含义。

这个 4096 维向量所在的地方,术语叫隐藏层(hidden layer / hidden state)。"隐藏"的意思很朴素:它是模型的内部状态,用户看不见,只存在于模型肚子里的层层计算之间。

3. 语义空间的魔法:国王 − 男人 + 女人 ≈ 女王

隐藏层最迷人的性质,是语义可以做"几何运算"。

把每个词的向量画在语义空间里(演示用 2 维,真实 4096 维),会发现一个惊人的规律:语义类比关系 = 空间中的平行向量,如图 3-3 所示。

国王女王几何图

图3-3 语义空间的几何运算:国王−男人+女人≈女王

具体过程:

国王 − 男人 + 女人 ≈ 女王 \text{国王} - \text{男人} + \text{女人} \approx \text{女王} 国王男人+女人女王

  1. 「男人 → 女人」这个箭头,方向代表"性别"这个语义维度
  2. 「男人 → 国王」这个箭头,方向代表"王室"这个语义维度
  3. 从「国王」出发,加上"性别方向"的箭头,落点附近最近的词就是「女王」

这个著名例子(word2vec 时代就发现)说明了一件事:隐藏层学到的不是编号,而是把语义编码成了几何关系。男人/女人共享"人类"维度、国王/女王共享"王室"维度——这些规律没有人教,纯粹从海量文本的统计共现中学出来。

顺便回答大纲里的问题——隐藏层解决了什么问题:它把"离散的、无语义的 token id"翻译成了"连续的、有几何结构的语义向量"。没有这一步,后面的注意力机制(第 7 篇)根本无从谈起——注意力要计算"词和词的相关性",而相关性就是向量距离,id 之间没有距离可言。

4. 嵌入层:一张 5.3 亿参数的查询表

4.1 嵌入层的真面目:就是个查表

说明:本系列的词表规模取自 DeepSeek-V3——其 token 词表大小为 129,280(128K),数据来源是 HuggingFace 官方模型仓库的 config.jsonvocab_size 字段:deepseek-ai/DeepSeek-V3。后续篇章涉及词表规模的计算,都以这个数字为准。

把 129,280 个 token 各配一个 4096 维向量,需要的存储就是一张大表(DeepSeek-V3 词表 129,280 行,宽度 4096):

E ∈ R 129280 × 4096 \mathbf{E} \in \mathbb{R}^{129280 \times 4096} ER129280×4096

参数量的算法: 129,280 × 4096 = 529,530,880 ≈ 5.3 129{,}280 \times 4096 = 529{,}530{,}880 \approx 5.3 129,280×4096=529,530,8805.3 亿个实数参数(每个数用 2 字节半精度浮点存储,约 1GB)。这就是嵌入层(Embedding Layer)。术语听起来玄乎,实现却朴素得让人失望:

**嵌入层就是一张查询表:token id 是行号,输入 id,返回那一行的 4096 个数字。**没有乘法,没有激活函数,就是查表。

注意区分两件事:整张嵌入表 E 是 129,280×4096(5.3 亿参数,常驻显存)对一句 6 token 的话,查表输出只是 6×4096(24,576 个数)——它只是从大表里捞出 6 行。

用代码描述(大模型实现细节,跳过不影响理解):

# 嵌入层的伪代码:一张大表 + 一次查表
class Embedding:
    def __init__(self, vocab_size=129280, d_model=4096):
        self.table = random_init(vocab_size, d_model)  # 训练前随机初始化:129,280×4096

    def forward(self, token_ids):        # 输入: [5237, 23872, 301, 16652, 19602, 1148] 长度 6
        return self.table[token_ids]     # 查表: 按 6 个行号取出 6 行 → 6×4096 矩阵

table[token_ids] 就是全部——token id 当行号用,六行拼成一个矩阵。

4.2 贯穿案例:6 个 token 的查表全过程

把我们贯穿案例的 6 个 token 代入,完整过程如图 3-4 所示。

Embedding查表

图3-4 嵌入层=查表:6个token id → 6×4096矩阵(演示6×4)

id=5237 查第 5237 行,拿到「今天」的向量 [ 0.32 , − 1.07 , 0.88 , 0.05 ] [0.32, -1.07, 0.88, 0.05] [0.32,1.07,0.88,0.05](演示值,真实是 4096 个数);id=23872 查第 23872 行,拿到「西安」的向量……6 次查表,拼出矩阵:

X = [ 0.32 − 1.07 0.88 0.05 − 0.58 0.91 0.27 − 0.72 0.41 − 0.22 0.95 0.18 − 0.41 1.22 − 0.19 0.66 0.77 0.15 − 0.93 0.44 − 0.12 0.87 0.33 − 1.05 ] \mathbf{X} = \begin{bmatrix} 0.32 & -1.07 & 0.88 & 0.05 \\ -0.58 & 0.91 & 0.27 & -0.72 \\ 0.41 & -0.22 & 0.95 & 0.18 \\ -0.41 & 1.22 & -0.19 & 0.66 \\ 0.77 & 0.15 & -0.93 & 0.44 \\ -0.12 & 0.87 & 0.33 & -1.05 \end{bmatrix} X= 0.320.580.410.410.770.121.070.910.221.220.150.870.880.270.950.190.930.330.050.720.180.660.441.05

shape 从 ( 6 , ) (6,) (6,) 变成 ( 6 , 4 ) (6, 4) (6,4)(真实规模 ( 6 , 4096 ) (6, 4096) (6,4096))。从此刻起,模型操作的对象从"整数"升级成了"矩阵"——第一篇学的矩阵乘法,从下一篇开始就要真刀真枪地上了。

4.3 这些数字哪来的:训练

表里的 5.3 亿个数字不是人填的,是训练出来的。训练开始前随机初始化;训练过程中,模型每次预测错了(比如该输出"晴"却输出了"雨"),误差会一路反传回嵌入表,微调每个 token 的向量——「西安」的向量会被推向"地名"语义区,「天气」的向量被推向"气象"语义区。几万亿 token 训练下来,这张表就成了一部"语义地图"。

这也解释了为什么嵌入层是模型参数量的重要组成部分:5.3 亿参数,约占一个 8B 模型总参数的 6.6%。

5. 嵌入矩阵的两个"不知道"

嵌入层解决了语义问题,但留下了两个大坑——这正是下一篇文章存在的理由。

坑一:矩阵不知道顺序。做个实验:把 X 的第 1 行(今天)和第 4 行(天气)交换,得到 X′,如图 3-5 所示。

矩阵无顺序

图3-5 嵌入矩阵的两个「不知道」:不知道顺序,也不知道语义

对矩阵乘法来说,X 和 X′ 只是被当作两张行排列不同的表,每行内容不变,后续计算对每一行的处理方式完全一样。“今天在天气前面"这个信息,矩阵本身完全没记录。比如"我吃苹果"和"苹果吃我”,同样的三个词,顺序不同意思就不同(一个是陈述,一个荒唐),但矩阵 X 完全分不出这两种情况。

坑二:矩阵里还没有"上下文"。第 3 行的「今天」向量,在整句话的语境里应该是"提问时间背景"的意思;但如果单看这一行,它和「今天吃了什么」里的「今天」是同一个向量。一个词的具体含义,要结合上下文才能确定——这个"结合上下文"的活儿,嵌入层干不了,得靠后面的 Transformer。

小结

这一篇,token 从整数进化成了矩阵:

  • 模型宽度 d_model=4096:一个 token 的语义用 4096 个数字表示,宽度是"大模型"之大的来源之一
  • one-hot 的致命伤(距离无意义+维度爆炸)逼出了稠密向量方案——隐藏层
  • 隐藏层把语义编码成几何关系:国王−男人+女人≈女王
  • 嵌入层=一张 129,280×4096 的查询表(约 5.3 亿参数),id 当行号,查表即得向量
  • 贯穿案例: [ 5237 , 23872 , 301 , 16652 , 19602 , 1148 ] [5237, 23872, 301, 16652, 19602, 1148] [5237,23872,301,16652,19602,1148] 查表成 6×4096 矩阵(演示 6×4)
  • 留下两个坑:矩阵不知道顺序、不知道上下文

下一篇预告

矩阵不知道顺序?那就手动把顺序信息加进去

下一篇讲位置编码(RoPE)——给每个 token 的向量打上"我在第几位"的标记。会看到三代方案的演进:正弦余弦绝对位置编码(原始 Transformer,2017)→ 可学习位置编码(GPT-2 时代)→ 旋转位置编码 RoPE(当前所有主流大模型的事实标准)。并且第一次用第一篇学的矩阵乘法,给我们的 6×4 矩阵亲手算一遍位置编码。

系列目录:

  1. 从一条直线到高维空间
  2. 分词与 token 表
  3. 隐藏层与嵌入(当前篇)
  4. 位置编码 RoPE
  5. Transformer Block 全景与层归一化
  6. QKV 三剑客
  7. 注意力权重与多头机制
  8. 残差连接与前馈网络
  9. 输出矩阵与多层堆叠
  10. 首 token 诞生与 KV-Cache

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

更多推荐