> For the complete documentation index, see [llms.txt](https://yeasy.gitbook.io/llm_internals/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yeasy.gitbook.io/llm_internals/di-yi-bu-fen-ji-chu-pian/04_position_encoding/4.2_learnable.md).

# 4.2 可学习位置编码：灵活性与局限

GPT、BERT 没有沿用正弦编码，而是给每个位置分配一个可训练的向量。[4.1.7 节](/llm_internals/di-yi-bu-fen-ji-chu-pian/04_position_encoding/4.1_sinusoidal.md)交代过原论文的口径：两种编码在翻译任务上结果几乎相同，选择正弦的理由只有一条，即它可能支持外推。本节把可学习方案的账算清楚：它花了多少参数、长度上限到底卡在哪里、越界时框架的真实表现是什么。

## 4.2.1 机制：一张表，一次查，一次相加

**可学习位置编码**（Learned Positional Embedding）就是一张普通的嵌入表 $$P \in \mathbb{R}^{L\_{\max} \times d\_{\text{model}}}$$，$$L\_{\max}$$ 是预设的最大序列长度。位置 $$pos$$ 的编码是这张表的第 $$pos$$ 行，与词嵌入相加后送入第一层：

$$X^{(0)} = \text{wte}\[\text{ids}] + \text{wpe}\[\text{position\_ids}]$$

按 [3.8.3 节](/llm_internals/di-yi-bu-fen-ji-chu-pian/03_components/3.8_gpt_inference_flow.md)的写法，形状是 `ids[T] → 词嵌入[T, d_model]`，`position_ids[T] → 位置向量[T, d_model]`，两者逐格相加得到 `X⁽⁰⁾[T, d_model]`。GPT-2 Small 的具体形状是 `wte[50257, 768]` 与 `wpe[1024, 768]`，同 3.8 表 3-17 的教学模型对齐时，把 768 换成 4、把 50,257 换成教学模型的 5 个候选词即可。

`position_ids` 从哪来是实现里最容易出错的一处。Prefill 时它是 `arange(T)`；带 KV 缓存解码时，新词元只有一个，它的位置号必须从已缓存的长度起算，即 `arange(1) + past_seen_tokens`。GPT-2 的实现正是这样写的。缓存长度算错一位，整条序列的位置就整体错位，而模型不会报错，只会变笨。序列打包（见 [6.4 节](/llm_internals/di-er-bu-fen-xun-lian-pian/06_training_techniques/6.4_batch_sequence.md)）时同理：几段互不相关的文本拼进同一行，每段的 `position_ids` 都要从 0 重新开始，否则第二段会被当成第一段的续写。

梯度只流向被取到的那些行。一个批次里出现的最长序列有多长，这一步就只更新前多少行；其余行的梯度是零。

## 4.2.2 它花了多少参数

位置表的参数量就是 $$L\_{\max} \times d\_{\text{model}}$$，与层数无关。表 4-3 给出三个公开配置。

| 模型          | 位置表形状          |        参数量 |       模型总参数 |     占比 |
| ----------- | -------------- | ---------: | ----------: | -----: |
| GPT-2 Small | 1,024 × 768    |    786,432 | 124,439,808 |  0.63% |
| BERT-base   | 512 × 768      |    393,216 | 109,482,240 |  0.36% |
| GPT-3 175B  | 2,048 × 12,288 | 25,165,824 |   约 1,750 亿 | 0.014% |

表 4-3：可学习位置表的参数量。形状取自各模型公开的 `config.json`（GPT-2 的 `n_positions`、`n_embd`；BERT 的 `max_position_embeddings`、`hidden_size`）与 GPT-3 论文表 2.1 的 `d_model` 和 2,048 上下文。总参数量按配置里的形状逐项累加得到：GPT-2 Small 为词表 38,597,376、位置表 786,432、12 层各 7,087,872 与末端归一化 1,536 之和；BERT-base 含词表、位置表、段嵌入、12 层与 pooler。

结论是位置表根本不贵。真正的代价不在参数量，而在 $$L\_{\max}$$ 被写死在了权重的形状里。

## 4.2.3 学到了什么，哪些行没学好

模型确实能学到位置的连续性：对训练好的位置表做可视化，通常能看到相邻行的向量彼此接近，远离的行逐渐分开。这一观察本书未核实到一手出处，但很容易自行复核——位置表就是一个 `[L_max, d_model]` 的矩阵，加载权重后算一遍行与行的余弦相似度即可。

更值得关注的是哪些行没学好。第 $$i$$ 行只在出现了长度大于 $$i$$ 的样本时才拿到梯度。预训练语料的长度分布通常严重偏短，于是靠后的行更新次数远少于靠前的行。这带来一个隐蔽的失效：模型的名义上限是 512 或 1,024，但接近上限的那几百行可能只被训练过很少次，质量不如前面的行。它不会报错，只表现为长输入上的性能下滑。相比之下，“第 513 行不存在”这类硬边界反而容易发现。

## 4.2.4 长度上限写在权重形状里

超出 $$L\_{\max}$$ 时发生的不是性能下降，而是查表越界。`nn.Embedding` 会直接抛出索引错误，框架层面就走不下去。实际工程有三种处理方式：

1. **截断**：把输入裁到 $$L\_{\max}$$ 以内，最常见，代价是丢掉内容。
2. **滑窗分块**：把长文切成若干段各自处理，段与段之间的依赖只能靠拼接策略补，缺点是跨段信息丢失。
3. **插值位置表**：把 $$L\_{\max}$$ 行按需插值成更多行，再用长数据微调。ViT 改输入分辨率时用的就是这一招，与 [4.3.5 节](/llm_internals/di-yi-bu-fen-ji-chu-pian/04_position_encoding/4.3_rope.md)的位置内插是同一个思路，只是作用在表上而不是角度上。

三种办法都要付出代价，这正是相对位置方案的动机。

## 4.2.5 对到实现

读源码时会遇到以下名字。GPT-2 的配置键是 `n_positions`（等价写法 `max_position_embeddings`），模块名是 `transformer.wpe`，与词嵌入 `transformer.wte` 并列。BERT 的配置键是 `max_position_embeddings`，模块名是 `embeddings.position_embeddings`；它还多一张 `token_type_embeddings`（`type_vocab_size` 为 2），三张表相加后再过一次 LayerNorm 和 Dropout，而 GPT-2 只相加两张表。

OPT 有一处容易踩的偏移：它的位置表长度是 $$L\_{\max} + 2$$，查表时位置号统一加 2，且位置号由 `cumsum(attention_mask) * attention_mask - 1` 算出，从而跳过左侧填充。多乘的那一次掩码不能省：省掉它，掩码为 0 的位置会继承前一格的累加值，而不是统一归到 −1。照抄别的模型的 `position_ids` 会错两位。

可学习绝对位置并未退场。GPT-3、OPT 用它；Whisper 的解码器用它（编码器用的是关掉梯度的正弦值）；ViT 及其后续视觉模型也用它。共同点是长度固定或有明确上限，此时“写死上限”不构成问题。

## 4.2.6 从绝对到相对

正弦编码和可学习编码都是**绝对位置编码**：每个位置号对应一个向量，这个向量与它和谁配对无关。把 [4.1.5 节](/llm_internals/di-yi-bu-fen-ji-chu-pian/04_position_encoding/4.1_sinusoidal.md)的四项展开拿过来，就能看清这种写法的结构性问题。位置 $$m$$ 的查询与位置 $$n$$ 的键打分时，分数里含有 $$p\_m$$ 与 $$p\_n$$ 各自的三项；把同一个片段从句首整体平移到句中，$$m$$ 和 $$n$$ 同时增加，这三项全部改变，只有内容-内容那一项不变。

语言里真正稳定的却是相对关系。“猫吃鱼”出现在开头还是中间，“猫”与“吃”的依存关系不变。理想的方案应当让分数只依赖 $$m - n$$。下一节的 RoPE 给出的正是这样一个构造：它保留 [4.1.4 节](/llm_internals/di-yi-bu-fen-ji-chu-pian/04_position_encoding/4.1_sinusoidal.md)证明过的那个旋转，扔掉被加到残差流里的向量。
