首页 / 文章

从下一个标记预测到可运行代码:大语言模型如何生成Python代码

依次解释令牌、softmax、温度参数、自注意力机制以及前馈层,并给出逐步排序示例,以及由预测结果衍生的严格限制条件。

发布者:REACTAPP.TOP阅读约 9 分钟1998 词

来源: How LLMs Actually Work: From Predicting the Next Word to Writing Code.

像ChatGPT和Copilot这样的工具能够编写函数、修复错误并解释算法,看起来似乎不仅仅是简单的文本补全。下面的思维模型从底层开始:令牌预测、注意力机制,以及为何完成句子的机制同时也能生成有效的Python代码。

一个被反复提出的问题

无论输出的是俳句、营销邮件还是归并排序,语言模型都在不断回答同一个问题:基于迄今为止所见的一切,下一个最可能的令牌是什么?

传统意义上的语义理解并不存在,模型在生成 def 这个词之前也不会先决定要编写函数。它是逐个预测标记的。由于模型拥有数十亿个参数,训练文本的标记数量更是高达数千亿,因此其预测结果看起来很像推理过程。

什么是标记

在讨论预测之前,先了解精确度的概念。模型处理的不是字符或单词,而是标记,即由字节对编码等算法生成的子词单元。大致来说:

  • 常见的单词通常会成为一个单独的标记,比如 "the"、"def" 和 "return"。
  • 罕见的单词则会被拆分成多个部分,因此 "unhappiness" 会变成 ["un", "happiness"]。
  • 代码符号通常是单个标记,例如 "("、":"、"==" 以及 "->"。
  • 对于 GPT-4 来说,一个标记大约相当于 0.75 个英文单词,而在代码中则更接近 3 到 4 个字符,因为代码中充斥着像 : 和 ( 这样的单字符标记。

    该模型还拥有词汇表,即一份固定的已知标记列表,其条目数量通常在 50,000 到 100,000 之间。在每一步中,模型会为每个条目分配一个概率,然后选择概率最高的条目,或者从概率分布中抽样,具体选择方式由温度参数决定。

    预测循环

    对于序列 “The cat sat on the” 来说,需要计算的是条件概率:

    P(next_token | "The", "cat", "sat", "on", "the")
    

    那是对整个词汇表的分布。网络进行一次前向传播后会生成一个与词汇表大小相同的向量,随后通过softmax函数将其转换为有效的概率分布:

    P(token_i) = exp(logit_i) / Σⱼ exp(logit_j)
    

    在贪婪解码中会选择概率最高的标记。否则会按照概率分布进行采样,temperature参数用于控制分布的离散程度:

    • temperature = 0时始终选择概率最高的标记,因此输出结果是确定的。
    • temperature = 1时直接从原始分布中采样,这样能产生更多变化且更具创意的结果。
    • 当该值大于1时,分布会变得平坦,输出结果更加随机。
    • 当该值小于1时,分布会变得更加集中,输出结果则更为保守。

    这就是为什么同一个提示在不同运行中可能会产生不同答案的原因。

    Transformer

    将标记序列映射为下一个标记的概率分布的模型就是Transformer,具体来说是仅用于解码的版本,这也是GPT、Llama、Mistral和Claude等模型的设计基础。其整体工作流程如下:

    Token IDs → Embedding layer → [Attention + FFN] × N layers → Linear head → Softmax → P(next token)
    

    各组成部分的顺序如下:

    1. 嵌入层。每个整数令牌ID都会被转换为大小为d_model的密集向量,例如Llama-3 8B中的值为4096。位置嵌入,或在现代模型中使用的RoPE,用于添加与顺序相关的信息。
    2. 重复N次的Transformer层。每层包含多头自注意力机制、前馈网络、在“预归一化”风格下应用于每个子层之前的层归一化操作,以及残差连接。
    3. 线性输出层与softmax函数。最终的隐藏状态会被映射为形状为[|V|]的逻辑值,再通过softmax函数生成概率分布。

    自注意力机制

    自注意力机制使某个标记能够查看序列中的其他标记,从而更全面地了解自身。这正是Transformer相比RNN的优势所在:每个标记都可以在一步之内与序列中的其他所有标记建立联系,且不会因距离而出现梯度衰减。

    对于包含个标记的序列,每个标记都会被以三种方式投影:

    Q = XWᴬ_Q    (Query: what am I looking for?)
    K = XWᴬ_K    (Key: what do I contain?)
    V = XWᴬ_V    (Value: what do I contribute?)
    

    此处 X 是形状为 [n, d_model] 的输入矩阵,而三个 W 矩阵则是学习得到的权重。作为查询的标记 i 与作为键的标记 j 之间的得分如下:

    score(i, j) = qᵢ · kⱼᵀ / √dₖ
    

    通过除以关键维度的平方根,可以防止当 dₖ 值较大时点积变得过大,从而避免将 softmax 推入梯度极小的区域。完整的计算公式为:

    Attention(Q, K, V) = softmax(QKᵀ / √dₖ) · V
    

    在像GPT这样的仅解码器模型中,注意力机制是因果性的,也就是被屏蔽的:标记只能关注到编号不超过自身的位置。在应用softmax函数之前,未来位置的得分会被设为负无穷大,因此它们的权重恰好为零。

    多头注意力机制会并行运行个独立的注意力函数,每个函数的维度为,然后将这些函数的输出拼接在一起:

    MultiHead(Q, K, V) = Concat(head₁, ..., headₕ) · W_O
    headᵢ = Attention(QWᵢ_Q, KWᵢ_K, VWᵢ_V)
    

    不同的头部会负责处理不同的关系。有的遵循句法依存关系,有的依据共指关系,还有的依据位置接近度。

    前馈网络

    在注意力机制之后,每个标记的表示会经过按位置划分的前馈网络处理:

    FFN(x) = max(0, xW₁ + b₁)W₂ + b₂
    

    像 Llama 和 Mistral 这样的现代架构则使用 SwiGLU 代替:

    FFN(x) = (SiLU(xW_gate) ⊙ xW_up) · W_down
    

    模型所记忆的大部分知识都存储在FFN中。它类似于基于事实关联的键值存储系统。注意力机制负责在各个标记之间传递信息,而FFN则用于查找存储的事实和模式。在大型模型中,FFN的宽度大约是的四倍。Llama-3 70B的值为8192,中间层维度为28672,大部分参数都集中在这一层。

    为何模型能够编写代码

    代码与自然语言一样,也是一种标记序列。对模型而言,Python函数并无特别之处,只不过是一系列标记的组合,比如def、sort、_list、(、arr、)、:等等。

    语法正确的输出完全是由训练数据分布决定的。可以考虑以下条件概率:

    P("(" | ..., "def", " quicksort") ≈ 1.0
    P(":" | ..., "def", " quicksort", "(", "arr", ")") ≈ 1.0
    P("return" | context inside function body) >> P("import" | same context)
    

    这些概率值呈现尖锐的峰值,因为模型已经见过数百万份遵循相同规则的Python文件。Python的语法并未在模型的任何部分被硬编码,而是源自语料库的统计结果。

    这也解释了为何模型有时会生成结构完整但逻辑错误的代码——它们只是根据训练数据中的分布来最大化下一个标记出现的可能性,而不会实际运行代码并进行检查。

    排序函数的生成过程

    假设提示语是“编写一个用于排序列表的Python函数”。模型会将其视为一个标记序列,并逐个添加输出标记。大致流程如下:

    • 第一步,在提示语之后:出现概率约为0.91的def。
    • 第二步,在def之后:出现概率约为0.78的sort。
    • 第三步:出现概率约为0.65的_list或(。
  • 第4步,在sort(之后:arr或lst,权重约为0.71。
  • 第5步至第7步:先出现),然后是:,接着是一行换行符,各部分的权重约为0.99。
  • 第8步:四空格缩进,权重约为0.97。
  • 在第5步至第8步中,模型对语法标记的判断趋于确定。但这并非真正理解Python,而只是从所有函数定义看起来都相同的语料库中学习到的简单模式填充。

    真正的决策发生在更早的第1步至第4步,此时需要确定函数名、参数名以及整体结构。这些决策会通过注意力机制受到提示内容的影响。在生成函数名时,提示中的sort标记会获得较高的注意力权重。

    代码生成中的温度参数

    Copilot及类似工具在处理代码时通常在较低温度下运行,数值约为0.1到0.3:

    • 语法标记需要近乎确定性地生成。
    • 往往只有一种正确的实现方式。
    • 在高温度下,自行设计的API会成为主要的故障来源。

    对于变量名、文档字符串和注释等需要创意的内容,稍高的温度是可以接受的。某些工具还会使用束搜索算法,该算法会同时保留前k个候选序列,最终选择联合概率最高的那个。

    为何更大规模的模型表现不同

    7B模型与70B模型拥有相同的架构,区别在于规模:层数更多、d_model值更大,注意力头也更多。然而规模更大的模型在多步推理、更准确的代码生成以及情境化学习能力等方面展现出质的提升。

    在大规模应用中,该模型学习的不仅仅是表面的标记统计信息,它还能构建出更深层模式的压缩表示,比如算法结构、逻辑依赖关系以及类型约束。一个700亿参数的模型已经看到了足够多的带错误的代码以及相应的测试失败案例,因此它能够将某些模式与代码的正确性联系起来。这就是规模假设的实证基础:在计算能力、数据量和参数数量方面,模型的性能会呈现平滑的增长曲线,而代码质量也会像其他技能一样可靠地随之提升。

    由设计带来的限制

    了解其工作机制后可以发现一些固有的限制:

    1. 执行问题。 模型内部不存在运行时环境。它输出的成果看似是正确执行后的结果,但实际上并未真正进行任何执行操作。
    2. 保证正确性。预测基于训练数据集的概率最大值,而非逻辑上的正确性。
    3. 真正的创新性。在训练数据中不存在统计先例的算法是无法被发明的。模型可以组合与调整,但无法创造新事物。
    4. 精确的召回率。不存在查找表,所有信息都存储在权重中,因此API、函数签名和库版本有时会出现错误。

    这些都不是需要修复的漏洞,它们直接源于架构设计,了解这些特点能让用户更高效地使用编程助手。

    简要概述

    • 标记是一个子词单元,大约相当于0.75个英文单词。
    • 每一步的预测都是对词汇表进行软最大值计算。
    • 自注意力机制通过缩放后的查询键乘积,让每个标记能够关注所有之前的标记。
    • 多头注意力机制并行运行多个此类机制,以捕捉不同的关系。
    • 前馈网络充当事实关联的键值存储器。
    • 代码生成与文本生成类似,语法是从训练数据统计中形成的。
    • 温度参数决定了采样分布的尖锐程度。
    • 其固有局限在于无法执行、无法保证正确性,也无法产生真正的创新性。

    该模型中的所有组件,从注意力机制和前馈网络到残差项和层归一化,都只有一个目的:在给定所有先前标记的情况下,尽可能精确地计算出下一个标记的条件概率。其余特性则是由此自然产生的。

    相关阅读

  • 实用笔记:大语言模型究竟如何工作——标记、变换器与下一个标记 —— 对《实用笔记:大语言模型究竟如何工作——标记、变换器与下一个标记》的详细操作指南:为采用该模式的团队提供代码框架、检查清单及可直接使用的代码片段。
  • 隐藏在您的RAG流程中的五十年信息检索研究 —— RAG分块、混合搜索、HyDE、重排序及评估方法其实都源自数十年前的信息检索技术。本文将梳理这些研究历史及其存在的局限性。
  • 实用笔记:ZQ Intelligence——面向金融领域的协调器与专业智能体 — 详细讲解实用笔记:ZQ Intelligence:面向金融领域的协调器与专业智能体,包括相关合同、检查项以及适用于采用该架构的团队的代码插入位置。
  • 为何印度语言在大型语言模型中需要更多令牌以及如何解决该问题 — 相同的句子在英语中需要5个令牌,而在泰米尔语中则需要34个令牌。本文将探讨BPE训练数据为何会导致这一现象,以及如何通过重新平衡和调整词汇量来解决该问题,还会说明词汇扩展的原理。