@s_scardapane:《Transformer Cookbook》,作者@pentagonalize、@davidweichiang 等人。对在Transformer权重中“硬编码”算法的优美介绍……
摘要
一条推文介绍了《Transformer Cookbook》这篇论文,该论文遵循RASP论文,对在transformer权重中硬编码算法(加法、查找、分支)提供了优美的介绍。
查看缓存全文
缓存时间: 2026/06/25 13:21
Transformer Cookbook,作者 @pentagonalize、@davidweichiang 等。这是一本精美的入门指南,介绍了如何在 Transformer 的权重中“硬编码”算法(加法、查找、分支),遵循开创性的 RASP 论文(https://srush.github.io/raspy/)。https://openreview.net/forum?id=sPshCSvDrX… — # 像 Transformer 一样思考 来源:https://srush.github.io/raspy/ ## 像 Transformer 一样思考 - 论文(https://arxiv.org/pdf/2106.06981.pdf)作者 Gail Weiss、Yoav Goldberg、Eran Yahav - 博客作者 Sasha Rush(https://rush-nlp.com/)和 Gail Weiss(https://gailweiss.github.io/) - 库和交互式笔记本:srush/raspy(https://github.com/srush/RASPy)
Transformer 模型是 AI 系统的基础。如今,关于“Transformer 是如何工作的?”有无数解释,通常基于 Transformer 核心的架构图。svg然而,这张图并没有提供关于该框架计算模型的任何直觉。随着研究人员对 Transformer 的工作方式产生兴趣,理解其机制变得越来越有用。像 Transformer 一样思考(https://arxiv.org/pdf/2106.06981.pdf)提出了一种用于 Transformer 类计算的计算框架。该框架使用离散计算来模拟 Transformer 计算。由此产生的语言 RASP(https://github.com/tech-srl/RASP)是一种编程语言,理想情况下,每个程序都可以编译成特定的 Transformer(事实上,David Lindner 及其同事最近发布了一个编译器(https://arxiv.org/abs/2301.05062),适用于 RASP 的大部分功能!)。在这篇博客文章中,我用 Python 重新实现了一个 RASP 变体(RASPy (https://github.com/srush/raspy))。该语言与原始版本大致兼容,但进行了一些我觉得有趣的语法修改。借助这种语言,该工作的作者 Gail Weiss 提供了一套具有挑战性的谜题,帮助读者逐步理解其工作原理。
#!pip install git+https://github.com/srush/RASPy
在深入语言本身之前,让我们先看一个用 Transformer 编码的例子。下面是一些计算 flip(即反转输入序列)的代码。该代码使用两个 Transformer 层,通过应用注意力和数学计算来实现结果。
def flip(): length = (key(1) == query(1)).value(1) flip = (key(length - indices - 1) == query(indices)).value(tokens) return flip flip()
svg## 目录 - 第一部分:作为代码的 Transformer(https://srush.github.io/raspy/#transformers-as-code) - 第二部分:用 Transformer 编码(https://srush.github.io/raspy/#coding-with-transformers)
Transformers as Code
我们的目标是定义一个计算形式系统,模仿 Transformer 的表达能力。我们将通过类比的方式逐步介绍,描述每个语言构造及其对应的 Transformer 方面。(有关正式语言规范,请参阅完整论文(https://arxiv.org/pdf/2106.06981.pdf))。语言的核心单元是序列操作,将一个序列转换为另一个相同长度的序列。我将在整个过程中将其称为变换。
Inputs
在 Transformer 中,基础层是输入到模型的数据。该输入通常包含原始令牌以及位置信息。svg在代码中,符号 tokens 代表最简单的变换。它返回传递给模型的令牌。默认输入是序列 “hello”。
tokens
svg如果我们想更改变换的输入,我们使用 input 方法传入替代值。
tokens.input([5, 2, 4, 5, 2, 2])
svg与 Transformer 一样,我们不能直接访问这些序列的位置。然而,为了模仿位置嵌入,我们可以访问一个索引序列。
indices
svgsop = indices sop.input("goodbye")
svg### Feed Forward Network
在输入层之后,我们到达前馈网络。在 Transformer 中,这个阶段可以独立地对序列的每个元素应用数学运算。svg在代码中,我们通过变换上的计算来表示这个阶段。数学运算符被重载,以表示对序列每个元素进行的独立计算。
tokens == "l"
svg结果是一个新的变换。一旦构建完成,它可以应用于新的输入。
model = tokens * 2 - 1 model.input([1, 2, 3, 5, 2])
svg操作可以组合多个变换。例如,涉及 tokens 和 indices 的函数。这里的类比是 Transformer 激活可以同时追踪多条信息。
model = tokens - 5 + indices model.input([1, 2, 3, 5, 2])
svg(tokens == "l") | (indices == 1)
svg我们提供了一些辅助函数,使编写变换更容易。例如,where 提供了一个类似“if”语句的结构。
where((tokens == "h") | (tokens == "l"), tokens, "q")
svg而 map 让我们定义自己的运算符,例如一个字符串到整数的变换。(用户应小心,只使用那些可以用简单神经网络计算的操作)。
atoi = tokens.map(lambda x: ord(x) - ord('0')) atoi.input("31234")
svg当链接这些变换时,使用函数通常更容易。例如,以下代码应用 where,然后 atoi,再加 2。
def atoi(seq=tokens): return seq.map(lambda x: ord(x) - ord('0')) op = (atoi(where(tokens == "-", "0", tokens)) + 2) op.input("02-13")
svg从这里开始,除非我们使用不同的输入序列,否则我们将假设输入是 ‘hello’ 并在图示中省略输入显示。
Attention Selectors
当我们开始应用注意力时,事情变得更有趣。这允许信息在序列的不同元素之间进行路由。svg我们首先定义模型的键和查询符号。键和查询实际上是变换,我们将它们广播并相互比较以创建选择器,这是我们与注意力模式的平行对应物。我们直接从变换创建它们。例如,如果我们想定义一个键,我们在变换上调用 key。
key(tokens)
svgquery 也是如此。(查询以列的形式呈现,以反映它们与我们从中创建的选择器的关系)。
query(tokens)
svg标量可以用作键或查询。它们会广播到基础序列的长度。
query(1)
svg通过在键和查询之间应用比较操作,我们创建了一个选择器,这是我们与注意力矩阵的平行对应物——尽管这个是没有权重的。选择器是一个二进制矩阵,指示在最终的注意力计算中,每个输出位置(行)将关注哪些输入位置(列)。在创建它的比较中,键值描述输入(列)位置,查询值描述输出(行)位置。
eq = (key(tokens) == query(tokens)) eq
svg一些例子: - 一个将每个输出位置与上一个输入位置匹配的选择器。
offset = (key(indices) == query(indices - 1)) offset
svg- 一个将每个输出位置与所有较早输入位置匹配的选择器。
before = key(indices) < query(indices) before
svg- 一个将每个输出位置与所有较晚输入位置匹配的选择器。
after = key(indices) > query(indices) after
svg选择器可以使用布尔运算合并。例如,这个选择器将每个输出位置聚焦在 1)较早的位置上,这些位置 2)包含与自己相同的原始输入令牌。我们通过在矩阵中包含两对键和查询来展示这一点。
before & eq
svg## Using Attention
给定一个注意力选择器,我们可以提供一个值序列进行聚合。我们通过求和选择器为真的那些值来表示聚合。(注意:在原始论文中,他们使用均值聚合,并展示了一个巧妙的构造,其中均值聚合能够表示求和计算。RASPy 默认使用求和,为了简单并避免分数。实际上,这意味着 RASPy 可能会低估转换为基于均值的模型所需的层数,最多相差 2 倍。)注意力聚合使我们能够计算直方图之类的函数。
(key(tokens) == query(tokens)).value(1)
svg在视觉上,我们遵循架构图。查询在左侧,键在顶部,值在底部,输出在右侧。svg一些注意力操作甚至可能不使用输入令牌。例如,要计算序列的 length,我们创建一个“全选”注意力选择器,然后从每个位置加 1。
length = (key(1) == query(1)).value(1) length = length.name("length") length
svg这是一个更复杂的例子,逐步展示。(这是面试中会问的那种东西!)假设我们要计算序列中相邻值的和,沿着滑动窗口进行。首先,我们应用前向截止,只关注那些距离不太远的位置。
WINDOW=3 s1 = (key(indices) >= query(indices - WINDOW + 1)) s1
svg然后是后向截止,只关注直到并包括我们自己的位置。
s2 = (key(indices) <= query(indices)) s2
svg取交集。
sel = s1 & s2 sel
svg最后聚合。
sum2 = sel.value(tokens) sum2.input([1,3,2,2,2])
svg这是一个简单的例子,产生了一个 2 层变换。第一层对应计算长度,第二层计算累积和。累积和必须进入第二层,因为它应用于一个使用长度的变换,所以只能在长度计算完成后才能计算。
def cumsum(seq=tokens): x = (before | (key(indices) == query(indices))).value(seq) return x.name("cumsum") cumsum().input([3, 1, -2, 3, 1])
svg## Layers
该语言支持构建更复杂的变换。它通过跟踪到目前为止计算的操作来跟踪层数。svg这是一个简单的例子,产生了一个 2 层变换。第一层对应计算长度,第二层计算累积和。
x = cumsum(length - indices) x.input([3, 2, 3, 5])
svg## Coding with Transformers
有了这个函数库,我们可以编写操作来完成复杂得惊人的任务。我们能产生一个 Transformer 用来执行两个任意长度数字的基本加法吗? 例如,给定字符串 “19492+23919”,我们能产生正确的输出吗?我们将在这里逐步介绍这些步骤及其解决方案。如果你想自己完成,我们提供一个版本(https://colab.research.google.com/github/srush/raspy/blob/main/Blog2.ipynb%22),你可以在那里亲自尝试!在深入主要任务之前,我们将做一些难度递增的挑战,以帮助我们建立一些直觉。
Challenge 1: Select a given index
产生一个序列,其中所有元素都具有索引 i 处的值。
def index(i, seq=tokens): x = (key(indices) == query(i)).value(seq) return x.name("index") index(1)
svg### Challenge 2: Shift
将序列中的所有令牌向右移动 i 个位置。(这里我们在聚合中引入了一个可选参数:当没有选择任何输入位置时使用的默认值。如果未定义,该值为 0。)
def shift(i=1, default="_", seq=tokens): x = (key(indices) == query(indices-i)).value(seq, default) return x.name("shift") shift(2)
svg### Challenge 3: Minimum
计算序列的最小值。(这个开始变得更难。我们的版本使用了 2 层注意力。)
def minimum(seq=tokens): sel1 = before & (key(seq) == query(seq)) sel2 = key(seq) < query(seq) less = (sel1 | sel2).value(1) x = (key(less) == query(0)).value(seq) return x.name("min") minimum()([5,3,2,5,2])
svg我们解决方案背后的想法是对输入位置进行隐式的完整排序:我们(隐式地)根据输入令牌值对位置进行排序,以输入位置作为平局破解器。我们的第一步是让每个位置关注排序中它之前的所有位置:sel1 关注具有相同输入令牌值的更早输入位置,sel2 关注具有更低输入令牌值的输入位置。然后我们从所有位置聚合 1,以得到每个位置在此排序中的位置(即,它前面有多少个其他位置)。最小值就是根据此排序在第一个位置的输入值(即,没有其他位置在它前面的那个)。
Challenge 4: First Index
计算具有令牌 q 的第一个索引,假设序列长度始终小于 100。(2 层)
def first(q, seq=tokens): return minimum(where(seq == q, indices, 99)) first("l")
svg### Challenge 5: Right Align
对填充序列进行右对齐,例如 ralign().inputs(‘xyz___’) = ‘–xyz’“ (2 层)
def ralign(default="-", sop=tokens): c = (key(sop) == query("_")).value(1) x = (key(indices + c) == query(indices)).value(sop, default) return x.name("ralign") ralign()("xyz__")
svg### Challenge 6: Split
在
相似文章
@shubh6200: 周末花了些时间阅读这篇文章,老实说真希望它早几年就存在。我们看的每个AI教程都…
一条推文推荐了一篇arXiv论文,该论文为应用数学家讲解了Transformer的数学基础,涵盖分词、嵌入、多头注意力和KV缓存。
@currying: 非常棒的13页讲解!
一条推文重点介绍了《理解Transformer与注意力机制》,这是一篇13页的论文,从应用数学的角度解释了Transformer架构和注意力机制。
@gordic_aleksa: 新深度博文时刻:Inside the Transformer: The Life of a Token 对现代密集Transformer的深入探讨,我…
一篇深入探讨现代密集Transformer内部工作原理的博文,涵盖YaRN(位置信息)、混合注意力(实现160k上下文长度)、soft capping、QK归一化,以及Transformer数学(包括FLOPs/Token公式和集群规模估算)。
透过镜子:直接读写Transformer
这篇论文揭示,只有少量的Transformer组件对token预测是必要的,并引入了直接读写这些组件的方法,只需极少更改。
@techwith_ram:Brandon Sandhu 对 Transformer 架构的推导 本文旨在从直觉和数学角度理解……
这篇由 Brandon Sandhu 撰写的论文提供了数学上严谨而又易于理解的 Transformer 架构推导,涵盖了分词、嵌入、注意力机制及其他核心组件,前提知识为线性代数、微积分、概率论和信息论。