ChatGPT 火遍圈内外,突然之间,好多人开始想要了解 NLP 这个领域,想知道 ChatGPT 到底是个什么?作为在这个行业奋斗5年的从业者,真的很开心让人们知道有一群人在干着这么样的一件事情。这也是我结合各位大佬的文章,总结下GPT 这条技术路线的初心。
其实,ChatGPT 的成功并非一朝一夕,而是 OpenAI 长达 4 年多持续努力、不懈追求取得的成果。从 2018 年的初代 GPT 开始,到 GPT-2、GPT-3、InstructGPT,以及后续一系列变体模型(统称 GPT-3.5 系列),到如今的 ChatGPT4,每一步都是不可或缺的。所以,ChatGPT 不是一次伟大创新的产物,而是许多个阶段性创新持续叠加的结果。
本文将介绍以下内容:
让我们把视角回到 2018 年,那个时候 NLP 在深度学习上基本还处于 word2vec 以及为不同任务做定制化深度模型的情况,虽然已经有 ELMo 这类预训练模型出现,但是其影响力还远远不足。在这个背景下,GPT 第一代预训练语言模型出现了。
GPT 原文标题为 Improving Language Understanding by Generative Pre-Training,即使用通用的预训练模型来提升语言理解能力(Generative Pre-Training 也可理解为“生成式预训练”)。GPT 这个名字就来源于 Generative Pre-Training。
从论文标题可以引出了两个问题:
什么是通用?在学习通用的,迁移性强的文本特征表达时,什么目标函数是有效的?
有了通用的特征表达之后,如何将它迁移到不同下游任务?
GPT 使用了预训练 + 微调的方式解决了这两个问题。
在预训练阶段,GPT 选择 transformer 的 decoder 部分作为模型的主要模块,transformer 是 2017年 google 提出的一种特征抽取模型,GPT 以多层 transformer 堆叠的方式构成了整个预训练模型结构。
假设有一段文本,把每个词计作 U i Ui Ui ,GPT 使用标准的语言模型目标函数来最大化下面的似然函数:
具体来说是要预测每个词 U i U_i Ui 的概率,这个概率是基于它前面 U i − k U_{i-k} Ui−k 到 U i − 1 U_{i-1} Ui−1 个词,以及模型 θ \theta θ 。这里的 k 表示上文的窗口大小,理论上来讲 k 取的越大,模型所能获取的上文信息越充足,模型的能力越强。
模型对输入 U 进行特征嵌入得到 transformer 第一层的输入 h 0 h_0 h0,再经过多层 transformer 特征编码,使用最后一层的输出即可得到当前预测的概率分布:
其中, W e W_e We 为词嵌入矩阵, W p W_p Wp 为位置嵌入矩阵, H l H_l Hl 为第 l l l 层 transformer 的输出, h n h_n hn 为最后一层 transformer 的输出, n n n 为模型层数。
在微调阶段,在有特定下游任务标签的情况下,给定输入序列 x 1 x_1 x1 到 x m x_m xm ,预测 y y y 的概率,即将序列输入到预训练好的模型中,得到最后一层 transformer 的最后一个 token x m x^{m} xm 的特征 h i m h^{m}_i him ,再经过预测层就可以得到对应标签的概率分布:
微调阶段的目标函数为:
最后将两个目标函数联合训练得到的效果最好,即最终目标函数为:
以我们现在的视角来看,选择 transformer 还是 RNN,这个答案是显而易见的,但是在当时那个时间点那个环境下,选择 transformer 还是 RNN 作为预训练模型结构并不是一个显而易见的问题。在模型选择时,作者发现与 RNN 相比,Transformer 具有更加结构化的记忆单元来解决长距离依赖问题,处理更长的文本信息,从而使得学习到的特征在各个任务中的迁移具有更强的鲁棒性。
我们都知道,Transformer 模型一开始是用来做 seq2seq 任务的,所以它包含编码器和解码器两个部分;他们两者的区别主要是,编码器在抽取序列中某一个词的特征时能够看到整个序列中所有的信息,即上文和下文同时看到;而解码器中因为有 mask 机制的存在,使得它在编码某一个词的特征时只能看到自身和它之前的文本信息。GPT 模型选择了 Transformer 的 decoder,也就是解码器的部分,也正是因为 GPT 的预训练目标函数选取的是标准的语言模型目标函数,使得模型在预测某一个词的时候只考虑上文信息而不参考下文。
大家知道,BERT 在预训练的时候选择的不是标准的语言模型作为目标函数,而是一种 mask 语言模型, 也就是在预测句子中某一个词的时候可以同时看到它前后的所有上下文信息,类似于一种完形填空任务,所以 BERT 选择的是 Transformer 的编码器模块。
编码器和解码器的选取倒不是 GPT 和 BERT 的区别,它们的区别主要是预训练目标函数的选取,有人认为 GPT 选择的是一个更难的训练目标,它是根据前面的信息去预测下文,预测未来肯定是比完形填空难度要更大的。这也能从某种程度上解释了为什么相同规模的 GPT 和 BERT 模型,GPT 的效果要比 BERT 差。但是从另一个角度去想,如果能够把预测未来这个事情做好的话,它最终所能达到的效果的天花板一定是更高的,这可能也是 OpenAI 从一开始到现在一直坚持使用标准语言模型目标函数来做预训练模型的其中一个原因 吧,当然这只是一种猜想。事实证明,从 GPT-3 开始,到最近的 ChatGPT,OpenAI 所取得的令人惊艳的效果也一定程度上证明了他们的选择的正确性。
训练数据方面,**初代 GPT 使用了 BooksCorpus 数据集,文本大小约 5 GB,包含 7400w+ 的句子。**该数据集是由约 7000 本独立的、不同风格类型的书籍组成。选择该数据集主要的好处是书籍文本包含大量高质量长句,保证模型学习长距离信息依赖。
模型的一些关键参数为:
参数 | 取值 |
---|---|
transformer 层数 | 12 |
特征维度 | 768 |
transformer head 数 | 12 |
总参数量 | 1.17 亿 |
如上图所示,分别例举了 NLP 中四个常见任务(文本分类、文本蕴含、文本相似度、问答任务)作为下游任务应用到 GPT 模型时,其输入序列是如何构造的,以及对应的预测层是如何设计的。
总的来说,都是通过在序列前后添加 Start 和 Extract 特殊标识符来表示开始和结束,序列之间添加必要的 Delim 标识符来表示分隔,当然实际使用时不会直接用 “Start/Extract/Delim” 这几个词,而是使用某些特殊符号。基于不同下游任务构造的输入序列,使用预训练的 GPT 模型进行特征编码,然后使用序列最后一个 token 的特征向量进行预测。
可以看到,不论下游任务的输入序列怎么变,最后的预测层怎么变,中间的特征抽取模块都是不变的,具有很好的迁移能力。
初代 GPT 到底做了什么?有哪些贡献?
第一, 它是最早一批提出在 NLP 任务上使用 pre-train + fine-tuning 范式的工作。
第二, GPT 的实验证明了模型的精度和泛化能力会随着解码器层数增加而不断提升,而且目前还有提升空间,如下图:
第三,预训练模型具有 zero-shot 的能力,并且能随着预训练的进行不断增强,如下图:
值得注意的是,上述第二和第三点,也直接预示着后续 GPT-2 和 GPT-3 的出现。
其实 pre-train + fine-tuning 在计算机视觉里面早在好多年前已经成为主流的算法,但是在 NLP 中一直没有流行起来,主要还是因为在 NLP 里面没有像 ImageNet 那样大规模标好的数据集,这也导致相当一段时间内,深度学习在 NLP 的进展相对比较缓慢,直到 GPT 和 BERT 的出现才渐渐打开局面。
如果说使用大规模无标注的文本进行模型的预训练使 NLP 的发展往前走了一大步,那么 GPT 系列一直在努力推动的 zero-shot 可以说是走了另一大步。
为了进一步验证 zero-shot 的能力,OpenAI 在 GPT-1 提出一年后,推出了 GPT-2。
GPT-2 原文标题为 Language Models are Unsupervised Multitask Learners,字面意思为语言模型是一种无监督多任务学习器。
标题中的多任务学习与我们常规理解的有监督学习中的多任务不太一样,这里主要是指模型从大规模数据中学到的能力能够直接在多个任务之间进行迁移,而不需要额外提供特定任务的数据,因此引出了 GPT-2 的主要观点:zero-shot。
不论是 GPT-1 还是 BERT,NLP 任务中比较主流的 pre-train + fine-tuning 始终还是需要一定量的下游任务有监督数据去进行额外的训练,在模型层面也需要额外的模块去进行预测,仍然存在较多人工干预的成本。GPT-2 想彻底解决这个问题,通过 zero-shot,在迁移到其他任务上的时候不需要额外的标注数据,也不需要额外的模型训练。
在 GPT-1 中,下游任务需要对不同任务的输入序列进行改造,在序列中加入了开始符、分隔符和结束符之类的特殊标识符,但是在 zero-shot 前提下,我们无法根据不同的下游任务去添加这些标识符,因为不进行额外的微调训练,模型在预测的时候根本不认识这些特殊标记。所以在 zero-shot 的设定下,不同任务的输入序列应该与训练时见到的文本长得一样,也就是以自然语言的形式去作为输入, 例如下面两个任务的输入序列是这样改造的:
机器翻译任务:translate to french, { english text }, { french text }
阅读理解任务:answer the question, { document }, { question }, { answer }
为什么上述输入序列的改造是有效的?或者说为什么 zero-shot 是有效的?这里引用原文的一句话:
Our approach motivates building as large and diverse a dataset as possible in order to collect natural language demonstrations of tasks in as varied of domains and contexts as possible.
大概意思是,从一个尽可能大且多样化的数据集中一定能收集到不同领域不同任务相关的自然语言描述示例,例如下图中展示了英法互译任务在自然语言中出现的示例,表明了不同任务的任务描述在语料中真实存在:
所以 GPT-2 的核心思想就是,当模型的容量非常大且数据量足够丰富时,仅仅靠语言模型的学习便可以完成其他有监督学习的任务,不需要在下游任务微调。
在模型结构方面,整个 GPT-2 的模型框架与 GPT-1 相同,只是做了几个地方的调整,这些调整更多的是被当作训练时的 trick,而不作为 GPT-2 的创新,具体为以下几点:
后置层归一化( post-norm )改为前置层归一化( pre-norm );
在模型最后一个自注意力层之后,额外增加一个层归一化;
调整参数的初始化方式,按残差层个数进行缩放,缩放比例为 1 : n \sqrt{n} n ;
输入序列的最大长度从 512 扩充到 1024;
其中,关于 post-norm 和 pre-norm 可以参考《Learning Deep Transformer Models for Machine Translation》。两者的主要区别在于,post-norm 将 transformer 中每一个 block 的层归一化放在了残差层之后,而 pre-norm 将层归一化放在了每个 block 的输入位置,如下图所示:
GPT-2 进行上述模型调整的主要原因在于,随着模型层数不断增加,梯度消失和梯度爆炸的风险越来越大,这些调整能够减少预训练过程中各层之间的方差变化,使梯度更加稳定。
最终 GPT-2 提供了四种规模的模型:
其中 117M 参数等价于 GPT-1 模型,345M 参数模型用于对标同期的 BERT-large 模型。
在训练数据方面,为了保证 zero-shot 的效果,必须要足够大且覆盖面广。所以 GPT-2 中的做法是,只爬取人工筛选过的web数据。但是人工筛选整个web的数据会非常昂贵,作为替代,GPT-2通过爬取 Reddit上所包含的链接(这里是以一种启发式的方法去获取人工筛选的数据,GPT-2认为Reddit这个社交媒体上挂的链接,可能是人们比较感兴趣或认为有价值的,因此存在数据质量问题的概率比较小)。最后得到的数据集叫 WebText。它选取了 Reddit 上的高质量帖子,最终得到 4500w 网页链接,800w 有效的文本文档,语料大小为 40G。
在实验效果上,由于 GPT-2 主要是做 zero-shot,所以在实验部分,很多的实验对比都是在无监督的设定下进行的,也就是说他对比的都是无监督的算法。
从上述效果可以看到,GPT-2 在较多任务上对比无监督算法取得了一定的提升,证明了 zero-shot 的能力。但是,在很多任务上与有监督微调的方法相比还是有一些差距的,这可能也是 GPT-2 在当时影响力没有那么大的一个原因。
整体来看,GPT-2 相比于 GPT-1 有如下几点区别:
- 主推 zero-shot,而 GPT-1 为 pre-train + fine-tuning;
- 训练数据规模更大,GPT-2 为 800w 文档 40G,GPT-1 为 5GB;
- 模型大小,GPT-2 最大 15 亿参数,GPT-1为 1 亿参数;
- 模型结构调整,层归一化和参数初始化方式;
- 训练参数,batch_size 从 64 增加到 512,上文窗口大小从 512 增加到 1024,等等;
虽然 GPT-2 主推的 zero-shot 在创新度上有比较高的水平,但是由于其在效果上表现平平,所以在业界并没有取得比较大的影响力,而 GPT-3 正是为了解决效果上的问题而提出的。GPT-3 不再去追求那种极致的不需要任何样本就可以表现很好的模型,而是考虑像人类的学习方式那样,仅仅使用极少数样本就可以掌握某一个任务,因此就引出了 GPT-3 标题 Language Models are Few-Shot Learners。
这里的 few-shot 不是像之前的方式那样,使用少量样本在下游任务上去做微调,因为在 GPT-3 那样的参数规模下,即使是参数微调的成本也是高到无法估计。
在模型结构上,GPT-3 延续使用 GPT 模型结构,但是引入了 Sparse Transformer 中的 sparse attention 模块(稀疏注意力)。
sparse attention 与传统 self-attention(称为 dense attention) 的区别在于:
dense attention:每个 token 之间两两计算 attention,复杂度 O(n²) sparse
attention:每个 token 只与其他 token 的一个子集计算 attention,复杂度 O(n*logn)
具体来说,sparse attention 除了相对距离不超过 k 以及相对距离为 k,2k,3k,… 的 token,其他所有 token 的注意力都设为 0,如下图所示:
使用 sparse attention 的好处主要有以下两点:
减少注意力层的计算复杂度,节约显存和耗时,从而能够处理更长的输入序列;
具有“局部紧密相关和远程稀疏相关”的特性,对于距离较近的上下文关注更多,对于距离较远的上下文关注较少;
关于 sparse attention 详情可参考《Generating Long Sequences with Sparse Transformers》。
最终 GPT-3 在训练过程中得到了如下不同规模的模型:
其中规模最大的模型称为 GPT-3,模型参数量为 1750 亿。
如上图所示,GPT-3 在下游任务的评估与预测时,提供了三种不同的方法:
Zero-shot:仅使用当前任务的自然语言描述,不进行任何梯度更新;
One-shot:当前任务的自然语言描述,加上一个简单的输入输出样例,不进行任何梯度更新;
Few-shot:当前任务的自然语言描述,加上几个简单的输入输出样例,不进行任何梯度更新;
其中 Few-shot 也被称为 in-context learning,虽然它与 fine-tuning 一样都需要一些有监督标注数据,但是两者的区别是:
最终通过大量下游任务实验验证,Few-shot 效果最佳,One-shot 效果次之,Zero-shot 效果最差:
上图中,横坐标为模型参数量,纵坐标为任务精度,图中大量灰色线表示不同下游任务,橙色/绿色/蓝色线是下游任务效果的平均值。
由于 GPT-3 在模型规模上的扩大,在训练数据方面也必须进行扩充来适配更大的模型使其发挥出相应的能力。
GPT-3 使用了多个数据集,其中最大的是 CommonCrawl,原始未处理的数据达到了 45TB,其实在 GPT-2 的时候他们就有考虑使用这个数据集,但是后来还是觉得这个数据集太脏了所以没用,但是现在 GPT-3 的模型规模太大了,使得训练对数据量的需求也增加了很多,他们不得不重新考虑这个数据集。因此,他们必须在这个数据集上做一些额外的数据清洗工作来尽量保证数据的质量。
数据处理主要包括以下几个部分:
其中“高质量数据”主要是指 BERT、GPT、GPT-2 使用过的数据,最终处理完成后使用的数据规模约 570G。
如上图所示,在实际实验过程中,对不同数据集按照一定的比例进行采样,这个比例不是按照原始数据量多少来划分的,不然这里基本采样到的就都是 common crawl 的数据了,可以看到这里 common crawl 的数据量比其他几个多很多。进行采样的原因主要考虑到,就算做了一些数据清洗还是觉得 common crawl 的数据质量不如其他几个。最终采样的时候,虽然 common crawl 的数据量是其他几个数据集的上百倍,但是实际占比是 60%,有 40% 的数据是能够保证质量的。
GPT-3 花了大部分篇幅介绍了各种 NLP 任务上的实验结果和分析,大家如果对某个任务感兴趣的话可以自行阅读一下论文对应的章节,本文就不做详细介绍了。
下图是 GPT-3 的一个重要分析结果:
图中横坐标为计算量,可以简单理解为模型规模或者数据量(不止如此),纵坐标为任务精度。可以看到,当我们想要线性的提升一个任务的效果时,往往需要指数级的提升模型的规模和所需的数据量。
虽然 GPT-3 取得了非常亮眼的效果,但是出于严谨的学术态度,论文里还是客观的分析了自己的一些局限性:
当生成文本长度较长时,GPT-3 还是会出现各种问题,比如重复生成一段话,前后矛盾,逻辑衔接不好等等;
模型和结构的局限性,对于某一些任务,比如填空类型的文本任务,使用单向的自回归语言模型确实存在一定的局限性,这时候如果同时考虑上文和下文的话,效果很可能会更好一些;
预训练语言模型的通病,在训练时,语料中所有的词都被同等看待,对于一些虚词或无意义的词同样需要花费很多计算量去学习,无法区分学习重点;
样本有效性或者利用率过低,训一个模型几乎要把整个互联网上的文本数据全都用起来,这与我们人类学习时所需要的成本存在非常大的差异,这方面也是未来人工智能研究的重点;
有一个不太确定的点是,模型到底是在“学习”还是在“记忆”?我们当然希望它能够学习,但是在使用数据量如此大的情况下,很难去判断它到底是什么样的;
众所周知,GPT-3 的训练和使用成本都太大了;
GPT-3 跟很多深度学习模型一样,都是不可解释的,没办法知道模型内部到底是如何作出一系列决策的;
模型最终呈现的效果取决于训练数据,这会导致模型会出现各种各样的“偏见”;
GPT-3 可能会被拿来做一些坏事,造成一定的社会影响。比如生成新闻稿,散布一些不实的消息,生成垃圾邮件,钓鱼邮件,论文造假之类的。
这里以“种族偏见”和“性别偏见”为例:
上图展示了模型的“种族偏见”倾向,当给模型输入“The {种族} woman was very”时,可以根据后续预测词的概率分布简单分析出 GPT-3 对不同种族的人具有一定的出词倾向性。图中纵坐标的 0 表示一种正常水平,大于 0 表示比较正面的反馈,小于 0 表示比较负面的反馈。可以看到亚洲人在模型的评价里算是比较高的,但是最下面的那条线是黑人,所以当一个模型对不同种族的人的差异有这么大的时候,还是比较可怕的。
上图展示了模型的“性别偏见”,当给模型输入 “he was very” 或 “she was very” 时,GPT-3 给出的词的概率分布具有一定的倾向性,可能会产生一些具有偏见的词,比如形容男人时经常出现“Lazy”。
虽然 GPT-3 存在上述问题,不过当这些社会问题被拿出来讨论时,也侧面反映了 GPT-3 的效果及其影响力。
整体来看,GPT-3 相比于 GPT-2 有如下几点区别:
- 效果上,超出 GPT-2 非常多,能生成人类难以区分的新闻文章;
- 主推 few-shot,相比于 GPT-2 的 zero-shot,具有很强的创新性;
- 模型结构略微变化,采用 sparse attention 模块;
- 海量训练语料 45TB(清洗后 570GB),相比于 GPT-2 的 40GB;
- 海量模型参数,最大模型为 1750 亿,GPT-2 最大为 15 亿参数;
GPT-3 虽然在各大 NLP 任务以及文本生成的能力上令人惊艳,但是他仍然还是会生成一些带有偏见的,不真实的,有害的造成负面社会影响的信息,而且很多时候,他并不按人类喜欢的表达方式去说话。在这个背景下,OpenAI 提出了一个概念“Alignment”,意思是模型输出与人类真实意图对齐,符合人类偏好。因此,为了让模型输出与用户意图更加 “align”,就有了 InstructGPT 这个工作。 论文地址:Training language models to follow instructions
with human feedback
InstructGPT 提出了一个理想化语言模型的三大目标:
从做研究的角度来讲,其实很多时候人们并不在意“Alignment”(对齐)问题,只要一个模型在评估的数据集上表现好,那基本就可以说是一个好模型;但是对于工业界来说,一个产品的安全性就显得尤为重要,历史上也出现过不止一次因为某个负面 case,引发社会争议,从而导致整个产品下线的例子,特别是对于大公司来讲,出现一次负面 case 通常是比较严重的。比如之前 google 和 facebook 就都出现过把照片中的黑人识别成黑猩猩的事件,然后随之而来的就是产品的整改,公关道歉之类的。所以从这个角度来讲,OpenAI 做 InstructGPT 的出发点是非常合理且重要的。
关于 InstructGPT 的技术方案,原文分为了三个步骤:有监督微调,奖励模型训练,强化学习训练;实际上可以把它拆分成两种技术方案,一个是有监督微调(SFT),一个是基于人类反馈的强化学习(RLHF),下面我们简单介绍这两种技术方案。
本质上来说,SFT 可以理解为人工标注了一批数据,然后去微调 GPT-3。但是值得一提的是,这里标注的数据与 GPT-3 之前用来做下游任务使用的 few-shot 格式,有非常本质的区别。
GPT-3 中的 few-shot 对于同一个下游任务,通常采用固定的任务描述方式,而且需要人去探索哪一种任务表述方式更好。显然这种模式与真实场景下用户的使用方式存在较大的 gap,用户在向 GPT-3 提问时才不会采用某种固定的任务表述,而是随心所欲地以自己的说话习惯去表达某个需求。InstructGPT 在 SFT 中标注的数据,正是为了消除这种模型预测与用户表达习惯之间的 gap。在标注过程中,他们从 GPT-3 的用户真实请求中采样大量下游任务的描述,然后让标注人员对任务描述进行续写,从而得到该问题的高质量回答。这里用户真实请求又被称为某个任务的指令,即 InstructGPT 的核心思想“基于人类反馈的指令微调”。
基于 SFT 得到的模型被用于后续的 RLHF 做进一步的模型优化。
如上图所示,以摘要生成任务为例,详细展示了如何基于人类反馈进行强化学习,最终训练完成得到 InstructGPT 模型。主要分为三步:
收集人类反馈:使用初始化模型对一个样本生成多个不同摘要,人工对多个摘要按效果进行排序,得到一批排好序的摘要样本;
训练奖励模型:使用第1步得到的样本集,训练一个模型,该模型输入为一篇文章和对应的一个摘要,模型输出为该摘要的得分;
训练策略模型:使用初始化的策略模型生成一篇文章的摘要,然后使用奖励模型对该摘要打分,再使用打分值借助 PPO 算法重新优化策略模型;
总的来说,InstructGPT 相对于之前的 GPT 系列,有以下几点值得注意:
- 解决 GPT-3 的输出与人类意图之间的 Align 问题;
- 让具备丰富世界知识的大模型,学习“人类偏好”;
- 标注人员明显感觉 InstructGPT 的输出比 GPT-3 的输出更好,更可靠;
- InstructGPT 在真实性,丰富度上表现更好;
- InstructGPT 对有害结果的生成控制的更好,但是对于“偏见”没有明显改善;
- 基于指令微调后,在公开任务测试集上的表现仍然良好;
- InstructGPT 有令人意外的泛化性,在缺乏人类指令数据的任务上也表现很好;
上图展示了从初代 GPT 到 ChatGPT 为止,OpenAI 公布的主要模型及其迭代路线。其中可能存在不太严谨的地方,比如 GPT3.5 的划分。关于 code-davinci 和 text-davinci,以及整个 GPT3.5 的详细介绍,强烈推荐阅读这篇博客:拆解追溯 GPT-3.5 各项能力的起源。
文献
[1] GPT: Improving Language Understanding by Generative Pre-Training
[2] GPT-2: Language Models are Unsupervised Multitask Learners
[3] GPT-3: Language Models are Few-Shot Learners
[4] InstructGPT: Training language models to follow instructions with human feedback
[5] Learning to summarize from human feedback
[6] Generating Long Sequences with Sparse Transformers
[7] Learning Deep Transformer Models for Machine Translation
[8] 拆解追溯 GPT-3.5 各项能力的起源
[9] 李沐: GPT,GPT-2,GPT-3 论文精读
[10] 李沐: InstructGPT 论文精读
[11] 为节约而生:从标准 Attention 到稀疏 Attention
[12] GPT / GPT-2 / GPT-3 / InstructGPT 进化之路