跳转至

LLaMA1

摘要

作者介绍了LLaMA. 这是一个由7B到65B个参数组成的基础语言模型集合. 他们在数万亿个tokens上训练模型, 并表明完全可以用公开可用的数据集来训练最先进的模型, 无需求助于专有并且无法访问的数据集. 特别是, LLaMA-13B在大多数基准测试的表现中都优于GPT-3(175B), LLaMA-65B和最好的模型Chinchilla-70B以及PaLM-540B相比都具有竞争力, 他们向研究界发布了所有的模型: https://github.com/meta-llama/llama.

背景

在海量文本语料库中训练的大型语言模型(LLMs)已经证明, 它们有能力根据文本指令或者少量示例执行新任务. 当模型扩展大足够大的规模的时候, 首次出现了这些few-shot特性. 由此产生了如何进一步扩展这些模型的研究方向. 这些努力基于参数越多性能越好的假设. 然而, Hoffmann等人的最新研究表明, 最佳性能不是由最大的模型实现的, 而是由在更多数据上训练较小的模型实现的.

Hoffman等人提出的缩放定律的目的是如何在特定的 训练预算 下以最佳的方式缩放数据集和模型大小. 然而, 这一目标忽略了 推理预算, 而推理预算在大规模使用语言模型的时候变得至关重要. 虽然为了达到某个性能水平, 有时训练一个更大的模型在总训练的花费上会更加偏移(可能更快达到目标性能), 但是真正上线使用的时候, 推理成本(包括速度, 显存占用, 计算资源等)才是长期的主要花费. 例如, Hoffmann等人在他们的研究中建议在200B的tokens上训练一个10B参数的模型, 但是作者发现, 即使是7B参数的模型, 在训练到1T(1万亿tokens)的时候, 性能依旧能够持续提高. 这说明小模型可以通过更长时间的训练来缩小和大模型的性能差距, 并且推理的时候会更加划算.

这项工作的任务是训练一系列的语言模型, 通过训练比通常情况下更多的tokens, 在各种推理预算下实现最佳的性能. 由此产生的模型被称为LLaMA, 参数范围从7B到65B不等. 和现有的最佳LLM相比, 它的性能极具竞争力. 例如, LLaMA-13B在大多数基准测试中都优于GPT-3, 尽管它的体积缩小了10倍. 他们相信, 由于该模型在单个GPU上运行, 它将有助于使得LLM的研究往大众化方向发展. 在大规模上, 他们的65B参数模型也能和Chinchilla或者PaLM-540B等最好的大型语言模型相媲美.

和Chinchilla, PaLM或者GPT-3不同的是, 他们只使用公开可用的数据, 这使得他们的工作和开源的精神是一致的. 大多数现有模型依赖的数据要么不是公开可用的, 要么没有记录(如Books-2TB或社交媒体对话). 也有一些例外, 特别是OPT, GPT-NeoX, BLOOM和GLM, 但是都无法和PaLM-62B或Chinchilla竞争.

在本文的剩余部分, 他们将会概述他们对transformer架构所做的修改以及他们的训练方法. 然后, 他们报告了他们模型的性能, 并在一组标准benchmark上和其他的LLM进行了比较. 最后, 他们利用responsible AI community的一些最新基准, 展示了他们的模型中存在的一些偏差和毒性.

方法论

作者的训练方法和之前的工作中描述的方法类似, 并收到Chinchilla缩放定律的启发, 他们使用标准的优化器在大量文本数据上训练大型transformers.

预训练数据

他们的训练集由下表所列的多个数据源混合而成, 涵盖了多个领域, 在大多数情况下, 他们重复使用已经被用于训练其他LLM的数据源, 但是仅限于公开可用的数据, 和开源精神兼容.

预训练数据. 列出了每个子集的采样占总体的比例. 当训练数据量为1.4T Tokens的时候, 每个子集的epochs数量, 以及每个子集的磁盘大小. 对于1T Tokens的预训练, 采样的比例是不变的.

