计算机无法直接理解文字。当一个语言模型接收到输入"苹果"时,它看到的不是水果的形象,而是一个冰冷的数字——Token ID。Embedding层的工作,就是把这个离散的整数转换成连续的高维向量,让模型能够开始"理解"语言的语义。

这个看似简单的转换,却是整个大语言模型架构中最基础也最精妙的设计之一。它决定了模型如何将人类语言映射到数学空间,直接影响着模型的表示能力和计算效率。

从整数索引到稠密向量

假设词表大小为 $V=50000$,嵌入维度为 $d=4096$。Embedding层本质上是一个形状为 $V \times d$ 的查找表(Lookup Table),每一行对应词表中一个Token的向量表示。

当输入Token ID为 3 时,Embedding层并不是执行矩阵乘法,而是直接取出矩阵的第3行。这种实现方式被称为"Embedding Lookup",其时间复杂度是 $O(1)$——无论词表多大,取出某一行的代价都是常数级。

graph TD
    subgraph "输入层"
        A["Token序列: 今天 天气 很好"]
        B["Token IDs: [1024, 2048, 4096, 128]"]
    end
    
    subgraph "Embedding矩阵 E ∈ R^{V×d}"
        C["第0行: [0.1, -0.3, 0.5, ...]"]
        D["第1024行: [0.8, 0.2, -0.1, ...]"]
        E["第2048行: [-0.4, 0.6, 0.3, ...]"]
        F["第4096行: [0.7, -0.5, 0.9, ...]"]
        G["第128行: [0.2, 0.4, -0.8, ...]"]
        H["..."]
    end
    
    subgraph "输出层"
        I["向量序列 shape: [4, 4096]"]
    end
    
    A --> B
    B -->|"查找"| D
    B -->|"查找"| E
    B -->|"查找"| F
    B -->|"查找"| G
    D --> I
    E --> I
    F --> I
    G --> I

从数学角度,Embedding查找可以等价地理解为矩阵乘法:将Token ID表示为One-Hot向量(仅在第 $i$ 个位置为1,其余为0),然后与Embedding矩阵相乘。

$$\mathbf{e}_i = \mathbf{E}^\top \cdot \mathbf{one\_hot}(i)$$

其中 $\mathbf{E} \in \mathbb{R}^{V \times d}$ 是Embedding矩阵,$\mathbf{one\_hot}(i) \in \mathbb{R}^{V}$ 是第 $i$ 个Token的One-Hot表示。

这个等价性揭示了一个关键事实:Embedding层在数学上等同于一个没有偏置项的线性层,输入是One-Hot向量。但实际实现中,直接存储One-Hot向量极其浪费内存($V$ 维向量只有1个非零元素),因此所有框架都采用查找表实现。

graph LR
    subgraph "方法一: 查找表"
        A1["Token ID: 3"] --> B1["直接索引"]
        B1 --> C1["返回第3行"]
    end
    
    subgraph "方法二: 矩阵乘法"
        A2["Token ID: 3"] --> B2["构建One-Hot"]
        B2 --> C2["[0,0,0,1,0,0,...]"]
        C2 --> D2["矩阵乘法"]
        D2 --> E2["返回第3行"]
    end
    
    style A1 fill:#e1f5fe
    style C1 fill:#c8e6c9
    style E2 fill:#c8e6c9

参数量与内存占用

Embedding层是大语言模型中参数量最大的组件之一。以词表大小 $V=32000$、嵌入维度 $d=4096$ 为例:

$$\text{参数量} = V \times d = 32000 \times 4096 = 131,072,000 \approx 131\text{M}$$

以FP32存储,每个参数占4字节,单个Embedding矩阵就需要约524MB显存。如果采用FP16或BF16,则减半至262MB。

