预测每个 Token 时,模型都会综合考虑上下文中的其他 Token——有些影响很大,有些则几乎可以忽略。两个 Token 之间的配对就是一条注意力关系;具有实际关联的配对,例如“她”与“Sarah”,或一次 getUser() 调用与 getUser 函数定义,会比无关配对更强烈地相互影响。包含 N 个 Token 的上下文,大约存在 N² 条关系。
模型表现出来的“理解”就存在于这些配对关系中。它能够判断代词所指,是因为“她”与“Sarah”之间的注意力关系足够强;它能够使用正确参数调用函数,是因为调用位置与先前读到的定义之间形成了有效关系。这些结果都不是从某处查询得来的——每次模型提供商请求都会为每一对 Token 重新计算。
N² 这个数量级值得认真体会,因为它的增长速度远超直觉:
| 上下文大小 | 配对关系(约 N²) |
|---|---|
| 1,000 tokens | 约 100 万 |
| 10,000 个 Token | 约 1 亿 |
| 100,000 tokens | 约 100 亿 |
而且,每组配对并不只计算一次。模型拥有多个注意力头——前沿模型的准确数量并未公开,但估计在 50 到 100 个之间是合理的——每个注意力头都会独立计算一版关系。因此,上表中的每一组配对都会在所有注意力头中重复出现,计算量相当可观。
对于任何具体任务,真正重要的关系都只有少数几条。你的指令与受其约束的代码之间的配对就是其中之一,关系池里的其他绝大多数内容都是噪声。两者的增长速度并不相同:重要关系的数量大致保持不变,而关系总量会随上下文长度按平方增长。当上下文为 1,000 个 Token 时,你关心的配对只是百万分之一;达到 100,000 个 Token 时,则变成百亿分之一。这就是注意力预算背后的算术;当重要关系分得的注意力过于稀薄时,你感受到的现象就是注意力退化。
用法:
“它总是把这次 diff 中的两个 user 符号弄混——看来我们已经进入迟钝区了。”
“是的,每个调用位置与其声明之间的注意力关系正在相互干扰——Token 形式相同,绑定对象却不同。给其中一个重命名,配对关系就会清晰得多。”