English CommonCrawl(67%). CommonCrawl是一个互联网上的大规模网页抓取数据集, 其中包括了来自世界各地的网站文本, CommonCrawl会定期发布不同时间段抓取的网页数据(称为dumps), 他们选择了2017到2020年期间的五份数据(dumps). 使用CCNet pipeline进行处理, CCNet是一种专门的流水线, 用于从CommonCrawl中高效地筛选, 清理, 过滤大规模文本. 在进行文本处理的时候, 经常会遇到同样的句子或者段落重复出现的情况. 他们使用了行级去重, 意思是对每行文本都进行重复检测, 保留唯一出现的文本, 并过滤掉重复行, 以减少数据冗余, 保证数据的多样性. 由于CommonCrawl包含多种语言的网页, 需要先检测文字语言, 只保留英文内容, 他们使用fastText提供的线性分类器来识别文本语言, 剔除检测到的非英文的页面或者行. 然后, 使用ngram语言模型过滤掉低质量内容. 他们还进一步细分了这些CommonCrawl页面, 判断某个页面是否有可能是Wikipedia所引用的参考资料页面, 还是知识随机抓取到的普通页面, 这个分类器的目标是选出相对更高质量, 更加权威的页面, 因为能被Wikipedia引用的网站, 通常在内容上更加可靠. 如果某个页面不被判定为Wikipedia所引用的参考资料, 那么它就会被过滤掉, 这样能够让后续训练数据集中包含更多具有可信度的内容.

C4(15%). C4是一个基于CommonCrawl构建的公开数据集. 作者在训练的时候发现, 如果模型在训练的时候能够使用不同的预处理方式得到的CommonCrawl数据, 会比只使用单一处理流程得到的数据具有更好地表现. 而C4数据集本身就是公开可用的, 是对CommonCrawl数据进行过一定的清洗和过滤后的产物. C4的数据预处理也包含去重和语言识别, C4在质量过滤上依赖大量启发式规则, 比如检测标点符号的存在, 网页中的单词或者句子数量是否符合合理范围等, 与之相比, CCNet更多地使用了更加复杂或者更定制地过滤策略(例如训练分类器来判断页面是否被Wikipedia引用..), 这些差异意味着, C4和CCNet在最终保留下来地文本类型, 文本分布上可能会有所不同, 这也正式使用多样化预处理数据能够提高性能的原因之一.

Github(4.5%). 作者使用的是Google BigQuery上面的公共Github数据集, 并且只保留使用Apache, BSD和MIT许可证的项目. 此外, 他们使用基于行长度或者字幕数字字符比例的启发式方法过滤低质量文件并且使用正则表达式删除文件头部的模板代码. 最后, 他们在文件级别上删除完全匹配的重复文件.

Wikipedia(4.5%). 作者使用了2022年6月到8月期间的维基百科数据, 涵盖了20种语言, 这些语言使用的是拉丁字母或者西里尔字母, 所涉及的语言代码如下: bg, ca, cs, da, de, en, es, fr, hr, hu, it, nl, pl, pt, ro, ru, sl, sr, sv, uk. 他们处理了数据以去除超链接, 注释和其他格式化模板.

Gutenberg和Books3(4.5%). 作者的训练数据集包括两个图书语料库: Gutenberg项目, 该项目包含了公共领域的书籍; Books3, 这是ThePile数据集的一部分, 这是一个用于训练的大型语言模型的公开可用数据集. 为了确保数据集的质量, 他们在书籍级别进行了去重处理, 移除了内容重叠超过90%的书籍.

ArXiv(2.5%). 作者处理了arXiv的\(\LaTeX\)文件. 使得数据集中加入科学数据. 具体步骤如下, 首先, 按照Lewkowycz等人的方法, 他们移除了每篇文章的前言和参考文献. 然后, 他们移除了\(\LaTeX\)文件中的注释, 以确保数据的一致性. 最后, 他们内联展开了用户定义和宏, 已增加不同论文之间数据的一致性. 通过上述的步骤, 他们得到了一个干净且一致的科学数据集.

Stack Exchange(2%). 作者将Stack Exchange的数据dump添加到了他们的数据库中. Stack Exchange是一个高质量的问答网站, 涵盖了多个领域, 从计算机科学到化学等. 具体处理步骤如下, 他们仅仅保留了28个最大的网站的数据, 移除了文本中的HTML标签, 以确保数据的纯度, 然后根据答案的评分(从最高到最低)对答案进行了排序, 以优先考虑高质量的答案.