graph TD
    subgraph "Embedding矩阵内存计算"
        A["词表大小 V = 32,000"]
        B["嵌入维度 d = 4,096"]
        C["参数量 = V × d"]
        D["131,072,000 参数"]
        E["FP32: 4字节/参数"]
        F["内存 = 524 MB"]
        G["FP16/BF16: 2字节/参数"]
        H["内存 = 262 MB"]
    end
    
    A --> C
    B --> C
    C --> D
    D --> E
    E --> F
    D --> G
    G --> H
    
    style F fill:#ffcdd2
    style H fill:#c8e6c9

对于更大的模型,Embedding层的参数量更加惊人:

模型 词表大小 嵌入维度 Embedding参数量
BERT-base 30,522 768 23.4M
GPT-2 50,257 1,600 80.4M
GPT-3 175B 50,257 12,288 617.5M
LLaMA 65B 32,000 8,192 262.1M

值得注意的是,Embedding层不仅存在于输入端,还存在于输出端(语言模型头)。在许多实现中,这两个矩阵是共享权重的,称为Weight Tying。

权重共享:数学上的对称性

Weight Tying的核心思想是:输入Embedding将Token映射到语义空间,输出Embedding将语义空间映射回Token概率分布。如果输入和输出使用相同的映射矩阵,模型需要学习一种一致的表示。

graph LR
    subgraph "输入端"
        A["Token ID"] --> B["输入Embedding<br/>E_in"]
        B --> C["语义向量"]
    end
    
    subgraph "Transformer层"
        C --> D["多层Transformer<br/>处理"]
        D --> E["输出向量"]
    end
    
    subgraph "输出端"
        E --> F["输出Embedding<br/>E_out"]
        F --> G["概率分布"]
    end
    
    H["权重共享:<br/>E_in = E_out^T"] -.-> B
    H -.-> F
    
    style H fill:#fff9c4

设输入Embedding矩阵为 $\mathbf{E}_{in} \in \mathbb{R}^{V \times d}$,输出Embedding矩阵为 $\mathbf{E}_{out} \in \mathbb{R}^{d \times V}$(转置后)。Weight Tying要求 $\mathbf{E}_{in} = \mathbf{E}_{out}^\top$。

这种设计有两个主要好处:

参数效率:不共享权重时,Embedding相关的参数量为 $2 \times V \times d$;共享后减半为 $V \times d$。对于GPT-3规模的模型,这能节省约6亿参数。

正则化效果:权重共享强制模型对"相似含义"的Token在输入和输出端使用相似的表示,起到了隐式的正则化作用。经验研究表明,这能改善模型的泛化能力,特别是在低资源场景下。

但Weight Tying并非没有代价。理论上,输入和输出的最优Embedding空间可能不完全一致:输入Embedding需要编码语义信息以供下游层处理,输出Embedding需要支持区分性的概率预测。2024年发表在ICML的研究表明,对于超大词表(如256K以上),Weight Tying可能引入性能损失,此时独立参数或部分共享可能是更好的选择。

静态表示与上下文的第一次交汇

Embedding层输出的是静态向量——同一个Token无论出现在什么语境中,其初始表示完全相同。“苹果"在"我吃了一个苹果"和"苹果公司发布了新手机"中,经过Embedding层后是同一个4096维向量。

这种静态性是Embedding层的本质限制,也是后续Transformer层存在的根本原因。Transformer的Self-Attention机制负责将静态表示转化为上下文相关的表示。

sequenceDiagram
    participant T as Token序列
    participant E as Embedding层
    participant P as 位置编码
    participant A as Self-Attention
    participant F as FFN
    participant O as 输出

    T->>E: Token IDs [42, 128, 256]
    E->>E: 查找静态向量
    Note over E: "苹果"总是同一个向量
    E->>P: 加上位置编码
    P->>A: 上下文无关表示
    A->>A: 计算注意力权重
    A->>A: 聚合上下文信息
    Note over A: 此时"苹果"获得上下文
    A->>F: 上下文相关表示
    F->>O: 最终表示

从表示能力角度,Embedding层捕获的是"类型的语义”——什么是一个词的本质含义;Self-Attention捕获的是"token的语义"——这个词在当前语境中的具体含义。

