这篇文章通过非传统的方式探讨了Transformer模型的工作原理,强调了它们作为状态模拟器的角色,能够根据上下文动态调整预测状态。作者分享了自己通过实验和代码分析逐步理解模型的经验,指出温度参数如何影响输出分布,并展示了模型在ASCII艺术生成等任务中的泛化能力。文章鼓励通过实践和质疑来建立对复杂系统的直觉理解,而非依赖传统的学术方法。
作者注
这是一篇实验性的文章——我在散步时录了一段长达一小时的语音备忘录,自言自语,想到什么说什么,有点像意识流。有些地方可能表达得比较乱,但它的长度是我之前写的文章的三倍,而且我发现整理转录稿比从头写要快得多(这次我总共花了不到4小时)。我觉得这种写作方式可能会捕捉到更多的见解。虽然表面上看起来有点乱,但这种更“原始”的思维记录可能比手写的内容更丰富。欢迎大家告诉我你们的想法。
引言
也许你不想用传统的方法去理解 Transformer架构(比如语言模型)。如果你像我一样,可能更喜欢一种非正式的方式——一种能帮助你从抽象层面理解这些模型在做什么的方式,而不需要一开始就掌握技术细节。
随着时间的推移,我自己也学会了那些技术细节,但我觉得如果采用更学术化的方式,比如“这是自注意力机制的工作原理,这是线性代数的形式,这是理解每个机械操作的步骤”,我可能不会在过去一年里学得那么高效。
我喜欢在理解这些系统时这样做:
我会从整个复杂的系统中挑出一部分,这部分我无法在一天内完全理解。然后我专注于这一部分,试着理解它在整个系统中的作用。我说的“作用”不是指“它应该做什么”,而是指如果去掉这部分,系统会失去什么功能?
你不可能一下子把所有部分都装进脑子里,如果你按操作顺序去学,可能也不会理解得很透彻,因为你只是专注于每个局部的操作,而没有思考为什么这个操作存在,也没有思考它的真正用途。你只是在学习操作的机械流程和结构,而不是理解它到底在做什么。
Transformer 是状态模拟器
我逐渐理解到,Transformer 本质上是一种状态模拟器。每个单独的预测都有自己的独立状态——它不会从前一个预测中继承状态。这一点很重要,因为语言并不是一种线性的、从左到右的因果进程,状态不会以线性速度推进。
状态可能会因为新信息而发生巨大变化。在上下文的早期,某个 token 可能对预测接下来的几百个 token 非常重要,但在几百个 token 之后,这些 token 可能就不再有用,或者被新的上下文信息否定,比如“这不相关”或“这是错的”。
语言不像那种线性的、从左到右的因果进程,状态不会以线性速度推进,因为状态可能会因为新信息而发生巨大变化。
我一时想不出很好的例子,但想象一下 Reddit 评论链,有人在解释如何做某件事,最后却说:“其实我全说错了,正确的方法是这样的。”模型需要能够区分和隔离这些信息,为每个 token 预测创建不同的状态,因为随着上下文的变化,这些 token 的含义也会发生变化。
所以每个单独的预测都有自己的状态,它不会从前一个预测中继承状态。之前的信息显然会影响状态,但 Transformer 可以根据过去的信息学习表示任意状态,而不需要实际生成那些过去的信息。因此,它们非常擅长即兴发挥和进行上下文学习,能够从你提供的例子中泛化,而不一定需要在这些例子上进行训练,因为它们被迫构建一个状态,表示过去信息向未来的演变。
输出层:分布,而不是“最可能”的 token
我最初对输出层的理解是完全错误的。我原本以为,当你使用温度设置(比如在 ChatGPT 或 API 中)时,较高的温度基本上是在注入某种随机性,就像某种噪声一样。我想我当时之所以这么认为,是因为我对扩散模型的工作原理有一些模糊的了解。
为了更好地理解它,我研究了 llama.cpp(这是一个推理软件,不是模型)的代码——我记得我当时用的是 ChatGPT 3.5 Turbo 之类的模型。我试图弄清楚:在这堆庞大的 C++ 代码中,温度到底在哪里?我想先理解一个组件,然后从那里开始,看看如何将其推广到其他部分。
我发现,温度本质上只是对分数进行线性除法。那么分数是什么呢?有些人告诉我,他们认为模型一次只生成几个 token,但事实并非如此。
每当你进行文本预测(比如最大化似然)时,发生的事情不仅仅是“预测最可能的下一个 token”或“预测最可能的 token”。在上下文窗口中的每个点上,你预测的下一个 token 需要表示所有可能的下一个 token 的分布,这些 token 平均来说最符合减少交叉熵损失的目标。
所以你不能仅仅提高预测单个最可能 token 的能力。你需要学会权衡这些 token 的分布,使其平均来说最符合实际情况。这种优化目标并不是试图预测最可能的下一个 token,而是试图拟合一个分布,表示每个 token 选择的可能状态,使得这个分布平均来说最符合数据。
有些位置比其他位置更具确定性,尤其是在编程语言中,你有这些正式的规则和约定,而在像英语这样的自然语言中,情况则更具响应性和即兴性。
你需要覆盖的不仅仅是下一个最可能的内容。你需要能够精确地权衡下一个 token 的可能性,因为在某些上下文中,你可能希望有很多变化。这样你才能平均来说获得最低的损失,而不会过度拟合到某个数据点,导致分布偏离实际情况。
当你以这种方式拟合数据时,你并不是在拟合最可能的下一个 token,而是在拟合最可能的下一个 token 分布,或者至少是数据所隐含的最准确的分布。这种分布不可避免地会学习到数据的结构,从而支持即兴发挥和上下文学习。因为通过梯度下降优化,网络内部更容易构建电路来抽象之前的内容,而不是仅仅记住之前的内容或找到浅层的模式。
温度的实际作用
当我发现每个下一个 token 点都在表示整个分布时,我开始思考温度除法是如何起作用的。
当你正常优化这些交叉熵损失目标时(比如在预训练或监督微调中),你使用的是 1.0 的温度——即默认的下一个 token 选择分布。它不会改变。温度之所以是除法,是因为你有这些单独的 token 分数,它们没有被归一化为概率分布(即每个 token 都有一个百分比概率)。你有这些原始分数,它们通过 softmax 函数归一化,使得所有分数加起来等于 100%。你还对它们进行指数运算,使得分布更集中在顶部,这对学习动态很有帮助。
当你除以一个较小的数(比如温度 0.5),你实际上是在做与乘以分数相同的事情。所以如果你有一个 token 的分数是 20,而你使用温度 0.5,这个分数会变成 40。因此,当你用一个小于 1 的数除以分数时,分数之间的相对差异会变得更大。
我发现这里并没有噪声。这一点的重要性在于它帮助我建立了直觉:你不是在做最可能的优化,而是在优化一般概率——为词汇表中的每个 token 学习最准确的一般概率分布。
在你的数据集中,某个位置上的 token 并不一定是最可能的选择,但我们仍然像它们是最可能的那样进行训练。因为平均来说,这种方式会平衡或抵消,从而学习到更泛化的概率分布。如果你有一个分布,其中某个位置上有多个可能的结果,而这些结果在现实中都是合理的,那么最可能的选择可能并不比其他选择更有可能,以至于过度拟合到 99% 或某个非常大的数字会扭曲分布。
通过表示一个分布,你可以平均来说获得更准确的、更低的交叉熵损失。这种分布通过对下一个 token 进行“对冲”而出现,而这种对冲来自于训练过程中试图拟合“下一个 token 是最可能的”这一假设(尽管它并不是)。平均来说,你会学习到一个不偏向某个 token 的分布,而是学习到在不同上下文中的变异性。
为什么低温会导致元模式崩溃
另一个有趣的现象是,当你使用 0.01 的温度时,模型会让最可能的 token 变得非常尖锐。在这种情况下,你会发现模型可能会一开始表现得很有条理,但随后就会陷入重复的模式——不仅仅是重复单个 token,而是重复结构或组合。它会过度关注上下文所暗示的“最低熵崩溃”。
这有时会让人感到困惑:“如果我们每次都选择最可能的 token,那为什么会出现这种重复或无意义的内容?”这是因为模型实际学习到的分布暗示了更多的变异性。我们在表达自己时有很多固有的变异性,编程语言中也有很多变异性。但模型仍然需要权衡结构和变异性,以找到最泛化的结果。
显然,模型在一定程度上学会了分析变异性,并根据这种变异性构建内部状态。这是因为目标迫使模型找到一种泛化的解决方案,理解接下来可能发生什么,而不是仅仅训练在之前发生的事情上。这使得模型能够即兴发挥,进行上下文学习,并且在我看来,这种能力将为未来这些模型解决真正困难的问题提供必要的脚手架,使它们真正具备代理能力,因为它们构建了足够泛化的启发式方法,可以转移到那些分布之外的任务上。
测试泛化能力:ASCII 艺术扩散实验
我记得我做了一个小实验,是关于 ASCII 艺术的。你知道那些网站可以把图片转换成 ASCII 文本表示吗?我想试试:我们能不能先随机生成一堆 ASCII 字符,然后慢慢把它变成一个更大的图像?
我的做法是:我有一个小画布,上面有一堆随机的 ASCII 符号。然后每次迭代时,我们扩大画布,并把所有内容按顺序放在上下文中。这有点像扩散过程——你从噪声开始,逐渐变成一个结构合理的图像。
我用了一些游戏中的角色渲染图——比如我用了一个 Captain Falcon 的例子来演示这个想法。
我想评估的是:在没有训练过类似内容的情况下,模型能否通过上下文学习产生类似的过程?记住,这些 ASCII 艺术的例子是我通过编程生成的,模型从未见过这些例子。
我预填充了上下文,给模型提供了一些这个过程的例子。我使用了 Anthropic 的选项,预填充了模型助手的初始响应,这在你想探测上下文学习时非常有用。
我注意到 Claude 3.5 Sonnet 表现得特别好。它能够从噪声开始,逐渐扩展画布,最终形成一个一致的形状。
这是一个非常有趣且困难的问题,因为模型必须:
1. 在增加受控噪声的同时转换状态,而不会迷失在噪声中。
2. 每次扩大画布,这改变了画布的实际形状。
3. 通过预测单个 token 来生成图像,图像的形状、大小和噪声都在变化。
所以它必须抽象出一个状态表示,既能借鉴之前的内容,又能在大小和噪声的维度上稍微转换它,最终完成整个图像。它必须完全通过上下文学习来完成这个过程——它在训练中从未见过 ASCII 艺术扩散。
Claude 3.5 Sonnet 通过了这个测试,而其他模型则失败了。Claude 3.0 Sonnet 虽然能扩大画布,但无法控制噪声,最终没有形成一致的图像。Claude 3.0 Haiku 则完全卡住了,无法扩大画布或控制噪声。
这个实验证明了 Transformer 显然不仅仅是在记忆符号,它们实际上在学习泛化的状态和分布。
学术现实:论文并不总是讲述完整的故事
我对这些系统的理解方式受到了学术论文的影响,这些论文并不总是讲述完整的故事。例如,有一篇著名的论文讨论了“深层冗余”,他们修剪了 Llama 70B 的后半部分,结果模型在 MMLU 基准测试上仍然有 78% 的分数。
但有人(Charles Goddard,他在 Arcee AI 工作)复现了这个实验,发现修剪后的模型在交叉熵损失上比 Llama 8B 模型还要差。虽然它的 MMLU 分数更高,但在实际预测上却差得多。
这些结果表明,学术论文有时会忽略一些重要的细节。类似的情况在学术界和论文中随处可见。
不同的泛化方法:Anthropic vs. OpenAI
我认为我们远未耗尽中等规模模型(比如 70B 参数)的泛化能力。Claude 模型在 ASCII 艺术实验中的表现差异表明,扩展规模有助于泛化,但在模型如何构建和转换状态方面仍有很多探索空间。
Anthropic 在预训练中非常注重多样性,他们希望模型能更广泛地理解世界,而不仅仅是过去十年的 Stack Overflow 和 Reddit。OpenAI 则似乎更专注于实际应用,尤其是在编程、数学和代码相关领域。
你可以从 OpenAI 的产品中看出,他们正在推出一个相当专门的模型(o3-mini),并将其定位为通用推理模型。但他们是否真的在优化写作或其他领域的高阶抽象能力呢?
在没有正式机器学习训练的情况下构建自己的理解
如果你想建立更强的直觉,你不必一开始就深入数学理论。显然,学习一些核心原则是有帮助的,但也有很大的空间去探索、思考和质疑你的假设,然后通过实验来验证这些假设是否符合现实。
这就是我如何建立如此强大的思维模型的,也是为什么我不断收到公司对我发现的内容的邀请。我在 Twitter 上建立了一个小众的追随者群体(@kalomaze),大约有 8000 名粉丝,包括许多博士、哈佛学者、机器学习研究人员等。
我从未看过 Karpathy 的教程,也从未上过机器学习课程,但我通过这种方式建立了我的思维模型。我没有仅仅阅读论文,而是尝试实现那些有趣的部分,然后发现研究人员如何隐藏与他们的主要主张相矛盾的结果。
我的方法是:
1. 质疑我对组件如何工作的假设。
2. 深入研究代码,看看实际发生了什么。
3. 运行实验来测试我的理解。
4. 构建思维模型并迭代改进。
这种方法帮助我发展出一些在正式教学中并不总是被强调的见解。虽然数学理解很有价值,但将 Transformer 视为建模分布和表示状态的系统,提供了一种同样强大的补充思维模型。
结论
Transformer 在做状态模拟,它们的架构是完全无状态的。它们学习的是接下来可能发生的事情的泛化分布。你需要以一种足够灵活的方式构建你的思维模型,以便真正理解缺少什么。
这些模型不仅仅是在记忆模式——它们在学习泛化的状态,并为每个预测构建不同的状态,这些状态可能有某种程度的重叠,也可能完全不同。
这大概是我从直觉角度理解 Transformer 的方式,而不是从数学角度。
这种方式需要时间吸收,而不是通过教程从头开始构建。
我并不是说你不能采用那种方法——我想再次强调,我从未看过 Karpathy 的教程,也从未上过机器学习课程,但我通过这种方式建立了我的思维模型。我没有仅仅阅读论文,而是尝试实现那些有趣的部分,然后发现研究人员如何隐藏与他们的主要主张相矛盾的结果。
如果你想在没有被教导的情况下有机地建立理解——如果你想像我一样自学——这就是我推荐的方法。当然,并不是每个人都想采用这种方法,但我认为它可能是真正理解这些系统的最高效方法之一。
补充:什么是状态分布
态分布是指在某个特定时刻,系统可能处于的各种状态及其对应的概率分布。在机器学习和自然语言处理中,状态分布通常用来描述模型在给定上下文或输入条件下,可能输出的各种结果及其出现的概率。
具体解释:
- 状态:在Transformer模型中,状态可以理解为模型在处理输入序列时,每个时间步(或每个token)的内部表示。这个状态包含了模型对当前上下文的理解和记忆。
- 分布:分布指的是模型对下一个可能输出的token的概率预测。例如,在语言模型中,给定一个句子“我喜欢吃___”,模型会预测下一个token可能是“苹果”、“香蕉”、“面条”等,每个token都有一个对应的概率值。
- 状态分布:结合状态和分布的概念,状态分布就是模型在某个特定状态下,对接下来可能出现的token的概率分布。这个分布反映了模型对当前上下文的理解,并基于这种理解做出预测。
假设我们有一个简单的语言模型,输入句子是“今天天气很___”。模型可能会生成以下状态分布:
- “好”:概率0.6
- “糟糕”:概率0.3
- “热”:概率0.1
重要性:
状态分布是Transformer模型的核心之一,因为它决定了模型如何根据上下文生成合理的输出。通过调整温度参数,可以控制这个分布的“尖锐”或“平滑”程度,从而影响生成文本的多样性和创造性。
总结来说,状态分布是模型在特定上下文中对可能输出的概率预测,反映了模型对当前状态的理解和预测能力。