分词器

作者使用BPE, Byte Pair Encoding, 对数据进行分词, 具体实现来自SentencePiece. 值得注意的是, 他们对所有的数字进行了拆分, 将其拆分为单个数字. 此外, 对于未知的UTF-8字符, 使用字节分解来处理. 这种分词方法可以有效地处理大规模数据, 并且可以很好地处理未知字符和数字.

这里简单介绍一下BPE算法. 在当前的语料中, 找出最常见的"邻接的两个符号"(这两个符号可以是字符级别, 也可以是更大的词元, 比如说"Go"和"o", 或者"Go"和"od", 其中"Go"或者"od"是前面的合并产生的); 然后, 将这个当前最常见的符号对合并成一个新的符号(或者说新的子词); 用新的符号替换掉预料中出现该符号对的所有位置; 重复以上步骤, 直到达到预设的子词词典大小(或者某种停止条件). 这样, 频率较高的符号对会被先合并, 合并的结果也会再次参与后续统计和合并, 最终形成一个层次化的子词结构.


总的来说, 整个训练数据集经过分词处理之后, 包含了大概1.4万亿个tokens. 在大多数训练数据中, 每个token仅在训练的过程中经过模型一次, 然而, 维基百科和图书领域的数据是例外, 这些数据经历了两个epochs, 也就是经过了模型两次.

架构

根据最近关于大语言模型的研究, 作者的网络基于Transformer架构. 他们利用了随后提出的各种改进, 并将其应用到了PaLM等许多不同的模型中. 以下是和原始架构的主要区别, 以及他们从哪些方面获得了这种改进的灵感(在括号内).

预归一化(GPT-3). 在标准的Transformer架构中, 每个子层(例如Self-Attention子层和Feed-Forward子层)往往遵循"子层+残差链接+LN"的顺序, 即对子层的输出进行归一化(Post-Norm). 而Pre-Norm则是在输入子层之前(而不是子层的输出之后)进行归一化, 它最早可以在一些改进型Transformer以及GPT-3等模型中看到. 在这里, 作者使用的是Zhang和Sennrich等人提出的RMSNorm.

SwiGLU激活函数(PaLM). 传统的Transformer通常使用的是ReLU或者GELU等激活函数. 而PaLM模型中的一种改进就是将激活函数替换为SwiGLU. SwiGLU是由Shazeer引入的一种激活函数变体, 结合了Swish和GLU的思路. 并且, 作者还提到了他们使用的Feed-Forward层的隐藏维度不再是4d, 而是2/3*4d, 这是为了在不显著损失性能的前提下减少参数量和计算量.

RoPE(GPTNeo). 在原始的Transformer, 我们通常使用的是绝对位置编码, 比如对序列中的第i个token加入固定的正弦/余弦位置编码, 或者直接学习到一个可训练的向量来表示位置. Retary嵌入(RoPE)是由Su等人引入的一种相对位置编码思想, 它通过对注意力计算中的queries和keys分量施加旋转变换, 来编码位置信息.

优化器

该模型使用的是AdamW优化器优化. Adam类型的优化器会有一个一阶动量和二阶动量的知识点, 一阶动量解决了往哪走的问题, 二阶动量解决的是走多快的问题, 参数的更新量会参考\(\frac{m_t}{\sqrt{v_t}+\epsilon}\). 作者采用的一阶, 二阶矩估计的指数衰减系数为\(\beta_1=0.9\), \(\beta_2=0.95\), 常见的\(\beta_2\)默认值是\(0.999\), 这里的设置意味着二阶动量的衰减更快, 减少对历史梯度的过渡平滑, 能够加快收敛.

AdamW和传统Adam优化器的区别在于, 他是"先更新, 再衰减". 传统Adam优化器实现权重衰减/正则化的方法是在损失函数中加入L2项\(\mathcal{L}_{\text{new}}(\mathbf{w})=\mathcal{L}(\mathbf{w})+\lambda||\mathbf{w}||^2\), 这种权重的衰减/正则化是随着梯度一起根据一阶动量/二阶动量的结果进行调度放大或者缩小的, 这种耦合可能导致正则化的效果变得不可控, 影响模型的收敛. AdamW解决这个问题的方式是将这两个过程解耦, 先更新, 再衰减/正则. 具体做法是, 不在损失函数中添加L2项, 而是先用Adam正常更新, 然后对参数做一次额外的衰减: 第一步, 先更新\(\mathbf{w}\leftarrow\mathbf{w}-\eta\cdot \hat{g}(\mathbf{w})\), 第二步, 额外的衰减/正则, \(\mathbf{w}\leftarrow \mathbf{w}\cdot (1-\eta\cdot \lambda_{\text{decay}})\). 这个\(\lambda_{\text{decay}}\)被设置为\(0.1\).

