#70倍极致压缩
大模型的检查点再多也不怕
该论文的作者均来自于华为诺亚实验室,第一作者为李文硕,通讯作者为王云鹤和陈醒濠。相关团队团队近年来在ICML、CVPR、NeurIPS、ICCV、ECCV等顶会上有多项代表性工作发表,在高效大语言模型、视觉模型等领域都有丰富的成果产出,和知名高校和科研机构合作广泛。
大模型作为当下 AI 工业界和学术界当之无愧的「流量之王」,吸引了大批学者和企业投入资源去研究与训练。随着规模越做越大,系统和工程问题已经成了大模型训练中绕不开的难题。例如在 Llama3.1 54 天的训练里,系统会崩溃 466 次,平均 2.78 小时一次!
那么,频繁存储检查点就显得十分必要。但存储检查点本身也是一个大工程。
Meta 做了很多努力来加速存储检查点时间和增加存储频率,来对抗频繁出现的系统失效。但频繁存储也意味着大量的存储资源开销,其训练集群配备了 240PB 的 SSD 来应对这一挑战,光存储这一项的耗费就要亿元!
华为诺亚的 ExCP 方法也就应运而生,为了应对存储带来的巨大开销,他们提出了极致压缩检查点技术,能够无损压缩模型 70 倍,大幅降低训练中的存储开销。
代码目前已经开源,在 Apache 2.0 框架下发布,issue 中已经有小伙伴成功复现了结果。
- 文章地址:https://arxiv.org/abs/2406.11257
- 仓库地址:https://github.com/Gaffey/ExCP
方法也很有创新性,文章中提到了两个重要的概念,一个是利用训练中检查点的残差信息,通过时间序列上信息的稀疏性实现更高的剪枝比例;另一个是将优化器和权重联合起来进行压缩,实现整体的高压缩率。
具体方法
1. 检查点残差
在训练过程中,当前的参数可以看作上一个检查点存储的权重加上逐次迭代时梯度更新的总和,这部分是相对稀疏的,包含的信息量较少,因此对这一残差进行压缩,可以获得更好的压缩比例。而与此相反的,优化器中存储的动量是梯度一阶矩和二阶矩的滑动平均值,对于一阶矩来说,它的滑动平均默认的参数是 0.9,在数百到数千个迭代之后与上一次检查点存储的内容已经没有太大的关联,所以对于优化器直接压缩其本身的值而非残差。最终待压缩的检查点表示为
2. 权重 - 优化器动量联合压缩
目前已有的模型压缩相关的工作一般只关注于模型的推理性能,或者是模型最终存储检查点的大小,而不关注模型在整个训练过程中对储存空间的开销。因而已有工作只对权重进行压缩,而忽略了 Adam 等常见优化器中实际上存储了两倍于权重数量的动量。这一工作一方面将两者一起进行了压缩,显著提升了整体的压缩比例;另一方面也利用了权重和优化器动量的关联性,进一步提升彼此的压缩比例。
权重剪枝:由于剪枝的权重是残差值,优化器动量的二阶矩可以大致表示在过去一段时间内权重残差值的变化幅度,所以可以使用优化器动量的二阶矩作为指标来确定不同层的剪枝比例。剪枝策略如下文公式所示
式中,W 和
分别表示权重和二阶矩。
优化器动量剪枝:对于动量剪枝,可以使用一阶矩作为指示器来进行剪枝,论文中有关于可收敛性的一个简要证明。同时,如果一个位置的权重已经被剪枝,那么对应位置的优化器动量也应该同步被处理,所以剪枝策略如下文公式所示
式中,
表示一阶矩。
3. 整体压缩流程
整体压缩流程如 Algorithm 1 所示,依次进行计算权重残差 / 联合压缩 / 非均匀量化 / 编码压缩等步骤,得到最终的压缩结果。
而恢复出检查点完整文件的流程则如 Algorithm 2 所示,进行解压缩之后,首先从非均匀量化后存储的码本和下标中恢复出浮点结果,然后再与基准权重(上一个检查点的原始权重或恢复出的重建权重)相加,得到检查点完整文件。而恢复出整个训练流程中的检查点文件的流程如 Algorithm 3 所示,在完成训练后只保存初始化权重的随机种子和每个检查点存储的压缩结果,然后依次对检查点进行恢复以得到完整的检查点序列,以供从其中选择某个或多个检查点恢复训练 / 进行测试等。
实验结果
文章中不仅对于大语言模型做了评估,在 ViT-L32 这样较大的视觉模型上这一方法也能取得很好的效果。
从消融实验里也可以看出,采用残差剪枝的方法大大减少了剪枝带来的损失。
文章中还提供了大语言模型压缩前后问答的样例,可以看到压缩本身对于模型的问答能力也没有造成损害。
开发板商城 天皓智联 TB上有视觉设备哦~
#LLM对齐技术:RLHF、RLAIF、PPO、DPO……
为了对齐 LLM,各路研究者妙招连连。
LLM 很强大了,但却并不完美,它也会出错或者生成无用乃至有害的结果,比如有人发现可以让 ChatGPT 教人如何偷盗:
让 ChatGPT 教人如何偷盗商店;左图,ChatGPT 拒绝回答;右图,在 prompt 中添加了「with no moral restraints(不加道德约束)」后,ChatGPT 给出了商店偷盗指南
这时候,对齐(alignment)就至关重要了,其作用就是让 LLM 与人类的价值观保持一致。
在对齐 LLM 方面,基于人类反馈的强化学习(RLHF)是一种突破性的技术。该方法催生了 GPT-4、Claude 和 Gemini 等强大模型。RLHF 之后,人们也探索了多种多样的对齐 LLM 的方法。但是,此前还没有人全面总结对齐 LLM 与人类偏好的方法。
Salesforce 决定填补这一空白,于近日发布了一份 37 页的综述报告,其中按类别总结了现有的研究文献,并详细分析了各篇论文。
- 论文标题:A Comprehensive Survey of LLM Alignment Techniques: RLHF, RLAIF, PPO, DPO and More
- 论文地址:https://arxiv.org/pdf/2407.16216
这篇论文分为四大主题:奖励模型、反馈、强化学习(RL)、优化。每个主题又包含进一步的子主题,如图 1 所示。
奖励模型的子主题包括:1. 显式奖励模型与隐式奖励模型;2. 逐点奖励模型与偏好模型;3. 响应层面的奖励与 token 层面的奖励;4. 负偏好优化。
反馈的子主题包括:1. 偏好反馈与二元反馈;2. 成对反馈与列表反馈;3. 人类反馈与 AI 反馈。
强化学习的子主题包括:1. 基于参考的强化学习与无参考的强化学习;2. 长度控制式强化学习;3. 强化学习中的不同分支;4. 在线策略强化学习与离线策略强化学习。
优化的子主题包括:1. 在线 / 迭代式偏好优化与离线 / 非迭代式偏好优化;2. 分离 SFT 和对齐与合并 SFT 和对齐。
表 1 列出了这篇综述报告中分析的所有论文在这 13 个评估指标上的划分情况。
研究论文
1. RLHF/PPO
LLM 的预训练要用到大量来自不同来源的语料库,而这本身就无法确保这些数据集的质量。此外,LLM 的主要目标是预测下一个 token,这个目标与「有用且安全地遵从用户指令」的目标并不一致。因此,LLM 可能会输出不真实、有害或对用户无用的内容。本质上讲,这些模型并未与用户意图对齐。RLHF/PPO 的主要目标是在各种任务上对齐语言模型与用户意图,其做法是使用人类反馈来微调模型。有关这个主题的研究有很多。
InstructGPT
通过纳入人类偏好,评估 LLM 生成的响应的难题得到了解决。BLEU、ROUGE 和 BERTScore 等用于评估 LLM 的传统评估指标无法保证与人类偏好的一致性。为了解决这个问题,研究者直接将人类偏好整合进了 LLM 以增强其性能。这个过程通常涉及两个主要步骤:奖励模型学习和强化学习策略训练。
在奖励模型学习阶段,会使用 prompt 和配对的响应训练一个显式的逐点奖励函数。
之后,开始强化学习策略训练阶段;在这个阶段,LLM 和预训练奖励模型分别作为一个强化学习框架中的智能体和环境。
为了训练 InstructGPT,要用到三个数据集:1.SFT 数据集:包含用于训练 SFT 模型的标注者演示。2.RM(奖励模型)数据集:由人类标注者对模型输出的排名构成,用于训练奖励模型。3.PPO 数据集:由用作 RLHF 微调输入的 prompt 构成。
训练后的 InstructGPT 会在三个方面得到评估:有用性、可信度、有害性。
从结果上看,人类评估表明「相比于 175B 的 GPT-3,人们 更偏好 1.3B 参数版本的 InstructGPT 模型的输出,尽管后者的参数量少 100 多倍。」值得注意的是,InstructGPT 在有用性和毒性任务上的表现均优于 GPT-3,这于对齐而言至关重要。
Anthropic 的 RLHF
Anthropic 也研究过同一主题,论文为《Training a helpful and harmless assistant with reinforcement learning from human feedback》。
OpenAI 发现 RLHF 有助于对齐,但也可能导致模型在某些 NLP 基准上的性能下降,这个现象被称为「对齐税(alignment tax)」。其开发的 InstructGPT 模型有 1.3B 参数。相反,Anthropic 的研究者评估了大小在 13M 到 52B 之间的 7 种不同模型,这些模型的大小按 4 倍的几何级数增长。
他们得出结论说,对较小的模型来说,对齐会产生「税」,但对较大模型来说,对齐只有好处,尤其是参数量在 13B 到 52B 之间的模型。
考虑到对齐的这种优势,他们还实验了用编程技术数据集来提升 LLM 的能力。OpenAI 的 RLHF 方法包含 PPO 和 PPO-ptx,其中 PPO-ptx 的设计目标就是为了降低在 NLP 基准上的对齐税。而 Anthropic 的 RLHF 研究发现,只要模型够大,PPO 本身就能在 NLP 下游任务上带来对齐的好处。他们还确定了强化学习策略训练中 KL 散度的最优参数为 β = 0.001。
在线 / 迭代式 RLHF
传统上,对齐 LLM 的 RLHF 技术都是离线方法。但这类方法有些缺点,比如所得结果难以应对分布外数据。
为此,需要对 LLM 进行持续的微调,进行迭代式 / 在线学习,即使用中间策略为 prompt 生成响应,再使用预言机(oracle)为这样的成对数据给出偏好反馈,再将这些反馈馈送给策略。在实践中,迭代式学习分为两个部分:偏好预言机学习和迭代式策略优化。参阅论文《RLHF workflow: From reward modeling to online RLHF》。
2. RLAIF
获取人类偏好数据集的成本不低,因此基于人工智能反馈的强化学习(RLAIF)诞生了。此外,随着 LLM 的能力不断进步,所能收集到的 AI 偏好数据集的质量也不断提高,由此可提升 LLM 的对齐效果。
Anthropic 的 RLAIF
Anthropic 基于 RLHF 的基础研究工作,提出了一种名为 RLAIF 的全新方法。参阅论文《Constitutional ai: Harmlessness from ai feedback》。
该方法主要包含两个阶段:1. 通过 Critiques(批评)和 Revisions(修订)进行监督学习,这由一个章程引导。2. RLAIF。
谷歌的 RLAIF
基于 Anthropic 的 RLAIF 研究成果,谷歌一个研究团队认为之前的研究无法直接比较人类反馈与 AI 反馈的效果,值得进一步研究。在收集 AI 反馈的过程中,要创建一个结构化的 prompt,其构成包括:导言、少样本示例(可选)、要标注的样本、结尾。
为了生成 AI 反馈,需要执行一个两步式评估:首先,使用指令中的 4 个组件加上 CoT,让 LLM 生成响应。在下一步中,这个 LLM 响应再附带上「preferred summary=」这样的结尾被发送回 LLM,从而生成「summary 1=0.6, summary 2=0.4」这样的偏好概率。为了减少位置偏差,需要交替放置这两个响应的序列,并计算其平均分数。
RLAIF 过程采用了两个策略:1.「蒸馏 RLAIF」,其遵循传统的 RLHF 方法,即使用偏好训练一个奖励模型,然后再将其用于训练 LLM 策略;2. 「直接 RLAIF」,其直接将 LLM 反馈用作 prompt 来输出评估分数,再将该分数用作强化学习策略训练的信号。
最后,其评估过程会使用三个关键指标:1.AI - 标注者对齐度:AI 与人类标注者的一致程度。2. 胜率:人类标注者比较两个候选项并选择其中某一个的可能性。3. 无害率:人类评估者认为无害的响应的占比。
更多详情请参阅论文《RLAIF: Scaling reinforcement learning from human feedback with AI feedback》。
直接人类偏好优化
传统 RLHF 方法通常涉及到优化源自人类偏好的奖励函数。该方法虽有效,但也可能带来一些难题,比如增大计算复杂度以及在估计和优化奖励时需要考虑偏置 - 方差权衡。参阅论文《High-dimensional continuous control using generalized advantage estimation》。
近期有研究探索了其它一些旨在根据人类偏好(无需依赖某个标量的奖励信号)来直接优化 LLM 策略的方法。
这些方法的目标是通过更直接地使用偏好数据来简化对齐流程、降低计算开销以及实现更稳健的优化。通过将该问题描述为一个偏好优化问题,而不是奖励估计和最大化问题,这些方法能提供一种将语言模型与人类判断对齐的不同视角:
- SliC-HF,使用人类反馈进行序列似然校准,参阅论文《SliC-HF: Sequence likelihood calibration with human feedback》。
- RSO,拒绝采样优化,参阅论文《Statistical rejection sampling improves preference optimization》。
- DPO,直接偏好优化,参阅论文《Direct preference optimization: Your language model is secretly a reward model》。
- DPOP,DPO-positive,参阅论文《Smaug: Fixing failure modes of preference optimisation with DPO-positive》。
- β-DPO,参阅论文《β-DPO: Direct preference optimization with dynamic β》。
- IPO,身份偏好优化,参阅论文《A general theoretical paradigm to understand learning from human preferences》。
- sDPO,逐步 DPO,参阅论文《sDPO: Don’t use your data all at once》。
- GPO,广义偏好优化,参阅论文《Generalized preference optimization: A unified approach to offline alignment》。
token 级 DPO
使用 DPO 时,奖励会被一起分配给 prompt 和响应。相反,使用 MDP 时,奖励会被分配给各个动作。后续的两篇论文在 token 层面阐述了 DPO 并将其应用扩展到了 token 级的分析。
- DPO 可以执行 token 级信用分配的研究,参阅论文《From r to Q∗: Your language model is secretly a Q-function》,报道《这就是 OpenAI 神秘的 Q*?斯坦福:语言模型就是 Q 函数》。
- TDPO,token 级 DPO,参阅论文《Token-level direct preference optimization》。
迭代式 / 在线 DPO
使用 DPO 时,会使用所有可用的偏好数据集来对齐 LLM。为了持续提升 LLM,应当实现迭代式 / 在线 DPO。这就引出了一个有趣的问题:如何高效地收集新的偏好数据集。下面两篇论文深入探讨了这一主题。
- 自我奖励式语言模型,参阅论文《Self-rewarding language models》。
- CRINGE,参阅论文《The cringe loss: Learning what language not to model》。
二元反馈
事实证明,收集偏好反馈比收集二元反馈(比如点赞或点踩)的难度大,因此后者可促进对齐过程的扩展。KTO 和 DRO 这两项研究关注的便是使用二元反馈来对齐 LLM。
- KTO,Kahneman-Tversky 优化,参阅论文《KTO: Model alignment as prospect theoretic optimization》。
- DRO,直接奖励优化,参阅论文《Offline regularised reinforcement learning for large language models alignment》。
融合 SFT 和对齐
之前的研究主要还是按顺序执行 SFT 和对齐,但事实证明这种方法很费力,并会导致灾难性遗忘。后续的研究有两个方向:一是将这两个过程整合成单一步骤;二是并行地微调两个模型,最终再进行融合。
- ORPO,比值比偏好优化,参阅论文《ORPO: Monolithic preference optimization without reference model》。
- PAFT,并行微调,参阅论文《PAFT: A parallel training paradigm for effective llm fine-tuning》。
长度控制式 DPO 和无参考 DPO
之前有研究表明,LLM 的输出往往过于冗长。为了解决这个问题,R-DPO 和 SimPO 的关注重心是在不影响生成性能的前提下实现对响应长度的控制。
此外,DPO 必需参考策略来确保已对齐模型不会与参考模型有太大偏差。相较之下,SimPO 和 RLOO 提出了一些方法,可以在不影响 LLM 效果的情况下消除对参考模型的需求。
- R-DPO,正则化 DPO,参阅论文《Disentangling length from quality in direct preference optimization》。
- SimPO,简单偏好优化,参阅论文《SimPO: Simple preference optimization with a reference-free reward》,报道《全面超越 DPO:陈丹琦团队提出简单偏好优化 SimPO,还炼出最强 8B 开源模型》。
- RLOO,REINFORCE Leave-One-Out,参阅论文《Back to basics: Revisiting reinforce style optimization for learning from human feedback in LLMs》。
逐列表的偏好优化
之前在 PPO 和 DPO 方面的研究关注的是成对偏好,而 RLHF 方面的研究则是收集逐列表的偏好来加速数据收集过程,之后再将它们转换成成对偏好。尽管如此,为了提升 LLM 的性能,直接使用逐列表的数据集来执行偏好优化是可行的。以下三篇论文专门讨论了这种方法。
- LiPO,逐列表偏好优化,参阅论文《LIPO: Listwise preference optimization through learning-to-rank》。
- RRHF,参阅论文《RRHF: Rank responses to align language models with human feedback without tears》。
- PRO,偏好排名优化,参阅论文《Preference ranking optimization for human alignment》。
负偏好优化
这些研究有一个共同前提:当前这一代 LLM 已经在翻译和总结等任务上超越了人类性能。因此,可以将 LLM 的输出视为期望响应,而无需依靠将人类标注的数据视为偏好响应;这样做是有好处的。反过来,不期望得到的响应依然也可被用于对齐 LLM,这个过程就是所谓的负偏好优化(NPO)。
- NN,否定负例方法,参阅论文《Negating negatives: Alignment without human positive samples via distributional dispreference optimization》。
- NPO,负例偏好优化,参阅论文《Negative preference optimization: From catastrophic collapse to effective unlearning》。
- CPO,对比偏好优化,参阅论文《Contrastive preference optimization: Pushing the boundaries of llm performance in machine translation》。
纳什学习
之前的研究通常是使用逐点奖励和 BT 模型来得到成对偏好。但是,这种方法比不上直接成对偏好建模并且无法解决成对偏好中的不一致问题。为了克服这些局限,一些研究提出了纳什学习方法。
- 根据人类反馈的纳什学习,参阅论文《Nash learning from human feedback》。
- SPPO,自博弈偏好优化,参阅论文《A minimaximalist approach to reinforcement learning from human feedback》。
- DNO,直接纳什优化,参阅论文《Direct nash optimization: Teaching language models to self-improve with general preferences》。
不同方法的比较
一些研究则是为了比较这些不同方法。这类研究可以阐释每种方法各自的优缺点。
- 评估 DPO 及其变体
论文《Insights into alignment: Evaluating dpo and its variants across multiple tasks》在推理、数学问题求解、可信度、问答和多任务理解等多种任务上全面评估了隐式奖励模型,即无强化学习算法,包括 DPO、KTO、IPO 和 CPO。这些评估涉及三个不同场景:1) 微调监督式微调(SFT)模型、2) 微调预训练模型、3) 微调指令模型。
该研究发现,在大多数基准上,KTO 比其它对齐方法更优。此外,研究表明,对齐并不会显著提升模型的推理和问答性能,但确实能大幅提升模型的数学问题求解能力。该研究还注意到了数据量的重要性,对齐方法在较小的数据子集上的性能最佳。此外,研究发现 KTO 和 CPO 能有效绕过 SFT 阶段,在不影响性能的前提下直接进入对齐阶段。相比之下,当绕过 SFT 阶段,直接进入对齐阶段时,DPO 和 IPO 会表现出明显的性能下降。
- DPO 是比 PPO 更好的 LLM 对齐方法吗?
论文《Is DPO superior to PPO for LLM alignment? A comprehensive study》表明,DPO 可能存在固有局限,可能会产生有偏差的解答,并可能由于分布变化而导致性能下降,
他们发现,DPO 训练出的策略倾向于未曾见过的响应,尤其是分布外的样本。而迭代式 / 在线 DPO 则能缓解这个问题,其做法是广泛探索响应空间并不断更新参考模型。相较之下,RLHF/PPO 则是通过优势归一化、大批量大小以及对参考模型使用指数移动平均来解决这些挑战。最终,这些发现表明 PPO 优于迭代式 / 在线 DPO,而这又进一步优于标准 DPO。
未来方向
通过分析过往论文,该团队确定了一些有待进一步探索的研究问题。
用于对齐评估的一般任务
不同论文使用了不同的任务来评估这些方法的性能。但是,GSM8K 等一些任务更关注推理,可能并不适合用于评估对齐性能。相反,TruthfulQA 等任务或那些关注毒性的任务应当优先考虑,以评估已微调 LLM 的毒性。应当想办法将这些任务组合起来,创建一个用于评估对齐的统一排行榜。
将隐式奖励模型、逐列表偏好和纳什学习用于更大规模的语言模型
目前,使用隐式奖励模型的最大模型的参数量也不过 70B。如果能将这些方法扩展用于更大的模型,比如 GPT-4 和 Claude-3 大小的模型,那应该能帮助我们更好地理解它们与 RLHF/PPO 的相对效果。
类似地,逐列表偏好模型也值得进一步研究。使用 RLHF 时,要使用逐列表偏好收集偏好数据集,之后再将其转换成多对成对偏好数据。大规模应用逐列表偏好模型的潜在问题依然有待解决。
最后,纳什学习可以解决人类标注者之间的不一致问题。如果能将纳什学习模型集成到更大规模的 LLM 中,就可以证明其捕获人性复杂性的能力。
有关二元反馈的实验
KTO 和 DRO 都采用了「点赞」和「点踩」这样的二元反馈机制,而不是成对偏好。这些二元反馈来自偏好数据集,其中将期望响应标记成正例,将不期望响应标记成负例。我们还需要对现实的二元数据集进行进一步研究。此外,相比于偏好数据,二元数据集更容易收集,因此有望使用更大规模的二元反馈数据集来进行对齐。但是,二元反馈中的噪声可能比偏好数据集中的噪声更加明显,因此如何有效滤除有噪声数据也是一个非常有趣的研究方向。
实验研究有用的 AI 反馈
目前的 AI 反馈主要包括 RLAIF 中的无害反馈和迭代式 DPO 中的反馈排名。但是,使用 RLAIF 时,有用反馈依然是由人类标注者提供。这种方法是合理的,因为生成有用响应的难度比识别有害反馈明显大得多。一个有趣的未来研究方向是使用 LLM 来生成有用的反馈,由此让 LLM 可以自我提升。
加速纳什学习
纳什学习方法可以有效建模成对偏好并解决人类标注之间的不一致问题。但是,它必需多次迭代才能收敛到最优策略。尽管其作者没有明说对齐所需的时间,但可猜测其会比 DPO 等隐式奖励模型慢得多。因此,提升纳什学习过程的速度也是一个值得关注的研究方向。
迭代 / 在线学习的终止
在使用迭代 / 在线训练时,确定终止迭代的时间很关键。之前有研究发现,迭代式学习有时会降低 LLM 在某些任务上的性能,这可能是过拟合的迹象。但是,目前还没有研究者探索如何确定终止迭代的合理 epoch。
简化 SFT + 对齐
当前的方法通常是以一种连续方式实现 SFT 和对齐。但是,这种方法往往会导致灾难性遗忘,并让整个训练过程变得更加费力。PAFT 方法减轻灾难性遗忘的方式是先分别微调 SFT 和对齐然后再将它们融合到一起,但这也会提升复杂性。相较之下,ORPO 技术是同时整合这两个过程,但却会导致性能下降。那么,该如何有效地将 SFT 和对齐组合起来实现高性能同时又维持高效率呢?这还是一个有待解决的挑战。
#大模型原理解读
生成式 AI 太火爆了,可以说无处不在,频频刷爆我们的朋友圈。你可能已经试用过 ChatGPT 了,甚至已经把它当作你的智能小助手。
但我知道很多人心里会有个疑问:这些 AI 模型的智能究竟是怎么来的?今天咱们就来聊聊这个话题。我会用大家都能懂的,而不是那些让人头大的高等数学术语来解释生成式文本模型的工作原理,揭开它的神秘面纱,把它变成简单的计算机算法。
#01
LLM 的功能
首先,我要澄清人们对 LLM 工作原理的一个重大误解。大多数人认为这些模型可以回答问题或与你聊天,但实际上它们只能接收你提供的一些文本作为输入,然后猜测下一个词(更准确地说,下一个 Token)是什么。
让我们从 Token 开始了解 LLM 的奥秘。
Token
Token 是 LLM 理解的文本基本单位。虽然将 Token 看作单词很方便,但对 LLM 来说,目标是尽可能高效地编码文本,所以在许多情况下,Token 代表的字符序列比整个单词都要短或长。标点符号和空格也被表示为 Token,可能是单独或与其他字符组合表示。
LLM 使用的所有 Token 统称为其词汇,因为它可以用来表示任何可能的文本。字节对编码(BPE)算法通常用于 LLM 生成 Token 词汇。为了让你对规模有个大致的了解,GPT-2 语言模型是开源的,可以详细研究,其词汇量为 50,257 个 Token。
LLM 词汇中的每个 Token 都有一个唯一的标识符,通常是一个数字。LLM 使用分词器在常规文本字符串和等效的 Token 数列表之间进行转换。如果你熟悉 Python 并想尝试 Token,可以安装 OpenAI 的 tiktoken 包:
$ pip install tiktoken
然后在 Python 提示符中尝试以下内容:
>>> import tiktoken
>>> encoding = tiktoken.encoding_for_model("gpt-2")
>>> encoding.encode("The quick brown fox jumps over the lazy dog.")
[464, 2068, 7586, 21831, 18045, 625, 262, 16931, 3290, 13]
>>> encoding.decode([464, 2068, 7586, 21831, 18045, 625, 262, 16931, 3290, 13])
'The quick brown fox jumps over the lazy dog.'
>>> encoding.decode([464])
'The'
>>> encoding.decode([2068])
' quick'
>>> encoding.decode([13])
'.'
在这个实验中,你可以看到对于 GPT-2 语言模型,Token 464 代表单词 “The”,而 Token 2068 代表单词 “quick”,包括一个前导空格。该模型使用 Token 13 表示句号。
由于 Token 是通过算法确定的,你可能会发现一些奇怪的现象,比如这三个变体的单词 “the”,在 GPT-2 中都编码为不同的 Token:
>>> encoding.encode('The')
[464]
>>> encoding.encode('the')
[1169]
>>> encoding.encode(' the')
[262]
BPE 算法并不总是将整个单词映射到 Token。事实上,使用频率较低的单词不能成为独立的 Token,必须使用多个 Token 进行编码。以下是一个使用两个 Token 编码的单词示例:
>>> encoding.encode("Payment")
[19197, 434]
>>> encoding.decode([19197])
'Pay'
>>> encoding.decode([434])
'ment'
下一个 Token 预测
如上所述,给定一些文本,语言模型会预测下一个紧跟其后的 Token。如果用 Python 伪代码展示可能会更清晰,下面是如何运行这些模型以获取下一个 Token 的预测:
predictions = get_token_predictions(['The', ' quick', ' brown', ' fox'])
该函数接收一个由用户提供的提示词编码而来的输入 Token 列表。在这个例子中,我假设每个单词都是一个独立的 Token。为了简化,我使用每个 Token 的文本表示,但正如你之前看到的,实际上每个 Token 会作为一个数字传递给模型。
这个函数的返回值是一个数据结构,它为词汇表中的每个 Token 分配一个紧随输入文本之后的概率。如果基于 GPT-2,这个函数的返回值将是一个包含 50,257 个浮点数的列表,每个浮点数预测相应 Token 将会出现的概率。
在上述例子中,你可以想象,一个训练良好的语言模型会给 Token “jumps” 一个较高的概率来紧跟提示词 “The quick brown fox” 后面。同样假设模型训练得当,你也可以想象,随机单词如 “potato” 继续这个短语的概率会非常低,接近于 0。
为了能够生成合理的预测,语言模型必须经过训练过程。在训练期间,它会被提供大量文本以进行学习。训练结束时,模型能够使用它在训练中见到的所有文本构建的数据结构来计算给定 Token 序列的下一个 Token 概率。
这与你的预期有何不同?我希望这现在看起来不再那么神奇了。
生成长文本序列
由于模型只能预测下一个 Token 是什么,因此生成完整句子的唯一方法是多次循环运行模型。每次循环迭代都会生成一个新的 Token,从返回的概率中选择该 Token。然后将该 Token 添加到下一次循环迭代的输入中,直到生成足够的文本为止。
让我们看一个更完整的 Python 伪代码,展示这种方法的工作原理:
def generate_text(prompt, num_tokens, hyperparameters):
tokens = tokenize(prompt)
for i in range(num_tokens):
predictions = get_token_predictions(tokens)
next_token = select_next_token(predictions, hyperparameters)
tokens.append(next_token)
return ''.join(tokens)
generate_text() 函数将用户提示作为参数。这可能是一个问题。
tokenize() 辅助函数使用 tiktoken 或类似库将提示转换为等效的 Token 列表。在 for 循环中,get_token_predictions() 函数是调用 AI 模型以获取下一个 Token 的概率,如前面的例子所示。
select_next_token() 函数的作用是获取下一个 Token 的概率(或预测)并选择最佳 Token 以继续输入序列。函数可以只选择概率最高的 Token,这在机器学习中称为 “贪心选择(greedy selection)”。更好的是,它可以使用随机数生成器来选择一个符合模型返回概率的 Token,从而为生成的文本添加一些变化。这也会使模型在多次给出相同提示时产生不同的响应。
为了使 Token 选择过程更加灵活,LLM 返回的概率可以使用超参数进行修改,这些超参数作为参数传递给文本生成函数。超参数允许你控制 Token 选择过程的 “贪婪” 程度。
如果你使用过 LLM,你可能熟悉 temperature 超参数。temperature 越高,Token 概率越平坦,这增加了选择不太可能的 Token 的机会,最终使生成的文本看起来更有创造性或更不寻常。你可能还使用了另外两个超参数,称为 top_p 和 top_k,它们控制被考虑选择的最高概率的 Token 数量。
一旦选定了一个 Token,循环就会迭代,模型会接收到一个包含新 Token 在末尾的输入,并生成另一个紧随其后的 Token。num_tokens 参数控制循环运行的迭代次数,换句话说,就是要控制生成多少文本。生成的文本可能(而且经常)在句子中间断开,因为 LLM 没有句子或段落的概念,它只处理一个 Token。为了防止生成的文本在句子中间断开,我们可以将 num_tokens 参数视为最大值而不是确切的 Token 数,在这种情况下,我们可以在生成句号 Token 时停止循环。
如果你已经理解了这些内容,那么恭喜你,你现在已经大致了解了 LLM 是如何工作的。在下一部分,我会更深入一些,但仍然尽量避免涉及支撑这一技术的数学原理,因为它相当复杂。
#02
模型训练
在不使用数学表达式的情况下讨论如何训练模型实际上是很困难的。我将从一个非常简单的训练方法开始展示。
鉴于任务是预测 Token 的后续 Token,一种简单的训练模型的方法是获取训练数据集中所有连续 Token,并用它们构建一个概率表。
让我们用一个简短的词汇表和数据集来做这个。假设模型的词汇表包含以下五个词元:
['I', 'you', 'like', 'apples', 'bananas']
为了使这个例子简短而简单,我不会将空格或标点符号作为 Token。
我们使用一个由三句话组成的训练数据集:
- I like apples
- I like bananas
- you like bananas
我们可以构建一个 5x5 的表格,并在每个单元格中写下表示单元格所在行的 Token 被单元格所在列的 Token 跟随的次数。以下是根据数据集中的三句话构建的表格:
- | I | you | like | apples | bananas |
I | 2 | ||||
you | 1 | ||||
like | 1 | 2 | |||
apples | |||||
bananas |
希望这很清楚。数据集中有两次出现 “I like”,一次出现 “you like”,一次出现 “like apples” 和两次出现 “like bananas”。
现在我们知道每对 Token 在训练数据集中出现的次数,我们可以计算每个 Token 跟随另一个 Token 的概率。为此,我们将每行中的数字转换为概率。例如,表格中间行的 Token “like” 后面跟一次 “apples” 和两次 “bananas”。这意味着 “like” 后面 33。3% 的概率是 “apples”,剩下的 66。7% 的概率是 “bananas”。
以下是计算出的所有概率的完整表格。空单元格的概率为 0%。
- | I | you | like | apples | bananas |
I | 100% | ||||
you | 100% | ||||
like | 33.3% | 66.7% | |||
apples | 25% | 25% | 25% | 25% | |
bananas | 25% | 25% | 25% | 25% |
对于 “I”、“you” 和 “like” 行来说,计算很简单,但 “apples” 和 “bananas” 行却出现了问题,因为它们没有任何数据。由于数据集中没有任何示例显示这些 Token 后面跟随其他 Token,这里我们的训练中存在一个 “漏洞”。
为了确保模型在缺乏训练的情况下仍能生成预测,我决定将 “apples” 和 “bananas” 后续 Token 的概率平均分配到其他四个可能的 Token 上,这显然可能会产生奇怪的结果,但至少模型在遇到这些 Token 时不会卡住。
训练数据中的漏洞问题实际上非常重要。在真正的 LLM 中,训练数据集非常庞大,因此你不会发现像我上面这个小例子中那样明显的训练漏洞。但由于训练数据覆盖率低而导致的小的、更难检测到的漏洞确实存在,并且相当普遍。
在这些训练不足的区域中,LLM 对 Token 的预测质量可能会很差,但通常是难以察觉的。这是 LLM 有时会产生幻觉的原因之一,这种情况发生在生成的文本读起来很流畅但包含事实错误或不一致时。
使用上面的概率表,你现在可以想象 get_token_predictions()函数的实现方式。在 Python 伪代码中它可能是这样的:
def get_token_predictions(input_tokens):
last_token = input_tokens[-1]
return probabilities_table[last_token]
比想象的更简单,对吧?这个函数接受一个由用户提示词生成的序列。它取序列中的最后一个 Token,并返回该 Token 在概率表中对应的那一行。
例如,如果你用 ['you', 'like'] 作为输入 Token 调用这个函数,那么该函数会返回 “like” 的那一行,“like” 会给予 “apples” 33.3% 的概率来继续句子,而 “bananas” 则是另外的 66.7%。根据这些概率,上面展示的 select_next_token() 函数每三次应该会选择一次 “apples”。
当 “apples” 被选为 “you like” 的延续时,句子 “you like apples” 就会形成。这是一个在训练数据集中不存在,但完全合理的原创句子。希望你开始了解这些模型如何通过重用模式和拼接它们在训练中学到的不同部分来生成看似原创的想法或概念。
上下文窗口
我在上一节中训练小模型的方法称为马尔可夫链。
这种技术的一个问题是,只使用一个 Token(输入的最后一个)来进行预测。任何出现在最后一个 Token 之前的文本在选择如何继续时都没有影响,所以我们可以说这种解决方案的上下文窗口等于一个 Token,这个窗口非常小。由于上下文窗口如此小,模型会不断 “忘记” 思路,从一个词跳到另一个词,缺乏一致性。
可以通过构建一个更大的概率矩阵来改进模型的预测。为了使用两个 Token 的上下文窗口,需要增加额外的表行,这些行代表所有可能的两个 Token 序列。在示例中使用的五个 Token 中,每一对 Token 将在概率表中新增 25 行,加上已经存在的 5 个单 Token 行。模型将不得不再次训练,这次不仅看 Token 对,还要看 Token 组的三元组。在每次 get_token_predictions() 函数的循环迭代中,当可用时,将使用输入的最后两个 Token 来查找较大概率表中的对应行。
但是,2 个 Token 的上下文窗口仍然不够。为了生成一致且至少有基本意义的文本,需要更大的上下文窗口。没有足够大的上下文,新生成的 Token 不可能与之前 Token 中表达的概念或想法相关联。那么我们该怎么办呢?将上下文窗口增加到 3 个 Token 将为概率表增加 125 行,并且质量仍然很差。我们需要将上下文窗口扩大到多大呢?
OpenAI 开源的 GPT-2 模型使用了一个 1024 个 Token 的上下文窗口。为了使用马尔可夫链实现这么大的上下文窗口,每行概率表都必须代表一个长度在 1 到 1024 个 Token 长的序列。使用上面示例中的 5 个 Token 词汇表,有 5 的 1024 次方种可能的序列长度为 1024 Token。需要多少表行来表示这些?我在 Python 会话中做了计算(向右滚动以查看完整数字):
>>> pow(5, 1024)
55626846462680034577255817933310101605480399511558295763833185422180110870347954896357078975312775514101683493275895275128810854038836502721400309634442970528269449838300058261990253686064590901798039126173562593355209381270166265416453973718012279499214790991212515897719252957621869994522193843748736289511290126272884996414561770466127838448395124802899527144151299810833802858809753719892490239782222290074816037776586657834841586939662825734294051183140794537141608771803070715941051121170285190347786926570042246331102750604036185540464179153763503857127117918822547579033069472418242684328083352174724579376695971173152319349449321466491373527284227385153411689217559966957882267024615430273115634918212890625
这行数太多了!而这只是表的一部分,因为我们还需要长度为 1023 的序列,1022 的序列,等等,一直到 1,因为我们想确保在输入中没有足够 Token 时也能处理较短的序列。马尔可夫链是有趣的,但它们确实存在一个很大的可扩展性问题。
而且一个 1024 Token 的上下文窗口已经不再那么大了。随着 GPT-3,上下文窗口增加到 2048 个 Token,然后在 GPT-3.5 中增加到 4096。GPT-4 开始时为 8192 个 Token,后来增加到 32K,然后又增加到 128K(没错,128,000 个 Tokens!)。现在开始出现上下文窗口为 1M 或更大的模型,这允许在做 Token 预测时有更好的一致性和回忆能力。
总之,马尔可夫链使我们以正确的方式思考文本生成问题,但它们有很大的问题,阻止我们考虑其作为可行的解决方案。
从马尔可夫链到神经网络
显然,我们不能再考虑使用概率表的方案,因为合理上下文窗口的概率表会需要庞大的 RAM。我们可以做的是用一个函数来替代概率表,该函数返回 Token 概率的近似值,这些概率是通过算法生成的,而不是存储在一个庞大的表格中。事实上,这正是神经网络擅长的事情。
神经网络是一种特殊的函数,它接受一些输入,对这些输入进行计算,然后返回一个输出。对于语言模型来说,输入是表示提示词的 Token,输出是下一个 Token 的预测概率列表。
神经网络之所以被称为 “特殊” 的函数,是因为它们除了函数逻辑外,还受一组外部定义的参数控制。最初,网络的参数是未知的,因此函数产生的输出完全没有用。神经网络的训练过程在于找到能使函数在训练数据集上表现最佳的参数,假设如果函数在训练数据上表现良好,那么在其他数据上也会表现良好。
在训练过程中,参数会使用一种称为反向传播的算法进行小幅度的迭代调整,这个算法涉及大量数学运算,所以本文不会详细讨论。每次调整后,神经网络的预测结果会略有改善。参数更新后,网络会再次根据训练数据集进行评估,评估结果用于指导下一轮调整。这个过程会持续进行,直到函数在训练数据集上表现出良好的下一个 Token 预测为止。
为了让你了解神经网络工作的规模,可以考虑 GPT-2 模型有大约 15 亿个参数,而 GPT-3 将参数数量增加到 1750 亿。据说 GPT-4 有大约 1.76 万亿个参数。以现有硬件条件训练这种规模的神经网络需要很长时间,通常是数周或数月。
有意思的是,由于参数众多,都是在没有人为干预的情况下通过漫长的迭代过程计算出来的,因此很难理解模型的工作原理。一个训练有素的 LLM 就像一个黑匣子,非常难以调试,因为模型的大部分 “思考” 都隐藏在参数中。即使是训练该模型的人也难以解释其内部工作原理。
层、Transformer 和注意力机制
你可能会好奇,在神经网络函数内部发生了哪些神秘的计算,在参数调优的帮助下,可以将一列输入 Token 转换为合理的下一个 Token 的概率。
一个神经网络被配置为执行一系列操作,每个操作称为一个 “层”。第一层接收输入,并进行某种转换。转换后的输入进入下一层,再次被转换。如此反复,直到数据到达最后一层,并进行最后一次转换,生成输出或预测结果。
机器学习专家设计出不同类型的层,对输入数据进行数学转换,并找出如何组织和组合层以达到预期的结果。有些层是通用的,而有些层则专为处理特定类型的输入数据而设计,如图像或在 LLM 中的 Token 化文本。
当前在 LLM 中用于文本生成最流行的神经网络架构称为 Transformer。使用这种设计的 LLM 被称为 GPT,即生成式预训练 Transformer。
Transformer 模型的显著特点是其执行的一种称为注意力机制的层计算,这使得它们能够在上下文窗口中的 Token 之间推导出关系和模式,并将这些关系和模式反映在下一个 Token 的概率中。
注意力机制最初用于语言翻译器,作为一种找到输入序列中最重要的 Token 以提取其含义的方法。这种机制使得现代翻译器能够在基本层面上 “理解” 一个句子,通过关注(或将 “注意力” 引向)重要的词或 Token。
#03
LLM 是否具有智能?
到现在,你可能已经开始对 LLM 在生成文本的方式上是否表现出某种形式的智能形成一种看法。
我个人并不认为 LLM 具备推理能力或提出原创思想的能力,但这并不意味着它们毫无用处。由于它们在上下文窗口中的 Token 上进行的巧妙计算,LLM 能够发现用户提示中存在的模式,并将这些模式与训练期间学到的类似模式相匹配。它们生成的文本主要由训练数据的片段组成,但它们将词(实际上是 Token)拼接在一起的方式非常复杂,在许多情况下,产生的结果感觉是原创且有用的。
鉴于 LLM 容易产生幻觉现象,我不会信任任何由 LLM 生成,未经人工验证就直接传递给终端用户的工作流。
未来几个月或几年内出现的更大规模的 LLM 会实现类似于真正智能的东西吗?我觉得这在 GPT 架构下不会发生,因为它有很多限制,但谁知道呢,也许通过未来的一些创新,我们会实现这一目标。