Let's build GPT: from scratch, in code, spelled out.

Let's build GPT: from scratch, in code, spelled out.

如何理解ChatGPT的工作原理

ChatGPT简介

  • 介绍了ChatGPT作为一个与AI互动的系统,能够执行基于文本的任务,例如创作关于AI重要性的俳句。
  • 演示了ChatGPT生成不同结果的能力,显示其为概率性系统,可以对同一提示产生多种回答。

ChatGPT的语言模型特性

  • 强调了ChatGPT是一个语言模型,通过建模单词或字符序列来完成给定的文本序列。
  • 提供了一些有趣的提示示例,展示用户如何利用ChatGPT进行幽默和创造性的写作。

Transformer架构的重要性

  • 讨论了支撑ChatGPT运作的神经网络及其基础——2017年的《Attention is All You Need》论文,该论文提出了Transformer架构。
  • 阐述了Transformer在机器翻译中的应用,以及它如何在接下来的五年内影响整个AI领域。

构建简化版语言模型

  • 表达了希望构建类似于ChatGPT但更简单版本的愿望,并指出训练过程复杂,需要大量互联网数据。
  • 提出了使用“Tiny Shakespeare”数据集进行训练,以便更好地理解字符级别语言模型。

模型训练与生成文本

  • 描述了如何通过分析角色之间的关系来预测下一个字符,从而生成类似莎士比亚风格的文本。
  • 展示了经过训练后生成无限莎士比亚风格文本的方法,并解释该过程与ChatGPT相似,但以字符为单位进行预测。

Nano GPT项目概述

  • 介绍Nano GPT,这是一个用于训练Transformer的小型GitHub项目,包含两份300行代码文件,用于定义和训练模型。

如何从零开始构建一个Transformer模型

训练与数据集

  • 使用开放的Web文本数据集进行训练,这是一个相当大的网页数据集,成功重现了GPT-2的性能。GPT-2是OpenAI在2017年发布的早期版本。
  • 本次讲座将从头开始编写代码库,逐步定义Transformer,并使用小型莎士比亚数据集进行训练,以生成无限的莎士比亚文本。

理解ChatGPT的工作原理

  • 目标是让观众理解和欣赏ChatGPT背后的工作原理,只需具备Python基础知识以及一些微积分和统计学知识即可。
  • 创建新的Google Colab Jupyter笔记本,以便于后续共享开发的代码,方便观众跟随学习。

数据处理与字符编码

  • 下载小型莎士比亚数据集,文件大小约为1MB,并读取文本内容,总字符数大约为100万。
  • 打印出前1000个字符以确认读取正确,这些字符基本符合预期。

词汇表与标记化策略

  • 利用Python中的set构造函数获取文本中所有出现过的字符,并创建有序列表,从而确定词汇表大小,共有65个可能字符,包括空格、特殊字符及大小写字母。
  • 开发输入文本的标记化策略,将原始字符串转换为整数序列,以便于模型处理。采用基于字符级别的语言模型,将单个字符翻译成整数。

编码与解码过程

  • 构建编码器和解码器,通过迭代所有字符创建查找表,实现字符串到整数列表及其反向映射。
  • 提到多种可能的编码方式,例如Google使用句子片段(SentencePiece)作为子词单位级别标记器,而OpenAI则使用名为Tick Token的库来实现对话生成。

子词编码与实践应用

  • 在实际应用中,通常采用子词编码方法,可以在较小词汇表下获得更长序列或在较大词汇表下获得短序列。

如何使用PyTorch处理莎士比亚文本数据

数据集准备与编码

  • 使用PyTorch库中的torch.tensor将莎士比亚的文本数据编码为整数序列,形成数据张量。
  • 观察到前1000个字符被转换为一个庞大的整数序列,其中每个整数代表特定的字符,例如0可能表示换行符,1可能表示空格。

数据集划分

  • 将数据集分为训练集和验证集,前90%作为训练数据,后10%作为验证数据,以评估模型是否过拟合。
  • 验证数据用于确保神经网络生成类似莎士比亚风格的文本,而不是简单地记忆原始文本。

