构建中文版的 nanoGPT - 位置嵌入( Position Embedding)

flyfish
参考

https://github.com/shaoshengsong/nanoGPT-cn

Input_Embedding=Token_Embedding+Position_Embedding Input\_Embedding = Token\_Embedding + Position\_Embedding Input_Embedding=Token_Embedding+Position_Embedding

位置编码

先说位置

  1. 文本是有序序列,每一个字词、Token 在句子里都有固定先后顺序、排布顺位
  2. 注意力数学计算本身没有时序、顺序概念,模型分不清字词谁在前、谁在后、距离远近
  3. 位置 = 每个 Token 在序列里的顺序编号、先后次序、相对远近关系
  4. 位置信息,就是给每一个 Token 附加专属时序标识,让模型看懂文本语序

再说位置编码的样子

要分情况
单点位置:向量
连续序列整体、编码总表:矩阵
批量输入模型:三维张量

1 基础单元

单个序列位置,对应一维向量
维度等于模型嵌入维度 dmodeld_{model}dmodel

2 连续序列整体

一段长度为 TTT 的文本序列,全部位置编码组合,构成二维矩阵
形状:[序列长度T, 嵌入维度dmodel][序列长度T,\ 嵌入维度d_{model}][序列长度T, 嵌入维度dmodel]

3 全局编码存储表

提前预定义、存储全部可用位置编码,本身是大尺寸二维参数矩阵

  • 原生三角函数位置编码:预计算固定大矩阵,上限为最大支持长度
  • nanoGPT 可学习位置编码:wpe 本身就是可训练参数矩阵,尺寸 [block_size, n_embd][block\_size,\ n\_embd][block_size, n_embd]

4 批量训练形态

送入模型批量数据,拓展为三维张量
形状:[批次数量B, 序列长度T, 嵌入维度dmodel][批次数量B,\ 序列长度T,\ 嵌入维度d_{model}][批次数量B, 序列长度T, 嵌入维度dmodel]

原始的Transformer架构图有Postional Encoding,nanoGPT的是Position_Embedding

标准Transformer的固定位置编码不可训练的原因

没有使用 nn.Parameter()

可训练的参数必须用 nn.Parameter() 包裹
这里从头到尾没有出现一次 nn.Parameter
对比 nanoGPT:wpe = nn.Embedding(...) 内部就是 Parameter,所以可训练

使用了 register_buffer('pe', pe)

buffer 的定义:模型的固定常量/预计算数据
不会被优化器更新
不会参与反向传播梯度计算
保存模型时会跟着保存,但永远不会变

所有值都是数学公式一次性算死

位置向量是:sin / cos 公式 → 直接算出结果 → 锁死不变
和训练数据、loss、梯度完全无关

self.dropout

self.dropout = nn.Dropout(dropout)
Dropout 本身没有可训练参数!
它只是一个随机失活的运算规则,不参与学习,不影响位置编码。

nanoGPT 的位置嵌入流程 =
随机初始化位置嵌入矩阵
→ 输入序列生成位置索引
→ 通过嵌入层查找对应向量
→ 与词嵌入相加
→ 训练中通过反向传播优化位置向量
→ 最终学习到能捕捉语序信息的位置表示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 先有:标准Transformer的固定位置编码(不可训练)
  2. 后来演化:GPT/nanoGPT把它替换成了可学习的嵌入层(位置嵌入也就是 Position_Embedding)

叫位置编码还是叫位置嵌入

固定位置编码

最早原版 Transformer 采用这种方案
没有专门的嵌入参数层,依靠正弦、余弦数学公式直接计算出位置对应的向量。
向量数值一经算出就固定不变,不会跟着训练更新,这就是最早形态的位置编码。

位置嵌入

后续 GPT、nanoGPT 这类纯解码器模型,放弃公式计算
改用嵌入层存放位置对应的向量,这些向量是模型参数,能在训练中不断学习调整优化,这种形式就被称作位置嵌入。

广义上:位置编码是统称
不管是公式计算,还是参数学习得到的位置向量,只要用来标识序列先后顺序,都可以叫做位置编码。
狭义上:
公式算出、不可训练 → 习惯直接叫位置编码
嵌入层存储、可训练更新 → 习惯叫做位置嵌入
位置嵌入是位置编码的一种实现形式。

怎么叫都可以没毛病

nanoGPT的位置嵌入

self.transformer = nn.ModuleDict(dict(
    wte = nn.Embedding(config.vocab_size, config.n_embd),  # 词嵌入
    wpe = nn.Embedding(config.block_size, config.n_embd),  # 位置嵌入(重点!)
    drop = nn.Dropout(config.dropout),
    h = nn.ModuleList([Block(config) for _ in range(config.n_layer)]),
    ln_f = LayerNorm(config.n_embd, bias=config.bias),
))

这一行:

wpe = nn.Embedding(config.block_size, config.n_embd)
  1. wpe = Word Position Embedding
    = 位置嵌入层
    专门存每个位置应该长什么样的向量。

  2. nn.Embedding(…)
    PyTorch 里的查表层。 = 一个二维矩阵
    可以把它理解成:一本字典,每一行对应一个位置的向量

  3. config.block_size
    模型能接受的最长句子长度
    比如 1024
    → 位置编号:0,1,2,3,…,1023
    → 字典一共 1024 行

  4. config.n_embd
    每个位置向量的维度 比如 768
    → 每一行有 768 个数字

最终得到一张表:

形状:[1024 个位置,768 维向量]
这张表是模型参数,会跟着训练一起变。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