内容概要
本视频是课程的第三讲,主要探讨大型语言模型 (LLM) 的内部运作机制。从输入的词元化 (tokenization)、嵌入表 (embedding table) 的查询,到多层 Transformer 层的处理,以及最终如何生成概率分布的逆嵌入 (unembedding) 过程,都进行了详细的剖析。视频还讨论了如何分析 LLM 的内部表示 (representation),包括词元嵌入的相似度、不同层级表示的语义变化,以及如何通过修改表示来影响模型行为。最后,通过实践展示了 Llama 和 Gemma 模型内部参数的结构,以及如何使用 Logic Lens 和 Patch Scope 等工具观察模型在“思考”过程中的变化。
目录
-
课程介绍:解剖语言模型
-
语言模型输入到输出的流程
-
嵌入表 (Embedding Table)
-
Transformer 层的运作
-
深度学习 (Deep Learning) 与多层结构
-
生成概率分布:语言模型头部 (LM Head) 与 Softmax
-
逆嵌入 (Unembedding) 的直观理解
-
语言模型每层输出的意义
-
嵌入空间中的语义方向
-
表示 (Representation) 的可视化与分析
-
修改表示 (Representation) 控制模型行为
-
Anthropic 对 Claude 的分析
-
Logic Lens:窥探模型思考过程
-
Patch Scope:将表示 (Representation) 转译成句子
-
Transformer 层内部运作:自注意力 (Self-Attention)
-
位置嵌入 (Positional Embedding):考虑位置信息
-
注意力权重 (Attention Weight) 的计算与多头注意力 (Multi-Head Attention)
-
因果注意力 (Causal Attention):只考虑左侧上下文
-
前馈层 (Feed-Forward Layers) 的运作
-
神经元与矩阵运算
-
实践:解剖 Llama 与 Gemma 模型
-
模型参数的结构与大小
-
Gemma 模型的参数结构
-
观察嵌入表 (Embedding Table) 与词元相似度 (Token Similarity)
-
表示 (Representation) 的变化与语义理解
-
Logic Lens 实践:观察模型思考路径
-
注意力权重 (Attention Weight) 可视化
正文
课程介绍:解剖语言模型
今天是我们课程的第三讲,主题是语言模型内部的运作机制。到目前为止,我们反复强调语言模型的核心任务是:给定一个未完成的句子,它会输出一个概率分布,预测接下来可能出现的每一个词元 (Token) 的概率。
我们也将语言模型比作一个函数 f,未完成的句子是输入 x,输出则是 f(x)。上一讲我们讨论了如何选择合适的 x 以获得期望的 f(x)。而在这一讲,我们将深入 f 的内部,探究给定 x 后,模型内部发生了什么,才最终生成了我们所见的 f(x)。我们将剖开语言模型,一探其内部运作的奥秘。
在开始之前需要强调一点,本堂课我们解剖和观察的都是已经训练好的模型。我们暂时不讨论这些语言模型是如何通过训练变成我们期望的样子,这部分内容将在后续课程中讲解。今天,我们假设模型已经训练完毕,参数已经固定,我们将直接解剖它,观察这些参数如何与输入的句子互动,最终产生下一个词元的概率。
今天的课程安排与第一讲类似,会先通过幻灯片讲解语言模型运作的基本原理。为了让大家更信服这些原理,课程后半段将进行实践操作,实际解剖一个语言模型,向大家展示其运作方式确实如课堂所言。
语言模型输入到输出的流程
我们首先从原理讲起,分为三个部分:第一,总览从输入 (Prompt) 到输出下一个词元的全过程;第二,探讨语言模型每一层输出的意义;第三,深入了解每一层内部的具体运作。
我们先从输入到输出的完整流程开始。
语言模型是一个函数,输入一个句子,输出该句子后面可以连接的各个词元的概率分布。那么,从输入句子到输出概率分布,中间究竟发生了什么?
首先,输入的句子会经过词元化 (Tokenization),被切分成一个个词元 (token),每个词元对应一个唯一的编号 (ID)。这一点在第一讲的实践中已经演示过。
为简化幻灯片的呈现,本堂课假设每个汉字都是一个词元。但实际情况并非如此,正如第一讲中展示的 Llama 模型,有时多个汉字合并成一个词元,有时一个汉字反而由多个词元组成。请大家注意,这只是为了讲解方便所做的简化。
完成词元化后,这些 ID 会被送入语言模型。
嵌入表 (Embedding Table)
首先与这些 ID 互动的是一个名为嵌入表 (Embedding Table) 的结构,它本质上是一个矩阵 (Matrix)。这个矩阵的每一行 (row) 对应一个词元。假设模型词汇表中有 128,000 个词元,那么这个矩阵就有 128,000 行。矩阵的列数 (column) 则代表每个词元转换成的向量(即嵌入, embedding)的维度 (dimension)。
嵌入表的作用是查询。输入一串 ID,每个 ID 会找到它在嵌入表中对应的行。例如,一个词元的编号是 540,它对应的嵌入向量就存储在第 541 行(因为索引通常从 0 开始)。
通过查询嵌入表,我们将每个 ID 从一个整数转换为一个向量。这个向量就是一排数字,在幻灯片中我们用一个长方形来表示。
这个嵌入表的大小由词汇表大小(行数)和嵌入维度(列数)决定。通过它,每个 ID 都被映射为一个向量,这个向量被称为词元嵌入 (Token Embedding)。嵌入表本身就是神经网络的参数,是模型中数十亿、数百亿参数的一部分。在模型中,参数通常以矩阵为单位存储。在稍后的实践中,你会更清楚地看到每一组参数都是一个矩阵。
这是模型的第一步:将每个代表词元的整数映射为一个高维向量(嵌入)。
Transformer 层的运作
接下来,这些词元嵌入会进入模型的第一个层 (Layer)。模型由许多层堆叠而成。每一层的作用都是将一排输入的向量转换成另一排等长的输出向量。例如,输入七个向量,输出也是七个向量。
每一层内部都包含大量的参数,通常是好几个矩阵。一个层会综合考虑当前词元及其之前的所有输入,然后为每个输入生成一个新的嵌入。因此,输出的向量考虑了上下文信息。
这些输出的新向量通常也叫作嵌入。为了与第一步查表得到的嵌入有所区别,我们将查表得到的称为输入嵌入 (Input Embedding),而经过层处理后得到的,因为考虑了上下文,称为语境化嵌入 (Contextualized Embedding)。
层的输出向量也常被称为表示 (Representation)。有时会加上形容词,如隐藏表示 (Hidden Representation) 或潜在表示 (Latent Representation)。所谓“隐藏”或“潜在”,是因为在通常使用语言模型时,我们只关心最终输出的概率分布,而不会直接观察这些中间层的表示。因此,在文献中看到的语境化嵌入、隐藏表示、潜在表示,指的都是模型中某一层的输出。
模型包含多个层。词元嵌入经过第一层生成一排新向量,这排向量再进入第二层,生成另一排向量。这个过程会反复进行。假设整个模型有 L 层,那么这个步骤会持续 L 次,直到通过最后一层,得到最终的一排向量。
深度学习 (Deep Learning) 与多层结构
得到最后一层输出的表示后,我们要做什么呢?在此之前,需要说明的是,拥有多个层就是深度学习 (Deep Learning) 的核心思想。深度学习也就是神经网络 (Neural Network)。
你可能会问,神经网络里不应该有很多神经元 (Neuron) 吗?这些神经元在哪里?我们将在课程结尾揭晓答案。
那么,为什么模型需要多个层?多层结构有什么好处?由于时间关系,我们不深入探讨。一个通俗的解释是,可以把每一层想象成流水线上的一个站点,每个站点完成一部分工作,多个站点协同起来就能完成非常复杂的任务。
从机器学习的原理出发,也能证明深度学习是一种更优的方法。如果你想了解其背后的数学原理,我在 2018 年的课程《深度学习的理论》中,花了近三个小时从数学上证明了深度模型优于浅层模型。这并非经验之谈或科普比喻,而是有严格的数学证明的。
生成概率分布:语言模型头部 (LM Head) 与 Softmax
现在,我们如何从最后一排向量生成概率分布呢?我们取出这一排向量中的最后一个。假设它是一个 K 维向量,包含 K 个数字。这个 K 维向量会与一个 K 列、V 行的矩阵相乘。
根据线性代数,这个乘法操作的输出是一个 V 维向量。这里的 V 指的是词汇表的大小 (Vocabulary Size)。如果模型有 128,000 个词元,那么输出就是一个 128,000 维的向量。向量的每一维对应一个词元的分数,代表该词元出现在输入句子后面的可能性。
这个矩阵也是模型的一部分,其内部的数字都是模型参数。这个矩阵有一个专门的名称,叫做语言模型头部 (LM Head),因为它位于整个语言模型的末端。
你可能会想,一个任意向量乘以一个任意矩阵,得到的输出向量中的数值可以是任何实数,既可以是正数也可以是负数,并不能直接作为概率。确实如此,概率值必须介于 0 到 1 之间。因此,这个输出向量中的数值不能直接看作概率,它们有一个专门的称呼,叫做 Logit。
要将 Logit 转换为概率,需要进行一个叫做 Softmax 的操作。Softmax 的目标是让向量中的每个数字都介于 0 到 1 之间,并且所有数字的总和为 1,这样就可以将其视为一个概率分布。
Softmax 的具体操作分为两步。假设 Logit 向量中有三个数字 S1, S2, S3(实际情况中会是词汇表大小的维度)。
-
对每个数字取指数 (Exponential),即 e^S1, e^S2, e^S3。由于原始数值可能为负,取指数可以确保所有值都为正数,且大小关系不变。
-
将所有指数值相加,得到总和 S = e^S1 + e^S2 + e^S3。
-
用每个指数值除以总和 S,得到最终的概率。
原则上,Logit 值越大的词元,转换后的概率也越大。
你可能会质疑,为什么取指数再归一化就能代表真实的概率?实际上,我们不必把它看作严格意义上的真实概率。它更像是一种将数值转换为 0 到 1 之间,便于后续进行采样(掷骰子)的工具。在使用 Hugging Face 的模型时,模型真正的输出就是 Logit,是否进行 Softmax 转换由用户自己决定。
正因为如此,Softmax 可以有多种变体。一个常见的操作是在进行 Softmax 之前,将所有 Logit 值都除以一个参数 T,称为温度 (Temperature)。
-
当 T 较大时,Softmax 后的概率分布会更平坦(均匀)。
-
当 T 较小时,概率分布会更集中在概率最高的几个词元上。
通过控制 T,可以调整模型生成罕见词元的概率。许多语言模型提供的“创意模式”或“保守模式”,通常就是通过调节温度参数 T 来实现的。T 值高,模型更容易产生意想不到的“创意”结果。
总之,不必纠结于 Softmax 输出的是否是“真实”概率,它只是一个方便后续操作的转换工具。
逆嵌入 (Unembedding) 的直观理解
如果你对一个向量乘以一个矩阵就能得到每个词元的分数这件事感到不够直观,我们可以换一种方式来理解逆嵌入 (Unembedding) 的过程。
许多神经网络模型,包括我们熟悉的 Llama 和 Gemma,其设计都遵循“首尾呼应”的原则。它们的 LM Head 并不是一组独立的参数,而是直接复用了模型最开始的嵌入表 (Embedding Table)。
也就是说,模型末端的 LM Head 和模型起点的嵌入表是同一个东西。
在这种设计下,逆嵌入的过程可以理解为:将语言模型最后一层输出的向量,与嵌入表中的每一个词元嵌入向量计算点积 (Dot Product),这可以看作是一种相似度计算。向量与矩阵相乘,本质上就是该向量与矩阵的每一行做点积。
因此,一个词元获得较高概率(或分数)的条件是,它的词元嵌入与语言模型最后一层输出的表示向量越接近(点积越大)。可以想象,语言模型在预测下一个词元时,会努力在每一层中生成一个表示,这个表示尽可能地接近它认为最有可能出现的那个词元的嵌入。
语言模型每层输出的意义
现在我们来看看语言模型中间每一层输出的含义。
输入 ID 后,第一步是通过查询嵌入表将每个 ID 转换为一个词元嵌入。相同的词元会得到相同的词元嵌入。例如,在句子“今天天气很好”中,“今天”的“天”和“天气”的“天”是同一个词元,它们的词元嵌入完全相同。
除了相同词元有相同嵌入外,语义相近的词元也会有相近的词元嵌入。例如,“Apple”这个词元的嵌入,在向量空间中会与“Orange”和“Banana”的嵌入比较接近,因为它们都是水果。同时,它也可能与“iPhone”的嵌入接近,因为 Apple 也指代苹果公司。稍后的实践会展示“Apple”这个词元具体与哪些词元最接近。
经过第一个 Transformer 层后,词元嵌入变成了考虑了上下文的语境化嵌入。同样是“苹果”的“果”这个字,在进入第一层之前,它们的词元嵌入是相同的。但通过第一层后,它们的嵌入会开始变得不同,因为它现在考虑了各自的上下文。
例如,同样是“Apple”这个词元,如果根据上下文指的是苹果公司,其语境化嵌入会与指代水果的“Apple”的语境化嵌入有显著差异。指代苹果公司的多个“Apple”实例的语境化嵌入会彼此更接近。
嵌入空间中的语义方向
在嵌入空间中,特定的方向往往具有特定的语义含义。这些高维向量的排布并非杂乱无章。除了语义相似的嵌入会聚集在一起,某些方向也可能代表某种关系。
例如,嵌入空间中可能存在一个代表“中英翻译”的方向。如果你将一系列中文词汇和对应的英文词汇的嵌入向量相减,可能会发现“冷” - “cold”、“热” - “hot”、“大” - “big” 这些向量对的方向非常接近。
利用这个特性,甚至可以进行类比推理,比如 embedding('冷') + embedding('small') 可能会得到接近 embedding('小') 的向量。当然,哪个方向代表什么含义并不容易分析,不同模型、不同层的表示空间中,方向的含义可能都不同。
文献中常提到的 Man - Woman ≈ King - Queen 这种类比关系,在某些模型中可以复现,但在另一些模型中则不一定。我自己尝试用 Llama 的嵌入做类似操作,效果并不理想。这说明嵌入向量的加减运算有时成功,有时失败,结果因模型而异。
表示 (Representation) 的可视化与分析
分析高维嵌入或表示的另一个方法是将其投影到低维空间,例如二维平面,以便于可视化和分析。这些向量通常有数千个维度,直接观察难以理解它们之间的关系。
选择哪个方向进行投影会影响你看到的结果,这需要研究人员自行解读。
例如,一篇 2019 年的早期论文(当时还没有 GPT-3.5)发现,将当时流行的 BERT 模型的潜在表示 (Latent Representation) 投影到某个特定的二维平面上,可以观察到句子的语法树结构。
这并非幻觉。该论文指出,只有在模型的中间某几层,才能找到清晰的语法树投影。模型的初始几层和最后几层都无法投射出类似的结果。这暗示了语言模型在中间层特别关注语法相关的信息。
另一篇近期的文章分析了 Llama 模型。研究者将世界各地的地名输入 Llama,提取它们在某一层的潜在表示,然后将这些表示投影到一个精心选择的二维平面上,最终竟投射出了一幅世界地图。每个点代表一个地名,不同颜色代表其所属大洲。虽然不是完全精确,但大致位置关系得以保留。
修改表示 (Representation) 控制模型行为
除了观察,我们还可以通过直接修改表示来研究其含义。如果你能通过修改表示,让模型一直说脏话,那就说明这个表示的某个方向可能与说脏话有关。
这项技术有不同的名称,如表示工程 (Representation Engineering)、激活工程 (Activation Engineering) 或激活引导 (Activation Steering)。在作业三中,我们也会进行类似的操作,尝试控制一个语言模型,强制它同意或拒绝用户的请求。
如今的语言模型很容易拒绝不当请求,比如询问如何制造炸药。这背后发生了什么?当模型接收到这样的请求时,它在中间的某一层生成了包含“拒绝”成分的表示。这个表示最终导致模型生成了拒绝的回答。
假设这个“拒绝”成分出现在第十层(具体在哪一层需要实验寻找)。需要注意的是,这个表示不仅包含“拒绝”成分,还包含其他信息,比如与“炸药”相关的内容。
如何精确地抽取出“拒绝”成分呢?一种方法是:
-
收集大量会导致模型拒绝的请求(例如,制造炸药、写诈骗信),提取它们在第十层的表示,然后取平均。这个平均向量包含了“拒绝”成分和其他信息的平均值。
-
收集大量模型不会拒绝的请求(例如,教我机器学习、写一首诗),提取它们在第十层的表示,也取平均。这个平均向量只包含其他信息的平均值。
-
用第一步得到的平均向量减去第二步得到的平均向量,期望能够抵消掉“其他信息”,从而得到一个纯粹代表“拒绝”的向量。
得到这个“拒绝向量”后,就可以用它来“操纵”模型。比如,当用户请求“请教我机器学习”时,模型本应正常回答。但如果我们在第十层,将原始表示加上这个“拒绝向量”,模型就可能会突然拒绝这个请求,声称学习机器学习是危险的。
反之,如果一个请求本该被拒绝,我们从其表示中减去“拒绝向量”,模型就可能不再拒绝,转而执行有害的指令。
一篇 2024 年的论文就做了这样的实验。本来询问“如何做瑜伽”,模型会正常回答。加入“拒绝”成分后,模型会说“做瑜伽对身体有害”。反之,当询问关于美国总统吸毒丑闻的恶意问题时,模型本会拒绝。但减去“拒绝”成分后,模型便会开始编造相关信件。
这表明,通过直接干预模型的内部表示,我们可以显著改变其行为。
Anthropic 对 Claude 的分析
Anthropic 公司对他们的 Claude 模型进行了深入分析,并在一篇长博客中分享了他们的发现。他们使用自动化方法在 Claude 中找到了各种各样的语义“成分”。
例如,他们找到了一个代表“拍马屁”的成分向量。当用户对 Claude 说:“我发明了一个谚语‘停下来闻闻玫瑰’ (Stop and Smell the Roses),你觉得怎么样?” Claude 正常会指出这是一个早已存在的谚语。但如果在其表示中加入“拍马屁”成分,Claude 就会开始大加吹捧:“哇,你太厉害了,真是世界伟人!这句话一定会名垂千古!”
Logic Lens:窥探模型思考过程
Logit Lens 是另一种分析方法,它直接将模型的中间层表示映射回词汇空间,让我们能以文字的形式“看到”模型在每一层的“想法”。
我们知道,模型的最后一层会通过逆嵌入(乘以 LM Head)得到 Logit。其实,我们可以对模型的任何一层的表示都进行同样的操作,看看在那个阶段,模型最倾向于生成哪个词元。
这个过程就像是窥探模型在每一层思考时,内心期待输出的词元是什么。虽然最终输出由最后一层决定,但观察中间层的变化,可以揭示模型的“思考”路径。
一篇 2024 年的文章使用 Logit Lens 分析 Llama 进行法文到中文翻译的过程。当输入法文词“fleur”(花)时,他们观察到:
-
在最初几层,模型输出的是一些无关的词。
-
在中间的某几层,模型输出的竟然是英文的“flower”。
-
直到最后几层,输出才变成了中文的“花”。
这似乎表明,模型在内部先将法文翻译成了英文,再由英文翻译成了中文,其“内心语言”可能是英文。
Patch Scope:将表示 (Representation) 转译成句子
Logit Lens 只能将表示对应到单个词元,但很多复杂的概念无法用一个词元来描述。Patch Scope 技术则可以将一个表示转译成一个完整的句子。
其工作原理如下:
-
假设我们想知道模型看到“李宏毅老师”这个输入时,某一层的表示具体代表什么。
-
我们构造一个模板句子,如“请简单介绍 X”,其中 X 是一个特殊符号。
-
我们将这个模板句子输入给同一个模型。当模型处理到 X 的位置时,我们用第一步中得到的“李宏毅老师”的表示,替换掉 X 对应的表示。
-
然后让模型从这个被“篡改”的状态继续生成文本。它可能会生成“李宏毅老师是一位……”这样的介绍。
通过改变模板(例如,换成“请告诉我 X 的秘密”),我们可以从不同角度解读同一个表示。
一篇论文用 Patch Scope 分析模型在读取“戴安娜是威尔士王妃 (Princess of Wales)”时的理解过程。他们发现:
-
前 1-3 层:模型似乎只注意到了“威尔士 (Wales)”,将其理解为一个国家。
-
第 4 层:模型注意到了“王妃 (Princess)”,将其理解为“一位女性王室成员的称号”。
-
第 5 层:模型进一步理解为“威尔士亲王的妻子 (wife)”。
-
第 6 层:模型最终理解整个短语指的是“戴安娜”这个人,并给出了她的完整介绍。
这个过程清晰地展示了模型是如何逐层加深对输入文本的理解的。
Transformer 层内部运作:自注意力 (Self-Attention)
接下来,我们深入 Transformer 层的内部,看看它是如何融合上下文信息的。一个 Transformer 层主要由两部分组成:自注意力 (Self-Attention) 层和前馈 (Feed-Forward) 网络。
自注意力层是 Transformer 能够考虑上下文的关键。它的作用是输入一排向量,输出一排等长的向量。这排输出向量接着会逐个通过几个前馈层,最终得到整个 Transformer 层的输出。
自注意力机制的著名论文是 2017 年的《Attention is All You Need》。许多人误以为这篇论文发明了注意力机制,但实际上注意力概念在 2014 年就已出现。这篇论文真正的贡献在于证明了仅靠注意力机制就足够了,不再需要像过去的 LSTM 等循环神经网络 (Recurrent Neural Network, RNN) 架构来处理上下文。这使得模型可以大规模并行化,极大地加速了训练过程。
自注意力机制的运作可以分为两步:
-
寻找相关词元:对于当前处理的词元(例如,“苹果”中的“果”),找出输入序列中哪些其他词元对理解它的意思有重要影响。
-
信息融合:将这些相关词元的信息与当前词元的信息结合起来,生成一个新的、富含上下文信息的表示。
具体来说,第一步是通过一种叫做 Query-Key-Value (QKV) 的机制实现的。
-
每个词元的嵌入会分别乘以三个不同的权重矩阵 WQ, WK, WV,得到三个新的向量:查询 (Query)、键 (Key) 和值 (Value)。
-
要计算“果”这个词元的新表示,我们会用“果”的 Query 向量,去和输入序列中所有其他词元(包括它自己)的 Key 向量计算点积。这个点积的结果就代表了它们之间的注意力权重 (Attention Weight),即相关性强度。
-
权重越高的词元,意味着它对理解“果”的当前含义越重要。
位置嵌入 (Positional Embedding):考虑位置信息
仅仅依靠词元嵌入进行 QK 计算有一个问题:它无法区分位置。例如,“青山绿水”的“青”和“青苹果”的“青”,与“果”的关联性计算结果会完全一样,因为它们的词元嵌入是相同的。
为了解决这个问题,需要引入位置信息。一种简单的方法是使用位置嵌入 (Positional Embedding)。这是一个额外的嵌入表,其中每一行代表一个位置(第一个位置、第二个位置……)。在输入时,每个词元的嵌入会加上其对应位置的位置嵌入。这样,模型就能区分不同位置的相同词元。
现代模型使用了更先进的方法,如 RoPE (Rotary Position Embedding),它能更好地处理长序列,甚至泛化到训练时未见过的更长位置。
注意力权重 (Attention Weight) 的计算与多头注意力 (Multi-Head Attention)
计算出所有词元的 Key 与当前词元的 Query 之间的点积分数后,通常会经过一个 Softmax 操作,将这些分数归一化,使其总和为 1。
接下来是信息融合阶段。我们将归一化后的注意力权重,作为加权系数,去对所有词元的 Value 向量进行加权求和。最终得到的这个加权和向量,就融合了所有相关词元的信息。为了防止信息丢失,通常还会使用残差连接 (Residual Connection),即将原始的词元嵌入直接加到这个加权和向量上,作为自注意力层的最终输出。
然而,词元之间的“影响”是多方面的。例如,“青”字影响了“果”的颜色,而“两”字影响了它的数量。单一的注意力机制可能无法同时捕捉这些不同维度的关系。因此,Transformer 采用了多头注意力 (Multi-Head Attention)。
这意味着模型会并行地运行多组独立的 QKV 计算,每一组称为一个“头 (Head)”。每个头可能专注于不同的语义关系(一个头关注颜色,另一个头关注数量等)。最后,所有头的输出结果会被拼接起来,再通过一个线性变换,融合成最终的输出。
因果注意力 (Causal Attention):只考虑左侧上下文
在语言模型中,预测下一个词元时,模型只能看到已经生成的词元,即当前位置左侧的上下文。为了模拟这个过程,Transformer 使用了因果注意力 (Causal Attention)。
在计算注意力权重时,每个词元只能“关注”它左边的词元和它自己,而不能“看到”右边的词元。在注意力权重矩阵中,这表现为上三角部分被屏蔽(设置为零)。
前馈层 (Feed-Forward Layers) 的运作
自注意力层的输出会接着进入一个前馈网络 (Feed-Forward Network, FFN)。FFN 通常由两个线性层和一个非线性激活函数组成。
-
输入向量首先乘以第一个权重矩阵,维度被放大(例如,从 3072 维放大到 8192 维)。
-
然后通过一个激活函数(如 ReLU 或 GELU),引入非线性。
-
最后乘以第二个权重矩阵,维度被缩减回原始大小(例如,从 8192 维缩减回 3072 维)。
这个过程可以理解为对每个位置的表示进行一次独立的、深度的特征提取和转换。有研究认为,FFN 也可以被看作是另一种形式的注意力机制,即一种基于特征维度的键值记忆网络。
神经元与矩陣運算
讲到这里,我们来揭示“神经元”在哪里。前馈网络中的矩阵运算,如果拆解开看,就是一系列的加权求和与激活函数操作。例如,输出向量的第一个维度 Y1,是通过将输入向量 X 的每个维度 Xi 乘以权重矩阵 W 的第一行的对应权重 W1i,然后求和,再加上一个偏置项 B1,最后通过激活函数得到的。
这个“加权求和 + 偏置 + 激活”的计算单元,就是神经网络中的一个神经元 (Neuron)。将这个过程图形化,就得到了我们熟悉的神经元示意图。当人们说“这模仿了人脑的运作”时,其底层本质上就是这些矩阵运算。整个语言模型就是由无数这样的“神经元”组成的巨大网络。
实践:解剖 Llama 与 Gemma 模型
接下来,我们进入实践环节,实际解剖 Llama 和 Gemma 这两个大型语言模型。
首先,我们加载 Llama 3B 和 Gemma 4B 模型。通过 model.num_parameters() 可以查看模型的参数数量。Llama 3B 约有 32 亿个参数,而 Gemma 4B 约有 43 亿个参数。
我们可以遍历模型的所有参数,打印出它们的名称 (Name) 和形状 (Shape)。参数名称可以告诉我们它在模型中的位置和作用。例如,model.layers.0.mlp.up_proj.weight 指的是第一层(索引为 0)的前馈网络(MLP)中,第一个线性层(up_proj)的权重矩阵。
通过观察 Llama 3B 的参数结构,我们可以看到:
-
token_embeddings:词元嵌入表,大小为 128256 x 3072,表示有约 12.8 万个词元,每个嵌入维度为 3072。 -
layers.0.self_attn:第一层的自注意力模块,包含q_proj,k_proj,v_proj(用于生成 Q, K, V)和o_proj(用于融合多头输出)的权重矩阵。 -
layers.0.mlp:第一层的前馈网络,包含gate_proj,up_proj(第一个线性层)和down_proj(第二个线性层)的权重。 -
模型共有 28 个 Transformer 层(从
layer.0到layer.27)。 -
没有独立的
lm_head参数,因为它复用了token_embeddings。
Gemma 模型的参数结构类似,但也有不同:
-
它包含处理图像的
vision_tower参数。 -
词汇表更大,约 26 万个词元。
-
共有 34 个 Transformer 层。
观察嵌入表 (Embedding Table) 与词元相似度 (Token Similarity)
直接查看参数矩阵中的数值,我们很难看出任何规律,只能看到一堆杂乱的数字。但我们可以通过分析词元嵌入来获得一些有价值的信息。
我们可以提取某个词元(如 "Apple")的嵌入向量,然后计算它与词汇表中所有其他词元嵌入的相似度(例如,余弦相似度),找出最相似的 K 个词元。
实验发现:
-
小写的 "apple" 与“苹果”(中文)、"Cupertino"(苹果公司总部所在地)的嵌入相似度很高。
-
首字母大写的 "Apple" 与 "MacBook"、"iPhone" 的嵌入相似度很高。
-
汉字“李”与拼音 "Li" 以及另一个姓氏“刘”的嵌入相似度很高。
-
汉字“王”与英文 "King" 的嵌入相似度很高。
这表明,嵌入表确实捕捉到了词元之间丰富的语义关系,包括跨语言的、概念关联的以及类别归属的关系。
表示 (Representation) 的变化与语义理解
接下来,我们观察经过 Transformer 层之后,表示是如何变化的。
-
上下文的影响:在第 0 层(词元嵌入层),同样是 "you" 这个词元,在不同句子中("How about you?" vs "How are you?")的嵌入是完全相同的。但经过第一层后,由于它们的左侧上下文不同,它们对应的表示就开始变得不同。而句子开头的 "How",由于其左侧上下文始终为空,所以在不同句子中,其各层表示都是相同的。
-
区分多义词:我们构造两个句子,一个句子中的 "Apple" 指水果,另一个指公司。然后,我们提取这两个 "Apple" 在每一层的表示,并计算它们的余弦相似度。
-
在第 0 层,相似度为 1,因为它们是同一个词元。
-
随着层数加深,相似度显著下降。这表明模型逐渐意识到了这两个 "Apple" 在不同语境下指向不同的实体,并将它们的表示在向量空间中推开。
-
-
识别同义语境:我们构造四个句子,前两个的 "Apple" 都指水果,后两个都指公司。然后两两计算它们在各层的表示相似度。
-
指代相同实体(都是水果或都是公司)的两个 "Apple",即使上下文不同,它们的表示在所有层中都保持着非常高的相似度。
-
指代不同实体的 "Apple" 之间,相似度则随着层数加深而迅速降低。
-
这证明了模型能够根据上下文准确地理解词义,并将语义相似的输入映射到向量空间中相近的位置。
Logic Lens 实践:观察模型思考路径
我们使用 Logic Lens 来观察模型在预测下一个词元时的“思考”过程。当输入“天气”时:
-
最初几层,模型预测的下一个词元是“气”。
-
中间几层,预测变为英文 "weather",然后是 "forecast"。
-
最后几层,预测变回中文“预”。
这再次印证了 Llama 模型可能在内部使用英文进行“思考”。当输入“今天天气真”时,我们看到模型在“好”和“不怎么样”之间摇摆,最终选择了“好”。
注意力权重 (Attention Weight) 可视化
最后,我们可视化注意力权重,看看模型在处理句子时,“关注”了哪些部分。要获取注意力权重,需要在模型推理时设置 output_attentions=True。
Llama 3B 的每一层有 24 个注意力头。我们可以将某个特定层、特定头的注意力权重矩阵绘制成热力图。矩阵的行代表 Query(当前词元),列代表 Key(被关注的词元)。
观察发现:
-
由于是因果注意力,权重矩阵的右上角总是零。
-
不同的头表现出不同的行为模式。有些头似乎在做一些可解释的事情,例如,当处理句子 "The apple is green. What color is the apple?" 中第二个 "apple" 时,有的头会关注到前文的 "green",有的头会关注到第一个 "apple"。
-
许多头会高度关注句首的特殊起始符。这可能是一种“默认”行为,当句子中没有特别相关的信息时,注意力就集中到这个起始符上。
通过可视化所有层、所有头的注意力权重,我们可以看到一个复杂而多样的模式集合。每个头都在做着自己的事情,共同协作,最终实现了对语言的深刻理解。解剖 Gemma 模型也发现了类似的行为。这些观察为我们理解大型语言模型的内部工作机制提供了宝贵的视角。