Transformer模型训练

  • 在训练Transformer时,不会一次性输入整个文本,而是随机抽取小块进行训练,以降低计算成本。
  • 每次训练时只处理固定长度的数据块(称为“块大小”),以提高效率并适应GPU的并行处理能力。

示例与上下文理解

  • 以块大小8为例,从训练集中提取9个字符,这些字符中包含多个示例,每个位置都可以预测下一个字符。
  • 在9个字符中,有8个独立示例,通过上下文来预测下一个字符,例如在“1847”之后预测“56”。

批量维度与效率

  • 为了提高效率,在输入Transformer时,会将多个小块堆叠成一个张量进行批量处理,但这些小块之间是独立的。

如何在Transformer中处理输入数据

随机数生成与批处理大小

  • 在随机数生成器中设置种子,以确保每次运行时看到的数字一致。
  • 批处理大小决定了每次前向和反向传播过程中处理的独立序列数量,最大上下文长度用于预测。

数据分割与随机偏移

  • 生成随机偏移量以从训练集中抓取数据块,这些偏移量是在0到数据长度减去块大小之间随机生成的。
  • 输入X是从这些随机位置提取的字符块,而目标Y则是相应位置上加一后的字符。

张量构建与输入格式

  • 将所有一维张量堆叠成一个四行八列的张量,形成一个包含32个独立示例的批次。
  • 每个输入对应于目标数组中的正确答案,确保Transformer能够同时处理所有示例并进行预测。

简单神经网络实现

  • 开始使用最简单的神经网络——二元语言模型,并直接实现PyTorch模块。
  • 创建一个二元语言模型类,并传入输入和目标进行初始化。

嵌入层与预测逻辑

  • 使用nn.embedding创建词嵌入表,每个整数索引对应于嵌入表中的一行。
  • 通过时间和通道将嵌入结果排列为张量,以便进行下一个字符的预测。

损失函数评估

  • 使用负对数似然损失(交叉熵)来评估预测质量,测量logits与目标之间的一致性。

如何处理PyTorch中的交叉熵损失

PyTorch交叉熵的输入要求

  • 在调用PyTorch的交叉熵函数时,虽然我们希望直接使用其功能形式,但会遇到错误信息。
  • PyTorch期望多维输入的通道维度为第二个维度,因此需要将形状从B x T x C调整为B x C x T。

重塑张量以符合要求

  • 通过给维度命名并重塑logits,使其变为二维数组,以便更好地符合PyTorch对维度的期望。
  • 将logits重塑为B x (T*C),这样可以保持通道维度作为第二个维度。

目标张量的处理

  • 目标张量当前形状为B x T,需要将其转换为一维,即B*T。
  • 使用负数来让PyTorch自动推断目标形状,这样在重塑后就能匹配交叉熵计算。

损失评估与模型生成

  • 当前损失值为4.87,预期损失约为4.1217,表明初始预测不够分散,有一定的信息熵。
  • 模型能够在某些数据上评估质量,并且接下来将进行文本生成。

文本生成过程

  • 生成函数负责扩展输入,从B x T扩展到B x (T+1)+(T+2)+(T+3),以实现批次和时间维度上的生成。
  • 预测结果会被连接到之前的idx上,形成新的idx,以便继续生成更多内容。

概率采样与索引更新

  • 在生成过程中,仅关注最后一步的logits,将其转化为概率,然后使用torch.multinomial进行采样。
  • 每个批次中只获取一个预测结果,最终形成一个新的p x (T+1)。

可选目标处理与模型测试

  • 如果没有提供目标,则不会计算损失,而是仅返回logits,以便于模型测试和验证。

如何启动生成模型

生成模型的初步设置

  • 在生成过程中,使用0作为序列的第一个字符,代表换行符,这样可以合理地开始生成。
  • 由于生成是在批处理层面进行的,需要从零维数组中索引,以获取时间步长,并将其转换为简单的Python列表。