BERT论文中的一个经典实验量化了这种差异:研究者将BERT的上下文相关表示与静态Word2Vec向量进行对比,发现平均只有不到5%的方差可以被静态向量解释。这意味着Transformer学到的绝大部分表示能力来自上下文处理,而非初始Embedding。

这引出一个有趣的问题:如果Embedding层如此"贫瘠",为什么不直接用随机初始化,让模型从零开始学习?

Embedding是如何被学习的

Embedding层的权重通过反向传播与整个模型一起训练。当模型预测错误时,损失函数产生的梯度会一路传回Embedding层,调整其权重矩阵中对应Token的向量。

梯度传播的关键在于:对于任何给定的训练样本,只有输入序列中实际出现的Token对应的Embedding行会收到梯度更新。这被称为"稀疏梯度"特性。

graph TD
    subgraph "前向传播"
        A["输入: 今天 天气 很好"] --> B["Token IDs: [1024, 2048, 4096, 128]"]
        B --> C["Embedding查找"]
        C --> D["Transformer层"]
        D --> E["预测: 明天 晴"]
    end
    
    subgraph "反向传播"
        F["损失计算"] --> G["梯度回传"]
        G --> H["只更新4行"]
    end
    
    E --> F
    
    subgraph "Embedding矩阵"
        I["第1024行 ✓ 更新"]
        J["第2048行 ✓ 更新"]
        K["第4096行 ✓ 更新"]
        L["第128行 ✓ 更新"]
        M["其他行 ✗ 不更新"]
    end
    
    H --> I
    H --> J
    H --> K
    H --> L
    
    style I fill:#c8e6c9
    style J fill:#c8e6c9
    style K fill:#c8e6c9
    style L fill:#c8e6c9
    style M fill:#ffcdd2

假设输入序列是"今天天气很好",Token ID为 $[1024, 2048, 4096, 128]$,反向传播时只有Embedding矩阵的第1024、2048、4096、128行会被更新,其余行的梯度为零。

这个特性对大规模词表至关重要。如果词表有100万Token,但每个训练样本只包含几百个Token,那么每次更新只涉及词表的万分之一。这使得训练大词表模型在计算上是可行的。

稀疏梯度的代价是优化器选择的限制。标准Adam优化器不支持稀疏梯度,需要使用SparseAdam或SGD。这解释了为什么一些大模型训练使用SGD而非Adam。

Embedding的学习动态还有一个特殊之处:不同Token被更新的频率差异极大。高频Token(如"的"、“是”)几乎在每个训练样本中出现,其Embedding向量被更新数十亿次;低频Token可能在整个训练过程中只出现几次,其Embedding几乎保持随机初始化状态。

这种不平衡可能导致表示质量的差异。2018年的一项研究发现,将Embedding学习率设置为其他层的10倍(即Embedding层使用更大的学习率),能改善低频Token的表示质量。这个技巧在机器翻译和低资源语言模型训练中被广泛采用。

嵌入维度:权衡的艺术

嵌入维度 $d$ 是一个关键的超参数,它决定了语义空间的"分辨率"。

维度过低会限制表示能力。如果 $d=64$,模型只能区分有限数量的语义概念,相似的词会被迫映射到相近的向量。这类似于用8位颜色表示图像——能看,但细节丢失严重。

维度过高则带来内存和计算开销。嵌入维度直接决定了Transformer中所有中间层的维度(通常 $d_{model} = d$),影响整个模型的参数量。更重要的是,Self-Attention的计算复杂度是 $O(n^2 \cdot d)$,维度增大会显著增加计算成本。

graph LR
    subgraph "维度选择权衡"
        A["维度过低 d=64"] --> B["优点: 内存小<br/>计算快"]
        A --> C["缺点: 表示能力弱<br/>语义混淆"]
        
        D["维度适中 d=4096"] --> E["优点: 平衡表示能力<br/>与计算成本"]
        D --> F["缺点: 需要更多显存"]
        
        G["维度过高 d=16384"] --> H["优点: 表示能力强"]
        G --> I["缺点: 计算昂贵<br/>可能过拟合"]
    end
    
    style A fill:#ffcdd2
    style D fill:#c8e6c9
    style G fill:#ffcdd2

