我对 Embedding 模型的几个灵魂拷问

最近在学习向量检索,越研究越觉得 Embedding 这个东西"熟悉又陌生"。说熟悉,是因为它无处不在;说陌生,是因为一些看似简单的问题,我其实一直没想清楚。


Q1    维度之谜

为什么 Embedding 的维度总是 768、1536、384?为什么不是 2 的次幂?

这个问题我相信很多人都有过:明明计算机世界里"2 的次幂"无处不在,为什么 Embedding 的维度偏偏是这些"奇怪"的数字?

答案其实藏在 Transformer 的内部结构里。

Transformer 内部的维度公式

Transformer 的注意力机制有一个核心公式:

总维度 (d_model) = 注意力头数 (n_heads) × 每头维度 (d_head)

chart1_dimension_structure

图1:注意力头数 x 每头维度 = Embedding 维度,三种经典配置

你看,1536 不是"随便定"的,而是 24 × 64 = 1536,这是 Transformer 结构推导出来的自然结果。

768 同理:12 × 64 = 768,这是 BERT-Base 的经典配置。384 = 6 × 64,轻量级模型的常见选择。

为什么每头维度偏爱 64?

经验上,d_head = 64 在表达能力、GPU 并行效率和数值稳定性之间取得了很好的平衡——这也是为什么你在大量 Transformer 论文里都能看到这个数字。

GPU 根本不在乎 2 的次幂

chart_kkk

图2:现代 GPU 更在意能被 64 整除,而非 2 的次幂

深度学习中,GPU 的 Tensor Core 以 tile 为单位做矩阵乘法,常见 tile 大小为 16×16 或 64×64。因此:

d_model mod 64 = 0    比    d_model = 2^n    更重要!

!!! tip "一句话总结"
768、1536、384 等维度来源于 Transformer 的注意力结构,它们满足"能被 64 整除"的 GPU 优化要求,而不是来自"2 的次幂"这个传统计算机工程原则。

顺带一提,维度越大不一定越好——训练数据质量、对比学习设计、损失函数,这些才是决定 Embedding 模型效果的关键。高维有时反而带来更多冗余和噪声。


Q2    语义空间之谜

以前 Word2Vec 好像大家通用,为什么现在每个 Embedding 模型都有自己的"空间"?它们是怎么训练的?

静态词向量 vs 上下文感知向量

chart3_static_vs_contextual

​ 图3:静态词向量 vs 上下文感知向量*

Word2Vec 时代,"苹果"永远只有一个向量。不管是"苹果很甜"还是"苹果公司发布新品",向量完全相同。

这叫静态 Embedding(Static Embedding),代表:Word2Vec、GloVe、FastText。

大家之所以"通用",是因为词表是固定的,整个语义空间是全局共享的——像一本公开词典。

现代 Embedding 的根本变化:语义任务 × 上下文

现代 Embedding 追求的不再只是"词语编码",而是:

  • 句子语义
  • 文档语义
  • 检索语义
  • 多语言对齐
  • 推理语义

……不同任务需要不同的"语义空间结构",这就是为什么模型越来越多。

语义空间是怎么训练出来的?

现代 Embedding 的核心训练方法是对比学习(Contrastive Learning)

chart4_contrastive_learning

图4:对比学习的核心——拉近语义相关,推远语义无关

你可以把 Embedding 模型理解成一个"高维空间雕塑家"——它不断调整"哪些句子靠近,哪些句子远离",最终形成一个语义几何空间。

不同模型,本来就不在同一个坐标系

chart5_embedding_spaces

​ 图5:三种不同的 Embedding 模型,各自形成不同的"语义聚类结构"*

模型A 里"苹果" = [1.2, 0.8],模型B 里"苹果" = [-7.3, 91.2],两个都没问题。因为真正重要的不是绝对坐标,而是相对距离关系

这和地图投影非常像——墨卡托投影和球面投影坐标体系不同,但城市之间的相对关系仍然保留。

!!! warning "注意"
正因如此,A 模型生成的 query 和 B 模型生成的 document 通常不能混用——它们不在同一个语义空间里,混用效果会崩。

常见 Embedding 模型对比

模型代表 擅长方向
bge 系列 中文检索,BGE 特有指令格式
E5 系列 通用检索,多语言支持好
jina-embedding 长文本,支持 8K+ tokens
voyage-retrieval 企业级检索,质量高
code-embedding 代码语义搜索

!!! tip "一句话总结"
以前 Word2Vec 是"全世界共享一本词典"。现在的 Embedding 是"每个模型都在学习自己的语义几何空间"。这些空间没有统一坐标系,不要求绝对值一致,只要求"语义距离关系"正确。


Q3    上手实践

如果要自己训练一个 Embedding 模型,应该从哪里开始?

好消息是:现在自己训练一个"小型 Embedding 模型"已经没有以前那么难了。

三条训练路线对比

chart6_training_routes

图6:三条训练路线对比,推荐新手从 SentenceTransformer 微调入门

推荐方案:SentenceTransformer 微调

训练的核心目标:学习函数 f(x) → R^d,语义相近的输入映射后向量接近;语义不同的,向量远离。

第一步:准备训练数据

Embedding 训练数据本质是"相似句子对":

text1(锚点) text2(正样本)
我喜欢苹果 我爱吃水果
深度学习是什么 神经网络介绍
北京天气 今天北京下雨

第二步:运行最小可用代码

from sentence_transformers import SentenceTransformer, InputExample
from sentence_transformers.losses import MultipleNegativesRankingLoss
from torch.utils.data import DataLoader

# 1. 加载预训练中文模型
model = SentenceTransformer('BAAI/bge-small-zh')

# 2. 准备训练数据(句子对)
train_examples = [
    InputExample(texts=["我喜欢苹果", "我爱吃水果"]),
    InputExample(texts=["深度学习是什么", "神经网络介绍"]),
    InputExample(texts=["北京天气", "今天北京下雨"]),
]

# 3. 定义数据加载器和损失函数
train_dataloader = DataLoader(train_examples, shuffle=True, batch_size=2)
train_loss = MultipleNegativesRankingLoss(model)

# 4. 开始训练
model.fit(
    train_objectives=[(train_dataloader, train_loss)],
    epochs=1,
    warmup_steps=10
)

# 5. 保存模型
model.save("./my_embedding_model")

第三步:验证效果

model = SentenceTransformer("./my_embedding_model")
emb1 = model.encode("我喜欢苹果")
emb2 = model.encode("我爱吃水果")
print(emb1.shape)  # (384,)

from sklearn.metrics.pairwise import cosine_similarity
score = cosine_similarity([emb1], [emb2])
print(score)  # 相近句子分数高 -> 训练成功

!!! warning "关键提示"
大型 Embedding 模型(如 E5、bge-large)和小模型原理完全相同,区别只在于样本量(数亿 vs 数千)、batch size(32768 vs 16)和负样本池大小。

数据才是真正的关键

这是很多人忽视的点:

Embedding 效果上限 ≈ 70% 取决于数据质量

你训练 Embedding,本质上是在训练"什么叫接近"——你在定义语义空间里的距离函数。数据决定了这个距离函数的上限。


总结

  1. 768/1536/384 来自 Transformer 结构:n_heads × d_head
  2. 每个模型语义空间不同,因为训练目标不同,空间没有统一坐标系
  3. 入门推荐 SentenceTransformer 微调,关键在数据质量

最后修改:2026 年 05 月 10 日
如果觉得我的文章对你有用,请随意赞赏