模型训练的重要性

  • 当前生成结果是随机的,因此需要对模型进行训练以减少随机性。
  • 尽管当前函数设计为通用,但在实际应用中只需关注最后一部分数据来进行预测。

优化器与训练过程

  • 创建PyTorch优化对象,选择Adam优化器而非传统的随机梯度下降(SGD),因为Adam在许多情况下表现更好。
  • 对于小型网络,可以使用较高的学习率,例如3E负3,而不是通常推荐的3E负4。

迭代与损失评估

  • 在每次迭代中,采样新数据、评估损失并更新参数,通过典型训练循环实现模型优化。
  • 初始损失约为4.7,通过迭代逐渐降低到大约3.6,显示出优化效果。

模型进展与改进方向

  • 随着训练次数增加,损失进一步降低至2.5左右,表明模型正在取得进展。
  • 当前模型仍然非常简单,仅依赖最后一个字符进行预测,需要使得各个token之间能够相互作用,以便更好地理解上下文。

脚本化与超参数设置

  • 将Jupyter Notebook中的代码转换为脚本,以简化工作流程并集中于最终产品。

数据加载与模型创建

数据与设备的迁移

  • 在加载数据时,需要确保将其移动到设备上,创建模型时也要将模型参数移动到设备。
  • 例如,神经网络的嵌入表包含一个双重权重,用于存储查找表,这个表会被移动到GPU,以加速计算。

训练循环中的损失估计

  • 在训练循环中,仅打印当前损失值是非常嘈杂的测量,因为每个批次的结果可能会有所不同。
  • 通常需要使用估计损失函数来平均多个批次的损失,从而减少噪声。

模型评估与训练模式

  • 设置模型为评估阶段后,再将其重置为训练阶段。当前模型在这两种模式下行为相同,因为没有Dropout层等。
  • 理解神经网络处于何种模式是良好的实践,因为某些层在推理和训练时表现不同。

内存效率与反向传播

使用上下文管理器

  • 使用torch.no_grad上下文管理器可以告知PyTorch不进行反向传播,从而提高内存使用效率。
  • 通过不存储中间变量,可以使得内存使用更加高效,这是一个良好的实践。

自注意力机制的数学技巧

自注意力块准备

  • 脚本运行后输出了训练损失和验证损失,显示出大约2.5的结果,为接下来的自注意力块编写做好准备。

令牌之间的信息流动

  • 创建一个形状为B x T x C(例如4 x 8 x 2)的张量,其中B、T和C分别表示批次、时间和通道信息。
  • 希望令牌之间能够相互交流,但未来位置的令牌不能影响当前令牌的信息流动。

信息传递方式及其局限性

平均化历史信息

  • 对于第五个位置的令牌,可以通过对之前所有元素进行平均来实现与过去的信息交流,这样形成一个特征向量以总结历史信息。

信息丢失问题

如何提高循环效率

循环与批处理维度

  • 当前使用的for循环在处理批次维度时效率不高,正在独立迭代时间和之前的标记。
  • 通过对零维度进行平均,得到一个一维向量X背景词,这个过程虽然简单,但并不高效。

平均计算的示例

  • 在计算过程中,第一个位置的两个元素相等,因为只对一个标记取平均;后续位置则是多个标记的平均值。
  • 最终结果是所有元素的垂直平均,尽管这个方法有效,但仍然存在效率问题。

矩阵乘法优化

  • 引入矩阵乘法作为数学技巧,通过简单矩阵(全为1)与随机数矩阵相乘来展示如何提高效率。
  • C矩阵中的数字是通过行与列的点积得出的,利用全为1的行简化了计算过程。

利用下三角矩阵

  • 使用torch库中的triangular函数,可以提取下三角部分,从而忽略某些元素,提高计算效率。
  • 通过这种方式,我们可以从B中提取特定行,而不是进行完整的点积运算。

动态求和与平均

  • 根据当前权重(全为1或0),动态地对B中的行进行求和或平均,这样可以灵活调整所需的数据。
  • 通过将A归一化,使其总和为1,从而实现对B中行的逐步平均,并最终得到所需结果。

