@s_scardapane:《Transformer Cookbook》,作者@pentagonalize、@davidweichiang 等人。对在Transformer权重中“硬编码”算法的优美介绍……

X AI KOLs Timeline 论文

摘要

一条推文介绍了《Transformer Cookbook》这篇论文,该论文遵循RASP论文,对在transformer权重中硬编码算法(加法、查找、分支)提供了优美的介绍。

*The Transformer Cookbook*,作者@pentagonalize、@davidweichiang 等人。对在transformer权重中“硬编码”算法的优美介绍(加法、查找、分支),遵循具有开创性的RASP论文(https://srush.github.io/raspy/)。https://openreview.net/forum?id=sPshCSvDrX……
查看原文
查看缓存全文

缓存时间: 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 在

相似文章

@currying: 非常棒的13页讲解!

X AI KOLs Timeline

一条推文重点介绍了《理解Transformer与注意力机制》,这是一篇13页的论文,从应用数学的角度解释了Transformer架构和注意力机制。

透过镜子:直接读写Transformer

arXiv cs.CL

这篇论文揭示,只有少量的Transformer组件对token预测是必要的,并引入了直接读写这些组件的方法,只需极少更改。