实践中的维度选择遵循模型规模的缩放定律。经验公式大致为:

$$d \approx \sqrt{\frac{N}{12 \times L}}$$

其中 $N$ 是模型参数量,$L$ 是层数。这个公式源于Transformer参数量的近似计算(忽略归一化层和偏置项):

$$N \approx 12 \times L \times d^2$$
模型规模 典型层数 嵌入维度
125M 12 768
1.3B 24 2048
7B 32 4096
70B 80 8192
175B 96 12288

一个容易被忽视的约束:嵌入维度必须能被注意力头数整除。这是因为多头注意力将嵌入维度分割给各个头。如果 $d=4096$,使用32个注意力头,每个头的维度是128;如果 $d$ 不能被头数整除,则需要额外的投影层调整,增加计算开销。

初始化策略

Embedding层的初始化对训练稳定性有重要影响。最常用的策略是从正态分布中随机采样,方差与嵌入维度相关:

$$\mathbf{E}_{ij} \sim \mathcal{N}\left(0, \frac{1}{\sqrt{d}}\right)$$

这是Xavier初始化的变体,确保前向传播时激活值的方差保持在合理范围。

对于预训练模型,Embedding初始化有更精细的策略。一些工作建议使用预训练的静态词向量(如Word2Vec或GloVe)初始化Embedding层,这能加速收敛,特别是在小数据场景。但随着预训练数据规模的扩大,这种做法的重要性下降——在大规模数据上从头训练的Embedding通常能学到更好的表示。

一个有趣的工程细节:PyTorch的nn.Embedding默认使用标准正态分布 $\mathcal{N}(0, 1)$ 初始化,而非Xavier初始化。这意味着在实际训练中,学习率warmup对Embedding层的稳定训练尤为重要。

从Embedding到注意力:Q、K、V的来源

Embedding层的输出经过位置编码后,进入Transformer的第一个子层——Self-Attention。这里发生了一个关键变换:静态Embedding被投影为Query、Key、Value三个向量。

$$\mathbf{Q} = \mathbf{X}\mathbf{W}_Q, \quad \mathbf{K} = \mathbf{X}\mathbf{W}_K, \quad \mathbf{V} = \mathbf{X}\mathbf{W}_V$$

其中 $\mathbf{X}$ 是Embedding层的输出(加上位置编码),$\mathbf{W}_Q, \mathbf{W}_K, \mathbf{W}_V \in \mathbb{R}^{d \times d}$ 是可学习的投影矩阵。

graph LR
    subgraph "输入"
        A["Embedding输出<br/>X ∈ R^{n×d}"]
    end
    
    subgraph "QKV投影"
        B["W_Q ∈ R^{d×d}"]
        C["W_K ∈ R^{d×d}"]
        D["W_V ∈ R^{d×d}"]
    end
    
    subgraph "输出"
        E["Query Q<br/>'我需要什么'"]
        F["Key K<br/>'我能提供什么'"]
        G["Value V<br/>'我的内容'"]
    end
    
    A -->|"矩阵乘法"| B
    A -->|"矩阵乘法"| C
    A -->|"矩阵乘法"| D
    B --> E
    C --> F
    D --> G
    
    style E fill:#bbdefb
    style F fill:#c8e6c9
    style G fill:#ffe0b2

这三个投影矩阵的作用是将Embedding空间"分化"为三个不同的子空间:

  • Query空间:编码"我需要什么信息"
  • Key空间:编码"我能提供什么信息"
  • Value空间:编码"我的实际内容是什么"

Embedding层学到的语义表示通过这些投影被赋予不同的"角色",使Self-Attention能够建模Token之间的复杂关系。Embedding质量直接影响这三个投影的效果——如果初始表示本身就缺乏语义区分度,投影后的问题会更加严重。

最新研究:扩展Embedding的边界

2025年Google Research提出的SCONE(Scalable Contextualized Offloaded N-gram Embedding)方法,尝试在不增加主显存的情况下扩展Embedding的表示能力。