向量化操作提升性能

  • 为了进一步提高效率,将生成一个权重数组,用于控制每一行在求平均时的重要性。

矩阵乘法与加权聚合的应用

批量矩阵乘法的实现

  • 介绍了批量矩阵乘法的概念,能够并行处理所有批次元素,并对每个批次元素进行 T x T 与 T x C 的相乘。
  • 结果生成 B x T x C 的结构,Expo 和 Expo2 应该是相同的,通过 torch.allclose 验证这一点。

加权和的计算

  • 使用批量矩阵乘法来实现加权和,这种加权和根据指定的 T x T 数组中的权重进行计算。
  • 权重以三角形形式存在,确保在第 t 维度上的 token 仅能获取前面 tokens 的信息,这是所需的功能。

Softmax 的应用

  • 引入第三种版本,使用 softmax 来处理数据。初始时 Trill 矩阵全为零,通过 masked fill 将其变为负无穷大。
  • 对每一行执行 softmax 操作,实现归一化,从而得到相同的矩阵结构。

自注意力机制中的亲和力

  • 权重从零开始,可以视为交互强度或亲和力,表示过去每个 token 在聚合时的重要性。
  • 设置负无穷大以阻止未来 tokens 与过去 tokens 通信,从而影响聚合过程。

自注意力块的发展

  • 利用下三角形式的矩阵乘法进行加权聚合,以便开发自注意力模块。

自注意力机制的实现

交互层与位置编码

  • 在线性层中实现交互,当前运行结果看似不稳定,但将继续构建。
  • 引入第二个位置嵌入表,每个位置从0到块大小减1都有自己的嵌入向量。
  • 通过嵌入表生成一个形状为T x C的矩阵,将标记嵌入与位置嵌入相加,形成新的表示x。

自注意力机制的核心

  • 当前模型对位置信息不敏感,因为它是平移不变的,但在自注意力模块中,位置信息将变得重要。
  • 自注意力机制是视频中的关键部分,将实现单个自注意力头。

权重初始化与信息聚合

  • 示例中通道数从2更改为32,使用4x8的标记排列,每个标记的信息为32维。
  • 当前代码简单地对过去和当前标记进行平均处理,通过创建下三角结构来屏蔽权重矩阵。
  • 初始化不同标记之间的亲和度为零,使每一行具有均匀数字,从而实现简单平均。

数据依赖的信息收集

  • 不同标记会对其他标记产生不同程度的兴趣,自注意力解决了这一问题。
  • 每个节点或标记在每个位置发出两个向量:查询(query)和键(key),分别表示“我在寻找什么”和“我包含什么”。

计算亲和度

  • 通过查询向量与所有键向量进行点积计算亲和度,如果查询和键对齐,则会有较高的互动性。
  • 实现单个自注意力头时,需要小心处理矩阵乘法,并转置K以适应批次维度。
  • 初始化线性模块并生成键和值,通过前馈操作得到B x T x 16大小的数据结构。

最终亲和度计算

自注意力机制的工作原理

加权聚合的变化

  • 介绍了加权聚合作为一种数据依赖的函数,强调每个批次元素将具有不同的权重,而不是之前统一的常数。
  • 通过观察输入的第零行,展示了输出权重的不均匀性,特别是最后一行中的第八个标记如何根据其内容和位置生成查询。

查询与键之间的关系

  • 第八个标记创建一个查询,寻找特定类型的信息(如元音和辅音),并通过节点发出键以建立高亲和度。
  • 当查询与键进行点积时,如果它们之间有高亲和度,则会通过softmax聚合大量信息。

聚合过程中的细节

  • 在没有掩蔽和softmax操作下,展示了点积输出的原始值范围,并解释了如何使用上三角掩蔽来限制节点间的信息交流。
  • 通过指数化和归一化处理,使得信息聚合分布良好,不再出现负值,从而实现数据依赖的信息聚合。

值向量的重要性

  • 在自注意力头中,引入“值”这一概念,通过线性变换计算得到,并在最终输出中乘以加权后的值向量。
  • 强调X向量代表每个标记私有的信息,而V则是用于单头聚合的信息。