此外, 他们使用"余弦退火"策略来控制学习率随训练进程的变化, 初始学习率逐渐下降, 直到最终达到最大学习率的10%. 也就是说, 如果最大学习率是\(\eta_{\text{max}}\), 则在训练的末期会衰减到\(0.1\times \eta_{\text{max}}\). 余弦退火的曲线较为平滑, 可以在训练后期保持一个相对较小, 但不是过低的学习率, 帮助模型逐渐收敛到更优解.

他们还使用了梯度裁剪, Gradient Clipping, 这是在深度神经网络训练过程中, 为了防止梯度爆炸或者数值不稳定而采取的一种手段. 它通过在反向传播的时候, 对计算出的梯度做出一定的限制(例如限制其范数或者绝对值不超过某个阈值), 从而使参数更新不会过大, 保证训练的稳定性, 在这里, 他们设置max为1.0.

在训练开始的时候, 会有2000步的学习率预热, 即学习率会从非常小的值逐渐上升到预定的最大学习率或者初始学习率. 这样做的原因是, 训练刚开始的时候, 模型的参数分布通常不太稳定, 若立即使用较大的学习率容易导致更新过猛或者不稳定, 而预热能够让模型更加平稳地过渡到正常阶段.

他们还根据模型的大小调整了学习率和batch大小, 如下表所示.

模型大小, 架构和优化器的超参数

高效实现

作者进行了多项对模型速度上的优化. 首先, 他们使用的是一种高效的因果多头注意力实现(说简单一点, 就是自回归, 掩码那一套的注意力), 由xformers库提供, 例如, 可以跳过无效的注意力计算, 无需存储注意力权重等等.

此外, 为了进一步提高训练的效率, 他们采用了魔改版"检查点"的方法. 常规的"检查点"方法的做法是在前向传播的时候, 并不是把所有的中间激活值都保留下来, 只选择保存一部分关键或者代价昂贵的激活值, 等到反向传播的时候, 需要用到这些激活值的时候, 若他们当时未被保存, 需要临时重新计算, 以换取显存的节省. 作者发现, PyTorch中实现"检查点"的方法是按照模块或者按照段, 整个模块/整个段保留/丢弃激活值的, 这种实现的细粒度/灵活度不太够, 导致在反向传播的时候需要计算的激活值的工作量比较大, 有点得不偿失. 所以, 他们自己实现了backward的函数, 可以精细地控制, 哪些激活需要保留, 哪些可以丢弃.

然而, 即使减少了重算的激活值, 大模型本身依然需要占用大量的显存. 因此, 他们还采用了模型并行和序列并行等方法, 把一个巨大的模型或者长序列分割到多个GPU/节点上, 分摊计算和内存负担. 在分布式训练中, GPU间常常需要进行all_reduce等通信操作, 来汇总梯度或者激活. 如果通信和计算是串行进行, 会造成等待和浪费, 为了进一步提高效率, 他们尽量让计算和通信同时进行(overlap), 从而最大程度地利用可用资源, 减少闲置时间.

在训练一个65B参数的模型的时候, 他们的代码在2048个 A100 GPU和80GB RAM上的处理速度是380 Tokens/sec/GPU. 这意味着对包含1.4T Tokens的数据集进行训练大约需要21天.


  1. Touvron, H., Lavril, T., Izacard, G., Martinet, X., Lachaux, M.-A., Lacroix, T., Rozière, B., Goyal, N., Hambro, E., Azhar, F., Rodriguez, A., Joulin, A., Grave, E., & Lample, G. (2023). LLaMA: Open and efficient foundation language models (No. arXiv:2302.13971). arXiv. https://doi.org/10.48550/arXiv.2302.13971 

评论