核心思想是引入N-gram Embedding作为补充。对于常见的词组(如"机器学习"、“深度学习”),除了单个Token的Embedding外,还存储一个N-gram级别的Embedding。这些N-gram Embedding存储在CPU内存中,需要时再加载到GPU。

graph TD
    subgraph "传统方法"
        A["Token Embedding"] --> B["GPU显存"]
    end
    
    subgraph "SCONE方法"
        C["Token Embedding"] --> D["GPU显存"]
        E["N-gram Embedding"] --> F["CPU内存"]
        F -->|"按需加载"| D
    end
    
    subgraph "效果"
        G["相同GPU占用"]
        H["更丰富的表示"]
        I["困惑度降低3-5%"]
    end
    
    D --> G
    D --> H
    E --> H
    H --> I
    
    style F fill:#fff9c4
    style I fill:#c8e6c9

这种方法在保持固定GPU显存占用的同时,提升了模型对常见词组的表示能力。实验显示,在相同计算预算下,SCONE能将困惑度降低3-5%。

另一个值得关注的方向是自适应Embedding。传统方法为所有Token分配相同维度的向量,但不同Token的信息密度差异很大。高频功能词(如"的"、“是”)可能只需要较少维度就能充分表示,而低频实词可能需要更高维度。自适应Embedding根据Token频率动态分配维度,在不损失表示能力的前提下节省内存。

实践中的考量

词表大小选择:更大的词表意味着更少的Token数量(每个Token承载更多信息),但Embedding层参数量线性增长。需要权衡压缩效率与模型容量。当前主流大模型的词表在30K-100K之间。

量化与压缩:Embedding层是量化的常见目标。将FP32 Embedding量化为INT8或INT4,能将内存占用减少4-8倍。研究表明,Embedding层对量化相对鲁棒——相比于Attention层,量化的精度损失更小。

微调策略:在下游任务微调时,Embedding层通常需要特殊处理。一些实践建议对Embedding层使用较小的学习率,或完全冻结Embedding层只微调上层。这在数据分布与预训练数据差异较大时尤其重要。

处理未登录词:对于不在词表中的Token,通常映射到特殊的[UNK] Token。更好的做法是使用子词分词(如BPE),将未登录词拆分为已知子词的组合,保证每个Token都有有意义的Embedding。

Embedding层作为语言模型的第一站,将离散的符号世界转换为连续的数学空间。这个转换的深度和质量,决定了后续所有计算的起点。理解Embedding层的工作原理,是理解整个大语言模型架构的基础。


参考资料

  1. Bengio, Y., et al. (2003). A Neural Probabilistic Language Model. Journal of Machine Learning Research, 3, 1137-1155.
  2. Mikolov, T., et al. (2013). Distributed Representations of Words and Phrases and their Compositionality. NeurIPS 2013.
  3. Vaswani, A., et al. (2017). Attention Is All You Need. NeurIPS 2017.
  4. Press, O., & Wolf, L. (2017). Using the Output Embedding to Improve Language Models. EACL 2017.
  5. Devlin, J., et al. (2019). BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding. NAACL 2019.
  6. Kitaev, N., et al. (2020). Reformer: The Efficient Transformer. ICLR 2020.
  7. Brown, T., et al. (2020). Language Models are Few-Shot Learners. NeurIPS 2020.
  8. Hoffmann, J., et al. (2022). Training Compute-Optimal Large Language Models. arXiv:2203.15556.
  9. Chowdhery, A., et al. (2023). PaLM 2 Technical Report. arXiv:2305.10403.
  10. Zhang, C., et al. (2025). Scaling Embedding Layers in Language Models. NeurIPS 2025.
  11. PyTorch Documentation. nn.Embedding. https://pytorch.org/docs/stable/generated/torch.nn.Embedding.html
  12. Sebastian Raschka. Embeddings and Linear Layers. https://sebastianraschka.com/llms-from-scratch/ch02/03_bonus_embedding-vs-matmul/