自注意力机制总结

  • 自注意力机制被描述为一种通信机制,其中每个节点可以从指向它的所有节点中汇总信息,以数据依赖方式进行加权求和。
  • 指出图结构与语言建模场景中的不同,以及自注意力可以应用于任意有向图形。

空间感知与位置编码

  • 注意到自注意力没有空间概念,因此需要对节点进行位置编码,以便它们了解自身在空间中的位置。

空间的概念与独立处理

批处理维度的独立性

  • 在计算相对位置编码时,需要特别添加空间的概念,这样才能将这些信息整合到向量中。
  • 批处理中的元素是独立的示例,它们之间不会相互交流,采用并行方式进行矩阵乘法。

有向图结构与节点交互

  • 在语言建模中,有向图的特定结构使得未来的标记不会与过去的标记通信,但在一般情况下,这并不是必须的约束。
  • 例如,在情感分析中,所有节点可能需要完全交流,以便预测句子的情感。

编码器块与解码器块

  • 使用自注意力机制时,编码器块允许所有节点完全交流,而解码器块则会保留三角形结构以防止未来节点影响过去节点。
  • 解码器被称为“解码”,因为它具有自回归格式,通过三角矩阵掩蔽来确保未来的信息不泄露给过去。

自注意力与交叉注意力

自注意力机制

  • 自注意力是指键、查询和值都来自同一来源(X),因此这些节点是在自我关注。

交叉注意力机制

  • 交叉注意力允许查询来自一个源,而键和值来自另一个外部源,例如编码一些上下文信息。
  • 当我们希望从其他节点提取信息时,会使用交叉注意力;而当仅需查看彼此时,则使用自注意力。

缩放注意力的重要性

注意力实现过程

  • 在“Attention is All You Need”论文中,我们已经实现了基于查询、键和值的乘法和聚合操作,但缺少了对头大小平方根倒数的除法步骤。

缩放的重要性

  • 缩放是重要的一步,因为如果输入为单位高斯分布,直接计算会导致方差过大,从而影响后续softmax操作。

初始化阶段控制方差

Softmax行为分析

  • 如果权重值过于极端,softmax将收敛到单一热向量,这意味着每个节点只聚合来自单一其他节点的信息,这是不理想的情况。

控制初始化方差的方法

自注意力机制的实现与多头注意力

自注意力机制的基础

  • 在节点上应用线性投影,创建一个名为 Trill 的变量,该变量不是模块的参数,而是一个缓冲区,需要通过注册缓冲区将其分配给模块。
  • 输入 X 后,计算键(keys)、查询(queries)和归一化处理,使用缩放注意力机制,并确保特征不与过去通信,从而形成解码器块。

解码器语言模型构建

  • 在构造函数中创建自注意力头(self attention head),保持头部大小与嵌入相同,将信息编码后输入自注意力头。
  • 输出传递到解码器语言模型头以生成 logits,这是将自注意力组件插入网络的最简单方式。

训练过程中的调整

  • 确保输入索引不超过块大小,以避免位置嵌入表超出范围,因此添加代码裁剪上下文。
  • 降低学习率并增加迭代次数,因为自注意力无法容忍过高的学习率,训练结果从 2.5 改进至 2.4,但文本生成效果仍需提升。

多头注意力机制的实现

  • 引入多头注意力概念,通过并行应用多个自注意力并连接结果来增强模型能力。
  • 在 PyTorch 中创建多个自注意力头,并在通道维度上连接输出,实现多通道通信。

通信渠道与数据收集

  • 使用四个独立的通信通道,每个通道收集八维向量,总体合并为原始嵌入大小,这类似于组卷积。
  • 经过测试后,验证损失从 2.4 降至约 2.28,多通道有助于更好地捕捉不同类型的数据进行解码。

网络结构中的前馈部分

  • 注意到网络中存在前馈部分,即简单的多层感知机(MLP),用于每个节点级别的计算。

模型架构与优化

前馈网络的实现

  • 介绍了一个简单的前馈单层网络,该层由线性变换和相对非线性组成,称为前馈嵌入。
  • 前馈网络在每个token级别独立应用,强调自注意力机制用于信息交流,而前馈则是对收集到的数据进行个体思考。

模型训练与性能改进

  • 在训练过程中,验证损失从2.28下降至2.24,尽管输出仍不理想,但情况有所改善。
  • 计划将通信与计算交替进行,这也是Transformer模型的特点,通过多头自注意力和前馈网络实现。

Transformer结构与参数设置

  • 讨论了嵌入维度、头数及其在组卷积中的类似性,设定头数为4,并计算出每个头的大小为8,以确保通道一致性。
  • 创建多个块以交替执行通信和前馈操作,从而提高模型的表达能力。

深度神经网络的优化问题

  • 尝试运行模型时发现结果不佳,原因在于深度神经网络面临优化问题,需要借鉴Transformer论文中的两种优化方法。

残差连接的重要性

  • 引入残差连接(skip connections),这种概念源于2015年的一篇论文,有助于缓解深度网络中的优化困难。
  • 残差路径允许数据转化并通过加法从先前特征中跳过,使得梯度传播更加高效。

梯度传播与初始化策略

  • 加法操作使得梯度均匀分布到两个分支,从而形成“梯度高速公路”,确保监督信号能够无阻碍地传递到输入层。
  • 虚拟块在初始化时几乎不影响残差路径,但随着优化过程逐渐发挥作用,有助于提升整体性能。

实现残差连接与投影层

  • 在块中实现残差连接,通过加法将自注意力和前馈结果合并回原始输入,以增强信息流动。

模型优化与层归一化的应用

输入输出维度与前馈网络

  • 在重新审视论文时,发现输入和输出的维度均为512,而前馈网络中的内层维度为2048,这意味着通道大小需要乘以四。
  • 通过将嵌入乘以四来增加计算量,并在残差块中扩展这一层,最终训练得到了2.08的验证损失,但开始出现轻微的过拟合现象。

生成效果与创新方法

  • 尽管生成效果尚未达到理想状态,但文本开始接近英语形式,显示出模型逐渐改善。
  • 引入了“层归一化”(Layer Norm),其实现方式类似于批量归一化(Batch Normalization),确保每个神经元在批次维度上具有单位高斯分布。

层归一化的实现细节

  • 复制并粘贴了之前开发的1D批量归一化模块,以处理32个100维向量,确保每列数据均为零均值和单位标准差。
  • 实现了层归一化后,行不再被默认规范化,而是对每个示例进行单独规范化,从而简化了计算过程。

Transformer中的层归一化应用

  • 在Transformer中引入了预先规范化(pre-norm)形式,与原始论文略有不同,现在通常在变换之前应用层归一化。
  • 需要两个层归一化模块,第一个用于输入特征,第二个则在进入自注意力和前馈网络之前进行处理。

模型扩展与改进

  • 添加了可训练参数gamma和beta,使得最终输出可能不再是单位高斯分布,通过优化过程来调整这些参数。
  • 在Transformer最后添加了一层归一化,以便更好地解码到词汇表中,从而使模型更加完整。

如何使用Dropout技术提高神经网络性能

Dropout的基本概念

  • 在残差连接之前,可以在多头扩展的最后一层和计算相似度时随机丢弃一些节点,以防止节点之间的通信。
  • Dropout源于2014年的一篇论文,它在每次前向和反向传播中随机关闭部分神经元,从而训练出一个子网络的集合。
  • 由于每次前向和反向传播中被丢弃的神经元不同,最终形成了一个集成模型,这是一种正则化技术。

超参数调整与模型规模

  • 为了应对过拟合,作者将批量大小增加到64,并将上下文块大小从8个字符增加到256个字符。
  • 学习率降低至适应更大的神经网络,嵌入维度设置为384,每个头部为64维,共有六个头部和六层结构,同时Dropout比例设定为0.2。

模型训练效果

  • 经过训练后,验证损失从2.07降至1.48,显示出显著改善。该模型在A100 GPU上训练约15分钟。
  • 如果没有GPU,在CPU或Macbook上无法复现此结果,需要减少层数和嵌入维度。

输出结果分析

  • 打印出的文本输出更加可识别,但仍然是无意义的字符串,与输入文本格式相似。
  • 输入文本来自莎士比亚作品,生成的文本虽然形式上类似,但内容并不合理。

Transformer架构解析

  • 本视频实现的是仅解码器Transformer,没有编码器部分,因此缺少交叉注意力模块,仅包含自注意力和前馈网络。

Transformer模型的编码与解码

编码过程

  • 在编码过程中,法语句子的内容被读取并生成tokens,使用Transformer模型进行处理,没有三角形掩蔽,因此所有tokens可以相互交流。

解码过程

  • 解码器在语言建模中增加了与编码器输出的连接,通过交叉注意力机制引入这些信息,使得查询仍然来自X,但键和值则来自编码器的输出。

交叉注意力机制

  • 交叉注意力机制使得解码不仅依赖于当前解码的过去信息,还依赖于完整编码的法语提示,从而形成一个编码-解码模型。

Nano GPT概述

  • Nano GPT包含两个主要文件:train.py和model.py。train.py负责训练网络,包括保存和加载检查点、预训练权重等复杂操作。

模型结构与实现

  • model.py中的自回归自注意力块与之前所做的非常相似,生成查询、键和值,并进行点积运算和softmax处理,同时支持dropout。

如何训练ChatGPT?

训练阶段划分

  • ChatGPT的训练分为两个阶段:预训练阶段和微调阶段。在预训练阶段,使用大量互联网数据来训练仅有解码器的Transformer,以生成文本。

参数与数据集规模对比

如何训练大型语言模型

预训练阶段的挑战

  • 在预训练阶段,模型的规模通常超过一万亿参数,需要在大量互联网数据上进行训练。
  • 训练这样的大型模型需要数千个GPU相互协作,这对基础设施提出了巨大的挑战。
  • 完成预训练后,模型并不会直接回答问题,而是生成文档内容,表现为“文档补全器”。
  • 模型可能会产生不确定的行为,例如用问题回应问题或忽略用户的问题。

微调阶段的重要性

  • 微调阶段旨在将模型调整为助手角色,OpenAI的博客中提到这一过程分为三个步骤。
  • 收集特定格式的数据(如问题和答案),以便微调模型,使其能够更好地理解用户提问。
  • 第二步是让不同评审者对模型的响应进行评分,以此来建立奖励模型,从而优化输出质量。

强化学习与政策优化

  • 使用PPO(策略梯度强化学习优化器)来微调采样策略,以提高生成答案的质量和相关性。
  • 整个微调过程复杂且多步骤,将文档补全能力转变为有效的问题回答能力。

数据获取与复制难度

  • 大部分用于微调的数据并未公开,因此复制这一阶段非常困难,仅限于OpenAI内部使用。

小型GPT与架构相似性

  • 本次讨论还涉及到一个小型GPT项目,该项目基于2017年的论文《Attention is All You Need》进行开发,并在小型莎士比亚文本上取得了良好的结果。
  • 虽然架构基本相同,但小型GPT的规模仅为大型GPT的一万分之一到百万分之一之间。
Video description

We build a Generatively Pretrained Transformer (GPT), following the paper "Attention is All You Need" and OpenAI's GPT-2 / GPT-3. We talk about connections to ChatGPT, which has taken the world by storm. We watch GitHub Copilot, itself a GPT, help us write a GPT (meta :D!) . I recommend people watch the earlier makemore videos to get comfortable with the autoregressive language modeling framework and basics of tensors and PyTorch nn, which we take for granted in this video. Links: - Google colab for the video: https://colab.research.google.com/drive/1JMLa53HDuA-i7ZBmqV7ZnA3c_fvtXnx-?usp=sharing - GitHub repo for the video: https://github.com/karpathy/ng-video-lecture - Playlist of the whole Zero to Hero series so far: https://www.youtube.com/watch?v=VMj-3S1tku0&list=PLAqhIrjkxbuWI23v9cThsA9GvCAUhRvKZ - nanoGPT repo: https://github.com/karpathy/nanoGPT - my website: https://karpathy.ai - my twitter: https://twitter.com/karpathy - our Discord channel: https://discord.gg/3zy8kqD9Cp Supplementary links: - Attention is All You Need paper: https://arxiv.org/abs/1706.03762 - OpenAI GPT-3 paper: https://arxiv.org/abs/2005.14165 - OpenAI ChatGPT blog post: https://openai.com/blog/chatgpt/ - The GPU I'm training the model on is from Lambda GPU Cloud, I think the best and easiest way to spin up an on-demand GPU instance in the cloud that you can ssh to: https://lambdalabs.com . If you prefer to work in notebooks, I think the easiest path today is Google Colab. Suggested exercises: - EX1: The n-dimensional tensor mastery challenge: Combine the `Head` and `MultiHeadAttention` into one class that processes all the heads in parallel, treating the heads as another batch dimension (answer is in nanoGPT). - EX2: Train the GPT on your own dataset of choice! What other data could be fun to blabber on about? (A fun advanced suggestion if you like: train a GPT to do addition of two numbers, i.e. a+b=c. You may find it helpful to predict the digits of c in reverse order, as the typical addition algorithm (that you're hoping it learns) would proceed right to left too. You may want to modify the data loader to simply serve random problems and skip the generation of train.bin, val.bin. You may want to mask out the loss at the input positions of a+b that just specify the problem using y=-1 in the targets (see CrossEntropyLoss ignore_index). Does your Transformer learn to add? Once you have this, swole doge project: build a calculator clone in GPT, for all of +-*/. Not an easy problem. You may need Chain of Thought traces.) - EX3: Find a dataset that is very large, so large that you can't see a gap between train and val loss. Pretrain the transformer on this data, then initialize with that model and finetune it on tiny shakespeare with a smaller number of steps and lower learning rate. Can you obtain a lower validation loss by the use of pretraining? - EX4: Read some transformer papers and implement one additional feature or change that people seem to use. Does it improve the performance of your GPT? Chapters: 00:00:00 intro: ChatGPT, Transformers, nanoGPT, Shakespeare baseline language modeling, code setup 00:07:52 reading and exploring the data 00:09:28 tokenization, train/val split 00:14:27 data loader: batches of chunks of data 00:22:11 simplest baseline: bigram language model, loss, generation 00:34:53 training the bigram model 00:38:00 port our code to a script Building the "self-attention" 00:42:13 version 1: averaging past context with for loops, the weakest form of aggregation 00:47:11 the trick in self-attention: matrix multiply as weighted aggregation 00:51:54 version 2: using matrix multiply 00:54:42 version 3: adding softmax 00:58:26 minor code cleanup 01:00:18 positional encoding 01:02:00 THE CRUX OF THE VIDEO: version 4: self-attention 01:11:38 note 1: attention as communication 01:12:46 note 2: attention has no notion of space, operates over sets 01:13:40 note 3: there is no communication across batch dimension 01:14:14 note 4: encoder blocks vs. decoder blocks 01:15:39 note 5: attention vs. self-attention vs. cross-attention 01:16:56 note 6: "scaled" self-attention. why divide by sqrt(head_size) Building the Transformer 01:19:11 inserting a single self-attention block to our network 01:21:59 multi-headed self-attention 01:24:25 feedforward layers of transformer block 01:26:48 residual connections 01:32:51 layernorm (and its relationship to our previous batchnorm) 01:37:49 scaling up the model! creating a few variables. adding dropout Notes on Transformer 01:42:39 encoder vs. decoder vs. both (?) Transformers 01:46:22 super quick walkthrough of nanoGPT, batched multi-headed self-attention 01:48:53 back to ChatGPT, GPT-3, pretraining vs. finetuning, RLHF 01:54:32 conclusions Corrections: 00:57:00 Oops "tokens from the _future_ cannot communicate", not "past". Sorry! :) 01:20:05 Oops I should be using the head_size for the normalization, not C