我自己的原文哦~                                 https://blog.51cto.com/whaosoft/12371157

#LLM~幻觉

最近,来自哈尔滨工业大学和华为的研究团队发表了一篇长达50页的综述,细致地盘点了有关LLM幻觉问题你该知道的所有事。

幻觉,老朋友了。

自打LLM进入我们的视野,幻觉问题就一直是一道坎,困扰着无数开发人员。

当然,有关大语言模型幻觉的问题已经有了无数研究。

最近,来自哈工大和华为的团队发表了一篇50页的大综述,对有关LLM幻觉问题的最新进展来了一个全面而深入的概述。

这篇综述从LLM幻觉的创新分类方法出发,深入探究了可能导致幻觉的因素,并对检测幻觉的方法和基准进行了概述。

这其中肯定也少不了业内比较有代表性的减轻幻觉的方法。

论文地址:https://arxiv.org/abs/2311.05232

下面,我们就来看一看本篇综述中主要讲了些什么内容。

想深入学习的朋友,可以移步文章底部的参考链接,阅读论文原文。

幻觉大分类

首先,先来看看有哪些种类的幻觉。

​编辑

上图中,左边是事实性的幻觉。当LLM被问到谁是第一个在月球上漫步的人时,LLM编了个人物出来,甚至还说得有模有样。

右边则是文本摘要模型中的忠实度问题,可以看到LLM在看到这段新闻后,直接把年份概括错了。

在本篇综述中,研究人员深入分析了LLM中幻觉的起源,涵盖了从数据、训练到推理阶段的一系列促成因素。

在这一框架内,研究人员指出了与数据相关的潜在原因。例如,有缺陷的数据源和未优化的数据利用,或是在预训练和对齐过程中可能会诱发幻觉的训练策略,以及源于解码策略的随机性和推理过程中不完善的表征等等。

此外,研究人员还全面概述了专为检测LLM中的幻觉而设计的各种有效方法,以及与LLM幻觉相关的基准的详尽概述,和作为评估LLM产生幻觉的程度和检测方法有效性的试验平台。

下图即为本篇综述所涉及到的内容、前人研究,以及论文。

​编辑

下图是一张更为详细的LLM幻觉种类图。

​编辑

在事实型幻觉和忠实度幻觉下,还包括更为细致的分类。

事实型幻觉:

a)事实不一致

当问LLM,谁是第一位登月的人时,LLM回答说是加加林,而非阿姆斯特朗。这种属于答案与事实不一致,因为确有加加林其人,所以不属于捏造。

b)事实捏造

当让LLM介绍一下独角兽的起源时,LLM并没有指出世界上没有独角兽这种生物,反倒是编了一大段。这种现实世界中没有的,称之为捏造。

忠实度幻觉又包括:指令-答案的不一致、文本不一致,以及逻辑不一致。

a)指令-答案不一致

当LLM被要求翻译一个问句时,LLM输出的答案实际上回答了问题,没有进行翻译。因此是一种指令和答案的不一致。

b)文本不一致

这类不一致更多出现在概括类任务中。LLM可能会罔顾给出的文本,总结一个错的出来。

c)逻辑不一致

在被要求给出2x+3=11的方程解法时,第一步LLM指出,两边同时减去3,得到2x=8.接下来在两边除以2的操作中,LLM输出的答案是3.

8除以2怎么会等于3呢?

幻觉产生原理

数据

接下来,综述开始梳理有关幻觉产生原理的内容。

​编辑

第一类,数据问题。

·错误信息和偏见。鉴于对大规模语料库的需求日益增长,启发式数据收集方法被用来有效收集大量数据。

这种方法在提供大量数据的同时,可能会无意中引入错误信息,增加出现模仿性错误的风险。此外,社会偏见也会在无意中被引入LLMs的学习过程。

这些偏差主要包括重复偏差和各种社会偏差(Social Biases)。

要知道,LLM预训练的主要目的是模仿训练分布。所以当LLM在事实不正确的数据上接受训练时,它们可能会无意中放大这些不准确的数据,从而可能导致事实不正确的幻觉。

神经网络,尤其是大型语言模型,具有记忆训练数据的内在倾向。研究表明,这种记忆趋势会随着模型规模的扩大而增强。

然而,在预训练数据中存在重复信息的情况下,固有的记忆能力就会出现问题。这种重复会使 LLM 从泛化转向记忆,最终产生重复偏差,即LLM会过度优先回忆重复的数据,导致幻觉,最终偏离所需的内容。

除了这些偏见,数据分布的差异也是产生幻觉的潜在原因。

下一种情况是,LLM通常会存在知识边界。

虽然大量的预培训语料库为法律硕士提供了广泛的事实知识,但它们本身也有局限性。这种局限性主要体现在两个方面:缺乏最新的事实知识和专业领域知识。

虽说LLM在通用领域的各种下游任务中表现出了卓越的性能,但由于这些通用型LLMs主要是在广泛的公开数据集上进行训练,它们在专业领域的专业知识受到缺乏相关训练数据的内在限制。

因此,当遇到需要特定领域知识的问题时,如医学和法律问题,这些模型可能会表现出明显的幻觉,通常表现为捏造事实。

此外,还有过时的事实知识。除了特定领域知识的不足,LLMs知识边界的另一个内在限制是其获取最新知识的能力有限。

蕴含在LLM中的事实知识具有明确的时间界限,随着时间的推移可能会过时。

这些模型一旦经过训练,其内部知识就永远不会更新。

而鉴于我们这个世界的动态性和不断变化的本质,这就构成了一个挑战。当面对超越其时间范围的领域知识时,LLMs往往会采用捏造事实或提供过去可能正确,但现在已经过时的答案的方法来试图「蒙混过关」。

下图中,上半部分即为LLM缺失特定领域内的专业知识——phenylketonuria(苯丙酮尿)。

下半部分即为最简单的一个知识过时的案例。2018年韩国平昌举办冬奥会,2022年北京举办冬奥会。LLM并没有有关后者的知识储备。

​编辑

由此可见,LLM中与数据有关的幻觉主要源于错误的数据源和不佳的数据利用情况。数据源中的错误信息和固有偏差不仅会传播模仿性虚假信息,还会引入有偏差的输出,从而导致各种形式的幻觉。

在处理特定领域的知识或遇到快速更新的事实知识时,LLM所拥有知识的局限性就会变得很明显。

在数据利用方面,LLMs 往往会捕捉到虚假的相关性,在回忆知识(尤其是长尾信息)和复杂推理场景中表现出困难,从而进一步加剧幻觉。

这些挑战突出表明,亟需提高数据质量,增强模型更有效地学习和回忆事实知识的能力。

训练

现在,综述把目光转向LLM的训练阶段。

LLM的训练过程主要包括两个主要阶段:

预训练阶段,LLMs在这一阶段学习通用表征并捕捉广泛的知识。

对齐阶段,LLMs在这一阶段进行调整,以更好地使用户指令和人类的基本价值观保持一致。虽然这一过程使LLM 具备了还算不错的性能,但这些阶段中的任何不足都可能无意中导致幻觉的发生。

预训练是LLM的基础阶段,通常采用基于transformer的架构,在庞大的语料库中进行因果语言建模。

然而,固有的架构设计和研究人员所采用的特定训练策略,可能会产生与幻觉相关的问题。如上所说,LLM通常采用基于transformer的架构,遵循GPT建立的范式,它们通过因果语言建模目标获取表征,OPT和Llama-2等模型都是这一框架的典范。

除了结构缺陷,训练策略也起着至关重要的作用。值得注意的是,自回归生成模型的训练和推理之间的差异导致了暴露偏差(Exposure Bias)现象。

而在对齐阶段,一般涉及两个主要过程,即监督微调和从人类反馈中强化学习(RLHF),是释放LLM能力并使其符合人类偏好的关键一步。

虽然对齐能显著提高 LLM 响应的质量,但也会带来产生幻觉的风险。

主要分为两方面:能力不对齐和信念不对齐(Capability Misalignment、Belief Misalignment)。

如何检测幻觉?

检测LLM中的幻觉对于确保生成内容的可靠性和可信度来说至关重要。

传统的衡量标准主要依赖于词语重叠,无法区分可信内容和幻觉内容之间的细微差别。

这一挑战凸显了针对LLM幻觉采用更先进的检测方法的必要性。研究人员指出,鉴于这些幻觉的多样性,检测方法也相应地有所不同。 

这里仅详细介绍一例——

·检索外部事实

如下图所示,为了有效地指出LLM输出中不准确的事实,一种比较直观的策略是,直接将模型生成的内容与可靠的知识来源进行比较。

这种方法与事实检查任务的工作流程非常吻合。然而,传统的事实核查方法往往出于实用性考虑而采用了简化假设,导致在应用于复杂的现实世界场景时有可能会出现偏差。

在认识到这些限制因素以后,一些研究者提出,要更加重视真实世界的场景,即从时间受限、未经整理的网络资源中获取证据。

他们首创了一种全自动的工作流,集成多个组成部分,包括原始文档检索、细粒度检索、真实性分类等等。

当然,还有不少其他研究者提出了另外一些办法,比如FACTSCORE,专门用于长文本生成的细粒度事实度量。

​编辑

其它方法还包括不确定性估计,如下图所示。

​编辑

有关忠实度幻觉的检测,也有不少相关研究,如下图所示。

​编辑

其中包括基于事实度量:通过检测生成内容与源内容之间的事实重叠度来评估忠实度。

基于分类器的度量:利用经过训练的分类器来区分生成内容与源内容之间的关联程度。

基于QA的度量方法:利用问题解答系统来验证源内容与生成内容之间的信息一致性。

不确定性估计:通过测量模型对其生成输出的置信度来评估忠实度。

基于prompt的度量方法:让LLM充当评估者,通过特定的prompt策略来评估生成内容的忠实度。

之后,哈工大团队还将较为前沿的减轻幻觉的方法进行了整理,针对上述提到的各类问题,分别提供可行的解决办法。

​编辑

总结

总而言之,在论文的最后,哈工大的研究人员表示,在这份全面的综述中,他们对大型语言模型中的幻觉现象进行了深入研究,深入探讨了其潜在原因的复杂性、开创性的检测方法和相关基准,以及有效的缓解策略。

虽然开发者们在这个问题上已经有了不少进步,但大型语言模型中的幻觉问题仍然是一个令人关注的持续性问题,需要继续研究。

此外,本篇论文还可以作为推进安全可信的AI的指路明灯。

哈工大团队表示,希望通过对幻觉这一复杂问题的探索,为这些有志之士提供宝贵的见解,推动AI技术向更可靠、更安全的方向发展。

参考资料:

​https://arxiv.org/abs/2311.05232​

.....

#xxx

.....

#xxx

.....

#xxx

.....

#Gemini1.0

迄今为止规模最大,能力最强的谷歌大模型来了。时代变了?

当地时间 12 月 6 日,谷歌 CEO 桑达尔・皮查伊官宣 Gemini 1.0 版正式上线。

这次发布的 Gemini 大模型是原生多模态大模型,是谷歌大模型新时代的第一步,它包括三种量级:能力最强的 Gemini Ultra,适用于多任务的 Gemini Pro 以及适用于特定任务和端侧的 Gemini Nano。谷歌大杀器终于来了,最大规模震撼发布:真超GPT4,三大版本,手机直接可用

现在,谷歌的类 ChatGPT 应用 Bard 已经升级到了 Gemini Pro 版本,实现了更为高级的推理、规划、理解等能力,同时继续保持免费。谷歌预计在明年初将推出「Bard Advanced」,其将使用 Gemini Ultra。

这是 Bard 问世以来最大的更新。

自 ChatGPT 发布以来,我们一直对谷歌声称的竞品 Gemini 模型的能力非常好奇,这款大模型早在今年 3 月就有了风声,5 月的 I/O 大会上进入「即将推出」的状态。

随着知情人士不断透露新信息,我们能了解到:据说 Gemini 有万亿参数,训练动用的算力是 GPT-4 的五倍。但 Gemini 的正式发布却似乎因为各种原因而屡遭推迟。

为了与 OpenAI 和微软展开竞争,谷歌果断从 PaLM 2 切换到了 Gemini 上,甚至在今年 4 月份直接把谷歌大脑(Google Brain)和 DeepMind 合并在了一起,Gemini 就由新组成的 Google DeepMind 汇合两个实验室的力量进行攻关。

可见谷歌在大模型军备竞赛上孤注一掷的心态。

那么,Gemini 真的能够给我们带来惊喜吗?除了在各种 Benchmark 上拿到最优成绩,甚至超越人类以外,有趣的是,在新闻发布会上,面对记者有关「Gemini 相比以前的大模型有哪些新能力」的提问,Google DeepMind 产品副总裁 Eli Collins 回答说:「我怀疑有」,表示谷歌仍然在努力了解 Gemini Ultra 的全部能力。

以下为谷歌 CEO 皮查伊的声明:  

每一次技术变革都是推进科学发现、加速人类进步和改善生活的机会。我相信我们现在所看到的人工智能转变将是我们一生中最深刻的转变,远远大于之前向移动或网络的转变。人工智能有潜力为世界各地的人们创造从日常生活到非凡的机会。它将带来新一波的创新和经济进步,并以前所未有的规模推动知识、学习、创造力和生产力。

这让我感到兴奋:有机会让人工智能为世界各地的每个人提供帮助。

作为一家人工智能优先的公司,我们已经走过了近八年的历程,进步的步伐只会不断加快:数百万人现在在我们的产品中使用生成式人工智能来完成一年前无法完成的事情,从寻找答案到更复杂的问题使用新工具进行协作和创造的问题。与此同时,开发人员正在使用我们的模型和基础设施来构建新的生成式人工智能应用程序,世界各地的初创公司和企业正在利用我们的人工智能工具不断成长。

这是令人难以置信的势头,然而,我们才刚刚开始触及可能性的表面。

我们正在大胆而负责任地开展这项工作。这意味着我们的研究要雄心勃勃,追求能够为人类和社会带来巨大利益的能力,同时建立保障措施并与政府和专家合作,应对人工智能变得更加强大的风险。我们将继续投资最好的工具、基础模型和基础设施,并在我们的人工智能原则的指导下将它们引入我们的产品和其他产品中。

谷歌大模型 Gemini 正式发布

谷歌 DeepMind CEO 和联合创始人 Demis Hassabis 代表 Gemini 团队正式推出了大模型 Gemini。

Hassabis 表示长久以来,谷歌一直想要建立新一代的 AI 大模型。在他看来,AI 带给人们的不再只是智能软件,而是更有用、更直观的专家助手或助理。

今天,谷歌大模型 Gemini 终于亮相了,成为其有史以来打造的最强大、最通用的模型。Gemini 是谷歌各个团队大规模合作的成果,包括谷歌研究院的研究者。

特别值得关注的是,Gemini 是一个多模态大模型,意味着它可以泛化并无缝地理解、操作和组合不同类型的信息,包括文本、代码、音频、图像和视频。

谷歌表示,Gemini 还是他们迄今为止最灵活的模型,能够高效地运行在数据中心和移动设备等多类型平台上。Gemini 提供的 SOTA 能力将显著增强开发人员和企业客户构建和扩展 AI 的方式。

目前,Gemini 1.0 提供了三个不同的尺寸版本,分别如下:

  • Gemini Ultra:规模最大、能力最强,用于处理高度复杂的任务;
  • Gemini Pro:在各种任务上扩展的最佳模型;
  • Gemini Nano:用于端侧(on-device)任务的最高效模型。

谷歌对 Gemini 模型进行了严格的测试,并评估了它们在各种任务中的表现。从自然图像、音频和视频理解,到数学推理等任务,Gemini Ultra 在大型语言模型研发被广泛使用的 32 个学术基准测试集中,在其中 30 个测试集的性能超过当前 SOTA 结果。

另外,Gemini Ultra 在 MMLU(大规模多任务语言理解数据集)中的得分率高达 90.0%,首次超越了人类专家。MMLU 数据集包含数学、物理、历史、法律、医学和伦理等 57 个科目,用于测试大模型的知识储备和解决问题能力。

针对 MMLU 测试集的新方法使得 Gemini 能够在回答难题之前利用其推理能力进行更仔细的思考,相比仅仅根据问题的第一印象作答,Gemini 的表现有显著改进。

在大多数基准测试中,Gemini 的性能都超越了 GPT-4。

更多细节,请查看详细的测试报告:https://storage.googleapis.com/deepmind-media/gemini/gemini_1_report.pdf

在最新版本的 MMMU 测试集中,Gemini Ultra 也取得了得分为 59.4% 的最佳成绩。增强版的测试集由需要慎重推理的多模态任务组成。

在图像基准方面的测试中,Gemini Ultra 不需要从图像中提取文本就能进行 OCR 处理,这凸显了 Gemin 内置的强大多模态能力,也初步显示了 Gemini 具有更复杂推理能力的先兆。

下一代全方位能力升级

Gemini 在设计时原生地支持多模态,从一开始便在不同模态上进行了预训练,然后利用额外的多模态数据进行微调以提升有效性。因此,Gemini 能够无缝地理解和推理各种输入,远远优于现有多模态模型,并且它的能力在几乎每个领域都是最强的。

复杂推理能力

Gemini 1.0 具有复杂多模态推理能力,可以帮助理解复杂的书面和视觉信息。这使得它尤其擅长发现海量数据中难以辨别的知识。Gemini 1.0 通过阅读、过滤和理解信息具有了从数十万份文件中提取 insights 的超凡能力,这有助于科学、金融等诸多领域以超快的速度取得新突破。

同时理解文字、图像、音频以及更多模态的信息

经过训练,Gemini 1.0 可以同时识别和理解文本、图像、音频等,因此它能够更全面地理解输入中信息的细节,也能回答与复杂主题相关的问题。因此,它特别擅长对数学和物理等复杂学科的问题进行推理。

如下图所示,一位老师画了一个滑雪者从斜坡上下来的物理问题,而一位学生则提出了一个解决方案来计算滑雪者在斜坡底部的速度。利用Gemini的多模态推理能力,该模型能够读懂凌乱的笔迹,正确理解问题的表述,将问题和解决方案都转换为数学公式,识别出学生在解决问题时出错的具体推理步骤,然后给出问题的正确解决方案。

高级编码

Gemini 可以理解、解释和生成流行编程语言(如 Python、Java、C++、Go)的高质量代码,具备强大的跨语言工作和推理复杂信息的能力使其成为世界领先的编码基础模型之一。

Gemini Ultra 在多个编码基准测试中表现出色,包括 HumanEval(用于评估编码任务性能的重要行业标准)和 Natural2Code(谷歌内部数据集),该数据集使用作者生成的源代码而不是基于网络的信息。

Gemini 还可以用作更高级编码系统的引擎。两年前,谷歌推出了 AlphaCode,这是第一个在编程竞赛中达到竞争性水平的人工智能代码生成系统。

使用 Gemini 的专门版本,谷歌创建了更先进的代码生成系统 AlphaCode 2,它擅长解决超出编码范围、涉及复杂数学和理论计算机科学的竞争性编程问题。

经过与原始 AlphaCode 在相同平台上进行评估,AlphaCode 2 展现出巨大的改进,解决的问题数量几乎是原来的两倍。

专用 TPU 训练

谷歌使用内部设计的张量处理单元 (TPU) v4 和 v5e 在人工智能优化基础设施上对 Gemini 1.0 进行了大规模训练,并将其设计为最可靠、可扩展的训练模型和最高效的服务模型。

在 TPU 上,Gemini 的运行速度明显快于早期规模较小、能力较弱的模型。这些定制设计的 AI 加速器是谷歌人工智能产品的核心,这些产品为搜索、YouTube、Gmail、谷歌地图、Google Play 和 Android 等数十亿用户提供服务。它们还帮助世界各地的公司经济高效地训练大规模人工智能模型。

今天,谷歌同时发布了迄今为止最强大、最高效、可扩展的 TPU 系统 —Cloud TPU v5p,专为训练尖端的人工智能模型而设计。新一代 TPU 将加速 Gemini 的发展,帮助开发人员和企业客户更快地训练大规模生成式 AI 模型,让新产品和新功能更快地与客户见面。

Google 数据中心内的一排 Cloud TPU v5p AI 加速器超级计算机。

谷歌旗下产品将全线升级

从今天开始,谷歌将在其产品中添加 Gemini,例如 Bard 将使用 Gemini Pro 的微调版本来执行更高级的推理、规划、理解等任务。这也是 Bard 自推出以来最大的升级。

升级版 Bard 将在 170 多个国家 / 地区提供英语版本,并且在不久的将来会扩展到更多模态,并支持更多种语言。

谷歌还将 Gemini 引入了 Pixel。Pixel 8 Pro 将是第一款运行 Gemini Nano 的智能手机。

Pixel 8 Pro 在录音机应用中使用 Gemini Nano 来总结会议音频,即使没有网络连接也可以实现。

在接下来的几个月中,Gemini 将陆续出现在谷歌更多的产品和服务中,包括搜索、广告、Chrome、Duet AI 等等。

谷歌表示其已经在搜索中试验了 Gemini,它使用户的搜索生成体验 (SGE) 速度更快,延迟减少了 40%,同时质量也得到了提升。

使用指南及未来规划

最后,开发者如何使用 Gemini?

从 12 月 13 日开始,开发人员和企业客户可以通过 Google AI Studio 或 Google Cloud Vertex AI 中的 Gemini API 访问 Gemini Pro。

从 Pixel 8 Pro 设备开始,Android 开发人员还可以通过 AICore 使用 Gemini Nano 进行构建。Android AICore 是 Android 14 中的一项新系统服务,可处理模型管理、运行时、安全功能等,简化用户将 AI 融入应用程序的工作。

AICore 通过 Gemini Nano 实现低秩适应 (LoRA) 微调。这个强大的概念使应用程序的开发人员能够根据自己的训练数据创建小型 LoRA 适配器。LoRA 适配器由 AICore 加载,从而产生针对应用程序自身用例进行微调的大型语言模型。 

另外,谷歌剧透了 Gemini Ultra 将会在不久后发布,以及 Bard 的下一步升级计划。

Gemini Ultra 模型目前正处于信任和安全检查阶段,包括由可信赖的外部各方组成的红队(red team),并使用微调和人类反馈强化学习(RLHF)进一步完善模型。

在这个过程中,谷歌会先向部分客户、开发人员、合作伙伴以及安全和责任专家提供 Gemini Ultra,供其进行早期实验和反馈,然后在明年初向开发人员和企业客户推出。

Gemini Ultra 是谷歌最大、功能最强大的模型,专为高度复杂的任务而设计。普通用户体验 Gemini Ultra 的首个方式会是通过 Bard Advanced,谷歌将在明年年初推出 Bard Advanced。

谷歌表示,未来将努力扩展 Gemini 的功能,包括在规划和记忆方面的进步,以及增加上下文窗口以处理更多信息,从而做出更好的响应。

博客链接:​https://blog.google/technology/ai/google-gemini-ai/#scalable-efficient​

.....

#Agent4Rec

一直以来,推荐系统领域面临模型线上线下效果差距大的痛点问题,昂贵的线上 A/B 测试成本使得广大研究人员望而却步,也造成学术界的推荐系统研究与工业界的实际应用间的巨大割裂。随着大语言模型展现出类人的逻辑推理和理解能力,基于大语言模型的智能体(Agent)能否模拟真实用户的交互行为,从而构建一个可靠的虚拟推荐 A/B 测试场景,以帮助推荐研究的应用落地,是一个急迫、重要且极具经济价值的问题。

为了回答这个问题,来自新加坡国立大学 NExT++ 实验室团队构建了 Agent4Rec,一个由 1000 名 agents 构成的电影推荐系统模拟器。这些 agent 由真实用户初始化,由 ChatGPT-3.5 驱动,根据用户喜好与特质,对封装的不同推荐算法和其推荐的电影做出个性化反应。这些个性化反应模拟真实用户在推荐系统中的行为,包括观看或拒看电影,给电影评分,翻到下一页电影推荐列表,疲倦度估计,因不满意或疲惫退出推荐系统,给推荐算法进行评价等。广泛的实验评估表明,Agent4Rec 里的 agent 能大概率反映真实世界的用户行为。

  • 论文链接:https://arxiv.org/abs/2310.10108
  • 代码链接:https://github.com/LehengTHU/Agent4Rec

Agent4Rec 平台构建

Agent4Rec 中的每一个用户,也即 agent,由 profile module、memory module、action module 构成。作者使用 MovieLens-1m 中的真实用户数据初始化 agent 档案。根据用户的历史交互生成 agent 的电影偏好,根据用户的历史活跃度、从众性和观影多样性生成 agent 的交互特征。

推荐系统将采取逐页推荐的方式,根据特定的推荐算法向用户推荐电影列表。仿照真实的手机 APP 推荐场景,每页将展示 4 部电影。每部电影的信息包括电影名、历史评分、电影简介等。每个 agent 将根据自身的电影喜好、疲惫程度以及个人记忆对推荐的电影做出反应,如观看或评价电影。同时,历史推荐内容和 agent 行为将被存储在记忆中,agent 通过 reflection 的方式总结对推荐系统的满意度和自身的疲惫程度。Agent 在每一页推荐结束后,都根据自身满意度和疲倦度,选择翻到下一页或者退出推荐系统。在用户退出系统之后,采访用户退出推荐系统的原因和对推荐电影的评价。

Agent 行为模拟真实性检验

用大语言模型智能体模拟人类行为最关键的问题,在于评估 agent 能够多大程度的模拟用户的真实喜好。Agent4Rec 在推荐场景下首次给出了一个实验级别的回答。

为了衡量用户的电影喜好能否被 agent 正确捕捉,作者首先让 agent 对用户交互过的测试集中的电影和随机采样的负样本电影进行喜爱与否判断。结果表明,agent 能够捕捉约 70% 的用户喜好。

在验证了 agent 的电影喜好合理性的基础上,1000 个 agent 被投放到逐页推荐场景下,agent 可以选择提前退出推荐系统,或在达到 5 页之后强制退出,同时 Agent 对选择观看的电影进行 1 到 5 分的评分。下图实验结果表明 agent 的评分与真实数据中的用户评分呈现分布一致性。

为了验证 Agent4Rec 作为 A/B 测试平台的可能性,作者将 5 个常见的推荐策略部署到 Agent4Rec 平台,收集 agent 反馈(平均观影比例、平均喜爱数、平均喜爱比例、平均退出页数、用户平均满意度)。下表结果表明,基于算法的推荐系统(MF、MultVAE、LightGCN)表现大幅优于基于策略的推荐系统(Random、Pop)。且总体而言,LightGCN 的表现优于其他算法。这一结果证明,agent 能对不同的推荐系统的推荐结果进行分辨。在未来,一个精心设计的基于大语言模型的推荐系统模拟器或许能够充当理想的离线 A/B 测试平台,并给出符合企业需求的用户评价指标。

仿照现实生活中推荐系统会根据用户反馈进行更新的场景,作者在完成一轮推荐之后,将 agent 选择的高分电影或未观看的电影以正样本加入训练集,重新训练推荐系统,并将重新训练的推荐算法再次部署到 Agent4Rec 平台。结果表明,将 agent 选择的高分电影对推荐系统进行再训练,在离线指标与模拟的 “在线” 指标上均得到了提升。而将 agent 不喜欢的电影作为数据增强则在大多数情况下起到了负向的效果。这从侧面说明 agent 的行为与真实用户行为对齐。

同时,推荐系统中的真实用户往往具有不同的特质,如活跃性、从众性和观影多样性等。作者根据数据集中用户的不同统计信息,将 agent 在每个特质上分为 3 组并给出不同的用户画像。在模拟完成后,收集 agent 的交互次数、agent 评分与用户历史评分的均方误差、agent 交互电影种类数这三个指标,作为 agent 活跃性、从众性、观影多样性特质衡量。实验结果表明,在三个组间 agent 的平均表现符合预期,存在显著差异。     

在个体层面,agent 的表现也与真实用户呈现一致性。以下图中的用户观影多样性为例,每个用户的真实观影种类数与 Agent4Rec 中的 agent 所观看的电影种类数呈现一致趋势。

作者还通过消融实验研究了不同特质初始化对 agent 行为起到的作用。下述实验结果表明,没有个性化的特质初始化,agent 的行为呈现趋同,与现实生活中的真实用户行为长尾分布有别。

探索推荐系统中尚未解决的问题

获得一个真实的推荐系统模拟器,将极大地帮助推荐研究工作的推进。鉴于 Agent4Rec 对用户较大程度的真实行为模拟,作者探索了两个有意思的待解决问题。

首先,作者利用 Agent4Rec 平台收集多维度推荐数据,探究推荐系统中潜在的因果关系。作者选取 5 个推荐系统中常见的变量:电影质量、电影流行度、电影曝光率、电影浏览量、电影评分,通过 DirectLiNGAM 建模一个带权有向无环因果图,分析这 5 个变量间的因果关系。下述因果图的左半部分说明,电影评分只受电影质量与电影流行度的正向影响。因果图的右半部分说明电影的质量和流行度将共同影响电影的曝光率,进而影响电影被点击次数。这反映了推荐系统中的的流行度偏差效应:更流行的物品被更多曝光,进一步导致物品流行度环路放大效应。

作者还进一步探究了推荐系统中信息茧房问题。作者不断将 agent 选择的物品作为正样本加入训练集,训练新的推荐算法并收集 agent 反馈。随着模拟与重新训练的轮数增多,推荐系统对个体用户推荐的第一大类电影的比例逐渐上升,且推荐系统对个体用户推荐的平均电影种类数下降。这一现象表明,用户接受的信息种类将在推荐算法的干预下越来越单一。

总结与展望

本篇工作探索了基于大语言模型的智能体(Agent)模拟真实推荐场景下用户行为的可能性。尽管大语言模型仍存在诸如幻觉在内的种种问题,但 Agent4Rec 上的多智能体仍在多个方面展现出了和真实用户群体一致的行为。期待在未来,一个精心设计的基于大语言模型的 agent 平台,能够足够真实地模拟推荐场景的各个维度,为学术界和工业界的研究提供更多便利。

.....

#EAGLE

大语言模型(LLM)被越来越多应用于各种领域。然而,它们的文本生成过程既昂贵又缓慢。这种低效率归因于自回归解码的运算规则:每个词(token)的生成都需要进行一次前向传播,需要访问数十亿至数千亿参数的 LLM。这导致传统自回归解码的速度较慢。

近日,滑铁卢大学、加拿大向量研究院、北京大学等机构联合发布 EAGLE,旨在提升大语言模型的推理速度,同时保证模型输出文本的分布一致。这种方法外推 LLM 的第二顶层特征向量,能够显著提升生成效率。大模型推理效率无损提升3倍

  • 技术报告:https://sites.google.com/view/eagle-llm
  • 代码(支持商用 Apache 2.0):https://github.com/SafeAILab/EAGLE

EAGLE 具有以下特点:

  • 比普通自回归解码(13B)快 3 倍;
  • 比 Lookahead 解码(13B)快 2 倍;
  • 比 Medusa 解码(13B)快 1.6 倍;
  • 可以证明在生成文本的分布上与普通解码保持一致;
  • 可以在 RTX 3090 上进行训练(1-2 天内)和测试;
  • 可以与 vLLM、DeepSpeed、Mamba、FlashAttention、量化和硬件优化等其他平行技术结合使用。

加速自回归解码的一种方法是投机采样(speculative sampling)。这种技术使用一个更小的草稿模型,通过标准自回归生成来猜测接下来的多个词。随后,原始 LLM 并行验证这些猜测的词(只需要进行一次前向传播进行验证)。如果草稿模型准确预测了 α 词,原始 LLM 的一次前向传播就可以生成 α+1 个词。

在投机采样中,草稿模型的任务是基于当前词序列预测下一个词。使用一个参数数量显著更少的模型完成这个任务极具挑战性,通常会产生次优结果。此外,标准投机采样方法中的草稿模型独立预测下一个词而不利用原始 LLM 提取的丰富语义信息,导致潜在的效率低下。

这个局限启发了 EAGLE 的开发。EAGLE 利用原始 LLM 提取的上下文特征(即模型第二顶层输出的特征向量)。EAGLE 建立在以下第一性原理之上:

特征向量序列是可压缩的,所以根据前面的特征向量预测后续特征向量比较容易。

EAGLE 训练了一个轻量级插件,称为自回归头(Auto-regression Head),与词嵌入层一起,基于当前特征序列从原始模型的第二顶层预测下一个特征。然后使用原始 LLM 的冻结分类头来预测下一个词。特征比词序列包含更多信息,使得回归特征的任务比预测词的任务简单得多。总之,EAGLE 在特征层面上进行外推,使用一个小型自回归头,然后利用冻结的分类头生成预测的词序列。与投机采样、Medusa 和 Lookahead 等类似的工作一致,EAGLE 关注的是每次提示推理的延迟,而不是整体系统吞吐量。

EAGLE——一种增强大语言模型生成效率的方法

上图显示了 EAGLE 与标准投机采样、Medusa 以及 Lookahead 关于输入输出的区别。下图展示了 EAGLE 的工作流程。在原始 LLM 的前向过程中,EAGLE 从第二顶层收集特征。自回归头以这些特征以及此前生成的词的词嵌入作为输入,开始猜下一个词。随后,使用冻结的分类头(LM Head)确定下一个词的分布,使 EAGLE 能够从这个分布中进行采样。通过多次重复采样,EAGLE 进行了类似树状的生成过程,如下图右侧所示。在这个例子中,EAGLE 的三次前向传播 “猜” 出了 10 个词组成的树。

EAGLE 使用轻量级的自回归头来预测原始 LLM 的特征。为了确保生成文本分布的一致性,EAGLE 随后验证预测的树状结构。这个验证过程可以使用一次前向传播完成。通过这个预测和验证的循环,EAGLE 能够快速生成文本词。

训练自回归头代价很小。EAGLE 使用 ShareGPT 数据集进行训练,该数据集包含不到 70,000 轮对话。自回归头的可训练参数数量也很少。如上图中的蓝色部分所示,大多数组件都是冻结的。唯一要额外训练的是自回归头,这是一个单层 Transformer 结构,具有 0.24B-0.99B 参数。即使是 GPU 资源不足的情况下,也可以训练自回归头。例如,Vicuna 33B 的自回归头可以在 8 卡 RTX 3090 服务器上在 24 小时内完成训练。

为什么使用词嵌入来预测特征?

Medusa 仅使用第二顶层的特征来预测下一个词,下下个词......与 Medusa 不同,EAGLE 还动态地将当前采样得到的词嵌入作为自回归头输入的一部分来进行预测。这额外的信息帮助 EAGLE 处理抽样过程中不可避免的随机性。考虑下图中的例子,假设提示词是 “I”。LLM 给出了 “I” 后面跟着 “am” 或 “always” 的概率。Medusa 不考虑是抽样了 “am” 还是 “always”,直接预测 “I” 下下个词的概率。因此,Medusa 的目标是,在只给定 “I” 的基础上,预测 “I am” 或 “I always” 的下一个词。由于抽样过程的随机性,Medusa 的相同输入 “I” 可能有不同的下下个词输出 “ready” 或 “begin”,导致输入和输出之间缺乏一致的映射。相比之下,EAGLE 的输入包括了抽样结果的词嵌入,确保了输入和输出之间的一致映射。这种区别使 EAGLE 能够考虑抽样过程建立的上下文,进而更准确地预测后续词。

树状生成结构

与投机采样、Lookahead 和 Medusa 等其他猜测 - 验证框架不同,EAGLE 在 “猜词” 阶段采用类似树状的生成结构,进而实现了更高的解码效率。如图所示,标准投机采样和 Lookahead 的生成过程是线性或链式的。Medusa 的方法由于在猜测阶段无法构建上下文,故通过笛卡尔积生成树,导致相邻层之间形成全连接图。这种方法经常导致无意义的组合,例如 “I am begin”。对比之下,EAGLE 创建了一个更稀疏的树结构。这种稀疏的树结构防止形成无意义的序列,将计算资源集中在更合理的词组合上。

多轮投机采样

标准投机采样方法在进行 “猜词” 的过程中保持了分布的一致性。为了适应树状猜词场景,EAGLE 将这种方法扩展成了多轮递归形式。下面呈现了多轮投机采样的伪代码。在树状生成过程中,EAGLE 记录了每个抽样词对应的概率。通过多轮投机采样,EAGLE 确保最终生成的每个词的分布与原始 LLM 的分布保持一致。 

更多实验结果

下图展示了 EAGLE 在 Vicuna 33B 上关于不同任务中的加速效果。涉及大量固定模板的 “编程”(coding)任务显示出最佳的加速性能。

代码

​  https://github.com/SafeAILab/EAGLE/issues​

.....

#LLM-FP4

这篇文章给出了大模型 FP 量化的解决方案。

大语言模型 (LLM) 压缩一直备受关注,后训练量化(Post-training Quantization) 是其中一种常用算法,但是现有 PTQ 方法大多数都是 integer 量化,且当比特数低于 8 时,量化后模型的准确率会下降非常多。想较于 Integer (INT) 量化,Floating Point (FP) 量化能更好的表示长尾分布,因而越来越多的硬件平台开始支持 FP 量化。而这篇文章给出了大模型 FP 量化的解决方案。文章发表在 EMNLP 2023 上。解决LLaMA、BERT等部署难题:首个4-bit浮点量化LLM来了

  • 论文地址:https://arxiv.org/abs/2310.16836
  • 代码地址:https://github.com/nbasyl/LLM-FP4

要了解本文,必须要先具备基本的有关 Floating Point Format 以及 Floating Point Quantization 的知识,首先 Floating Point Number 可以用以下公式表示:

​编辑

s 代表正负符号位 (sign bit),m 代表尾数位 (mantissa bits),e 代表指数位 (exponent bits)。p 是一个介于 0 到 2^e - 1 之间的值,用来表示当前数字该被划分到哪一个指数区间,d 取 0 或 1 的值,用来表示第 i 个 mantissa bit。b 是 bias,一个用来调整 exponent 区间的整数值。

接下来介绍 Floating Point Quantization 是怎么运作的,首先输入值必须经过一个 scale and clip 的步骤,先把 input clip 到 Floating Point 能表示的最大区间 (±Qmax),如以下公式所示:

​编辑

可以看到类似于 integer 量化,FP 量化也会加入一个 full-precision 的缩放因子 (scaling factor) 来缩放 input 到合适的区间。而缩放因子在运算矩阵乘法的时候,和低比特的矩阵乘法分开计算,所以并不会造成很大的 overhead。融入了这个 full-precision 的缩放因子之后,不同的 quantized tensor 能够被相应地 clip 到不同的最大最小值区间。在实际使用过程中,会根据输入 tensor 的值域确定需要的量化区间,然后利用公式 (4) 推导出相对应的 bias。注意公式 (4) 里的 bias 可以被用作实数值的缩放因子,见公式 (2)(3)。

Floating-Point Quantization 的下一个步骤是在决定好量化区间后把区间内的值分配到相对应的量化区间内,这个步骤称为 Compare and Quantize:

​编辑

上图直观说明了量化的过程,当前的输入值,在用公式 5 比较过后,量化到不同的量化区间中。

在得到量化过的 activation 和 weight 后,这里的 scaling factor 提到前面先计算,而达到如下的 efficient matrix multiplication,完成矩阵乘法的加速:

​编辑

接着本文指出 FP 量化的准确度,和 exponent bits 的设定以及量化的区间息息相关。

如下图所示,不同的 FP format (浮点数的指数位 / 尾数位设定) 之间存在巨大的量化误差差异,只有当选取合适的 FP format 时,FP Quantization 比 INT Quantization 能更好的表示长尾分布。这个现象也在之前的论文中得到验证 [1]。

​编辑

而这篇文章提出了对应的解决方案,用一个 search-based 浮点量化算法,统筹搜索出最适合的浮点数的指数位 / 尾数位设定以及对应的量化区间。

除此之外,另一个同时出现在各种不同类别 Transformer 模型 (Bert,LLaMA,ViT) 中的现象也会严重影响量化的难度:那就是模型的 activation 中不同 channel 之间的数量级会有很高的差异,而同 channel 之间的量级十分一致。之前 LLM.int8 [2] 和 SmoothQuant [3] 也有类似的发现,不过这篇文章指出这个现象不仅仅存在于 LLM 中,并且在其他 Transformer 模型里也有类似现象 如下如所示,LLaMA 与 BERT 以及 DeIT-S 中的 activation 的分布都发现了类似的现象:

​编辑

从图中可以看到,那些异常大的 channel 都比剩余的 channel 大很多,所以在量化 activation tensor 的过程中,量化的精度很大程度会被这些异常值决定,从而抑制其他 channel 值的量化区间,最终降低整体影响量化精度。这会导致量化的最终结果崩坏,尤其当比特数降到一定程度的时候。值得注意的是,只有 tensor-wise 和 token-wise 量化可以在 efficient matrix multipilication 的时候将 scaling factor 提取出来,而 channel-wise 量化是不支持 efficient matrix multipilication 的,见下图。

​编辑

为了解决这个问题,同时维持高效率矩阵乘法 (Efficient Matrix Multiplication),本文利用少量的校正资料集,预先算出 activation 的每个 channel 的最大值,从而计算缩放因子。然后将这个缩放因子一拆为二,拆解成一个 per-tensor 的实数乘以 per-channel 的 2 的幂。而这个 2 的整数次方即用 FP 里的 exponent bias 表示。完整的过程可以用以下公式表示:

​编辑

进一步地,在 calibration 完成之后,这个 per-channel exponent bias 就不再变化,因此可以和 weight quantization 一起进行预计算 (pre-compute),将这个 per-channel exponent bias 整合进量化后的 weights 中,提高量化精度。完整的过程如以下公式:

​编辑

可以看到在 pre-shifted 后,原本 activation 中的 full-precision per-channel biases 的位置变成了一个 tensor-wise 的实数 scaling factor ,而被拆解出来的整数 per-channel biases 被移到了 weight 中原本 integer bias 的位置,如公式 4。     

从而这个方法 (pre-shifted exponent bias) 能在维持 efficient matrix multiplication 的原则下,更好得提高量化精度,方法的直观展示如下图所示:

​编辑

最后本文展示 Floating Point Quantization (FPQ) 方法,在 LLaMA, BERT 以及 ViTs 模型上,4-bit 量化皆取得了远超 SOTA 的结果。特别是,这篇文章展示了 4-bit 量化的 LLaMA-13B 模型,在零样本推理任务上达到平均 63.1 的分数,只比完整精度模型低了 5.8 分,且比之前的 SOTA 方法平滑量高出了 12.7,这是目前少数已知可行的 4-bit 量化方案了。

​编辑

.....

#LLM~端侧方案

端侧LLM毫无疑问会成为各手机厂商在2024年的主战场。从国内各手机厂透露的信息来看,大家几乎都把希望寄托在了芯片厂身上,自身能做的、会做的工作太少。希望苹果的工作对国内厂商们有启发、借鉴意义。

论文链接:LLM in a flash: Efficient Large Language Model Inference with Limited Memory

1. Flash Memory and DRAM

在移动端设备中(如手机),DRAM可理解为“运行时内存”,Flash Memory可理解为“存储空间”。做一个简单的类比,在PC中,DRAM对应于内存;Flash Memory对应于硬盘存储(注意:仅仅是对应于,实现方案并不一样)。

在通常的LLM推理阶段,LLM都是直接加载到DRAM中的。一个7B半精度LLM,完全加载进DRAM所需的存储空间超过14GB。考虑到目前主流手机的DRAM最高也就16GB的水平,在端侧直接使用DRAM来加载7B LLM面临巨大挑战。

图1给出了一个移动端标准的存储结构示意图。

​编辑

图1 移动端存储结构示意图

Flash Memory的特点是大存储,低带宽。也就是说,Flash Memory可存储的内容多(图中的100GB),但数据传输速率低(图中的1GB/s)。而DRAM的特点是小存储,高带宽。

现在的问题是:模型大小 > DRAM,所以无法将模型全部加载进DRAM。

苹果的解决方案是将LLM放在Flash Memory中,在每次需要进行推理时,仅仅将部分必要参数加载到DRAM中

苹果的整个方案重点解决两个问题:

  • 如何快速识别出哪些模型参数是必要的
  • 考虑到由Flash memory到DRAM的带宽较低,如何加快由Flash memory到DRAM的传输效率
    论文中从三个不同方面做了尝试,下面分别介绍。

2. 减少数据传输量

这部分介绍论文中采用的三种降低数据传输量的方法。

2.1 方法一:Selective Persistence Strategy

对于常见的LLM而言,它的模型参数主要由Attention参数MLP参数两部分构成,其中Attention参数占比约为1/3,MLP参数占比约为2/3。除此,还有参数量级可忽略不计的Embedding层的参数。

因为Attention参数量相对较少,所以苹果的方案是将Attention参数和Embedding层的参数直接加载到DRAM中。

这就是所谓的Selective Persistence Strategy,其意为:有选择性地把部分参数常驻在DRAM中。而这部分常驻的参数就是Attention参数和Embedding参数。原因是因为它们占比较小。

2.2 方法二:Anticipating ReLU Sparsity

这里主要借鉴了DejaVu的思路:MLP层的输出只有不到10%的值是激活状态(不为0)。一般把这种现象称为稀疏性。稀疏性越强,则非激活状态的值就越多。

所以我们也可把这句话“MLP层的输出只有不到10%的值是激活状态”简写作“MLP层的稀疏性超过90%”。

要注意,此处的稀疏性一般称为“Contextual Sparsity”。也就是说,MLP层的哪些神经元会激活,与当前的输入相关

苹果照搬了DejaVu的方法,使用一个两层MLP来预测哪些神经元会激活。方法也很简单,假设神经元个数为4096,只需要将MLP的输出层的大小设为4096,并为每一个输出使用sigmoid来做一个二分类即可(“选择”or“不选择”)。

注意1:不同Transformer层使用的预测模型不同。

注意2:同一个Transformer层中的MLP一般有两层。它们的激活神经元始终保持相同。

在能够准确预测的前提下,每次在推理时动态加载预测为激活神经元对应的参数即可。

这里有对DejaVu详细介绍的文章:[ICML'23] DejaVu:LLM中的动态剪枝

2.3 方法三:Sliding Window

根据2.2小节中介绍的稀疏性可知,在每一次LLM进行前向推理时,它都需要使用模型预测每一个MLP层中激活神经元的编号,并将所需的神经元所对应的权重由Flash memory加载到DRAM中。

因为LLM的推理阶段是逐token进行的,这意味着在生成不同token的时候,需要加载到DRAM中的MLP的参数也不同。

​编辑

这就是Sliding Window的核心思想:保留处理过去k个token时的激活神经元所对应的参数在DRAM中,并在处理当前token时只对:1)部分多余的参数进行删除;2)缺少的参数进行加载。图2是原文中的示意图。

​编辑

图2 Sliding Window示意图

图中上图表示在处理当前token “Was”之前,前5个token(k=5)的激活神经元(淡蓝色偏绿部分)。图中下图表示在处理当前token “Was”之时,需要新加入的神经元(蓝色部分)和需要删除的神经元(分红部分)。

Sliding Window的核心假设是LLM在处理相邻token时产生的稀疏性具有相似性。原文没有仔细分析和论证这个假设。

3 提高传输吞吐量

3.1 Bundling Columns and Rows

通常LLM中的MLP层包含两个全连层。在忽略激活函数的情况下,这两个全连层可以写为:

​编辑

图3 将两个全连层的列与行拼接存储

​编辑

这样做的好处是原本需要两次I/O,现在只需要一次了。虽然总的数据读取量并没有变,但读取大块、连续的数据会比读取大量小块、非连续数据更加高效,因此整体传输吞吐量提升了。

3.2 Bundling Based on Co-activation

这是一个原文尝试过,但被验证为无效的策略。既然原文提到了,所以这里也罗列出来。

原文中猜测某些神经元之间可能存在一些紧密联系。比如对于两个神经元a和b,当a激活时,b也会激活(或者当b激活时,a也会激活)。

因此可以通过分析来找到每个神经元的“closest friend”(与该神经元同时激活频率最高的其它某个神经元)。然后在存储Flash memory中存储时,也将它们的参数拼接存在一起。这样的读取效率更高。

但该方法之所以无效,主要原因是可能会存在某个神经元i,它是其它很多神经元的“closest friend”。这样导致的问题则是神经元i被额外传输了太多次,导致实际的I/O成本增加了。

4 Optimized Data Management in DRAM

虽然DRAM的数据读取速度比Flash memory快很多,但当需要对其中数据进行大量、高频读写时,它的时耗仍然不可忽略。在本文介绍的内容中,对DRAM的读写主要发生在对MLP层中所需神经元对应参数的删除与新增(参考图2)。

为此,论文中设计了一种特殊的数据结构来对DRAM中的数据做精细化管理。该数据结构的核心变量如下:

  • Matrix:按照“Bundling Columns and Rows”的方法存储激活神经元所对应的参数
  • bias:激活神经元所对应的bias参数
  • num_used:激活神经元的个数
  • last_k_active:过去k个token所对应的激活神经元编号
  • Pointer:当前行参数对应的神经元编号

​编辑

图4 Optimized Data Management in DRAM

​编辑

通过预分配一个足够大的空间,可以避免因反复分配而导致的额外开销。下面来说明基于该数据结构的一些操作的高效实现方法。

该矩阵中的行对应的是当前存储在DRAM中激活神经元的参数。前文提到(2.3小节),当处理新的token时,需要将不会被激活的神经元删除,并添加新的会被激活的神经元。所以最重要的两个操作是“删除”和“新增”。

当需要删除某个神经元时(如图4中左图标红部分,对应的是编号为10的神经元),只需将num_rows的数量减1,并将最后一行Copy至被删除行,结果如图4的中图所示。虚线框表示当前有效的数据。

当需要“新增”时,直接将其对应的参数由Flash memory中copy至该矩阵中即可,无需额外分配存储空间。

5. 实验结果

苹果这篇paper的主要关注点在于:让LLM在运行时内存受限的情况下能高效地跑起来。所以论文的实验主要对比了各种情况下I/O导致的时耗,如下图所示。

​编辑

图5 实验结果

图5中的实验使用的是OPT 6.7B模型,半精度。表中第一行和第二行都是基准baseline。第一行假设初始模型全部在Flash memory中,那么为了完成一次完整的推理,需要将模型全部加载一遍,整个I/O耗时2130ms。

第二行对应于假设模型有一半参数提前在DRAM中的整个加载耗时。

第三行到第五行对应于应用了Predictor(2.2小节)、Windowing(2.3小节)和Bundling(3.1小节)后对应的耗时。

效率提升非常明显。

.....

#LLM推理MLSys优化

近日,CMU Catalyst 团队推出了一篇关于高效 LLM 推理的综述,覆盖了 300 余篇相关论文,从 MLSys 的研究视角介绍了算法创新和系统优化两个方面的相关进展。

在人工智能(AI)的快速发展背景下,大语言模型(LLMs)凭借其在语言相关任务上的杰出表现,已成为 AI 领域的重要推动力。然而,随着这些模型在各种应用中的普及,它们的复杂性和规模也为其部署和服务带来了前所未有的挑战。LLM 部署和服务面临着密集的计算强度和巨大的内存消耗,特别是在要求低延迟和高吞吐量的场景中,如何提高 LLM 服务效率,降低其部署成本,已经成为了当前 AI 和系统领域亟需解决的问题。

来自卡内基梅隆大学的 Catalyst 团队在他们的最新综述论文中,从机器学习系统(MLSys)的研究视角出发,详细分析了从前沿的 LLM 推理算法系统的革命性变革,以应对这些挑战。该综述旨在提供对高效 LLM 服务的当前状态和未来方向的全面理解,为研究者和实践者提供了宝贵的洞见,帮助他们克服有效 LLM 部署的障碍,从而重塑 AI 的未来。

论文链接:https://arxiv.org/abs/2312.15234

该论文的第一作者是卡内基梅隆大学的 Xupeng Miao(苗旭鹏)博士后研究员,合作者还包括 Tianqi Chen 和 Zhihao Jia 助理教授。此外,其他学生作者也均来自于 CMU Catalyst Group 实验室,该实验室由 Zhihao Jia 与 Tianqi Chen(陈天奇)在 CMU 共同主持,致力于集成来自于机器学习算法、系统、硬件等多方面的优化技术,构造自动化的机器学习系统。此前,该实验室还推出了 SpecInfer, MLC-LLM, SpotServe [ASPLOS‘24] 等开源项目,推进 LLM 大模型相关系统的研究和应用。实验室主页:https://catalyst.cs.cmu.edu。

综述概览

该综述系统地审视了现有 LLM 推理技术,覆盖了 300 余篇相关论文,从算法创新系统优化两个方面展开介绍。论文以此为基础,对现有工作设计了一套清晰且详尽的分类法,突出了各种方法的优势和局限性,逐类别搜集整理并介绍了每种方法的相关论文。除此之外,论文还对当前的主流 LLM 推理框架在系统设计与实现方面进行了深入的对比和分析。最后,作者对未来如何继续提高 LLM 推理效率进行了展望,在技术层面提出了六大潜在发展方向

分类法

​编辑

算法创新

这一节对提出的各种算法和技术进行了全面分析,旨在改进大规模 Transformer 模型推理的原生性能缺陷,包括解码算法架构设计、和模型压缩等等。

​编辑

解码算法:在这一部分中,我们回顾了在图 2 中展示的几种 LLMs 推理优化过程的新颖解码算法。这些算法旨在减少计算复杂度,并提高语言模型推理在生成任务中的总体效率,包括:

  • 非自回归解码:现有 LLMs 的一个主要限制是默认的自回归解码机制,它逐个顺序生成输出 token。为解决这一问题,一种代表性的工作方向是非自回归解码 [97, 104, 108,271],即放弃自回归生成范式,打破单词依赖并假设一定程度的条件独立性,并行解码输出 token。然而,尽管这类方法解码速度有所提高,但大多数非自回归方法的输出质量仍不如自回归方法可靠。
  • 投机式推理:另一类工作是通过投机执行思想 [47] 实现并行解码。自回归 LLM 推理过程中的每个解码步骤都可以被视为带有条件分支的程序执行语句,即决定接下来生成哪个 token。投机式推理 [51, 155] 先使用较小的草稿模型进行多步解码预测,然后让 LLM 同时验证这些预测以实现加速。然而,将投机解码应用于 LLMs 时仍然存在一些实际挑战,例如,如何使解码预测足够轻量且准确,以及如何借助 LLMs 实现高效的并行验证。SpecInfer [177] 首次引入基于 tree-based speculative decoding 和 tree attention,并提出了一个低延迟 LLM 服务系统实现,该机制也被后续多个工作 [48, 118, 168, 185, 229, 236, 274, 310] 直接采用。
  • 提前退出:这类方法主要利用 LLMs 的深层多层结构,在中间层提前推出推理,中间层输出可以通过分类器转化成输出的 token,从而降低推理开销 [117, 147, 163, 167, 234, 272, 282, 291, 308],它们也被称为自适应计算 [68, 219]。
  • 级联推理:这类方法级联了多个不同规模的 LLM 模型,用于分别处理不同复杂度的推理请求,代表性工作包括 CascadeBERT [157] 和 FrugalGPT [53]。

架构设计:

  • 配置缩小:直接缩小模型配置。
  • 注意力简化:最近出现了很多研究工作,它们主要是将之前的长序列高效注意力机制 [240] 应用在 LLM 上,以缩短上下文,减少 KV 缓存,以及注意力复杂度,同时略微降低解码质量(如滑动窗口 [129, 299]、哈希 [198]、dilated [74]、动态选择等等)。表 1 中总结了一些近期的热门方法和之前的工作之间的对应关系。

​编辑

  • 激活共享:这类方法主要是通过共享 attention 计算的中间激活来降低推理内存开销,代表性工作包括 MQA [220] 和 GQA [32]。
  • 条件计算:这类方法主要是指稀疏专家混合模型(Sparse MoE),比如最近大火的 Mistrial 7Bx8 模型就属于此类。
  • 循环单元:尽管 Transformer 已经替代了 RNN 模型,但考虑到注意力机制的二次复杂性,人们始终未曾放弃将 recurrent unit 机制重新引入 LLM 的尝试,比如 RWKV [200]、RetNet [235],以及状态空间模型 [91, 102, 103, 176] 等等。 

模型压缩:

  • 知识蒸馏:这类方法以大型的教师模型为监督,训练一个小型的学生模型。大多数之前的方法都在探索白盒蒸馏 [106, 133, 214, 233, 255],需要访问整个教师模型的参数。由于基于 API 的 LLM 服务(如 ChatGPT)的出现,一些黑盒蒸馏模型吸引了很多关注 [238,59, 273, 201, 313],这些模型通常具有更少的模型参数,与原始 LLMs(如 GPT-4 [195])相比,在各种下游任务上表现出了相当的性能。
  • 网络剪枝:过去几年中,网络剪枝方法 [180, 215, 215] 已被广泛研究,但并非所有方法都可以直接应用于 LLMs,需要考虑重新训练可能带来的过高计算成本,以及评估剪枝是否可以在底层系统实现上取得效率提升。大致上可以分为结构化剪枝 [80, 149, 174, 216, 172] 和半结构化稀疏化 [40, 87, 232, 251, 276] 等。

系统优化

本节研究 LLM 推理系统优化技术,以加速 LLM 推理,而不改变 LLM 计算语义。这一工作的目标是通过改进用于大型语言模型推理的底层系统和框架来提高系统效率,包括低比特量化、并行计算、内存管理、请求调度、和内核优化等等,详细内容可以参见论文原文。

软件框架

​编辑

论文还对一些目前最先进的基于 GPU 的开源 LLM 推理系统进行了深入的分析,并从多个方面总结了它们在设计与实现伤的差异。

未来方向

  • 专用硬件加速器的发展:生成型 LLM 服务效率的显著提升可能在很大程度上依赖于专用硬件加速器的发展和提升,尤其是软硬协同设计方法。例如,让内存单元更加接近处理单元,或是针对 LLM 算法数据流优化芯片架构,这些硬件优化可以在很大程度上为 LLM 推理在软件层面带来便利和机会。
  • 高效有效的解码算法:开发更高效的解码算法可以显著提高服务效率。受对实时应用更快生成速度的需求驱动,一个有前途的方向是广义的投机式推理(generalized speculative inference),不仅会带来显著加速,同时保持相同的生成质量。正如 SpecInfer 中所指出的,广义的投机式推理中,用于生成草稿 token 的小模型可以被替换为任何快速的 token 生成方法,比如自定义函数、召回方法、甚至早停机制和非自回归解码等等。
  • 长上下文 / 序列场景优化:随着应用场景变得更加复杂,处理更长的上下文或序列的需求不断增长。服务长序列负载的 LLM 需要解决算法和系统两方面的挑战。在算法方面,它们依然面临长度泛化失效问题,甚至可能出现 “loss in the middle” 的情况。目前的解法主要是通过召回增强、序列压缩和缓存来尽可能缩短序列长度并保存相关信息。
  • 探索替代基础架构:尽管 Transformer 模型和自注意力机制目前主导着 LLM 领域,但探索替代架构是未来研究的一个有前景的方向。例如,一些最新研究探索了无注意力方法,使用纯 MLP(多层感知机)架构来替代注意力机制,可能会改变目前 LLM 推理优化的格局。
  • 在复杂环境中的部署探索:随着 LLM 应用的扩展,探索并优化它们在各种复杂环境中的部署成为一个关键的未来方向。这一探索不仅限于传统的基于云的部署,还包括边缘计算、混合计算(cloud+edge)、去中心化计算以及廉价的可抢占资源等。
  • 特定需求的自动适应:应用特定需求的多样性创造了一系列创新的 LLM 服务优化机会,例如模型微调(parameter-efficient fine-tuning)、向量数据库检索、多模态负载等等。这些独特的挑战也要求将 LLM 服务技术自动且顺利地集成到现有 IT 基础设施中,将优化空间扩展到整个 LLM 生命周期。

总结

总的来说,该综述不仅是对当前 LLM 服务优化研究的全面概述,也为未来在这一领域的探索和发展指明了方向。通过深入了解这些先进的解决方案,研究者和实践者可以更好地理解和应对在实际应用中部署大型语言模型时面临的挑战。

.....

#大模型LLM~技术精要

抛开是否有财力做超大型LLM这个因素,如果单从技术角度看,国内外的差距主要来自于对LLM的认知以及未来应往何处去的发展理念的不同。

ChatGPT出现后惊喜或惊醒了很多人。惊喜是因为没想到大型语言模型(LLM,Large Language Model)效果能好成这样;惊醒是顿悟到我们对LLM的认知及发展理念,距离世界最先进的想法,差得有点远。我属于既惊喜又惊醒的那一批,也是典型的中国人,中国人善于自我反思,于是开始反思,而这篇文章正是反思的结果。

实话实说,国内在LLM模型相关技术方面,此刻,距离最先进技术的差距进一步加大了。技术领先或技术差距这事情,我觉得要动态地以发展的眼光来看。在Bert出现之后的一到两年间,其实国内在这块的技术追赶速度还是很快的,也提出了一些很好的改进模型,差距拉开的分水岭应该是在 GPT 3.0出来之后,也就是2020年年中左右。在当时,其实只有很少的人觉察到:GPT 3.0它不仅仅是一项具体的技术,其实体现的是LLM应该往何处去的一个发展理念。自此之后,差距拉得越来越远,ChatGPT只是这种发展理念差异的一个自然结果。所以,我个人认为,抛开是否有财力做超大型LLM这个因素,如果单从技术角度看,差距主要来自于对LLM的认知以及未来应往何处去的发展理念的不同。

国内被国外技术甩得越来越远,这个是事实,不承认也不行。前阵子网上很多人担忧说国内AI现在处于“危急存亡之秋”,我觉得倒也不至于这么严重。君不见,这个世界上,具备这么超前眼光的只有OpenAI一家吗?包括Google在内,其实对于LLM发展理念的理解,明显都落后OpenAI一个身位。现实是OpenAI表现过于优秀,把所有人都甩开了,不仅仅是国内。

我觉得,OpenAI对LLM在理念及相关技术方面,领先国外的Google、DeepMind大约半年到一年的时间,领先国内大概两年左右的时间。在LLM这个事情上,感觉梯队很明显,Google应该是排在第二位,最能体现Google技术眼光的是PaLM和Pathways,推出时间大概在22年2月到4月间,同一时期,OpenAI推出的却是InstructGPT,从这里就可以看出Google和OpenAI的差距了,至于为何这么说,你看了我后面的正文后大概能理解。DeepMind之前的重心一直在强化学习攻克游戏和AI for science这些方面,切入LLM其实很晚,应该是21年才开始重视这个方向,目前也处于追赶状态。Meta就更不用说了,重心一直不在LLM上,目前感觉也发力开始追赶。这还是目前做得最好的一批机构,尚且如此,更何况国内呢?我觉得情有可原。至于OpenAI关于LLM的理念是什么,我在本文的最后一部分,会谈谈我的认知。

本文梳理自GPT 3.0出现之后的主流LLM技术,在此之前的主流技术可以参考:

​https://zhuanlan.zhihu.com/p/254821426​

我相信看完这两篇文章,能够让您对LLM领域的技术脉络,LLM技术发展过程中出现过的不同发展理念,乃至未来可能的发展趋势,有比较清晰的认知。当然,很多地方讲的内容是我个人看法,有很大的主观性,错漏难免,所以还请谨慎参考。

本文试图回答下面一些问题:ChatGPT是否带来了NLP乃至AI领域的研究范式转换?如果是,那会带来怎样的影响?LLM从海量数据中学到了什么知识?LLM又是如何存取这些知识的?随着LLM规模逐步增大,会带来什么影响?什么是In Context Learning?为什么它是一项很神秘的技术?它和Instruct又是什么关系?LLM具备推理能力吗?思维链CoT又是怎么做的?等等,相信看完,能让您对这些问题有一个答案。

首先,在谈LLM技术现状前,先宏观地谈下我心目中的研究范式转换问题。这样,我们才能“先见森林,再见树木”,对具体技术为何会是如此变化有个更清晰的认知。

潮流之巅:NLP研究范式的转换

如果我们把时间线往前拉得更长一些,回到NLP领域的深度学习时代,在更长时间窗口内观察技术变迁及其影响,可能会更容易看清其中的一些关键节点。我个人认为,在最近10年来NLP领域的技术发展过程中,可能存在两次大的研究范型转换。

范式转换1.0:从深度学习到两阶段预训练模型

这个范式转换所涵盖的时间范围,大致在深度学习引入NLP领域(2013年左右),到GPT 3.0出现之前(2020年5月左右)。

在Bert和GPT模型出现之前,NLP领域流行的技术是深度学习模型,而NLP领域的深度学习,主要依托于以下几项关键技术:以大量的改进LSTM模型及少量的改进CNN模型作为典型的特征抽取器;以Sequence to Sequence(或叫encoder-decoder亦可)+Attention作为各种具体任务典型的总体技术框架。

在这些核心技术加持下,NLP领域深度学习的主要研究目标,如果归纳一下,是如何有效增加模型层深或模型参数容量。就是说,怎么才能往encoder和decoder里不断叠加更深的LSTM或CNN层,来达成增加层深和模型容量的目标。这种努力,尽管确实不断增加了模型层深,但是从解决具体任务的效果角度看,总体而言,不算很成功,或者说和非深度学习方法相对,带来的优势不算大。

深度学习之所以不够成功,我认为主要原因来自于两个方面:一方面是某个具体任务有限的训练数据总量。随着模型容量的增加,需要靠更大量的训练数据来支撑,否则即使你能把深度做起来,任务效果也做不上去。而在预训练模型出现之前,很明显这是NLP研究领域一个严重问题;另外一个方面是LSTM/CNN特征抽取器,表达能力不够强。意思是就算给你再多的数据也没用,因为你不能有效地吸收数据里蕴含的知识。主要应该是这两个原因,阻碍了深度学习在NLP领域的成功突围。

Bert/GPT这两个预训练模型的出现,无论在学术研究角度看,还是工业应用角度来看,都代表了NLP领域的一个技术飞跃,并带来了整个领域研究范式的转换。这种范式转换带来的影响,体现在两个方面:首先,是部分NLP研究子领域的衰退乃至逐步消亡;其次,NLP不同子领域的技术方法和技术框架日趋统一,在Bert出现后一年左右,技术栈基本收敛到两种技术模式中。关于这两点,我们分头来谈。

影响一:中间任务的消亡

NLP是一个宏观研究领域的统称,里面有五花八门具体的子领域与子方向,如果仔细分析,从任务的性质角度,可以把这些任务分成两大类:一类可以叫做“中间任务”,一类可以称为“最终任务”。

典型的中间任务包括:中文分词、词性标注、NER、句法分析、指代消解、语义Parser等,这类任务一般并不解决应用中的实际需求,大多数是作为那些解决实际需求任务的中间阶段或者辅助阶段存在的,比如几乎没有需求说,我要一个句法Parser,把这个句子的句法分析树给用户看看,用户不需要看到这些NLP的中间阶段处理结果,他只关心某个具体任务你有没有干好。“最终任务”包括比如文本分类、文本相似性计算、机器翻译、文本摘要等等,有很多。这类任务的特点是每个子领域都解决某个实际需求,任务结果基本能直接呈现给用户,比如用户确实存在给你一句英文,告诉他中文是什么的需求。

按理说,“中间任务”就不应该出现,而之所以会存在,这是NLP技术发展水平不够高的一种体现。在技术发展早期阶段,因为当时的技术相对落后,很难一步做好有难度的最终任务。比如机器翻译,早期技术要做好机器翻译是很困难的,于是科研人员就把难题分而治之,分解成分词、词性标注、句法分析等各种中间阶段,先把每个中间阶段做好,然后再拼起来完成最终任务,这也是没办法的事情。

但是自从Bert/GPT出现之后,其实就没有必要做这些中间任务了,因为通过大量数据的预训练,Bert/GPT已经把这些中间任务作为语言学特征,吸收到了Transformer的参数里,此时我们完全可以端到端地直接解决那些最终任务,而无须对这种中间过程专门建模。这里可能争议最大的是中文分词,其实道理也是一样的,哪些字应该组成一个词,这个其实你不用管,让LLM自己当特征去学就行了,只要对于解决任务有帮助,它自然会去学该学的合理分词方式,也未必一定要和我们人类理解的分词规则相同。

基于以上认知,其实在Bert/GPT一出现,你就应该得出这类NLP的中间阶段的任务,会逐步退出历史舞台这个结论。

影响二:不同研究方向技术路线的统一

在说明具体影响前,我们先讨论下另外一种NLP任务划分方式,这对于理解后面内容有帮助。如果对“最终任务”进一步进行分类,又大致可以分为两大不同类型的任务:自然语言理解类任务和自然语言生成类任务。如果排除掉“中间任务”的话,典型的自然语言理解类任务包括文本分类、句子关系判断、情感倾向判断等,这种任务本质上都是分类任务,就是说输入一个句子(文章),或者两个句子,模型参考所有输入内容,最后给出属于哪个类别的判断。自然语言生成也包含很多NLP研究子方向,比如聊天机器人、机器翻译、文本摘要、问答系统等。生成类任务的特点是给定输入文本,对应地,模型要生成一串输出文本。这两者的差异主要体现在输入输出形式上

自从Bert/GPT模型诞生后,出现了明显的技术统一趋向。首先,NLP中不同的子领域,其特征抽取器都逐渐从LSTM/CNN统一到Transformer上。其实,自Bert公开后不久,就应该意识到,这必然会成为技术趋势。至于其原因,在几年前我写的这篇:

放弃幻想,全面拥抱Transformer:自然语言处理三大特征抽取器(CNN/RNN/TF)比较

​https://zhuanlan.zhihu.com/p/54743941​

中做了说明和分析,感兴趣的同学可参考。而且,目前Transformer不仅统一了NLP诸多领域,也正在逐步地替换图像处理各种任务中被广泛使用的CNN等其它模型的进程之中,类似的,多模态模型目前也基本都采用了Transformer模型。这种Transformer从NLP出发,攻城略地逐步统一AI越来越多领域的趋势,起始于2020年底出现的Vision Transformer (ViT) ,之后蓬勃发展,到目前已大获成功,且其继续向更多领域拓展的势头会越来越迅猛。

其次,大多数NLP子领域的研发模式切换到了两阶段模式:模型预训练阶段+应用微调(Fine-tuning)或应用Zero/Few Shot Prompt模式。更准确地说,NLP各种任务其实收敛到了两个不同的预训练模型框架里:对于自然语言理解类任务,其技术体系统一到了以Bert为代表的“双向语言模型预训练+应用Fine-tuning”模式;而对于自然语言生成类任务,其技术体系则统一到了以GPT 2.0为代表的“自回归语言模型(即从左到右单向语言模型)+Zero /Few Shot Prompt”模式。至于为何会分化成两条技术路线,有其必然性,关于这点我们放在后面解释。

这两种模式,看似比较相像,但其背后蕴含了迥异的发展思路,也会导向不同的未来发展方向。不过遗憾的是,我们中的绝大多数人,在当时都低估了GPT 这条发展路线的潜力,而把视觉中心聚焦到了Bert这种模式上。

范式转换2.0: 从预训练模型走向通用人工智能 (AGI,Artificial General Intelligence)

这个范式转换所涵盖的时间范围,大致在GPT3.0出现之后(20年6月左右),一直到目前为止,我们应该正处于这个范式转换过程中。

ChatGPT是触发这次范型转换的关键节点,但是在InstructGPT出现之前,其实LLM处于这次范式转换前的一个过渡期。

过渡期:以GPT 3.0为代表的“自回归语言模型+Prompting”模式占据统治地位

前面说过,在预训练模型发展的早期,技术框架收敛到了Bert模式和GPT模式这两种不同的技术范型,而且人们普遍更看好Bert模式一些,相当多数的后续技术改进,都是沿着Bert那条路走的。但是,随着技术的继续发展,你会发现,目前规模最大的LLM模型,几乎清一色都是类似GPT 3.0这种“自回归语言模型+Prompting”模式的,比如GPT 3、PaLM、GLaM、Gopher、Chinchilla、MT-NLG、LaMDA等,没有例外。为什么会这样呢?背后一定有其必然性,我认为可能主要源于两个原因。

​编辑

首先,Google的T5模型,在形式上统一了自然语言理解和自然语言生成任务的外在表现形式。如上图所示,标为红色的是个文本分类问题,黄色的是判断句子相似性的回归或分类问题,这都是典型的自然语言理解问题。在T5模型里,这些自然语言理解问题在输入输出形式上和生成问题保持了一致,也就是说,可以把分类问题转换成让LLM模型生成对应类别的字符串,这样理解和生成任务在表现形式就实现了完全的统一。

这说明自然语言生成任务,在表现形式上可以兼容自然语言理解任务,若反过来,则很难做到这一点。这样的好处是:同一个LLM生成模型,可以解决几乎所有NLP问题。而如果仍然采取Bert模式,则这个LLM模型无法很好处理生成任务。既然这样,我们当然倾向于使用生成模型,这是一个原因。

第二个原因,如果想要以零示例提示语(zero shot prompting)或少数示例提示语(few shot prompting)的方式做好任务,则必须要采取GPT模式。现在已有研究(参考:On the Role of Bidirectionality in Language Model Pre-Training)证明:如果是以fine-tuning方式解决下游任务,Bert模式的效果优于GPT模式;若是以zero shot/few shot prompting这种模式解决下游任务,则GPT模式效果要优于Bert模式。这说明了,生成模型更容易做好zero shot/few shot prompting方式的任务,而Bert模式以这种方式做任务,是天然有劣势的。这是第二个原因。

但是问题来了:为什么我们要追求zero shot/few shot prompting这种方式来做任务呢?要解释清楚这个问题,我们首先需要搞清楚另外一个问题:什么样的LLM模型,对我们是最理想的?

​编辑

上图展示了一个理想的LLM该有的样子。首先,LLM应该具备强大的自主学习能力。假设我们把世界上能获得的所有文本或者图片等不同类型的数据喂给它,它应该能够自动从中学习到里面包含的所有知识点,学习过程不需要人的介入,并且能灵活应用所学知识,来解决实际问题。因为数据是海量的,要吸收所有知识,就要非常多的模型参数来存储知识,所以这个模型必然会是一个巨无霸模型。

其次,LLM应该能解决NLP任何子领域的问题,而不仅支持有限领域,甚至它应该可以响应NLP之外其它领域的问题,最好是任意领域的问题都能得到很好地回答。

再者,当我们使用LLM解决某个具体领域问题的时候,应该用我们人类习惯的表达方式,就是说LLM应该理解人类的命令。这体现出让LLM适配人,而不是反过来,让人去适配LLM模型。人适配LLM的典型例子,比如绞尽脑汁去尝试各种不同的prompt,以试图找到好的提示语,才能很好地解决手头问题。关于这点,上图在人类和LLM交互的接口层,举了几个例子,说明什么是好的人使用LLM模型的接口形式。

看完这个理想中的LLM,我们再回头解释上面遗留的问题:为什么我们要追求zero shot/few shot prompting这种方式来做任务呢?有两个原因。

第一,这个LLM模型规模必然非常巨大,有能力作出这个模型,或改动这个模型参数的机构必然很少。而任务需求方是千千万万的中小机构甚至是个人,就算你把模型开源出来,他们也无力部署这个模型,更不用说再用Fine-tuning这种模式去修改模型参数了。所以,我们应该追求不修正模型参数,就能让任务需求方完成任务的方式,也就是应该采取prompt模式完成任务,而非Fine-tuning模式(由此可看出,soft prompting技术方向是违背这个发展趋势的)。模型制作方则将LLM作成公用服务,以LLM as Service的模式运行。作为服务支持方,考虑到千变万化的用户需求,所以LLM模型制作方更要追求让LLM能完成尽可能多类型的任务,这是附带的影响,也是为何超级大模型一定会追求走向AGI的现实因素。

第二,zero shot prompting也好,few shot prompting也好,甚至促进LLM推理能力的思维链(CoT,Chain of Thought)Prompting也好,就是上图中接口层中的现有技术。具体而言,zero shot prompting的初衷,其实就是人类和LLM的理想接口,直接用人类所习惯的任务表述方式让LLM做事情,但是发现LLM并不能很好地理解,效果也不好。经过继续研究,转而发现:对于某项任务,如果给LLM几个示例,用这些示例来代表任务描述,效果会比zero shot prompting好,于是大家都去研究更好的few shot prompting技术。可以理解为,本来我们希望LLM能够用人类常用的命令方式来执行某个任务,但是目前技术还做不到,所以退而求其次,用这些替代技术来表达人类的任务需求。

如果理解了上述逻辑,很容易得出如下结论:few shot prompting(也被称为In Context Learning)只是一种过渡时期的技术。如果我们能够更自然地去描述一个任务,而且LLM可以理解,那么,我们肯定会毫不犹豫地抛弃这些过渡期的技术,原因很明显,用这些方法来描述任务需求,并不符合人类的使用习惯。

这也是为何我将GPT 3.0+Prompting列为过渡期技术的原因,ChatGPT的出现,改变了这个现状,用Instruct取代了Prompting,由此带来新的技术范式转换,并产生若干后续影响。

影响一:让LLM适配人的新型交互接口

在理想LLM的背景下,我们再来看ChatGPT,能更好理解它的技术贡献。ChatGPT应该是目前所有的现有技术里,最接近理想LLM的技术方法。如果归纳下ChatGPT最突出特点的话,我会用下面八个字:“能力强大,善解人意”。

“能力强大”这一点,我相信应该主要归功于ChatGPT所依托的基础LLM GPT3.5。因为ChatGPT 尽管加入了人工标注数据,但是量级只有数万,这个规模的数据量,和训练GPT 3.5模型使用的几千亿token级别的数据量相比,包含的世界知识(数据中包含的事实与常识)可谓沧海一粟,几可忽略,基本不会对增强GPT 3.5的基础能力发挥什么作用。所以它的强大功能,应该主要来自于隐藏在背后的GPT 3.5。GPT 3.5对标理想LLM模型中的那个巨无霸模型。

那么,ChatGPT向GPT 3.5模型注入新知识了吗?应该是注入了,这些知识就包含在几万人工标注数据里,不过注入的不是世界知识,而是人类偏好知识。所谓“人类偏好”,包含几方面的含义:首先,是人类表达一个任务的习惯说法。比如,人习惯说:“把下面句子从中文翻译成英文”,以此表达一个“机器翻译”的需求,但是LLM又不是人,它怎么会理解这句话到底是什么意思呢?你得想办法让LLM理解这句命令的含义,并正确执行。所以,ChatGPT通过人工标注数据,向GPT 3.5注入了这类知识,方便LLM理解人的命令,这是它“善解人意”的关键。其次,对于什么是好的回答,什么是不好的回答,人类有自己的标准,例如比较详细的回答是好的,带有歧视内容的回答是不好的,诸如此类。这是人类自身对回答质量好坏的偏好。人通过Reward Model反馈给LLM的数据里,包含这类信息。总体而言,ChatGPT把人类偏好知识注入GPT 3.5,以此来获得一个听得懂人话、也比较礼貌的LLM。

可以看出,ChatGPT的最大贡献在于:基本实现了理想LLM的接口层,让LLM适配人的习惯命令表达方式,而不是反过来让人去适配LLM,绞尽脑汁地想出一个能Work的命令(这就是instruct技术出来之前,prompt技术在做的事情),而这增加了LLM的易用性和用户体验。是InstructGPT/ChatGPT首先意识到这个问题,并给出了很好的解决方案,这也是它最大的技术贡献。相对之前的few shot prompting,它是一种更符合人类表达习惯的人和LLM进行交互的人机接口技术。

而这必将启发后续的LLM模型,继续在易用人机接口方面做进一步的工作,让LLM更听话。

影响二:很多NLP子领域不再具备独立研究价值

就NLP领域而言,这次范式转换,意味着很多目前独立存在的NLP研究领域,将被纳入LLM的技术体系,进而不再独立存在,逐步消失。经过第一次范式转换,尽管NLP中很多“中间任务”,继续作为独立研究领域存在不再必要,但是大多数“最终任务”,仍然是以独立研究领域存在的,只是切换成在“预训练+fine-tuning”框架下,面对领域独有问题,陆续提出新的改进方案。

目前研究表明,很多NLP任务,随着LLM模型规模增长,效果会大幅提升。据此,我觉得可得到如下推论:大多数某领域所谓“独有”的问题,大概率只是缺乏领域知识导致的一种外在表象,只要领域知识足够多,这个所谓领域独有的问题,就可以被很好地解决掉,其实并不需要专门针对某个具体领域问题,冥思苦想去提出专用解决方案。也许AGI的真相超乎意料地简单:你只要把这个领域更多的数据交给LLM,让它自己学习更多知识即可。

在这个背景下,同时,ChatGPT证明了我们现在是可以直接去追求理想LLM模型的,那么,未来的技术发展趋势应该是:追求规模越来越大的LLM模型,通过增加预训练数据的多样性,来涵盖越来越多的领域,LLM自主从领域数据中通过预训练过程学习领域知识,随着模型规模不断增大,很多问题随之得到解决。研究重心会投入到如何构建这个理想LLM模型,而非去解决某个领域的具体问题。这样,越来越多NLP的子领域会被纳入LLM的技术体系,进而逐步消失。

我认为,判断某个具体领域是否该立即停止独立研究,其判断标准可采取以下两种方法,占其一即可:第一,判断某个任务,是否LLM的研究效果超过人类表现,对于那些LLM效果超过人类的研究领域,已无独立研究的必要。举个例子,GLUE与SuperGLUE测试集合里的很多任务,目前LLM效果已超过人类表现,与这个数据集合密切相关的研究领域,其实就没有继续独立存在的必要。第二,对比两种模式的任务效果,第一种模式是用较大的领域专用数据进行Fine-tuning,第二种是few-shot prompting或instruct-based方法。如果第二种方法效果达到或超过第一种方法,则意味着这个领域没有继续独立存在的必要性。如果用这个标准来看,其实很多研究领域,目前fine-tuning效果还是占优的(因为这种模式领域训练数据量大),看似还可独立存在。但是考虑到很多任务随着模型规模增大,few shot prompting效果持续增长,随着更大模型的出现,这个拐点很可能短期就会达到。

如果上述猜测成立,将意味着如下残酷事实:对于很多NLP领域的研究人员,将面临往何处去的选择,是继续做领域独有问题呢?还是放弃这种看似前途不大的方式,转而去建设更好的LLM?如果选择转向去建设LLM,又有哪些机构有能力、有条件去做这个事情呢?你对这个问题的回答会是什么呢?

影响三:更多NLP之外的研究领域将被纳入LLM技术体系

如果站在AGI的视角,参照之前描述的理想LLM模型,它所能完成的任务,不应局限于NLP领域,或某一两个学科领域,理想中的LLM应该是领域无关的通用人工智能模型,它现在在某一两个领域做得好,不代表只能做这些任务。ChatGPT的出现,证明了现在这个时期,我们去追求AGI是有可行性的,而现在是抛开“领域学科”这个思维束缚的时候了。

ChatGPT除了展示出以流畅的对话形式解决各种NLP任务外,也具备强大的代码能力。很自然的,之后越来越多其它的研究领域,也会被逐步纳入LLM体系中,成为通用人工智能的一部分。

​编辑

LLM从NLP向外进行领域拓展,一个自然的选择就是图像处理及多模态相关任务。目前已经有些工作在尝试把多模态融入,让LLM成为一个支持多模态输入输出的通用人机接口,典型的例子包括DeepMind的Flamingo和微软的“Language Models are General-Purpose Interfaces”,上图展示了这种方式的概念结构。

我的判断是无论是图像还是多模态,未来被融入LLM成为好用的功能,可能比我们想象的进度要慢。主要原因在于:尽管图像领域最近两年也一直在模仿Bert预训练的路子,尝试引入自监督学习,释放模型自主从图像数据中学习知识的能力,典型技术就是“对比学习”和MAE,这是两条不同的技术路线。然而,从目前效果来看,尽管取得了很大的技术进步,但貌似这条路尚未走通,这体现在图像领域预训练模型应用到下游任务,带来的效果收益,远不如Bert或GPT应用在NLP下游任务那样显著。所以,图像预处理模型仍需深入探索,以释放图像数据的潜力,而这会迟滞它们被统一到LLM大模型的时间。当然,如果哪天这条路被趟通,大概率会复现NLP领域目前的局面,就是图像处理各个研究子领域可能会逐步消失,被融入到大型LLM中来,直接完成终端任务。

除了图像与多模态,很明显,其它领域也会逐渐被纳入到理想LLM中来,这个方向方兴未艾,是具备高价值的研究主题。

以上是我对范式转换的个人思考,接下来,我们来梳理下GPT 3.0之后LLM模型的主流技术进展。如理想LLM模型所示,相关的技术其实可以分为两大类;一类是关于LLM模型如何从数据中吸收知识,也包括模型规模增长对LLM吸收知识能力带来的影响;第二类是关于人如何使用LLM内在能力来解决任务的人机接口,包括In Context Learning和Instruct两种模式。思维链(CoT)prompting这种LLM推理技术,本质上也属于In Context Learning,因为比较重要,我就把它们单独拎出来讲一下。

学习者:从无尽数据到海量知识

从目前研究结果看,Transformer是足够强大的特征抽取器,尚不需要做特别的改进。那么通过预训练过程,Transformer学到了什么?知识是如何存取的?我们又如何修正错误知识?本节讲述这方面的研究进展。

求知之路:LLM学到了什么知识

LLM从海量自由文本中学习了大量知识,如果把这些知识做粗略分类的话,可以分为语言类知识和世界知识两大类。

语言类知识指的是词法、词性、句法、语义等有助于人类或机器理解自然语言的知识。关于LLM能否捕获语言知识有较长研究历史,自从Bert出现以来就不断有相关研究,很早就有结论,各种实验充分证明LLM可以学习各种层次类型的语言学知识,这也是为何使用预训练模型后,各种语言理解类自然语言任务获得大幅效果提升的最重要原因之一。另外,各种研究也证明了浅层语言知识比如词法、词性、句法等知识存储在Transformer的低层和中层,而抽象的语言知识比如语义类知识,广泛分布在Transformer的中层和高层结构中。

世界知识指的是在这个世界上发生的一些真实事件(事实型知识,Factual Knowledge),以及一些常识性知识(Common Sense Knowledge)。比如“拜登是现任美国总统”、“拜登是美国人”、“乌克兰总统泽连斯基与美国总统拜登举行会晤”,这些都是和拜登相关的事实类知识;而“人有两只眼睛”、“太阳从东方升起”这些属于常识性知识。关于LLM模型能否学习世界知识的研究也有很多,结论也比较一致:LLM确实从训练数据中吸收了大量世界知识,而这类知识主要分布在Transformer的中层和高层,尤其聚集在中层。而且,随着Transformer模型层深增加,能够学习到的知识数量逐渐以指数级增加(可参考:BERTnesia: Investigating the capture and forgetting of knowledge in BERT)。其实,你把LLM看作是一种以模型参数体现的隐式知识图谱,如果这么理解,我认为是一点问题也没有的。

“When Do You Need Billions of Words of Pre-training Data?”这篇文章研究了预训练模型学习到的知识量与训练数据量的关系,它的结论是:对于Bert类型的语言模型来说,只用1000万到1亿单词的语料,就能学好句法语义等语言学知识,但是要学习事实类知识,则要更多的训练数据。这个结论其实也是在意料中的,毕竟语言学知识相对有限且静态,而事实类知识则数量巨大,且处于不断变化过程中。而目前研究证明了随着增加训练数据量,预训练模型在各种下游任务中效果越好,这说明了从增量的训练数据中学到的更主要是世界知识。

记忆之地:LLM如何存取知识

由上可知,LLM确实从数据中学到了很多语言类及世界知识。那么,对于某条具体的知识,LLM把它存储到了哪里?又是如何提取出来的?这也是一个有意思的问题。

显然,知识一定存储在Transformer的模型参数里。从Transformer的结构看,模型参数由两部分构成:多头注意力(MHA)部分占了大约参数总体的三分之一,三分之二的参数集中在FFN结构中。MHA主要用于计算单词或知识间的相关强度,并对全局信息进行集成,更可能是在建立知识之间的联系,大概率不会存储具体知识点,那么很容易推论出LLM模型的知识主体是存储在Transformer的FFN结构里。

​编辑

但这样的定位,粒度还是太粗,无法很好回答具体某条知识是如何存储与提取的,比如 “中国的首都是北京”这条知识,以三元组表达就是<北京,is-capital-of,中国>,其中“is-capital-of”代表实体间关系。这条知识它存储在LLM的哪里呢?

“Transformer Feed-Forward Layers Are Key-Value Memories”给出了一个比较新颖的观察视角,它把Transformer的FFN看成存储大量具体知识的Key-Value存储器。如上图所示(图左是原始论文图,其实不太好理解,可以看做了注释的图右,更好理解些),FFN的第一层是个MLP宽隐层,这是Key层;第二层是MLP窄隐层,是Value层。FFN的输入层其实是某个单词对应的MHA的输出结果Embedding,也就是通过Self Attention,将整个句子有关的输入上下文集成到一起的Embedding,代表了整个输入句子的整体信息。

​编辑

而且这篇文章还指出,Transformer低层对句子的表层模式作出反应,高层对语义模式作出反应,就是说低层FFN存储词法、句法等表层知识,中层和高层存储语义及事实概念知识,这和其它研究结论是一致的。

要我猜,把FFN看成Key-Value存储器这种思路,很可能不是最终的正确答案,但是距离最终正确答案的距离,估计也不太远。

知识涂改液:如何修正LLM里存储的知识

既然我们已知具体的某条世界知识存储在某个或者某些FFN节点的参数里,自然会引发另外一个问题:我们能否修正LLM模型里存储的错误或者过时的知识呢?比如对于问题:“英国的现任首相是谁?”鉴于近年来英国首相频繁更迭,你猜LLM更倾向输出“鲍里斯”还是更青睐“苏纳克”?很明显训练数据中包含“鲍里斯”的数据会更多,这种情况很大可能LLM会给出错误回答,于是我们就有修正LLM里存储的过时知识的必要性。

如果归纳下,目前有三类不同方法来修正LLM里蕴含的知识:

第一类方法从训练数据的源头来修正知识。“Towards Tracing Factual Knowledge in Language Models Back to the Training Data”这篇文章的研究目标是:对于指定的某条知识,我们是否可以定位到是哪些训练数据导致LLM学会了这条知识?答案是肯定的,这意味着我们可以逆向追踪到某条知识对应的训练数据源头。如果利用这项技术,假设我们想要删除某条知识,则可首先定位到其对应的数据源头,删除数据源,然后重新预训练整个LLM模型,这样即可达成删除LLM中相关知识的目的。但是这里有个问题,如果修正一小部分知识,我们就需要重新做一次模型预训练,这样做明显成本太高。所以这种方法不会太有发展前景,可能比较适合那种对于某个特定类别数据的一次性大规模删除场合,不适合少量多次的常规知识修正场景,比如可能比较适合用来做去除偏见等去toxic内容的处理。

第二类方法是对LLM模型做一次fine-tuning来修正知识。一个直观能想到的方法是:我们可以根据要修正成的新知识来构建训练数据,然后让LLM模型在这个训练数据上做fine-tuning,这样指导LLM记住新的知识,遗忘旧的知识。这个方法简单直观,但是也有一些问题,首先它会带来灾难遗忘问题,就是说除了忘掉该忘的知识,还忘掉了不该忘的知识,导致这么做了之后有些下游任务效果下降。另外,因为目前的LLM模型规模非常大,即使是做fine-tuning,如果次数频繁,其实成本也相当高。对这种方法感兴趣的可以参考“Modifying Memories in Transformer Models”。

另外一类方法直接修改LLM里某些知识对应的模型参数来修正知识。假设我们想要把旧知识<英国,现任首相,鲍里斯>,修正到<英国,现任首相,苏纳克>。首先我们想办法在LLM模型参数中,定位到存储旧知识的FFN节点,然后可以强行调整更改FFN中对应的模型参数,将旧知识替换成新的知识。可以看出,这种方法涉及到两项关键技术:首先是如何在LLM参数空间中定位某条知识的具体存储位置;其次是如何修正模型参数,来实现旧知识到新知识的修正。关于这类技术的细节,可以参考“Locating and Editing Factual Associations in GPT”和“Mass-Editing Memory in a Transformer”。理解这个修正LLM知识的过程,其实对于更深入理解LLM的内部运作机制是很有帮助的。

规模效应:当LLM越来越大时会发生什么

我们知道,近年来,LLM模型规模在快速增长,目前效果最好的LLM模型,其参数规模大都超过了千亿(100B)参数规模。比如,OpenAI的GPT 3的规模为175B,Google的LaMDA规模为137B,PaLM的规模为540B,DeepMind的Gogher规模为280B等,不一而足。国内也有中文巨型模型,比如清华&智谱GLM规模130B,华为“盘古”规模200B,百度“文心”规模260B,浪潮“源1.0”规模245B。那么,一个很自然的问题就是:随着LLM模型规模不断增长,会发生些什么呢?

预训练模型的应用往往是两阶段的:预训练阶段,及具体场景应用阶段。在预训练阶段,其优化目标是交叉熵,对GPT这种自回归语言模型来说,也就是看LLM是否正确预测到了下一个单词;而场景应用阶段,一般要看具体场景的评价指标。一般我们的直觉是:如果LLM模型在预训练阶段的指标越好,自然它解决下游任务的能力就越强。然而,事实并非完全如此。现有研究已证明,预训练阶段的优化指标确实和下游任务表现出正相关关系,但是并非完全正相关。也就是说,只看预训练阶段的指标,来判断一个LLM模型是否够好,这是不够的。基于此,我们分头来看在这两个不同阶段,随着LLM模型增大,有什么影响。

​编辑

首先,我们先看在预训练阶段,随着模型规模逐步增大,会发生什么。OpenAI在“Scaling Laws for Neural Language Models”中专门研究了这个问题,并提出LLM模型所遵循的“伸缩法则”(scaling law)。如上图所示,这个研究证明:当我们独立增加训练数据量、模型参数规模或者延长模型训练时间(比如从1个Epoch到2个Epoch),预训练模型在测试集上的Loss都会单调降低,也就是说模型效果越来越好。

既然三个因素都重要,那么我们在实际做预训练的时候,就有一个算力如何分配的决策问题:假设用于训练LLM的算力总预算(比如多少GPU小时或者GPU天)给定,那么是应该多增加数据量、减少模型参数呢?还是说数据量和模型规模同时增加,减少训练步数呢?此消彼长,某个要素规模增长,就要降低其它因素的规模,以维持总算力不变,所以这里有各种可能的算力分配方案。最终OpenAI选择了同时增加训练数据量和模型参数,但是采用早停策略(early stopping)来减少训练步数的方案。因为它证明了:对于训练数据量和模型参数这两个要素,如果只单独增加其中某一个,这不是最好的选择,最好能按照一定比例同时增加两者,它的结论是优先增加模型参数,然后才是训练数据量。假设用于训练LLM的算力总预算增加了10倍,那么应该增加5.5倍的模型参数量,1.8倍的训练数据量,此时模型效果最佳。

DeepMind的一项研究(参考:Training Compute-Optimal Large Language Models)更深入地探究了这个问题,其基本结论和OpenAI的结论差不多,比如确实需要同时增加训练数据量和模型参数,模型效果才会更好。而很多大模型在做预训练的时候,并没有考虑这一点,很多LLM大模型只是单调增加模型参数,而固定住了训练数据量,这个做法其实是不对的,限制了LLM模型的潜力。但是它修正了两者的比例关系,认为训练数据量和模型参数是同等重要的,也就是说,假设用于训练LLM的算力总预算增加了10倍,那么应该增加3.3倍的模型参数量,3.3倍的训练数据量,这样模型效果才最好。

这意味着:增加训练数据量的重要性,比我们之前所认为的,还要重要。基于这个认知,DeepMind在设计Chinchilla模型时,在算力分配上选择了另外一种配置:对标数据量300B、模型参数量280B的Gopher模型,Chinchilla选择增加4倍的训练数据,但是将模型参数降低为Gopher的四分之一,大约为70B。但是无论预训练指标,还是很多下游任务指标,Chinchilla效果都要优于规模更大的Gopher。

这带给我们如下启示:我们可以选择放大训练数据,并同比例地减少LLM模型参数,以达到在不降低模型效果的前提下,极大缩小模型规模的目的。缩小模型规模有很多好处,比如在应用的时候,推理速度会快很多等,无疑这是一个很有前途的LLM发展路线。

以上是从预训练阶段来看模型规模的影响,如果从LLM解决下游具体任务效果的角度来看,随着模型规模增大,不同类型的任务有不同的表现,具体而言,有以下三类情况。

​编辑

第一类任务完美体现了LLM模型的scaling law,就是说随着模型规模逐步放大,任务的表现越来越好,如上图里的(a)图所示。这类任务通常符合如下共性:它们往往都是知识密集型任务,也就是说如果LLM模型包含的知识量越多,这类任务表现越好。而很多研究已经证明越大的LLM模型学习效率越高,也就是说相同训练数据量,模型越大任务效果越好,说明面对的即使是同样的一批训练数据,更大的LLM模型相对规模小一些的模型,从中学到了更多的知识。更何况一般情况下,在增大LLM模型参数的时候,往往会同步增加训练数据量,这意味着大模型可以从更多数据中学习更多的知识点。这些研究可以很好地解释上图,为何随着模型规模增大,这些知识密集型的任务效果越来越好。大多数传统的自然语言理解类任务,其实都属于这种知识密集型任务,而很多任务在近两年获得了极大的效果提升,甚至超过了人类表现。很明显,这大概率是LLM模型的规模增长带来的,而非归功于某项具体的技术改进。

第二类任务展现出LLM具备某种“涌现能力(Emergent Ability)”,如上图(b)所示。所谓“涌现能力”,指的是当模型参数规模未能达到某个阀值时,模型基本不具备解决此类任务的任何能力,体现为其性能和随机选择答案效果相当,但是当模型规模跨过阀值,LLM模型对此类任务的效果就出现突然的性能增长。也就是说,模型规模是解锁(unlock)LLM新能力的关键,随着模型规模越来越大,会逐渐解锁LLM越来越多的新能力。这是个很神奇的现象,因为它意味着如下让人对未来可报乐观预期的可能:或许很多任务,目前LLM还不能很好地解决,甚至站在现在这个时刻的我们看起来,LLM完全没有能力解决这类任务,但因LLM具备“涌现能力”,所以如果我们继续推大模型,也许某一天它的这项能力就被突然解锁了。LLM模型的规模增长会给我们带来意想不到的精彩礼物。

“Beyond the Imitation Game: Quantifying and extrapolating the capabilities of language models”这篇文章指出,这类体现出“涌现能力”的任务也有一些共性:这些任务一般由多步骤构成,要解决这些任务,往往需要先解决多个中间步骤,而逻辑推理能力在最终解决这类任务中发挥重要作用。思维链(Chain of Thought)Prompting是典型的增强LLM推理能力的技术,能大幅提升此类任务的效果,关于CoT技术,在随后小节内容会做解释,此处暂不展开。

问题是,为何LLM会出现这种“涌现能力”现象呢?上述文章以及“Emergent Abilities of Large Language Models”给出了几个可能的解释:

一种可能解释是有些任务的评价指标不够平滑。比如说有些生成任务的判断标准,它要求模型输出的字符串,要和标准答案完全匹配才算对,否则就是0分。所以,即使随着模型增大,其效果在逐步变好,体现为输出了更多的正确字符片段,但是因为没有完全对,只要有任何小错误都给0分,只有当模型足够大,输出片段全部正确才能得分。也就是说,因为指标不够平滑,所以不能体现LLM其实正在逐步改善任务效果这一现实,看起来就是“涌现能力”这种外在表现。

另外一种可能的解释是:有些任务由若干中间步骤构成,随着模型规模增大,解决每个步骤的能力也在逐步增强,但是只要有一个中间步骤是错的,最终答案就是错的,于是也会导致这种表面的“涌现能力”现象。

当然,上面的解释目前还都是猜想,至于为何LLM会出现这种现象,还需要进一步更深入的研究。

​编辑

还有少部分任务,随着模型规模增长,任务的效果曲线展现出U形特性:随着模型规模逐渐变大,任务效果逐渐变差,但是当模型规模进一步增长,则效果开始越来越好,呈现出U形增长趋势,如上图所示的粉红色PaLM模型在两个任务上的指标走势。为何这些任务表现得如此特殊呢?“Inverse scaling can become U-shaped”这篇文章给出了一种解释:这些任务,内部其实隐含了两种不同类型的子任务,一种是真正的任务,另外一种是“干扰任务(distractor task)”。当模型规模小的时候,无法识别任意一种子任务,所以模型的表现跟随机选择答案差不多,当模型增长到中等规模的时候,主要执行的是干扰任务,所以对真正的任务效果有负面影响,体现为真正任务效果的下降,而当进一步增加模型规模,则LLM可以忽略干扰任务,执行真正的任务,体现为效果开始增长。

对于那些随着模型规模增大,效果一直下降的任务,如果采用思维链(CoT)Prompting,则部分任务的表现转换为遵循Scaling law,即模型规模越大效果越好,而其它任务则转换为U性增长曲线。这其实侧面说明了:此类任务应属于推理类型的任务,所以加入CoT后任务表现会发生质的变化。

人机接口:从In Context Learning到Instruct理解

一般我们经常提到的人和LLM的接口技术包括:zero shot prompting、few shot prompting、In Context Learning,以及Instruct。这些其实都是表达某个具体任务的描述方式。不过如果你看文献,会发现叫法比较乱。

其中Instruct 是ChatGPT的接口方式,就是说人以自然语言给出任务的描述,比如“把这个句子从中文翻译成英文”,类似这种。zero shot prompting我理解其实就是现在的Instruct的早期叫法,以前大家习惯叫zero shot,现在很多改成叫Instruct。尽管是一个内涵,但是具体做法是两种做法。早期大家做zero shot prompting,实际上就是不知道怎么表达一个任务才好,于是就换不同的单词或者句子,反复在尝试好的任务表达方式,这种做法目前已经被证明是在拟合训练数据的分布,其实没啥意思。目前Instruct的做法则是给定命令表述语句,试图让LLM理解它。所以尽管表面都是任务的表述,但是思路是不同的。

而In Context Learning和few shot prompting意思类似,就是给LLM几个示例作为范本,然后让LLM解决新问题。我个人认为In Context Learning也可以理解为某项任务的描述,只是Instruct是一种抽象的描述方式,In Context Learning是一种例子示范的例子说明法。当然,鉴于目前这几个叫法用的有点乱,所以上述理解仅代表个人看法。

所以我们此处只对In Context Learning和Instruct进行介绍,不再提zero shot和few shot了。

神秘的In Context Learning

​编辑

Fine-tuning和In Context Learning表面看似都提供了一些例子给LLM,但两者有质的不同(参考上图示意):Fine-tuning拿这些例子当作训练数据,利用反向传播去修正LLM的模型参数,而修正模型参数这个动作,确实体现了LLM从这些例子学习的过程。但是,In Context Learning只是拿出例子让LLM看了一眼,并没有根据例子,用反向传播去修正LLM模型参数的动作,就要求它去预测新例子。既然没有修正模型参数,这意味着貌似LLM并未经历一个学习过程,如果没有经历学习过程,那它为何能够做到仅看一眼,就能预测对新例子呢?这正是In Context Learning的神奇之处。这是否让你想起了一句歌词:“只是因为在人群中多看了你一眼 再也没能忘掉你容颜”,而这首歌名叫“传奇”。你说传奇不传奇?

看似In Context Learning没从例子里学习知识,实际上,难道LLM通过一种奇怪的方式去学习?还是说,它确实也没学啥?关于这个问题的答案,目前仍是未解之谜。现有一些研究各有各的说法,五花八门,很难判断哪个讲述的是事实的真相,甚至有些研究结论还相互矛盾。这里提供几个目前的说法,至于谁对谁错,只能你自己把握了。当然,我认为追求这个神奇现象背后的真相,是一个好的研究课题。

​编辑

总而言之,目前这还是一个未解之谜。

神奇的Instruct理解

我们可以把Instruct当作一种方便人类理解的任务表述,在这个前提下,目前关于Instruct的研究可以分成两种:偏学术研究的Instruct,以及关于人类真实需求描述的Instruct。

​编辑

我们先来看第一种:偏学术研究的Instruct。它的核心研究主题是多任务场景下,LLM模型对Instruct理解的泛化能力。如上图中FLAN模型所示,就是说有很多NLP任务,对于每个任务,研究人员构造一个或者多个Prompt模版作为任务的Instruct,然后用训练例子对LLM模型进行微调,让LLM以同时学习多个任务。训练好模型后,给LLM模型一个它没见过的全新任务的Instruct,然后让LLM 解决zero shot任务,从任务解决得是否足够好,来判断LLM模型是否有对Instruct理解的泛化能力。

如果归纳下目前的研究结论(可参考“Scaling Instruction-Fine-tuned Language Models”/“Super-NaturalInstructions: Generalization via Declarative Instructions on 1600+ NLP Tasks”),能够有效增加LLM模型Instruct泛化能力的因素包括:增加多任务的任务数量、增加LLM模型大小、提供CoT Prompting, 以及增加任务的多样性。如果采取任意一项措施,都可以增加LLM模型的Instruct理解能力。

第二种是人类真实需求下的Instruct,这类研究以InstructGPT和ChatGPT为代表。这类工作也是基于多任务的,但是和偏向学术研究类工作最大的不同,在于它是面向人类用户真实需求的。为什么这么说呢?因为它们用于LLM多任务训练的任务描述Prompt,是从大量用户提交的真实请求中抽样而来的,而不是固定好研究任务的范围,然后让研究人员来写任务描述prompt。这里所谓的“真实需求”,体现在两个方面:首先,因为是从用户提交的任务描述里随机抽取的,所以涵盖的任务类型更多样化,也更符合用户的真实需求;其次,某个任务的prompt描述,是用户提交的,体现了一般用户在表达任务需求时会怎么说,而不是你认为用户会怎么说。很明显,这类工作改出来的LLM模型,用户体验会更好。

InstructGPT论文里,也拿这种方法和FLAN那种Instruct based方法做了比较。首先在GPT3上用FLAN提到的任务、数据以及Prompt模版进行微调,来在GPT 3上复现FLAN方法,然后和InstructGPT进行比较,因为InstructGPT的基础模型也是GPT3,所以只有数据和方法的差别,两者可比,结果发现FLAN方法的效果,距离InstructGPT有很大的差距。那么背后的原因是什么呢?论文分析数据后认为,FLAN方法涉及到的任务领域相对少,是InstructGPT涉及领域的子集,所以效果不好。也就是说,FLAN论文里涉及到的任务和用户真实需求是不符的,而这导致在真实场景下效果不够好。而这对我们的启示是:从用户数据中收集真实需求,这事情是很重要的。

In Context Learning和Instruct的联系

如果我们假设In Context Learning是用一些例子来具象地表达任务命令,Instruct是一种更符合人类习惯的抽象任务描述。那么,一个很自然的问题是:它们之间有什么联系吗?比如,我们是否能够提供给LLM完成某个任务的若干具体示例,让LLM找出其对应的自然语言描述的Instruct命令?

​编辑

目前有零星的工作在探索这个问题,我认为这个方向是很有研究价值的。先说答案,答案是:Yes,LLM Can。“Large Language Models Are Human-Level Prompt Engineers”是做这个方向很有趣的工作,如上图所示,对于某项任务,给LLM一些示例,让LLM自动生成能够描述这项任务的自然语言命令,然后它再用LLM生成的任务描述去测试任务效果。它使用的基础模型是GPT 3和InstructGPT,经过这项技术加持后,LLM生成的Instruct的效果相比未采用这项技术的GPT 3 以及InstuctGPT来说,指标有极大地提升,而且在一些任务上超过人类的表现。

这说明了:具象的任务示例和任务的自然语言描述之间,有种神秘的内在联系。至于这种联系到底是什么?我们目前对此还一无所知。

智慧之光:如何增强LLM的推理能力

目前很多研究已证明LLM对于知识具有强大的记忆能力,但是,一般我们不会因为一个人记忆能力强,就说这人很聪明,是否具有强大的推理能力,往往是我们判断一个人是否聪明的重要标准。类似的,如果LLM的效果想让人觉得很惊艳,强大的推理能力是必备的。推理能力本质上是综合运用很多相关知识点,去推导出新知识或新结论。关于LLM的推理能力,是最近一年来LLM里最重要和热门的研究领域之一。于是,我们关心的问题就是:LLM具备推理能力吗?如果具备,那么它的推理能力够强吗?

这两个问题目前的答案似乎应该是:当模型规模足够大的时候,LLM本身是具备推理能力的,在简单推理问题上,LLM已经达到了很好的能力,但是复杂推理问题上,还需要更多深入的研究。

如果梳理现有LLM推理相关工作的话,我把它们归到两大类,体现出挖掘或促进LLM推理能力不同的技术思路:第一类研究比较多,可以统称为基于Prompt的方法,核心思想是通过合适的提示语或提示样本,更好地激发出LLM本身就具备的推理能力,Google在这个方向做了大量很有成效的工作。第二类做法是在预训练过程中引入程序代码,和文本一起参与预训练,以此进一步增强LLM的推理能力,这应该是OpenAI实践出的思路。比如ChatGPT肯定具备很强的推理能力,但它并不要求用户必须提供一些推理示例,所以ChatGPT强大的推理能力,大概率来源于使用代码参与GPT 3.5的预训练。

这两种思路其实大方向是迥异的:利用代码增强LLM推理能力,这体现出一种通过增加多样性的训练数据,来直接增强LLM推理能力的思路;而基于Prompt的方法,它并不会促进LLM本身的推理能力,只是让LLM在解决问题过程中更好地展示出这种能力的技术方法。可以看出,前者(代码方法)治本,后者治标。当然,两者其实也是互补的,但从长远看,治本的方法更重要。

基于Prompt的方法

这方面工作非常多,如果归纳一下的话,大致可以分为三条技术路线。

​编辑

第一种思路是直接在问题上追加辅助推理Prompt。这种方法简单直接,但在众多领域都很有效。这个做法是由“Large language models are zero-shot reasoners”提出的,也被称为zero-shot CoT。具体而言,分为两个阶段(如上图所示),第一阶段在提问的问题上追加“Let’s think step by step”这句提示语,LLM会输出具体的推理过程;第二阶段,在第一阶段的问题后,拼接LLM输出的具体推理过程,并再追加Prompt=“Therefore, the answer (arabic numerals) is”,此时LLM会给出答案。如此简单的操作,却可以大幅增加LLM在各项推理任务中的效果,比如在数学推理测试集GSM8K上,加上提示语后,推理准确率直接从原先的10.4%提升到了40.4%,可谓神奇。

为什么LLM会具备给一句“Let’s think step by step”提示语,就能列出详细的推理步骤并算出答案呢?其原因目前尚无定论,我的猜测是:很可能因为预训练数据里面存在大量的此种数据,就是以“Let’s think step by step”开头,然后后面是详细的推理步骤,最后给出答案,而LLM在预训练的时候记住了这些模式。而当我们输入这个提示语的时候,激发LLM模糊得“回忆”起某些例子的推导步骤,于是即可模仿这些例子进行步骤推理并给出答案。当然这只是我的无依据推论,若事实真的如此,如果你看过后面介绍的标准CoT做法,会发现Zero-shot CoT 本质上和标准CoT很可能没什么区别,只是标准CoT由人工来写推理步骤的示例,而Zero-shot CoT大概率是通过提示语,激活了记忆中的某些包含推理步骤的示例,很可能是如此区别。而标准CoT效果比Zero-Shot CoT效果好也完全可以理解,因为毕竟靠LLM回忆示例,精准性估计不会太高,而人工给出的示例,准确性是有保障的,所以自然标准CoT效果会更好。

这侧面说明了一个道理,就是LLM本身是具备推理能力的,只是我们没有办法把它的这种能力激发出来而已,通过合适的提示语来进行两步提示,就在一定程度上可以释放出它的这种潜力。另外,对于中文,很可能存在另外一个黄金提示语,比如“详细解题思路如下”,类似这种,因为中文语料在讲解推理步骤的时候,经常用的引导句和“让我们一步一步来思考”应该是不同的,这是明显的西方说法,而探索出这个中文黄金提示语,其实也是很有必要的。

第二种思路一般被称为基于示例的思维链(few-shot CoT,Chain of Thought)Prompting。这个方向目前是LLM推理研究的主方向,很多工作都是在这个思路上做的,我们简单介绍几个效果显著的代表性工作,基本能代表CoT的技术发展方向。

​编辑

CoT的主体思想其实很直白;为了教会LLM模型学会推理,给出一些人工写好的推理示例,示例里把得到最终答案前,一步步的具体推理步骤说清楚,而这些人工写的详细推理过程,就是思维链Prompting,具体例子可参照上图中蓝色文字部分。CoT的意思是让LLM模型明白一个道理;就是在推理过程中,步子不要迈得太大,否则很容易出错,改变思维模式,化大问题为小问题,步步为营,积小胜为大胜。最早明确提出CoT这个概念的文章是“Chain of thought prompting elicits reasoning in large language models”,论文发布于22年1月份,虽然做法很简单,但是应用CoT后LLM模型的推理能力得到了巨大提升,GSM8K数学推理测试集准确率提高到60.1%左右。当然,这种给出详细推理步骤和中间过程的思想,并非CoT最早提出的,更早一些的“scratchpad”技术(可参考:Show Your Work: Scratchpads for Intermediate Computation with Language Models)首先采用了类似的思路。

​编辑

CoT提出不久,很快在22年3月份,一项被称为“Self-Consistency”的改进技术就将GSM8K测试集准确率提高到74.4%,提出这项改进的论文是“Self-Consistency Improves Chain of Thought Reasoning in Language Models”。“Self-Consistency”的思路也很直观(参考上图):首先可以利用CoT给出几个写了推理过程的示例,然后要求LLM对给定的问题进行推理,如果是CoT,直接输出一个推理过程和答案,整个过程就结束了。“Self-Consistency”则不然,它要求LLM输出多个不同的推理过程和答案,然后采用投票的方式选出最佳答案,思路非常简单直接,但是效果也确实好。“Self-Consistency”其实是教导LLM学会这么一个道理:孔乙己说过茴香豆的“茴”字有四种写法,类似的,一个数学题的正确解法也可以有很多种,每个不同的推导过程都指向最终的答案。条条大路通罗马,虽说也有个别迷路走到北京的,但是迷路的毕竟是少数,看看大多数人走到哪里,哪里就是正确答案。简单的方法往往蕴含着深刻的哲学含义,是不是这道理?

再往后,“On the Advance of Making Language Models Better Reasoners”这个工作在“Self-Consistency”基础上,进一步集成了“从一个Prompt问题拓展到多个Prompt问题、检查推理中间步骤的正确性以及对多个输出的回答加权投票”这三个改进点,将GSM8K测试集准确率提高到83%左右。

​编辑

第三种思路体现了一种分治算法的思想。当然这个所谓“分治”是我归纳的,别人没这么说。这种思路的核心思想是:对于一个复杂的推理问题,我们把它分解成若干容易解决的子问题,一一解决掉子问题后,我们再从子问题的答案推导复杂问题的答案。你看这确实比较类似分治算法的思想吧。我个人觉得,这种思路可能才是揭示问题本质、最终解决LLM复杂推理问题正宗的道路。我们以“Least-to-most prompting”技术为例来说明这种思路的一种具体实现方式,如上图所示:它分为两个阶段,第一个阶段,从原始问题我们可以得知最终要问的问题是什么,我们假设最终问题是Final Q,然后从原始问题填充Prompt模版:“如果要解决Final Q问题,那么我需要先解决”,然后把原始问题和这个Prompt交给LLM,让LLM模型给出答案,等于让LLM给出最终问题的前置子问题Sub Q;接下来我们进入第二个阶段,让LLM先回答刚才拿到的子问题Sub Q,并拿到对应的答案,然后原始问题拼接子问题Sub Q及对应答案,再去问LLM最终那个问题Final Q,此时LLM会给出最后的答案。如此这般,体现出拆解子问题,并从子问题的答案逐步找出最终答案的思路。

代码预训练增强LLM推理能力

以上是目前利用Prompt激发LLM模型推理能力的三种主流做法,而关于LLM的推理能力,目前还观察到一个有趣且费解的现象:除了文本外,如果能够加入程序代码一起参与模型预训练,则能大幅提升LLM模型的推理能力。这个结论从不少论文的实验部分都可以得出(可以参考:AUTOMATIC CHAIN OF THOUGHT PROMPTING IN LARGE LANGUAGE MODELS/Challenging BIG-Bench tasks and whether chain-of-thought can solve them等论文的实验部分)。

​编辑

上图给出了一份实验数据,来自于论文“On the Advance of Making Language Models Better Reasoners”,其中GPT3 davinci就是标准的GPT 3模型,基于纯文本训练;code-davinci-002(OpenAI内部称为Codex)是同时在Code和NLP数据上训练的模型。如果比较两者效果,可以看出,不论采用具体哪种推理方法,仅仅是从纯文本预训练模型切换到文本和Code混合预训练模型,在几乎所有测试数据集合上,模型推理能力都得到了巨大的效果提升,比如我们以“Self Consistency”方法为例,在大多数据集合上的性能提升,都直接超过了20到50个百分点,这是很恐怖的性能提升,而其实在具体推理模型层面,我们什么也没做,仅仅是预训练的时候除了文本,额外加入了程序代码而已。

除了这个现象,从上图数据中,我们还可以得出其它一些结论,比如GPT 3这种纯文本预训练模型,其实是具备相当程度的推理能力的,除了在GSM8K这种数学推理上效果比较差外,其它推理数据数据集合表现也还可以,前提你需要采用合适的方法,来激发出它本身就具备的这种能力;再比如,text-davinci-002,也就是在code-davinci-002基础上加入instruct fine-tuning后的模型(就是加入InstructGPT或ChatGPT模型的第一步),其推理能力要弱于Codex,但是有其它研究表明它在自然语言处理任务又要强于Codex。而这貌似说明了,加入instruct fine-tuning,会损害LLM模型的推理能力,但是会在一定程度上提升自然语言理解能力。而这些结论其实都是很有意思的,也能启发后续进一步的思考和探索。

那么,一个自然的疑问是:为何预训练模型可以从代码的预训练中获得额外的推理能力?确切原因目前未知,值得深入探索。我猜测可能是因为原始版本的Codex(只使用代码训练,可参考文献:Evaluating Large Language Models Trained on Code)的代码训练是从文本生成代码,而且代码中往往包含很多文本注释,本质上这类似于预训练模型做了<文本,Code>两种数据的多模态对齐工作。而数据中必然包含相当比例的数学或逻辑问题的代码、描述和注释,很明显这些数学类或逻辑推理类的数据,对于解决下游数学推理问题是有帮助的,我猜大概率原因在此。

关于LLM推理能力的思考

上面介绍了LLM推理的主流技术思路和现有的一些结论,接下来谈谈我对LLM模型推理技术的思考,以下内容纯个人推断,没有太多证据,还请谨慎参考。我的判断是:虽然最近一年来,关于激发LLM的推理能力,这方面的技术进展很快,也取得了很大的技术进步,但是总体感觉是,我们可能走在正确的方向上,但是距离接触到真正的问题本质还有一段距离,对此要有更深入的思考和探索。

首先,我比较赞同上述分治算法的主体思路,对于复杂的推理问题,我们应该把它拆解成若干简单的子问题,因为子问题对于LLM来说回答正确的概率就大很多,让LLM一一回答子问题后,再逐步推导出最终答案。受到“Least-to-most prompting”技术的启发,如果进一步思考,我觉得LLM推理本质上很可能会是如下两种可能的其中之一:不断和LLM进行交互的图上推理问题,抑或是不断和LLM进行交互的程序流程图执行问题。

​编辑

先说图上推理问题,如上图所示,假设我们有办法能够把复杂问题拆解成由子问题或者子步骤构成的图结构,图中的节点是子问题或者子步骤,图中的边代表了子问题之间的依赖关系,就是说只有回答好子问题A,才能回答子问题B,而且图中大概率存在循环结构,就是反复做某几个子步骤。假设我们能够得到上述的子问题拆解图,那么可以根据依赖关系,引导LLM一步一步按照图结构,回答必须首先回答的子问题,直到推导出最终答案。

​编辑

再说程序流程图问题,参考上图,假设我们有办法把复杂问题拆解成子问题或子步骤,并产生一个由子步骤构成的类似程序流程图的结构,在这个结构里,有些步骤会反复执行多次(循环结构),有些步骤的执行需要进行条件判断(条件分支)。总而言之,在执行每个子步骤的时候和LLM进行交互,得到子步骤的答案,然后按照流程不断执行,直到输出最终答案。类似这种模式。假设这个思路大致正确的话,也许可以从这个角度来解释为何加入代码会增强预训练模型的推理能力:大概率因为<文本,代码>的多模态预训练模型,在模型内部是通过类似这种隐含的程序流程图作为两个模态的桥梁,将两者联系起来的,即由文本描述到隐含的流程图,再映射到由流程图产生具体的代码。也就是说,这种多模态预训练,可以增强LLM模型从文本构建出隐含的流程图并按照流程图执行的能力,也就是加强了它的推理能力。

当然,上述思路最大的问题是,我们如何根据文本描述的问题,能够靠LLM模型,或者其它模型,得到图结构或者流程图结构?这个可能是其中的难点。一种可能的思路就类似继续增强文本和更高质量的代码预训练,走隐式学习内部隐含结构的方法。而目前的CoT技术,如果套到上述思路来思考的话,可以这么理解:标准CoT,其实就是靠自然语言文本来描述图结构或者程序流程图的;而“Least-to-most prompting”技术,则是试图根据最后一个图节点,靠倒推来试图推导出其中的图结构,但是很明显,目前的方法限制了它倒推的深度,也就是说它只能推导出非常简单的图结构,这正是限制它能力的所在。

未来之路:LLM研究趋势及值得研究的重点方向

这里列出一些我个人认为比较重要的LLM研究领域,或值得深入探索的研究方向。

探索LLM模型的规模天花板

尽管继续推大LLM模型的规模,这事看似没有技术含量,但是其实这个事情异常重要。我个人判断,自从Bert出现以来,到GPT 3,再到ChatGPT,大概率这些给人印象深刻的关键技术突破,核心贡献都来自于LLM模型规模的增长,而非某项具体技术。说不定,揭开AGI真正的钥匙就是:超大规模及足够多样性的数据、超大规模的模型,以及充分的训练过程。再者,做超大规模的LLM模型,对技术团队的工程实现能力要求是非常高的,也不能认为这事情缺乏技术含量。

那么继续推大LLM模型规模,有什么研究意义呢?我觉得有两方面的价值。首先,如上所述,我们已知,对于知识密集型的任务,随着模型规模越大,各种任务的效果会越来越好;而对很多推理类型的有难度的任务,加上CoT Prompting后,其效果也呈现出遵循Scaling law的趋向。那么,很自然的一个问题就是:对于这些任务,LLM的规模效应,能将这些任务解决到何种程度?这是包括我在内,很多人关心的问题。其次,考虑到LLM具备的神奇的“涌现能力”,如果我们继续增加模型规模,它会解锁哪些让我们意想不到的新能力呢?这也是很有意思的问题。考虑到以上两点,我们仍然需要不断增大模型规模,看看模型规模对解决各类任务的天花板在哪里。

当然,这种事情也就只能说说,对99.99%的从业者来说,是没有机会和能力做这个事情的。要做这个事情,对研究机构的财力及投入意愿、工程能力、技术热情,都有极高的要求,缺一不可。能做这事情的机构,粗估下来,国外不超过5家,国内不超过3家。当然,考虑到成本问题,未来也许会出现“股份制大模型”,就是有能力的几家机构合作,群策群力,一起来共建超级大模型的现象。

增强LLM的复杂推理能力

正如之前对LLM推理能力的叙述,尽管LLM在最近一年推理能力得到了很大的提升,但是很多研究(参考:Limitations of Language Models in Arithmetic and Symbolic Induction/Large Language Models Still Can’t Plan)表明,目前LLM能够解决得比较好的推理问题,往往都相对简单,LLM的复杂推理能力仍然薄弱,比如即使是简单的字符拷贝推理或者加减乘除运算,当字符串或者数字非常长的时候,LLM推理能力会极速下降,再比如行为规划能力等复杂推理能力很弱。总而言之,加强LLM的复杂推理能力,应该是LLM未来研究中最重要的环节之一。

前文有述,加入代码加入预训练,这是一种直接增强LLM推理能力的方向。这个方向目前研究尚显不足,更像是实践经验的总结,探索背后的原理,并进而引入更多类型除代码外的新型数据来增强LLM的推理能力,这可能是更本质提升推理能力的方向。

LLM纳入NLP之外更多其它研究领域

目前的ChatGPT擅长NLP和Code任务,作为通向AGI的重要种子选手,将图像、视频、音频等图像与多模态集成进入LLM,乃至AI for Science、机器人控制等更多、差异化更明显的其它领域逐步纳入LLM,是LLM通往AGI的必经之路。而这个方向才刚刚开始,因此具备很高的研究价值。

更易用的人和LLM的交互接口

如前所述,ChatGPT的最大技术贡献即在此。但是很明显,目前的技术并不完美,肯定还有很多命令LLM理解不了。所以,沿着这个方向,寻找更好的技术,来让人类使用自己习惯的命令表达方式,而LLM又能听懂,这是个新的,且非常有前景的技术方向。

建设高难度的综合任务评测数据集

好的评测数据集,是引导技术不断进步的基石。随着LLM模型逐步增大,任务效果快速提升,导致很多标准测试集快速过时。也就是说,这些数据集合相对现有技术来说,太容易了,在没有难度的测试集合下,我们不知道目前技术的缺陷和盲点在哪里。所以构建高难度的测试集合,是促进LLM技术进步的关键所在。

目前行业应出现了一些新的测试集,有代表性的包括BIGBench、OPT-IML等。这些测试集合体现出一些特性,比如相对LLM现有技术具备一定的难度、综合了各种各样多种类型的任务等。

受到ChatGPT的启发,我觉得除此外应纳入另一考虑因素:体现真实用户需求。就是说,这些任务的表述由用户真实发起,这种方式构建出来的LLM模型,才能解决用户实际需求。

除此外,相信LLM会快速将能力溢出到NLP之外的领域,而如何融入更多其它领域的评测数据,也是需要提前去考虑。

高质量数据工程

对于预训练模型来说,数据是其根本,预训练过程可以理解为从数据中吸取其中所包含知识的过程。因此,我们需要进一步加强对高质量数据的挖掘、收集及清洗等工作。

关于数据,需要考虑两个方面:数据的质量和数量。而根据T5的对比实验,我们可以得出结论:在数量和质量两个因素里,质量优先,正确的道路应该是在保证数据质量的前提下,再去增大数据规模。

数据质量,包括数据的信息含量以及数据的多样性等多个衡量标准,比如Wiki明显就属于世界知识密度极高的高质量数据,这是从信息含量来说的;而增加数据类型的多样性,无疑是激发LLM各种新能力的根本,比如加入问答网站的数据,对于LLM的QA能力提升是有直接帮助的。多样化的数据赋予了LLM更好解决更多不同类型任务的能力,所以,这可能是数据质量里最关键的标准。

关于数据数量,原则上互联网上公开发布的数据都可以纳入LLM模型的预训练过程。那么,它的极限在哪里?“Will we run out of data? An analysis of the limits of scaling datasets in Machine Learning” 对此进行了估算,结论是到2026年左右,高质量的NLP数据将会用光,低质量NLP数据会在2030到2050年用光,而低质量图像数据会在2030到2060年用光。而这意味着:要么到时我们有新类型的数据源,要么我们必须增加LLM模型对数据的利用效率。否则,目前这种数据驱动的模型优化方式将会停止进步,或者收益减少。

超大LLM模型Transformer的稀疏化

目前规模最大的LLM中,有相当比例的模型采取了稀疏(Sparse)结构,比如GPT 3、PaLM、GLaM等,GPT 4大概率也会走稀疏模型路线。之所以采用Sparse 化的模型,主要好处是它可以极大减少LLM的训练时间和在线推理时间。Switch Transformer论文里指出:在相同算力预算的前提下,使用稀疏化Transformer,相对Dense Transformer,LLM模型的训练速度可以提升4倍到7倍。为何Sparse模型可以加快训练和推理时间呢?这是因为尽管模型参数巨大,但是对于某个训练实例,Sparse模型通过路由机制,只使用整个参数中的一小部分,参与训练和推理的活跃参数量比较少,所以速度快。

我认为未来超大的LLM模型大概率会收敛到稀疏模型。主要有两个原因:一方面,现有研究表明(参考:Large Models are Parsimonious Learners: Activation Sparsity in Trained Transformers),标准的Dense Transformer在训练和推理时,它本身也是稀疏激活的,就是说只有部分参数会被激活,大部分参数没有参与训练和推理过程。既然这样,我们不如直接迁移到稀疏模型;另外,毫无疑问LLM模型的规模会继续推大,而高昂的训练成本是妨碍其进一步扩大模型的重要阻力,使用稀疏模型可以极大降低超大模型的训练成本,所以随着模型规模越大,稀疏模型带来的收益越明显。考虑到这两个方面,大概率未来更大的LLM模型会采用稀疏模型方案。

那为何目前其它大规模模型不走稀疏模型的路线呢?因为Sparse模型存在训练不稳定、容易过拟合等问题,不太容易训练好。所以,如何修正稀疏模型面临的问题,设计出更容易训练的稀疏模型,是很重要的未来研究方向。 

取经之路:复刻ChatGPT时要注意些什么

如果希望能复刻类似ChatGPT这种效果令人惊艳的LLM模型,综合目前的各种研究结论,在做技术选型时需要重点权衡如下问题:

首先,在预训练模式上,我们有三种选择:GPT这种自回归语言模型,Bert这种双向语言模型,以及T5这种混合模式(Encoder-Decoder架构,在Encoder采取双向语言模型,Decoder采取自回归语言模型,所以是一种混合结构,但其本质仍属于Bert模式)。我们应选择GPT这种自回归语言模型,其原因在本文范式转换部分有做分析。目前看,国内LLM在做这方面技术选型的时候,貌似很多都走了Bert双向语言模型或T5混合语言模型的技术路线,很可能方向走偏了。

第二,强大的推理能力是让用户认可LLM的重要心理基础,而如果希望LLM能够具备强大的推理能力,根据目前经验,最好在做预训练的时候,要引入大量代码和文本一起进行LLM训练。至于其中的道理,在本文前面相关部分有对应分析。

第三,如果希望模型参数规模不要那么巨大,但又希望效果仍然足够好,此时有两个技术选项可做配置:要么增强高质量数据收集、挖掘、清理等方面的工作,意思是我模型参数可以是ChatGPT/GPT 4的一半,但是要想达到类似的效果,那么高质量训练数据的数量就需要是ChatGPT/GPT 4模型的一倍(Chinchilla的路子);另外一个可以有效减小模型规模的路线是采取文本检索(Retrieval based)模型+LLM的路线,这样也可以在效果相当的前提下,极大减少LLM模型的参数规模。这两个技术选型不互斥,反而是互补的,也即是说,可以同时采取这两个技术,在模型规模相对比较小的前提下,达到超级大模型类似的效果。

第四,超级大模型因为模型规模大,所以训练成本过高,导致很少有机构有能力去做这件事。而且由上文分析可见,继续不断推大LLM模型规模是肯定会发生、也应该去做的事情。于是,如何通过技术手段降低LLM的训练成本就很重要。LLM的特征抽取器Sparse化是有效降低模型训练及推理成本的技术选择。由此可见,随着模型越来越大,LLM模型Sparse化是一个应该考虑的选项。

第五,ChatGPT是目前最接近理想LLM的技术方案,而理想中的LLM应该是以一个几乎无所不能的基础通用大模型作为依托,来支持各种各样的上层任务类型。目前看,支持越来越多的任务类型,主要是通过增加LLM预训练数据的多样性来达成的,数据多样性越好,LLM能够支持的任务类型就越丰富。所以,应该重视通过增加数据多样性来增加LLM新能力的思路。

第六,易用的人机操作接口。人类用他们自己习惯的表达方式来描述任务,而LLM要能够理解这些Instruct的真实含义。另外,也要注意这些Instruct是符合人类真实需求的,就是说,要从最终用户那里收集任务表述方式,而不能靠研发人员自己的臆想或猜测。ChatGPT给我最大的启发其实是这一点,至于是否用增强学习我倒觉得不重要,其它替代技术应该也能做类似的事情。

ChatGPT:为什么是OpenAI

为什么是OpenAI作出了ChatGPT,而不是其它机构呢?我们在这里可以做个简单分析。

在本文开头,我们提到了OpenAI看待LLM的理念。OpenAI是怎么看待LLM的呢?回顾它不断推出的技术,可以看出,它其实从GPT 1.0开始,基本就坚定地把LLM看做是通往AGI的一条必由之路。具体而言,在OpenAI眼中,未来的AGI应该长这个样子:有一个任务无关的超大型LLM,用来从海量数据中学习各种知识,这个LLM以生成一切的方式,来解决各种各样的实际问题,而且它应该能听懂人类的命令,以便于人类使用。其实对LLM发展理念的理解,在前半部分,就是“构建一个任务无关的超大型LLM,让它从海量数据中学习各种知识”,这一点几乎是大家的共识,能体现出OpenAI眼光的其实是后半部分。

OpenAI的理念比较超前,对自我定位从一开始就定得比较高,始终坚定不移地探索上述方式是否可以实现AGI。OpenAI之所以能作出ChatGPT,胜在一个是定位比较高,另一个是不受外界干扰,态度上坚定不移。

我们可以回顾下它走的一些关键路程:GPT 1.0走的是生成模式的自回归语言模型路线,比Bert出来的还早些。Bert证明了:双向语言模型对于很多NLP理解类任务,效果比自回归这种单向语言模型效果更好。尽管如此,GPT 2.0并没有因此切换到双向语言模型这条路上,仍然走文本生成的路,而且开始尝试零示例(zero shot)prompt和少量示例(few shot)prompt。其实这时候, OpenAI心目中的AGI已经开始浮出水面,逐渐显示出轮廓了。只是因为zero shot/few shot效果比Bert+fine-tuning差的比较远,所以大家都没太当回事,甚至不理解它为什么要始终坚持走单向语言模型的路线。这个时候,我估计即使是OpenAI自己,也不一定能确保这条路肯定能走通。

但是,这不妨碍它继续在这条路上往后走。GPT 3.0已经展示出了比较强大的zero shot/few shot prompt能力,这时候OpenAI心目中的AGI已经完全漏出水面,轮廓清晰,而且它的效果也证明了这条路,是有较大可能走得通的。GPT 3.0是一个决定LLM发展方向的叉路口和分水岭,与之对应的另外一条路是“Bert+fine-tuning”模式。在这个岔路口,不同的从业者选择走上了不同的道路,后面的技术差距也是从这里开始拉开的。很遗憾地是,国内很多从业者选择继续在“Bert+fine-tuning”这条路上往后走,这也是造成今天落后局面的一个关键时间节点。再往后,就是InstructGPT和ChatGPT了,OpenAI通过ChatGPT证明了一点;虽然我们距离真正的AGI,可能还有很长的路要走,但是通过超大LLM走向AGI这条路,目前看是可行的。

.....

#LLM~找不到推理错误

今年,大型语言模型(LLM)成为 AI 领域关注的焦点。LLM 在各种自然语言处理(NLP)任务上取得了显著的进展,在推理方面的突破尤其令人惊艳。但在复杂的推理任务上,LLM 的表现仍然欠佳。

那么,LLM 能否判断出自己的推理存在错误?最近,剑桥大学和 Google Research 联合开展的一项研究发现:LLM 找不到推理错误,但却能使用该研究提出的回溯(backtracking)方法纠正错误。LLM 找不到推理错误,但却能纠正错误!

  • 论文地址:https://arxiv.org/pdf/2311.08516.pdf
  • 数据集地址:https://github.com/WHGTyen/BIG-Bench-Mistake

这篇论文引起了一些争论,有人提出异议,比如在 Hacker News 上,有人评论这篇论文的标题言过其实,有些标题党。也有人批评说其中提出的校正逻辑错误的方法基于模式匹配,而非采用逻辑方法,这种方法其实容易失败。

Huang 等人在论文《Large language models cannot self-correct reasoning yet》中指出:自我校正或许是能有效地提升模型输出的风格和质量,但鲜有证据表明 LLM 有能力在没有外部反馈的情况下识别和纠正自身的推理和逻辑错误。比如 Reflexion 和 RCI 都使用了基本真值的纠正结果作为停止自我校正循环的信号。

剑桥大学和 Google Research 的研究团队提出了一种新思路:不再把自我校正看作一个单一过程,而是分成错误发现和输出校正两个过程:

  • 错误发现是一种基础推理技能,已经在哲学、心理学和数学领域得到了广泛的研究和应用,并催生了批判性思维、逻辑和数学谬误等概念。我们可以合理地认为发现错误的能力也应该是 对 LLM 的一项重要要求。但是,本文结果表明:当前最佳的 LLM 目前还无法可靠地发现错误。
  • 输出校正涉及部分或完全修改之前生成的输出。自我校正是指由生成输出的同一模型来完成校正。尽管 LLM 没有发现错误的能力,但本文表明:如果能提供有关错误的信息(如通过一个小型的监督式奖励模型),LLM 可以使用回溯方法校正输出。

本文的主要贡献包括:

  • 使用思维链 prompt 设计方法,任何任务都可以变成错误发现任务。研究者为此收集并发布了一个 CoT 类型的轨迹信息数据集 BIG-Bench Mistake,该数据集由 PaLM 生成,并标注了第一个逻辑错误的位置。研究者表示,BIG-Bench Mistake 在它的同类数据集中,是首个不局限于数学问题的数据集。
  • 为了测试当前最佳 LLM 的推理能力,研究者基于新数据集对它们进行了基准评测。结果发现,当前 SOTA LLM 也难以发现错误,即便是客观的明确的错误。他们猜测:LLM 无法发现错误是 LLM 无法自我校正推理错误的主要原因,但这方面还有待进一步研究。
  • 本文提出使用回溯方法来校正输出,利用错误的位置信息来提升在原始任务上的性能。研究表明这种方法可以校正原本错误的输出,同时对原本正确的输出影响极小。
  • 本文将回溯方法解释成了「言语强化学习」的一种形式,从而可实现对 CoT 输出的迭代式提升,而无需任何权重更新。研究者提出,可以通过一个经过训练的分类器作为奖励模型来使用回溯,他们也通过实验证明了在不同奖励模型准确度下回溯的有效性。

BIG-Bench Mistake数据集

BIG-Bench 由 2186 个 CoT 风格的轨迹信息集合组成。每个轨迹由 PaLM 2-L-Unicorn 生成,并标注了第一个逻辑错误的位置。表 1 展示了一个轨迹示例,其中错误位于第 4 步。

​编辑

这些轨迹来自 BIG-Bench 数据集中的 5 个任务:词排序、跟踪经过混洗的对象、逻辑推演、多步算术和 Dyck 语言。

他们使用 CoT prompt 设计法来调用 PaLM 2,使其解答每个任务的问题。为了将 CoT 轨迹分成明确的步骤,他们使用了论文《React: Synergizing reasoning and acting in language  models》中提出的方法,分开生成每一步,并使用了换行符作为停止 token。

在该数据集中,生成所有轨迹时,temperature = 0。答案的正确性由精确匹配决定。

基准测试结果 

表 4 报告了 GPT-4-Turbo、GPT-4 和 GPT-3.5-Turbo 在新的错误发现数据集上的准确度。

​编辑

对于每个问题,可能的答案有两种情况:要么没有错误,要么就有错误。如有错误,则数值 N 则会指示第一个错误出现的步骤。

所有模型都被输入了同样的 3 个 prompt。他们使用了三种不同的 prompt 设计方法:

  • 直接的轨迹层面的 prompt 设计 
  • 直接的步骤层面的 prompt 设计 
  • CoT 步骤层面的 prompt 设计

相关讨论

研究结果表明,这三个模型都难以应对这个新的错误发现数据集。GPT 的表现最好,但其在直接的步骤层面的 prompt 设计上也只能达到 52.87 的总体准确度。

这说明当前最佳的 LLM 难以发现错误,即使是在最简单和明确的案例中。相较之下,人类在没有特定专业知识时也能发现错误,并且具有很高的一致性。

研究者猜测:LLM 无法发现错误是 LLM 无法自我校正推理错误的主要原因。

prompt 设计方法的比较

研究者发现,从直接轨迹层面的方法到步骤层面的方法再到 CoT 方法,无错误的轨迹准确度显著下降。图 1 展示了这种权衡。

​编辑

研究者猜测其原因是模型生成的输出的数量。这三种方法涉及到生成越来越复杂的输出:直接的轨迹层面的 prompt 设计方法需要单个 token,直接的步骤层面的 prompt 设计方法每步需要一个 token,CoT 步骤层面的 prompt 设计每步需要多个句子。如果每次生成调用都有一定的概率识别出错误,那么对每条轨迹的调用越多,模型识别出至少一个错误的可能性就越大。

将错误位置作为正确性代理的少样本 prompt 设计

研究者探究了这些 prompt 设计方法能否可靠地决定一个轨迹的正确性,而不是错误位置。

他们计算了平均 F1 分数,依据为模型能否预测轨迹中是否存在错误。如果存在错误,则假设模型预测的是该轨迹是 incorrect_ans。否则就假设模型预测的是该轨迹是 correct_ans。

使用 correct_ans 和 incorrect_ans 作为正例标签,并根据每个标签的出现次数进行加权,研究者计算了平均 F1 分数,结果见表 5。

​编辑

这个加权 F1 分数表明,对于确定最终答案的正确性而言,通过 prompt 寻找错误是一个很糟糕的策略。

回溯

Huang 等人指出 LLM 无法在没有外部反馈的情况下自我校正逻辑错误。但是,在许多真实世界应用中,通常没有可用的外部反馈。

研究者在这项研究中采用了一种替代方案:用一个在少量数据上训练的轻量级分类器替代外部反馈。与传统强化学习中的奖励模型类似,这个分类器可以检测 CoT 轨迹中的任何逻辑错误,然后再将其反馈给生成器模型以提升输出。如果想要最大化提升,可以进行多次迭代。

研究者提出了一种简单的回溯方法,可以根据逻辑错误的位置来提升模型的输出:

  • 模型首先生成一个初始的 CoT 轨迹。在实验中,设置 temperature = 0。
  • 然后使用奖励模型确定轨迹中错误的位置。
  • 如果没有错误,就转向下一个轨迹。如果有错误,则再次向模型输入 prompt 以执行相同的步骤,但这一次 temperature = 1,生成 8 个输出。这里会使用同样的 prompt 以及包含错误步骤之前所有步骤的部分轨迹。
  • 在这 8 个输出中,过滤掉与之前的错误一样的选项。再从剩下的输出中选择对数概率最高的一个。
  • 最后,用新的重新生成的步骤替换之前步骤,再重新设置 temperature = 0,继续生成该轨迹的剩余步骤。

相比于之前的自我校正方法,这种回溯方法有诸多优势:

  • 新的回溯方法不需要对答案有预先的知识。相反,它依赖于有关逻辑错误的信息(比如来自训练奖励模型的信息),这可以使用奖励模型一步步地确定。逻辑错误可能出现在 correct_ans 轨迹中,也可能不出现在 incorrect_ans 轨迹中。 
  • 回溯方法不依赖于任何特定的 prompt 文本或措辞,从而可减少相关的偏好。 
  • 相比于需要重新生成整个轨迹的方法,回溯方法可以通过复用已知逻辑正确的步骤来降低计算成本。
  • 回溯方法可直接提升中间步骤的质量,这可能对需要正确步骤的场景来说很有用(比如生成数学问题的解),同时还能提升可解释性。       

研究者基于 BIG-Bench Mistake 数据集实验了回溯方法能否帮助 LLM 校正逻辑错误。结果见表 6。

​编辑

∆accuracy✓ 是指在原始答案是 correct_ans 时,在轨迹集合上的 accuracy_ans 之差。

∆accuracy✗ 则是对于 incorrect_ans 轨迹的结果。

这些分数结果表明:校正 incorrect_ans 轨迹的收益大于改变原本正确的答案所造成的损失。此外,尽管随机基准也获得了提升,但它们的提升显著小于使用真正错误位置时的提升。注意,在随机基准中,涉及步骤更少的任务更可能获得性能提升,因为这样更可能找到真正错误的位置。

为了探索在没有好的标签时,需要哪种准确度等级的奖励模型,他们实验了通过模拟的奖励模型使用回溯;这种模拟的奖励模型的设计目标是产生不同准确度等级的标签。他们使用 accuracy_RM 表示模拟奖励模型在指定错误位置的准确度。

当给定奖励模型的 accuracy_RM 为 X% 时,便在 X% 的时间使用来自 BIG-Bench Mistake 的错误位置。对于剩余的 (100 − X)%,就随机采样一个错误位置。为了模拟典型分类器的行为,会按照与数据集分布相匹配的方式来采样错误位置。研究者也想办法确保了采样的错误位置与正确位置不匹配。结果见图 2。

​编辑

可以看到 ∆accuracy✓ 的损失在 65% 时开始趋于稳定。事实上,对于大多数任务,在 accuracy_RM 大约为 60-70% 时,∆accuracy✓ 就已经大于 ∆accuracy✗ 了。这表明尽管更高的准确度能得到更好的结果,但即便没有黄金标准的错误位置标签,回溯也依然有效。

.....

#LLM~推理优化

近日,CMU Catalyst 团队推出了一篇关于高效 LLM 推理的综述,覆盖了 300 余篇相关论文,从 MLSys 的研究视角介绍了算法创新和系统优化两个方面的相关进展。

在人工智能(AI)的快速发展背景下,大语言模型(LLMs)凭借其在语言相关任务上的杰出表现,已成为 AI 领域的重要推动力。然而,随着这些模型在各种应用中的普及,它们的复杂性和规模也为其部署和服务带来了前所未有的挑战。LLM 部署和服务面临着密集的计算强度和巨大的内存消耗,特别是在要求低延迟和高吞吐量的场景中,如何提高 LLM 服务效率,降低其部署成本,已经成为了当前 AI 和系统领域亟需解决的问题。

来自卡内基梅隆大学的 Catalyst 团队在他们的最新综述论文中,从机器学习系统(MLSys)的研究视角出发,详细分析了从前沿的 LLM 推理算法系统的革命性变革,以应对这些挑战。该综述旨在提供对高效 LLM 服务的当前状态和未来方向的全面理解,为研究者和实践者提供了宝贵的洞见,帮助他们克服有效 LLM 部署的障碍,从而重塑 AI 的未来。

论文链接:https://arxiv.org/abs/2312.15234

该论文的第一作者是卡内基梅隆大学的 Xupeng Miao(苗旭鹏)博士后研究员,合作者还包括 Tianqi Chen 和 Zhihao Jia 助理教授。此外,其他学生作者也均来自于 CMU Catalyst Group 实验室,该实验室由 Zhihao Jia 与 Tianqi Chen(陈天奇)在 CMU 共同主持,致力于集成来自于机器学习算法、系统、硬件等多方面的优化技术,构造自动化的机器学习系统。此前,该实验室还推出了 SpecInfer, MLC-LLM, SpotServe [ASPLOS‘24] 等开源项目,推进 LLM 大模型相关系统的研究和应用。实验室主页:https://catalyst.cs.cmu.edu。

综述概览

该综述系统地审视了现有 LLM 推理技术,覆盖了 300 余篇相关论文,从算法创新系统优化两个方面展开介绍。论文以此为基础,对现有工作设计了一套清晰且详尽的分类法,突出了各种方法的优势和局限性,逐类别搜集整理并介绍了每种方法的相关论文。除此之外,论文还对当前的主流 LLM 推理框架在系统设计与实现方面进行了深入的对比和分析。最后,作者对未来如何继续提高 LLM 推理效率进行了展望,在技术层面提出了六大潜在发展方向

分类法

​编辑

算法创新

这一节对提出的各种算法和技术进行了全面分析,旨在改进大规模 Transformer 模型推理的原生性能缺陷,包括解码算法架构设计、和模型压缩等等。

​编辑

解码算法:在这一部分中,我们回顾了在图 2 中展示的几种 LLMs 推理优化过程的新颖解码算法。这些算法旨在减少计算复杂度,并提高语言模型推理在生成任务中的总体效率,包括:

  • 非自回归解码:现有 LLMs 的一个主要限制是默认的自回归解码机制,它逐个顺序生成输出 token。为解决这一问题,一种代表性的工作方向是非自回归解码 [97, 104, 108,271],即放弃自回归生成范式,打破单词依赖并假设一定程度的条件独立性,并行解码输出 token。然而,尽管这类方法解码速度有所提高,但大多数非自回归方法的输出质量仍不如自回归方法可靠。
  • 投机式推理:另一类工作是通过投机执行思想 [47] 实现并行解码。自回归 LLM 推理过程中的每个解码步骤都可以被视为带有条件分支的程序执行语句,即决定接下来生成哪个 token。投机式推理 [51, 155] 先使用较小的草稿模型进行多步解码预测,然后让 LLM 同时验证这些预测以实现加速。然而,将投机解码应用于 LLMs 时仍然存在一些实际挑战,例如,如何使解码预测足够轻量且准确,以及如何借助 LLMs 实现高效的并行验证。SpecInfer [177] 首次引入基于 tree-based speculative decoding 和 tree attention,并提出了一个低延迟 LLM 服务系统实现,该机制也被后续多个工作 [48, 118, 168, 185, 229, 236, 274, 310] 直接采用。
  • 提前退出:这类方法主要利用 LLMs 的深层多层结构,在中间层提前推出推理,中间层输出可以通过分类器转化成输出的 token,从而降低推理开销 [117, 147, 163, 167, 234, 272, 282, 291, 308],它们也被称为自适应计算 [68, 219]。
  • 级联推理:这类方法级联了多个不同规模的 LLM 模型,用于分别处理不同复杂度的推理请求,代表性工作包括 CascadeBERT [157] 和 FrugalGPT [53]。

架构设计:

  • 配置缩小:直接缩小模型配置。
  • 注意力简化:最近出现了很多研究工作,它们主要是将之前的长序列高效注意力机制 [240] 应用在 LLM 上,以缩短上下文,减少 KV 缓存,以及注意力复杂度,同时略微降低解码质量(如滑动窗口 [129, 299]、哈希 [198]、dilated [74]、动态选择等等)。表 1 中总结了一些近期的热门方法和之前的工作之间的对应关系。

​编辑

  • 激活共享:这类方法主要是通过共享 attention 计算的中间激活来降低推理内存开销,代表性工作包括 MQA [220] 和 GQA [32]。
  • 条件计算:这类方法主要是指稀疏专家混合模型(Sparse MoE),比如最近大火的 Mistrial 7Bx8 模型就属于此类。
  • 循环单元:尽管 Transformer 已经替代了 RNN 模型,但考虑到注意力机制的二次复杂性,人们始终未曾放弃将 recurrent unit 机制重新引入 LLM 的尝试,比如 RWKV [200]、RetNet [235],以及状态空间模型 [91, 102, 103, 176] 等等。

模型压缩:

  • 知识蒸馏:这类方法以大型的教师模型为监督,训练一个小型的学生模型。大多数之前的方法都在探索白盒蒸馏 [106, 133, 214, 233, 255],需要访问整个教师模型的参数。由于基于 API 的 LLM 服务(如 ChatGPT)的出现,一些黑盒蒸馏模型吸引了很多关注 [238,59, 273, 201, 313],这些模型通常具有更少的模型参数,与原始 LLMs(如 GPT-4 [195])相比,在各种下游任务上表现出了相当的性能。   
  • 网络剪枝:过去几年中,网络剪枝方法 [180, 215, 215] 已被广泛研究,但并非所有方法都可以直接应用于 LLMs,需要考虑重新训练可能带来的过高计算成本,以及评估剪枝是否可以在底层系统实现上取得效率提升。大致上可以分为结构化剪枝 [80, 149, 174, 216, 172] 和半结构化稀疏化 [40, 87, 232, 251, 276] 等。

系统优化

本节研究 LLM 推理系统优化技术,以加速 LLM 推理,而不改变 LLM 计算语义。这一工作的目标是通过改进用于大型语言模型推理的底层系统和框架来提高系统效率,包括低比特量化、并行计算、内存管理、请求调度、和内核优化等等,详细内容可以参见论文原文。

软件框架

​编辑

论文还对一些目前最先进的基于 GPU 的开源 LLM 推理系统进行了深入的分析,并从多个方面总结了它们在设计与实现伤的差异。

未来方向

  • 专用硬件加速器的发展:生成型 LLM 服务效率的显著提升可能在很大程度上依赖于专用硬件加速器的发展和提升,尤其是软硬协同设计方法。例如,让内存单元更加接近处理单元,或是针对 LLM 算法数据流优化芯片架构,这些硬件优化可以在很大程度上为 LLM 推理在软件层面带来便利和机会。
  • 高效有效的解码算法:开发更高效的解码算法可以显著提高服务效率。受对实时应用更快生成速度的需求驱动,一个有前途的方向是广义的投机式推理(generalized speculative inference),不仅会带来显著加速,同时保持相同的生成质量。正如 SpecInfer 中所指出的,广义的投机式推理中,用于生成草稿 token 的小模型可以被替换为任何快速的 token 生成方法,比如自定义函数、召回方法、甚至早停机制和非自回归解码等等。
  • 长上下文 / 序列场景优化:随着应用场景变得更加复杂,处理更长的上下文或序列的需求不断增长。服务长序列负载的 LLM 需要解决算法和系统两方面的挑战。在算法方面,它们依然面临长度泛化失效问题,甚至可能出现 “loss in the middle” 的情况。目前的解法主要是通过召回增强、序列压缩和缓存来尽可能缩短序列长度并保存相关信息。
  • 探索替代基础架构:尽管 Transformer 模型和自注意力机制目前主导着 LLM 领域,但探索替代架构是未来研究的一个有前景的方向。例如,一些最新研究探索了无注意力方法,使用纯 MLP(多层感知机)架构来替代注意力机制,可能会改变目前 LLM 推理优化的格局。
  • 在复杂环境中的部署探索:随着 LLM 应用的扩展,探索并优化它们在各种复杂环境中的部署成为一个关键的未来方向。这一探索不仅限于传统的基于云的部署,还包括边缘计算、混合计算(cloud+edge)、去中心化计算以及廉价的可抢占资源等。
  • 特定需求的自动适应:应用特定需求的多样性创造了一系列创新的 LLM 服务优化机会,例如模型微调(parameter-efficient fine-tuning)、向量数据库检索、多模态负载等等。这些独特的挑战也要求将 LLM 服务技术自动且顺利地集成到现有 IT 基础设施中,将优化空间扩展到整个 LLM 生命周期。

总结

总的来说,该综述不仅是对当前 LLM 服务优化研究的全面概述,也为未来在这一领域的探索和发展指明了方向。通过深入了解这些先进的解决方案,研究者和实践者可以更好地理解和应对在实际应用中部署大型语言模型时面临的挑战。

,.....

#LLM-Shearing

手把手教你剪「羊驼」,陈丹琦团队提出LLM-Shearing大模型剪枝法,给 Llama 2(羊驼)大模型剪一剪驼毛,会有怎样的效果呢?今天普林斯顿大学陈丹琦团队提出了一种名为 LLM-Shearing 的大模型剪枝法,可以用很小的计算量和成本实现优于同等规模模型的性能。

自大型语言模型(LLM)出现以来,它们便在各种自然语言任务上取得了显著的效果。不过,大型语言模型需要海量的计算资源来训练。因此,业界对构建同样强大的中型规模模型越来越感兴趣,出现了 LLaMA、MPT 和 Falcon,实现了高效的推理和微调。

这些规模不等的 LLM 适用于不同的用例,但从头开始训练每个单独的模型(即使是 10 亿参数小模型)还是需要大量计算资源,这对于大多数科研机构而言仍是很大的负担。

因此在本文中,普林斯顿大学陈丹琦团队试图解决以下问题:能否利用现有预训练 LLM 来构建一个规模更小、通用且在性能上有竞争力的 LLM,同时比从头开始训练需要的计算量少得多?

研究者探索利用结构化剪枝来实现目标。这里的问题是,对于通用 LLM,剪枝后的模型会出现性能下降,尤其是在剪枝后没有大量计算投入的情况。他们使用的高效剪枝方法可以用来开发规模更小但仍具有性能竞争力的 LLM,并且与从头开始训练相比,训练需要的计算量也大大减少。

  • 论文地址: https://arxiv.org/abs/2310.06694
  • 代码地址: https://github.com/princeton-nlp/LLM-Shearing
  • ModelsSheared-LLaMA-1.3B, Sheared-LLaMA-2.7B

在对 LLM 进行剪枝之前,研究者确定了两个关键技术挑战,一是如何确定最终的性能强大、推理高效的剪枝结构?LLM 目前的结构化剪枝技术没有指定的目标结构,导致剪枝后模型在性能和推理速度方面不理想;二是如何继续预训练剪枝后的模型以达到预期性能?他们观察到,与从头开始训练模型相比,使用原始预训练数据来训练会导致不同域出现不同的损失减少。

针对这两个挑战,研究者提出了「LLM - shearing」算法。这种新颖的剪枝算法被称为「定向结构化剪枝」,它将源模型剪枝为指定的目标架构,该结构通过现有预训练模型的配置来确定。他们表示,该剪枝方法在源模型中搜索子结构,并在资源受限的情况下最大程度地保持性能。此外设计一种动态批量加载算法,它能根据损失减少率按比例加载每个域的训练数据,从而高效利用数据并加速整体性能的提升。

最终,研究者将 LLaMA2-7B 模型剪枝成了两个较小的 LLM,分别是 Sheared-LLaMA-1.3B 和 Sheared-LLaMA-2.7B,证实了其方法的有效性。

​编辑

他们仅仅使用 500 亿个 token(即 OpenLLaMA 预训练预算的 5%)进行剪枝和继续预训练,但对于 11 个代表性下游任务(如常识、阅读理解和世界知识)以及开放式生成的指令调整,这两个模型的性能仍然优于其他同等规模的流行 LLM,包括 Pythia、INCITE 和 OpenLLaMA。

​编辑

不过要提到一点,在这篇论文发布 Sheared-LLaMA-3B 的时候,最强 3B 开源模型的纪录已经被 StableLM-3B 打破了。

此外,下游任务性能轨迹表明,使用更多 token 来进一步训练剪枝后的模型,将带来更大的收益。研究者只对最多 70 亿参数的模型进行了实验,但 LLM-shearing 具有高度通用性,可以在未来的工作中扩展到任何规模的大型语言模型。

方法介绍

给定一个现有的大模型 M_S(源模型),本文目标是研究如何有效地生成一个更小、更强的模型 M_T(目标模型)。该研究认为这需要两个阶段来完成:

  • 第一阶段将 M_S 剪枝为 M_T,虽然这样减少了参数数量,但不可避免地导致性能下降;
  • 第二阶段持续预训练 M_T,使其性能更强。

结构化剪枝

结构化剪枝可以去除模型大量参数,从而达到压缩模型并加速推理的效果。然而,现有的结构化剪枝方法会导致模型偏离常规架构的配置。例如 CoFiPruning 方法产生的模型具有不统一的层配置,与标准的统一层配置相比,这样会产生额外的推理开销。

本文对 CoFiPruning 进行了扩展,以允许将源模型剪枝为指定的任何目标配置。例如,本文在生成 2.7B 模型时使用 INCITE-Base-3B 架构作为目标结构。

此外,本文还在不同粒度的模型参数上学习一组剪枝掩码( pruning mask),掩码变量如下所示:

​编辑

每个掩码变量控制是否剪枝或保留相关的子结构。例如,如果对应的 z^layer= 0,则需要删除这个层。下图 2 说明了剪枝掩码如何控制被剪枝的结构。

​编辑

剪枝之后,本文通过保留与每个子结构中的掩码变量相关的最高得分组件来最终确定剪枝后的架构,并继续使用语言建模目标对剪枝后的模型进行预训练。

动态批量加载

该研究认为对剪枝后的模型进行大量预训练是很有必要的,这样才能恢复模型性能。

受其他研究的启发,本文提出了一种更有效的算法,即动态批量加载,其可以根据模型性能简单地动态调整域比例。算法如下:

​编辑

实验及结果

模型配置:本文将 LLaMA2-7B 模型作为源模型,然后进行结构化剪枝实验,他们将 LLaMA2-7B 压缩成两个较小的目标尺寸 2.7 B 和 1.3B 参数,并将剪之后的模型与相同尺寸的模型进行了性能比较,包括 OPT-1.3B、Pythia-1.4B、OPT-2.7B、 Pythia-2.8B、INCITE-Base-3B、OpenLLaMA-3B-v1、OpenLLaMA-3B-v2。表 8 总结了所有这些模型的模型体系结构细节。

​编辑

数据:由于 LLaMA2 的训练数据并不是公开访问的,因此本文使用了 RedPajama 数据集 。表 1 提供了本文模型和基线模型使用的预训练数据。

​编辑

训练:研究者在所有实验中最多使用了 16 个 Nvidia A100 GPU (80GB)。

SHEARED-LLAMA 优于同等大小的 LM

本文表明,Sheared-LLaMA 明显优于现有的类似规模的 LLM,同时只使用一小部分计算预算来从头开始训练这些模型。

下游任务:表 2 展示了 Sheared-LLaMA 和类似大小的现有预训练模型的零样本和少样本在下游任务上的性能。

​编辑

指令调优:如图 3 所示,与同等规模的所有其他预训练模型相比,指令调优的 Sheared-LLaMA 实现了更高的获胜率。

​编辑

图 4 显示了 INCITEBase-3B 模型开始时的精度要高得多,但其性能在持续的预训练过程中趋于稳定。

​编辑

分析

最后,研究者对本文方法的优势进行了分析。

动态批量加载的有效性

其中,研究者从以下三个方面的影响来分析动态批量加载的有效性:(1) 跨域的最终 LM 损失,(2) 整个训练过程中每个域的数据使用情况,(3) 下游任务性能。结果均基于 Sheared-LaMA-1.3B 算法。

跨域损失差异。动态批量加载的目的是平衡各域的损失降低率,使损失在大致相同的时间内达到参考值。图 5 中绘制了模型损耗(原始批量加载和动态批量加载)与参考损耗之间的差异,相比之下,动态批量加载能均匀地减少损失,各域的损失差异也非常相似,这表明数据使用效率更高。

​编辑

数据使用情况。表 3 对比了 RedPajama 的原始数据比例和动态加载的域数据使用情况(图 7 展示了整个训练过程中域权重的变化)。与其他域相比,动态批量加载增加了 Book 和 C4 域的权重,这表明这些域更难恢复剪枝模型。 

​编辑

下游性能。如图 6 所示,与在原始 RedPajama 分布上训练的模型相比,使用动态批量加载训练的剪枝模型获得了更好的下游性能。这表明,动态批量加载所带来的更均衡的损失减少可以提高下游性能。

​编辑

与其他剪枝方法的对比

此外,研究者将 LLM-shearing 方法与其他剪枝方法进行了比较,并报告了验证困惑度,它是衡量整体模型能力的一个有力指标。

由于计算上的限制,下面的实验控制了所有比较方法的总计算预算,而不是将每种方法运行到最后。

如表 4 所示,在相同稀疏度下,本文的目标剪枝模型的推理吞吐量比非均匀剪枝 CoFiPruning 模型更高,但困惑度略高。

​编辑

其他分析

表 5 显示,在控制 token 总量的情况下,增加剪枝开销可以持续改善困惑度。然而,由于剪枝比持续的预训练更昂贵,研究者将 0.4B 的 token 分配给剪枝。

​编辑

.....

#LLMの推理过程

以LLAMA为例

本文借助llama这个模型快速入门LLM的推理过程,技术细节很多都是通用的,也适合其他的LLM。本篇也可以作为CV快速入门NLP简易指南。

这篇也算是总结的性质,仅需要的知识点都列出来,简单介绍一番。可能说的不够详细,想看具体细节的小伙伴可参考文中引用的相关详细文章。

本篇也可以作为CV快速入门NLP简易指南(当然也是老潘的个人笔记)。当然只是推理哈,不涉及到训练,仅是从部署的角度考虑这个模型,个人也不是NLP专家,如对文中内容有疑问,欢迎讨论。

什么是LLM

llama是LLM(LLM指大语言模型)模型的一种结构,llama这种模型的任务就是在阅读前n个单词后预测句子中下一个单词,输出取决于过去和现在输入,与未来无关。

过程大概是这样,每次输入模型会带上上一次输出的结果(不同于CV模型,CV模型输入只需要一次即可,一次推理即可得到结果):

​编辑

LLM-生成

一般来说,LLM模型主要由两个块组成:

  • 编码器(左侧):编码器接收输入并构建其表示形式(特征)。这意味着该模型被优化为从输入中获取理解(比如输入文本判断这段话是高兴还是难受)。
  • 解码器(右侧):解码器使用编码器的表示形式(特征)以及其他输入来生成目标序列。这意味着该模型被优化用于生成输出。

​编辑

编码器和解码器

这些部分都可以根据任务独立使用:

  • Encoder-only models:适用于需要理解输入的任务,例如句子分类和命名实体识别。
  • Decoder-only models:适用于生成性任务,如文本生成
  • Encoder-decoder models or sequence-to-sequence models:适用于需要输入的生成性任务,例如翻译或摘要。whao~开发板商城~aiot物联网设备

LLAMA属于Decoder-only models,只有decoder层。

llama相关的知识点

llama的decoder部分的结构取自transformer,关于transformer的介绍知乎上太多,这里不赘述了,想详细理解transformer的建议看知乎上的其他优秀文章以及b站沐神的讲解

transformer的结构如下:

​编辑

transformer

对于llama来说,只用了decoder部分,重点关注这几个概念:

  • Tokenization 分词器
  • Embedding 嵌入层
  • Positional Encoding 位置编码
  • Self-attention 自注意力机制
  • Multi-head attention 多头注意力与采用掩码机制的多头注意力
  • Batch Norm & Layer Norm 批标准化/层标准化   llama用的是RMSNorm
  • ResNet 残差网络

具体的可以查阅相关的博文,这里同样不进行赘述,只会简单描述下过程。拿到llama模型后,主要关注:

  • 模型的结构,包含哪些算子哪些op,模型复杂度
  • 模型的前后处理,前后处理实现细节,模型的执行方式
  • 模型各种参数配置以及其他一些细节

llama的整个执行过程分了好多步,涉及到很多前后处理,前后处理又涉及到很多细节的步骤,之后也会介绍。

先过一下知识点。

分词器、token、embeding

主要是分词、编码、Tokenizer(tokenization)、embed(embedding)的过程。

​编辑

分词过程

什么是分词?也就是Tokenizer的作用。

分词器可将原始文本转换为由token组成的文本的初始数值表征。

分词器之所以是模型的重要构成部分之一,是因为模型可借此妥善应对人类语言的复杂性。例如,分词器可将凝集性语言中的词分解为更易管理的组成部分、处理原始语料库中不存在的新词或外来词/特殊字符,并确保模型生成紧凑(尽可能精简)的文本表征。

每种语言都有可选用的数量众多且各不相同的分词器。大部分基于 Transformer 的架构均使用经过训练的分词器,这些分词器旨在充分缩短典型序列的长度。WordPiece(应用于 BERT)、SentencePiece(应用于 T5 或 RoBerta)等分词器同样具有多个变体,这是因为众多的语言和不同的专业领域(如医疗健康语料库)采用了这些分词器,且这些分词器在训练方式上也有所不同(选用不同的最大标记数,或以不同的方式处理大小写)。

然后看回代码。

首先看tokenizer,运行llama的时候我们会调用​​tokenizer = AutoTokenizer.from_pretrained(args.model, use_fast=False)​​。

如果我们模型传入的是llama的某个模型(llama-7b)那么返回的就是LlamaTokenizer:

class LlamaTokenizer(PreTrainedTokenizer):  
    """  
    Construct a Llama tokenizer. Based on byte-level Byte-Pair-Encoding.  
...

这个类是LLAMA模型的分词器(tokenizer)的实现,基于字节级的字节对编码(Byte-Pair Encoding)。这个分词器的主要功能是将文本字符串转换为模型可以理解的数字序列,反之亦然。这里假设输入为 '"this is a python code:"'  。

具体我们看干了些啥,创建好tokenizer之后我们执行:​​input_ids = tokenizer.encode(args.text, return_tensors="pt").to(dev)​​,这里又分两步:

  • 第一步是 Converts a string in a sequence of tokens (string), using the tokenizer. Split in words for word-based vocabulary or sub-words for sub-word-based vocabularies (BPE/SentencePieces/WordPieces). 这里调用 ​​self.sp_model.encode(text, out_type=str)​​,sp_model就是sentencepiece中的一个函数,执行完出来变为  ['▁"', 'this', '▁is', '▁a', '▁python', '▁code', ':"']
  • 第二步将token string转变为 token id  -> Converts a token string (or a sequence of tokens) in a single integer id (or a sequence of ids), using the vocabulary. 具体就是个for循环,对之前分好的tokens一个一个转。

最后我们得到:

input_ids  
tensor([[   0,  376, 1366,  338,  263, 3017,  775, 6160]], device='cuda:0')  
input_ids.shape  
torch.Size([1, 8])

至于如何转换为embedding,之后会调用:

​inputs_embeds = self.embed_tokens(input_ids)​​​,其中embeds的shape是​​torch.Size([1, 8, 4096])​​。

在自然语言处理(NLP)中,嵌入(Embedding)是一种将离散变量(如单词、短语、或者文档)转换为连续向量的方法。这种转换的目的是让计算机能更好地理解和处理自然语言数据。embedding矩阵的本质是一个查找表 ,每个单词会定位这个表中的某一行,而这一行就是这个单词学习到的在嵌入空间的语义。

自注意力 Self-Attention

Transformer模型的一个关键特点是使用了称为注意力层的特殊层。“Attention Is All You Need”。

这一层会告诉模型,在处理每个单词的表示时,要对你传递给它的句子中某些单词特别关注(并且忽略其他单词)。

把这个概念放到背景中,考虑从英语翻译成法语的任务。给定输入“你喜欢这门课程”,翻译模型还需要关注相邻单词“你”,以获得正确翻译动词“like”的形式,因为在法语中,“like”根据主语不同而有不同变化形式。然而,句子其余部分对于该单次翻转来说没有用处。同样地,在翻转“This”时,模型也需要注意到单次“course”,因为“This”根据相关名字是否男性或女性而有不同意思表达方式。再次强调,在更复杂的句子(和更复杂的语法规则)中,模型需要特别关注可能出现在句子较远位置上以正确翻译每个单次的单词。

同样的概念适用于与自然语言相关的任何任务:一个单次本身具有意义,但是该意义深受上下文影响,这可以是正在研究的单次之前或之后的任何其他单次(或多个)。

​编辑

Self-attention是Transformer的核心,其允许模型考虑到序列中的其他标记,以便更好地理解每个标记的上下文。每个标记的新表示形式是由它自己和其他标记的交互得到的。

位置编码

由于Transformer的结构没有考虑到标记的顺序,所以我们需要加入位置编码来给模型提供词元在序列中的位置信息。这些编码会被添加到词嵌入向量中。

多头注意力(Multi-head Attention)

多头注意力是对自注意力机制的扩展。它将自注意力分解为多个“头”,每个头在不同的表示空间中学习和应用自注意力。这允许模型同时捕捉到各种不同类型的信息。在有掩码的多头注意力中,掩码被用于阻止模型查看某些不应该看到的信息,例如在生成新的标记时阻止查看未来的信息。现在基本都使用MHA,一般不用单头。

批标准化(Batch Norm) & 层标准化(Layer Norm)

这些都是用于正规化激活的技术,可以加速学习,提高模型的性能。

批标准化是在整个批次的数据上进行标准化,而层标准化则是在单个数据样本上进行标准化。RMSNorm是一种新的归一化方法,是对LayerNorm的一个改进,没有做re-center操作(移除了其中的均值项),可以看作LayerNorm在均值为0时的一个特例。

残差网络(ResNet)

老熟人了。通过在网络中添加跳跃连接(或称为“skip”连接),可以使得模型更容易地学习到恒等映射,从而避免了训练深度网络时常见的梯度消失问题。在Transformer中,每个子层(如自注意力层和前馈神经网络层)都有一个对应的残差连接,并且每个子层的输出都会进行层标准化。

LLAMA的模型结构

我们可以很轻易的通过huggingface代码库中看到llama的模型结构。

以hugging库中的7B模型为例,运行​​model = LlamaForCausalLM.from_pretrained(model, torch_dtype='auto')​​后,可以通过print看模型结构:

LlamaForCausalLM(  
  (model): LlamaModel(  
    (embed_tokens): Embedding(32000, 4096, padding_idx=31999)  
    (layers): ModuleList(  
      (0-31): 32 x LlamaDecoderLayer(  
        (self_attn): LlamaAttention(  
          (q_proj): Linear(in_features=4096, out_features=4096, bias=False)  
          (k_proj): Linear(in_features=4096, out_features=4096, bias=False)  
          (v_proj): Linear(in_features=4096, out_features=4096, bias=False)  
          (o_proj): Linear(in_features=4096, out_features=4096, bias=False)  
          (rotary_emb): LlamaRotaryEmbedding()  
        )  
        (mlp): LlamaMLP(  
          (gate_proj): Linear(in_features=4096, out_features=11008, bias=False)  
          (down_proj): Linear(in_features=11008, out_features=4096, bias=False)  
          (up_proj): Linear(in_features=4096, out_features=11008, bias=False)  
          (act_fn): SiLUActivation()  
        )  
        (input_layernorm): LlamaRMSNorm()  
        (post_attention_layernorm): LlamaRMSNorm()  
      )  
    )  
    (norm): LlamaRMSNorm()  
  )  
  (lm_head): Linear(in_features=4096, out_features=32000, bias=False)  
)

7B有32个​​LlamaDecoderLayer​​,每个Decoder包含一个LlamaAttention和LlamaMLP,然后是LlamaRMSNorm和head部分,核心的结构是transformer。

先看核心的​​LlamaDecoderLayer​​,7B有32个,而30B的话有60个,30B和7B的差别也就是decoder的个数和decoder的不同配置。

​编辑

7B-config和30B-config区别

看下7B模型的config,可以看到模型类型为float16,use_cache设置为true

{  
    "architectures": [  
        "LLaMAForCausalLM"  
    ],  
    "bos_token_id": 0,  
    "eos_token_id": 1,  
    "hidden_act": "silu",  
    "hidden_size": 4096,  
    "intermediate_size": 11008,  
    "initializer_range": 0.02,  
    "max_sequence_length": 2048,  
    "model_type": "llama",  
    "num_attention_heads": 32,  
    "num_hidden_layers": 32,  
    "pad_token_id": 0,  
    "rms_norm_eps": 1e-06,  
    "torch_dtype": "float16",  
    "transformers_version": "4.27.0.dev0",  
    "use_cache": true,  
    "vocab_size": 32000  
}

运行pipeline

llama的运行流程和大多数的LLM一样,流程如下:     

  • 分词encode,我理解相当于预处理
  • 输入input_ids后模型开始运行,这里会for循环运行好多次
  • 运行完后得到logits进行后处理预测下一个token
  • 循环往复直到所有要求数量的token都输出或者输出遇到了end_id

​编辑

LLM整体流程图,来自huggingface

再回顾下LLAMA的模型组成:

class LlamaModel(LlamaPreTrainedModel):  
    def __init__(self, config: LlamaConfig):  
        super().__init__(config)  
        self.padding_idx = config.pad_token_id  
        self.vocab_size = config.vocab_size  
        self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx)  
        self.layers = nn.ModuleList([LlamaDecoderLayer(config) for _ in range(config.num_hidden_layers)])  
        self.norm = LlamaRMSNorm(config.hidden_size, eps=config.rms_norm_eps)  
        self.gradient_checkpointing = False  
        # Initialize weights and apply final processing  
        self.post_init()  
    ...

主要部分就三个:

  • embed_tokens   嵌入层,它将输入的词(或者词的索引)映射到一个高维空间中,使得具有相似语义的词在这个空间中的距离更近。这个嵌入层在训练过程中会被优化,以更好地捕捉单词之间的语义关系。这里使用的嵌入层是一个 PyTorch 的 nn.Embedding 层,它将大小为 config.vocab_size 的词汇表中的每个词映射到一个 config.hidden_size 维的向量。self.padding_idx 是用于指定在嵌入矩阵中,哪个索引是用于填充的,通常这个填充索引对应的嵌入向量会被初始化为零,并在训练过程中保持为零
  • layers   num_hidden_layers个解码器
  • norm   RMSNorm 归一化函数,和layernorm类似

第一步 分词

调用​​tokenizer.encode(args.text, return_tensors="pt").to(DEV)​​,具体流程如下:

输入prompt -> '"this is a python code:"'

->  ['▁"', 'this', '▁is', '▁a', '▁python', '▁code', ':"']

->  [376, 1366, 338, 263, 3017, 775, 6160]

-> {'input_ids': tensor([[   0,  376, 1366,  338,  263, 3017,  775, 6160]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1]])}

-> 然后开始generation,开始第二步

第二步 配置

主要就是配置一些信息和做预处理。

设置min_length、max_length、top_p、temperature进入model.generate

-> 处理、配置、验证generation_config 设置 generation parameters

-> 设置模型的输入

inputs_tensor, model_input_name, model_kwargs = self._prepare_model_inputs(   
            inputs, generation_config.bos_token_id, model_kwargs   
        )

bos_token_id 是 0, inputs就是刚才传过来的input_ids。

-> 设置模型的参数

model_kwargs["attention_mask"] = self._prepare_attention_mask_for_generation(   
                inputs_tensor, generation_config.pad_token_id, generation_config.eos_token_id   
            )

其中 pad_token_id为 -1,eos_token_id为1,一般从config中获取。

-> 设置 max_length -> input_ids_seq_length = input_ids.shape[-1]

-> 决定generation mode

-> prepare distribution pre_processing samplers

-> prepare stopping criteria

-> 设置好 sample_gen_mode、 prepare logits warper

-> expand input_ids with num_return_sequences additional sequences per batch

​编辑

设置好的模型参数

第三步 sample

继续准备模型的输入,调用​​self.prepare_inputs_for_generation​​(如果提供了past_key_values,那么input_ids = input_ids[:, -1:],同时根据attention_mask和是否提供past_key_values计算出position_ids,也就是说提供了past_key_values的话,input_ids可以少计算很多)返回model_inputs。

​编辑

model_inputs

-> 开始进入auto-regressive generation的循环,是个while True

进入LlamaForCausalLM函数,输入刚才的​​model_inputs​​,主要包含input_ids、attention_mask、position_ids、past_key_values

-> 根据是否存在 past_key_values 更新 past_key_values_length 和 seq_length_with_past

-> 判断inputs_embeds是否存在判断是否需要调用​​self.embed_tokens​​,也就是说如果自己提供了embeds就不需要在这里单独embed了

这里调用 inputs_embeds = self.embed_tokens(input_ids),embeds的shape是​​torch.Size([1, 8, 4096])​​,8代表输入input_ids的长度,我这里是8

-> 调用_prepare_decoder_attention_mask函数,调用后的attention_mask维度为​​torch.Size([1, 1, 8, 8])​​:

attention_mask = self._prepare_decoder_attention_mask(  
            attention_mask, (batch_size, seq_length), inputs_embeds, past_key_values_length  
        )

进入一个for循环,  因为llama有很多​​self.layers = nn.ModuleList([LlamaDecoderLayer(config) for _ in range(config.num_hidden_layers)])​​​,都是一模一样的​​LlamaDecoderLayer​​:

for idx, decoder_layer in enumerate(self.layers):  
    if output_hidden_states:  
        all_hidden_states += (hidden_states,)  
    # 如果存在past_key_value,则一并传进去  
    past_key_value = past_key_values[idx] if past_key_values is not None else None  
    layer_outputs = decoder_layer(  
        hidden_states,  
        attention_mask=attention_mask,  
        position_ids=position_ids,  
        past_key_value=past_key_value,  
        output_attentions=output_attentions,  
        use_cache=use_cache  
    )  
  
    hidden_states = layer_outputs[0]  
    if use_cache:  
        next_decoder_cache += (layer_outputs[2 if output_attentions else 1],)  
    if output_attentions:  
        all_self_attns += (layer_outputs[1],)

这里插播个结构分析,我们重点看LlamaDecoderLayer函数。

LlamaDecoderLayer

函数的传入的参数:​​hidden_states [1,8,4096]、attention_mask [1,1,8,8]、position_ids [1,8]、past_key_value [[]] or None​​。

执行的程序也很简单,顺序执行以下步骤:

  • 首先归一化 self.input_layernorm(hidden_states)
  • 然后经过 self_attn
  • 残差 hidden_states = residual(之前residual = hidden_states) + hidden_states(经过self_attn后)
  • 全连接 self.post_attention_layernorm(hidden_states) -> self.mlp(hidden_states)
  • 继续残差 hidden_states = residual(之前经过self_attn后) + hidden_states(经过全连接后)

代码如下:

residual = hidden_states  
hidden_states = self.input_layernorm(hidden_states)  
# Self Attention  
hidden_states, self_attn_weights, present_key_value = self.self_attn(  
    hidden_states=hidden_states,  
    attention_mask=attention_mask,  
    position_ids=position_ids,  
    past_key_value=past_key_value,  
    output_attentions=output_attentions,  
    use_cache=use_cache,  
)  
hidden_states = residual + hidden_states  
# Fully Connected  
residual = hidden_states  
hidden_states = self.post_attention_layernorm(hidden_states)  
hidden_states = self.mlp(hidden_states)  
hidden_states = residual + hidden_states  
outputs = (hidden_states,)  
if output_attentions:  
    outputs += (self_attn_weights,)  
if use_cache:  
    outputs += (present_key_value,)  
return outputs

然后我们看下核心的LlamaAttention:

LlamaAttention

这个就是Multi-headed attention from 'Attention Is All You Need' paper。这个类的成员变量如下:

class LlamaAttention(nn.Module):  
    def __init__(self, config: LlamaConfig):  
        super().__init__()  
        self.config = config  
        self.hidden_size = config.hidden_size  
        self.num_heads = config.num_attention_heads # head的数量 这里是32  
        self.head_dim = self.hidden_size // self.num_heads  # head的大小 这里是128  
        self.max_position_embeddings = config.max_position_embeddings  
        if (self.head_dim * self.num_heads) != self.hidden_size:  
            raise ValueError(  
                f"hidden_size must be divisible by num_heads (got `hidden_size`: {self.hidden_size}"  
                f" and `num_heads`: {self.num_heads})."  
            )  
        # 线性层self.q_proj, self.k_proj, self.v_proj将输入hidden_states映射到num_heads * head_dim的维度,以分别获得查询、键、值tensor。  
        self.q_proj = nn.Linear(self.hidden_size, self.num_heads * self.head_dim, bias=False)  
        self.k_proj = nn.Linear(self.hidden_size, self.num_heads * self.head_dim, bias=False)  
        self.v_proj = nn.Linear(self.hidden_size, self.num_heads * self.head_dim, bias=False)  
        self.o_proj = nn.Linear(self.num_heads * self.head_dim, self.hidden_size, bias=False)  
        self.rotary_emb = LlamaRotaryEmbedding(self.head_dim, max_position_embeddings=self.max_position_embeddings)  
    ...

其中,多头机制的自注意力:

  • self.num_heads定义了attention head的数量
  • self.head_dim定义了每个head的大小,是hidden_size除以num_heads
  • 线性层self.q_proj, self.k_proj, self.v_proj将输入hidden_states映射到num_heads * head_dim的维度,以分别获得查询、键、值tensor

操作代码如下:

def forward(  
    self,  
    hidden_states: torch.Tensor,  
    attention_mask: Optional[torch.Tensor] = None,  
    position_ids: Optional[torch.LongTensor] = None,  
    past_key_value: Optional[Tuple[torch.Tensor]] = None,  
    output_attentions: bool = False,  
    use_cache: bool = False,  
) -> Tuple[torch.Tensor, Optional[torch.Tensor], Optional[Tuple[torch.Tensor]]]:  
    bsz, q_len, _ = hidden_states.size()  
  
    query_states = self.q_proj(hidden_states).view(bsz, q_len, self.num_heads, self.head_dim).transpose(1, 2)  
    key_states = self.k_proj(hidden_states).view(bsz, q_len, self.num_heads, self.head_dim).transpose(1, 2)  
    value_states = self.v_proj(hidden_states).view(bsz, q_len, self.num_heads, self.head_dim).transpose(1, 2)  
  
    kv_seq_len = key_states.shape[-2]  
    if past_key_value is not None:  
        kv_seq_len += past_key_value[0].shape[-2]  
    cos, sin = self.rotary_emb(value_states, seq_len=kv_seq_len)  
    query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin, position_ids)  
    # [bsz, nh, t, hd]  
    if past_key_value is not None:  
        # reuse k, v, self_attention  
        key_states = torch.cat([past_key_value[0], key_states], dim=2)  
        value_states = torch.cat([past_key_value[1], value_states], dim=2)  
    past_key_value = (key_states, value_states) if use_cache else None  
    attn_weights = torch.matmul(query_states, key_states.transpose(2, 3)) / math.sqrt(self.head_dim)  
    if attn_weights.size() != (bsz, self.num_heads, q_len, kv_seq_len):  
        raise ValueError(  
            f"Attention weights should be of size {(bsz * self.num_heads, q_len, kv_seq_len)}, but is"  
            f" {attn_weights.size()}"  
        )  
    if attention_mask is not None:  
        if attention_mask.size() != (bsz, 1, q_len, kv_seq_len):  
            raise ValueError(  
                f"Attention mask should be of size {(bsz, 1, q_len, kv_seq_len)}, but is {attention_mask.size()}"  
            )  
        attn_weights = attn_weights + attention_mask  
        attn_weights = torch.max(attn_weights, torch.tensor(torch.finfo(attn_weights.dtype).min))  
    # upcast attention to fp32  
    attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query_states.dtype)  
    attn_output = torch.matmul(attn_weights, value_states)  
  
    if attn_output.size() != (bsz, self.num_heads, q_len, self.head_dim):  
        raise ValueError(  
            f"`attn_output` should be of size {(bsz, self.num_heads, q_len, self.head_dim)}, but is"  
            f" {attn_output.size()}"  
        )  
    attn_output = attn_output.transpose(1, 2)  
    attn_output = attn_output.reshape(bsz, q_len, self.hidden_size)  
    attn_output = self.o_proj(attn_output)  
    if not output_attentions:  
        attn_weights = None  
    return attn_output, attn_weights, past_key_value

其中两点需要注意:

  • ​query_states = self.q_proj(hidden_states).view(bsz, q_len, self.num_heads, self.head_dim).transpose(1, 2)​​​就是多头计算,得到的结果query_states、key_states、value_states的维度是​​torch.Size([1, 32, 8, 128])​​,32代表头的数量、8是输入的input_ids长度,128代表头的大小,剩下的计算也是多头自注意力的部分,不多介绍了
  • 如果提供了past_key_value,则利用cache的机制,直接​​torch.cat([past_key_value[0], key_states], dim=2)​​即可,每次传入的input_ids只有最新的一个id

好了回到刚才的pipeline:

接着for循环完32个decoder层之后,需要进行norm操作:​​hidden_states = self.norm(hidden_states)​​。

输出后得到​​outputs.logits​​​维度为​​torch.Size([1, 8, 32000])​​,接下来对这个logits进行操作,以下是相关代码,比较长:

next_token_logits = outputs.logits[:, -1, :]  
# pre-process distribution  
next_token_scores = logits_processor(input_ids, next_token_logits)  
next_token_scores = logits_warper(input_ids, next_token_scores)  
...  
probs = nn.functional.softmax(next_token_scores, dim=-1)  
next_tokens = torch.multinomial(probs, num_samples=1).squeeze(1)  
  
# logits_processor中执行的操作  
  
class MinLengthLogitsProcessor(LogitsProcessor):  
    [`LogitsProcessor`] enforcing a min-length by setting EOS probability to 0.  
    def __init__(self, min_length: int, eos_token_id: Union[int, List[int]]):  
        if not isinstance(min_length, int) or min_length < 0:  
            raise ValueError(f"`min_length` has to be a non-negative integer, but is {min_length}")  
  
        if isinstance(eos_token_id, int):  
            eos_token_id = [eos_token_id]  
        if not all([isinstance(i, int) for i in eos_token_id]) or any([i < 0 for i in eos_token_id]):  
            logger.warning(f"`eos_token_id` has to be a list of positive integers, but is {eos_token_id}")  
  
        self.min_length = min_length  
        self.eos_token_id = eos_token_id  
  
    def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor) -> torch.FloatTensor:  
        cur_len = input_ids.shape[-1]  
        if cur_len < self.min_length:  
            for i in self.eos_token_id:  
                scores[:, i] = -float("inf")  
        return scores  
...  
# logits_warper调用的三个函数  
class TemperatureLogitsWarper(LogitsWarper):  
    def __init__(self, temperature: float):  
        if not isinstance(temperature, float) or not (temperature > 0):  
            raise ValueError(f"`temperature` has to be a strictly positive float, but is {temperature}")  
        self.temperature = temperature  
    def __call__(self, input_ids: torch.Tensor, scores: torch.Tensor) -> torch.FloatTensor:  
        scores = scores / self.temperature  
        return scores  
  
class TopKLogitsWarper(LogitsWarper):  
    def __init__(self, top_k: int, filter_value: float = -float("Inf"), min_tokens_to_keep: int = 1):  
        if not isinstance(top_k, int) or top_k <= 0:  
            raise ValueError(f"`top_k` has to be a strictly positive integer, but is {top_k}")  
  
        self.top_k = max(top_k, min_tokens_to_keep)  
        self.filter_value = filter_value  
  
    def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor) -> torch.FloatTensor:  
        top_k = min(self.top_k, scores.size(-1))  # Safety check  
        # Remove all tokens with a probability less than the last token of the top-k  
        indices_to_remove = scores < torch.topk(scores, top_k)[0][..., -1, None]  
        scores = scores.masked_fill(indices_to_remove, self.filter_value)  
        return scores  
  
class TopPLogitsWarper(LogitsWarper):  
    def __init__(self, top_p: float, filter_value: float = -float("Inf"), min_tokens_to_keep: int = 1):  
        top_p = float(top_p)  
        if top_p < 0 or top_p > 1.0:  
            raise ValueError(f"`top_p` has to be a float > 0 and < 1, but is {top_p}")  
        self.top_p = top_p  
        self.filter_value = filter_value  
        self.min_tokens_to_keep = min_tokens_to_keep  
  
    def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor) -> torch.FloatTensor:  
        sorted_logits, sorted_indices = torch.sort(scores, descending=False)  
        cumulative_probs = sorted_logits.softmax(dim=-1).cumsum(dim=-1)  
        # Remove tokens with cumulative top_p above the threshold (token with 0 are kept)  
        sorted_indices_to_remove = cumulative_probs <= (1 - self.top_p)  
        if self.min_tokens_to_keep > 1:  
            # Keep at least min_tokens_to_keep  
            sorted_indices_to_remove[..., -self.min_tokens_to_keep :] = 0  
        # scatter sorted tensors to original indexing  
        indices_to_remove = sorted_indices_to_remove.scatter(1, sorted_indices, sorted_indices_to_remove)  
        scores = scores.masked_fill(indices_to_remove, self.filter_value)  
        return scores

其中主要的过程:

  • 将 logits 传递给 logits_processor 和 logits_warper,在这两个方法中进行一些预处理过程,例如添加惩罚项或对概率分布进行修改,使得生成的结果更符合期望,具体调用了(这里用到了temperature参数,作用是调节模型生成的随机性,temperature通常被用于控制softmax函数的形状,从而影响生成序列的多样性,当temperature值接近0时,模型趋向于输出最可能的单个结果,也就是模型的输出趋向于确定性。这种情况下,所有的概率质量都集中在概率最大的那个输出上,其他的输出的概率几乎为0,当temperature值比较大(大于1)时,模型趋向于输出更多样化的结果,也就是增加了模型输出的随机性。在这种情况下,不同的输出之间的概率差异减小,使得即使概率较小的输出也有可能被选中)
  • TopKLogitsWarper类是一个用于处理模型输出分数(scores)的工具,主要用于进行所谓的“Top-K截断”。在自然语言生成的过程中,Top-K截断是一种常见的技巧,它的目标是在每个生成步骤中,只保留K个最可能的输出选项,而忽略其他的选项。这种方法可以降低生成过程的复杂性,并且可以减少不太可能的输出的干扰。
  • TopPLogitsWarper类实现了被称为"Top-p(或nucleus)抽样"的策略。该策略用于限制模型在每个生成步骤中所考虑的可能输出的范围。在Top-p抽样中,我们不再固定考虑概率最高的K个输出,而是根据概率分布的累积分布函数(CDF)来选择可能的输出。我们设置一个阈值P,然后选择输出,直到它们的累积概率大于等于P。由于这个方法根据概率分布动态地调整输出的数量,所以它可以更好地处理不同的分布情况,从而在某些情况下可以生成更自然的文本。
  • 最后,使用 softmax 函数将经过预处理的 logits 转换为概率分布,并利用 multinomial 方法从中采样得到下一个 token。最后,将该 token 添加到原始输入序列中,并进行下一次迭代,生成新的文本内容。如果需要记录中间变量,则将它们存储在相应的变量中,以便之后访问根据得到的新token,随后更新input_ids得到下一个token_id之后

得到next_tokens之后,执行以下代码:

input_ids = torch.cat([input_ids, next_tokens[:, None]], dim=-1)  
if streamer is not None:  
    streamer.put(next_tokens.cpu())  
model_kwargs = self._update_model_kwargs_for_generation(  
    outputs, model_kwargs, is_encoder_decoder=self.config.is_encoder_decoder  
)  
  
# if eos_token was found in one sentence, set sentence to finished  
if eos_token_id_tensor is not None:  
    unfinished_sequences = unfinished_sequences.mul(  
        next_tokens.tile(eos_token_id_tensor.shape[0], 1).ne(eos_token_id_tensor.unsqueeze(1)).prod(dim=0)  
    )  
  
# stop when each sentence is finished, or if we exceed the maximum length  
if unfinished_sequences.max() == 0 or stopping_criteria(input_ids, scores):  
    if not synced_gpus:  
        break  
    else:  
        this_peer_finished = True

其中:

  • 根据条件判断语句对生成结果进行一些后处理,例如将已完成的序列末尾添加 padding token,更新 model inputs 和 length 等等
  • 如果已经生成完整的句子,则将其标记为 finished,并检查是否满足终止条件。当全部句子都结束或达到最大长度时,停止生成

最后通过分词器进行decode即可得到所有结果,这个是支持batch的:

print(tokenizer.batch_decode(generated_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False)[0])

过程差不多就是这样的,整体pipeline结束。

落地相关

部署的时候,除了模型要搞好,确保模型可以输出正常结果的设置参数也需要整明白,需要暴露出来以便上游去调节。

CV模型的话,我们一般可调节的就是预处理的均值方差、图片RGB还是BGR(通道什么的)、输入数据范围,图像resize大小什么的;后处理的话,如果是检测模型,那就是iou阈值、nms阈值什么的。

对于llama来说,可调节的参数就有点多了。

举个例子,随便拿出一个gradio展示的LLM模型,可调节的参数如下:

  • seed   seed如果固定了,我们的输出也就固定了,我们使用chatgpt的时候,seed每次应该不一样
  • gen mode   sample 一般来说就是用这个 do_sample  ;greed  貌似这个最快;beam search  这个效果最好
  • end_id 模型训练的时候设置的结束id,模型在预测的时候,如果预测下一个token是end_id的话就应该终止预测了,就是这段话说完了
  • start_id 一般来说是在tokenizer中设置,在将输入文本encode的时候,设置的第一个token id

padding

LLM和CV模型一样,组batch的时候需求输入大小一致,比如[4,3,128,128],而NLP中输入的是input_ids,padding的方法如下:

input_sentences = [  
    "DeepSpeed is a machine learning framework",  
    "He is working on",  
    "He has a",  
    "He got all",  
    "Everyone is happy and I can",  
    "The new movie that got Oscar this year",  
    "In the far far distance from our galaxy,",  
    "Peace is the only way",  
]  
tokenizer.pad_token = 0  
input_tokens = tokenizer.batch_encode_plus(input_sentences, return_tensors="pt", padding=True)

bad_words和stop_words

LLM在生成token的时候需要避免一些:

  • 不想生成的token
  • 遇到就停止的token

kv-cache

kv-cache是LLM推理过程的一个优化点,可以减少计算,不过需要额外显存去存这些cache。

总结

可以参考hugglingface搭建服务的规则和一些细节,一般要支持:

  • 对于stream模式,要支持多个同时的请求
  • 对于非stream模式,需要支持多batch输入,也可以支持组batch
  • 对于stream模式,如果服务端认为生成token到结尾了,则可以主动断开连接并且返回终止符给客户端

一些概念

unconditional generation

在自然语言处理(NLP)中,"unconditional generation"是指模型在没有特定输入或提示的情况下生成文本。对比之下,"conditional generation"是指模型在给定某些输入或提示(例如,开头的一段文本或特定的任务描述)的情况下生成文本。

举个例子,如果我们让一个语言模型写一个故事,但不给它任何具体的主题或开头,这就是无条件生成。如果我们给模型一个特定的开头,比如"在一个深深的森林里,有一个小屋……",然后要求模型继续写下去,这就是有条件生成。

"start token"和"end token"是用来标识生成任务的开始和结束的特殊标记。在GPT-J(我在2021年9月之前的知识库中没有关于GPT-J的信息,因此我不确定这是何种模型)中,对于无条件生成任务,开始标记和结束标记可能是相同的。这可能意味着,在没有输入或提示的情况下开始和结束文本生成。

context len

在自然语言处理(NLP)中,"context window"(上下文窗口)是一种常见的概念,它指的是在处理某个词或词组时考虑的前后相关词汇的范围。这个范围可以是固定的,也可以是动态的,取决于具体的模型和任务。

例如,如果我们使用一个大小为5的上下文窗口来处理一个词,那么我们会考虑这个词前后各2个词作为其上下文。这样做的目的是获取更多的上下文信息,以便更好地理解和处理这个词。此外,某些类型的模型,如Transformer模型,实际上可以看作是没有固定上下文窗口大小的,因为它们使用的机制(如自注意力机制)允许它们在处理每个词时考虑整个输入序列的所有词。

值得注意的是,上下文窗口的大小和处理策略可能会对模型的性能和结果产生显著影响。较大的上下文窗口可能会捕获更多的长距离依赖关系,但也可能增加模型的计算复杂度。相反,较小的上下文窗口可能会减少计算复杂度,但可能无法捕获一些重要的长距离依赖关系。因此,选择合适的上下文窗口大小和处理策略通常需要根据具体的任务和数据进行调整。

参考

文中所有图片来源于以下的链接:

Transformer论文逐段精读【论文精读】(https://www.bilibili.com/video/BV1pu411o7BE/?spm_id_from=333.880.my_history.page.click&vd_source=eec038509607175d58cdfe2e824e8ba2)

​https://chat.openai.com/​

​https://huggingface.co/learn/nlp-course/chapter1/3?fw=pt​

​https://www.eet-china.com/mp/a256824.html​

.....

#VPGTrans

最近的多模态(对话)大模型将基于文本的 ChatGPT 的强大能力扩展到了多模态输入,实现强大的多模态语义理解,比如 GPT-4、BLIP-2、Flamingo 等。但对于很多研究者来说,训练一个多模态 GPT 代价非常昂贵。本文来自新加坡国立大学和清华大学的研究者提出了一个名为 VPGTrans 框架,以极低成本训练高性能多模态大模型。

  • 多模态对话模型 Demo:https://vpgtrans.github.io/
  • 论文:https://arxiv.org/pdf/2305.01278.pdf
  • 代码:https://github.com/VPGTrans/VPGTrans

1. 极低训练成本:通过我们提出的 VPGTrans 方法,可以快速 (少于 10% 训练时间) 将已有的多模态对话模型的视觉模块迁移到新的语言模型,且达到类似或更优效果。比如,相比于从头训练视觉模块,我们可以将 BLIP-2 FlanT5-XXL 的训练开销从 19000 + 人民币缩减到不到 1000 元:

图 1:基于我们的 VPGTrans 方法的 BLIP-2 训练开销缩减对比

2. 多模态大模型定制:通过我们的 VPGTrans 框架可以根据需求为各种新的大语言模型灵活添加视觉模块。比如我们在 LLaMA-7B 和 Vicuna-7B 基础上制作了 VL-LLaMA 和 VL-Vicuna。

3. 开源多模态对话模型:我们开源了 VL-Vicuna,可实现高质量的多模态对话:

2023 年是 AI 元年,以 ChatGPT 为代表的大语言模型 (LLM) 大火。LLM 除了在自然语言领域显示出巨大的潜力之外,也开始逐渐辐射到其他相关领域。比如,LLM 在多模态理解领域掀起了一股从传统预训练视觉语言模型 (VLM) 到基于大语言模型的视觉语言模型 (VL-LLM) 的变革。通过为 LLM 接入视觉模块,VL-LLM 可以继承已有 LLM 的知识,零样本泛化能力,推理能力和规划能力等。相关模型有 BLIP-2 [1],Flamingo [2],PALM-E 等。 

现有的常用的 VL-LLM 基本采取图 3 所示的架构:在一个基座 LLM 基础上训练一个视觉 soft prompt 生成模块 (Visual Prompt Generator, VPG),以及一个进行维度变换的线性层 (Projector)。在参数规模上,LLM 一般占主要部分 (比如 11B),VPG 占次要部分 (比如 1.2B),projector 最小 (4M)。在训练过程中,LLM 参数一般不会被更新,或者仅仅更新非常少量的参数。可训练参数主要来自于 VPG 和 projector。

实际上,即便基座 LLM 的参数冻结不训,但由于 LLM 的大参数量,训练一个 VL-LLM 的关键开销依然在于加载基座 LLM。因此训练一个 VL-LLM 依然无法避免极大的计算代价。比如,要得到 BLIP-2(基座 LLM 为 FlanT5-XXL)需要付出超过 600 个小时的 A100 训练时长。如果租用亚马逊的 A100-40G 机器,大概需要将近 2 万元人民币的费用。既然从零训练一个 VPG 代价如此昂贵,那么我们开始思考能否把一个已有的 VPG 迁移到新的 LLM 上来节省开销。

如图 4 所示,我们主要探索了两种类型的 VPG 的迁移:

(1) 跨 LLM 大小迁移 (TaS): 比如从 OPT-2.7B 到 OPT-6.7B。

(2) 跨 LLM 类型迁移 (TaT): 比如从 OPT 到 FlanT5。

其中 TaS 的意义在于:在 LLM 相关科研中,我们通常需要在小 LLM 上调参,再扩展到大 LLM。有了 TaS,我们可以在调参之后,把小 LLM 上已经训好的 VPG 直接迁移到大 LLM 上。TaT 的意义在于:不同功能种类的 LLM 层出不穷,比如今天有了 LLaMA,明天又有了 Alpaca 和 Vicuna。TaT 可以让我们利用已有的 VPG 快速为新语言模型添加视觉感知能力。

贡献

(1) 提出高效的方法:我们首先通过一系列的探究实验,探究了影响 VPG 迁移效率的关键因素。根据探索实验发现,我们提出了一个两阶段的高效迁移框架 VPGTrans。该框架可以大幅度缩减训练 VL-LLM 所需的计算开销和需要的训练数据。比如,相比于从头训练,我们通过 BLIP-2 OPT-2.7B 到 6.7B 的 VPG 迁移,可以仅用大约 10% 的数据和计算时间就达成各个数据集相似或更好的效果 (图 1)。训练花销从 17901 人民币到 1673 元。

(2) 得到有趣的发现:我们同时提供了 TaS 和 TaT 场景下一些有趣的发现,并尝试给出解释: a) TaS 场景下,使用 VPGTrans 从小到大迁移不会影响最终模型效果。b) TaS 场景下,越小的语言模型上训练的 VPG,迁移到大模型时效率越高,最终效果越好。c) TaT 场景下,越小的模型之间迁移的 gap 越大。在我们验证实验中,OPT-350M 和 FlanT5-base 使用 VPGTrans 互相迁移几乎和从头训练一样慢。

(3) 开源:我们使用 VPGTrans 得到了两个新的 VL-LLMs: VL-LLaMA 和 VL-Vicuna,并开源在了社区上。其中 VL-Vicuna 实现了高质量的多模态对话。欢迎小伙伴尝试:https://vpgtrans.github.io/.

高效率的 VPG 迁移方案: VPGTrans

首先我们进行一系列的探索验证实验,分析如何最大化对于 VPG 的迁移效率。接着我们基于这些重要观察提出一个解决方案。

探究实验

我们选取 BLIP-2 架构作为我们的基础模型,预训练语料采用 COCO 和 SBU,总共 1.4M 图文对。下游任务采用 COCO Caption,NoCaps,VQAv2,GQA 和 OK-VQA 的 zero-shot 设定进行评测 (对 caption 任务并非严格 zero-shot)。下面是我们的关键发现:

(1) 直接继承一个训练好的 VPG 可以加速收敛,但效果有限:我们发现,直接迁移一个 LLM 上训练好的 VPG 到大 LLM 可以加速模型收敛,但加速效果有限,且收敛后模型效果相比于从头训练 VPG 会掉点 (图 5 的 VQAv2、GQA 蓝线最高点均低于橘线)。我们猜测,这个掉点是由于随机初始化的 projector 会在训练起始阶段损伤 VPG 中已有的视觉感知能力。

(2) 先 warm-up 训练 projector 可以防止掉点,且进一步加速收敛:于是,我们固定住 VPG 和 LLM,先 warm-up 训练 projector 3 个 epoch,再解冻 VPG 进行下一步训练。我们发现,这样不仅可以避免掉点情况,还能够进一步加速 VPG 收敛 (图 6)。但值得强调的是,由于训练的主要开销在 LLM (参数巨多),仅仅训练 projector 的开销不会比同时训练 VPG 和 projector 的开销小太多。所以,我们开始探究加速 projector warm-up 的关键技术。

(4) projector 可以在超大学习率下快速收敛:我们进一步实验发现,projector 由于其参数量较少,可以使用 5 倍的正常学习率进行训练而不崩溃。通过 5 倍学习率的训练,projector warm-up 可以进一步被缩短到1个 epoch。

(5) 一个附加发现:虽然 projector warm-up 很重要,但仅训练 projector 是不够的。尤其在 caption 任务上面,仅仅训练 projector 的效果要比同时训练 VPG 的效果差一截 (图 5 绿线在 COCO Caption 和 NoCaps 均远低于蓝线)。这也就意味着,仅仅训练 projector 会导致欠拟合,无法充分对齐到训练数据。

如图 7 所示,我们的方法共分为两个阶段:

(1) 第一阶段:我们首先使用词向量转化器和原有 projector 进行融合作为新 projector 的初始化。然后用 5 倍学习率训练新 projector 一个 epoch。

(2) 第二阶段:直接正常训练 VPG 和 projector。

实验结果

加速比

表 1:我们的 VPGTrans 的相比于从头训练在各个数据集的加速比

如表 1 所示,我们测试了在不同迁移类型下,VPGTrans 在不同数据集上的加速比。VPGTrans 在某指定数据集 A 上的加速比是通过从头训练达到 A 上最佳效果 a 的轮数除以 VPGTrans 在 A 上效果超过 a 的最小训练轮数得到。比如,从头在 OPT-2.7B 上训练 VPG,在 COCO caption 达到最佳效果需要 10 个 epoch,但从 OPT-125M 迁移 VPG 到 OPT-2.7B,仅需 1 个 epoch 就能达到该最佳效果。则加速比为 10/1=10 倍。我们可以看到,无论是在 TaS 还是在 TaT 场景下,我们的 VPGTrans 都可以实现稳定的加速。

有趣的发现

我们选取了一个比较有趣的发现进行了说明,其他更多更有意思的发现请参照我们的论文。

TaS 场景下,越小的语言模型上训练的 VPG,迁移起来效率越高,最后模型效果越好。参考表 1,我们可以发现 OPT-1.3B 到 OPT-2.7B 的加速比要远小于 OPT-125M、OPT-350M 到 OPT-2.7b 的加速比。我们尝试提供了一个解释:一般越大的语言模型,由于其文本空间的维度更高,会更容易损害 VPG (VPG 一般都是类似于 CLIP 的预训练模型) 本身的视觉感知能力。我们通过类似于 linear probing 的方式进行了验证:

图 8:仅训练 linear projector 层的跨 LLM 大小迁移 (模拟 linear probing)

大规模实验

前文实验主要是在小规模场景下验证猜想。为了证明我们方法的有效性,我们模拟 BLIP-2 的预训练过程进行了大规模实验:

表 2:真实场景下的大规模实验结果

如表 2 所示,我们的 VPGTrans 在大规模场景下依然有效。通过 OPT-2.7B 到 OPT-6.7B 的迁移,我们仅用 10.8% 的数据和不到 10% 的训练时长达到了相似或更优的效果。尤其是,我们的方法在 BLIP-2 以 FlanT5XXL 为基座 LLM 下实现了 5% 左右的训练成本控制。

定制自己的 VL-LLMs

我们的 VPGTrans 可以快速为任意新的 LLMs 添加视觉感知模块,从而得到一个全新的高质量 VL-LLM。在本工作,我们额外训练了一个 VL-LLaMA 和一个 VL-Vicuna。其中 VL-LLaMA 的效果如下:

表3:VL-LLaMA 的效果展示     

同时,我们的 VL-Vicuna 可以进行多模态对话。我们和 MiniGPT-4 进行了简单的比较:

总结

在这项工作中,我们对 VPG 在 LLM 之间的可迁移性问题进行了全面调查。我们首先探讨了最大化迁移效率的关键因素。基于关键观察,我们提出了一种新颖的两阶段迁移框架,即 VPGTrans。它可以在显著降低训练成本的同时,实现相当或更好的性能。通过 VPGTrans,我们实现了从 BLIP-2 OPT-2.7B 到 BLIP-2 OPT-6.7B 的 VPG 迁移。相较于从零开始连接 VPG 到 OPT 6.7B,VPGTrans 仅需 10.7% 训练数据和不到 10% 的训练时长。此外,我们展示并讨论了一系列有趣发现及其背后的可能原因。最后,我们通过训练 VL-LLaMA 和 LL-Vicuna,展示了我们的 VPGTrans 在定制新的 VL-LLM 方面的实际价值。

 .....

#Qwen-14B

阿里又开源大模型了!

这次是纯粹的大语言模型,相比上一次的70亿,新模型的参数量来到了140亿

它名叫Qwen-14B,一上来就在一系列任务榜单中全部取得了第一,直接超过了Meta的340亿参数Llama 2版本。

Qwen-14B的训练数据达到3万亿tokens,不仅中英文都来得,序列长度也达到了8192。

用法也是老样子,完全开源,而且免费可用,目前在魔搭社区上已经可以试玩到Demo版本。

看起来,阿里的Qwen颇有点对标Meta的Llama,要搞出一整套“国内大模型开源全系列”那味了。

那么,Qwen-14B的效果究竟怎么样呢?我们这就试一试。

10个榜单超越340亿Llama 2

先来看看Qwen-14B的整体表现如何。

虽然Llama 2取得了一系列不错的“战绩”,不过至少官方提供的版本中,还不太具备说中文的能力。

因此,我们先来看看Qwen-14B的中文能力如何,然后再试试它在其他学科上的水平。

老样子,弱智benchmark送上:

看起来Qwen-14B也能准确应对。稍微难点的中文理解题

感觉还不错,顺便纠正了我的语病(手动狗头)。

那么,数学题表现如何呢?普通的鸡兔同笼试试:

表现也中规中矩。再试试稍微难一点的逻辑推理题,之前有人测试发现,60个大模型全部做错了。

问题如下:

Sally (a girl) has 3 brothers. Each brother has 2 sisters. How many sisters does Sally have?
莎莉(一个女孩)有3个兄弟。每个兄弟有2个姐妹。所以莎莉有几个姐妹?

PS:这道题的正确答案是1。

在做错的一系列大模型中,包括GPT-4和Claude 2,也包括Llama 2:

很遗憾,这里Qwen-14B也没做出来。

不过它的代码能力还可以,例如让写一下贪吃蛇的代码:

它很快给出了一个完整版的贪吃蛇代码出来,带注释那种:

import random

# 初始化蛇的初始位置和长度
snake = [(0, 0)]
snake_length = 1

# 初始化食物的位置
food = (random.randint(0, 10), random.randint(0, 10))

# 初始化游戏的分数
score = 0

# 初始化游戏的结束标志
game_over = False

# 游戏主循环
while not game_over:

# 打印游戏界面
for i in range(11):for j in range(11):if (i, j) in snake:print("O", end="")elif (i, j) == food:print("F", end="")else:print(".", end="")print()

    # 获取用户的输入
    direction = input("Enter direction (up, down, left, right): ")

    # 更新蛇的位置
    if direction == "up":
        snake.insert(0, (snake[0][0], snake[0][1] - 1))elif direction == "down":
        snake.insert(0, (snake[0][0], snake[0][1] + 1))elif direction == "left":
        snake.insert(0, (snake[0][0] - 1, snake[0][1]))elif direction == "right":
        snake.insert(0, (snake[0][0] + 1, snake[0][1]))

        # 更新蛇的长度
        snake_length += 1

    # 如果蛇吃到了食物,更新食物的位置和分数
    if snake[0] == food:
        food = (random.randint(0, 10), random.randint(0, 10))
        score += 1

        # 如果蛇撞到了墙或者自己的身体,游戏结束
        if snake[0][0] < 0 or snake[0][0] >= 11 or snake[0][1] < 0 or snake[0][1] >= 11 or snake[0] in snake[1:]:
        game_over = True

        # 打印游戏的分数
        print("Score: ", score)

        # 打印游戏结束的消息
        print("Game over!")

试了一下,可以直接运行玩耍:

据了解,Qwen-14B和Qwen-7B一样,所具备的也不仅仅是对话功能。

除了上述能力,Qwen-14B也学会了自己调用工具

例如,基于Code Interpreter(代码解释器)工具执行Python代码,直接做数学计算、数据分析和数据图表绘制。

团队也升级了Qwen-14B对接外部系统的技巧,不仅几步就能调用复杂插件,还能将它作为基座模型开发Agent等AI系统、完成复杂任务。

事实上,背后的Qwen-14B模型,也是个打榜小能手。

无论是在语言能力测试集上,如大规模多任务语言测评榜单MMLU、中文基础能力评估数据集C-Eval中;

还是在数学等其他学科的能力上,如小学数学加减乘除运算题GSM8K、数学竞赛数据集MATH等:

前后一共10个榜单,都取得了TOP 1的名次。

所以,Qwen-14B究竟是怎么做出来的?

训练数据超3万亿tokens

技术细节,还得从Qwen-14B的架构和训练数据说起。

作为一个参数140亿的大模型,Qwen-14B的结构细节长这样:

在整体架构上,团队借鉴了一些当前开源大模型的“神奇设计”,包括谷歌的PaLM以及Meta的Llama等。

包括SwiGLU的激活函数设计、ROPE的位置编码等,都有出现在Qwen-14B的结构设计中。

不仅如此,团队也针对词表长序列数据建模进行了优化。词表大小超过15万,更节省token数。

长序列数据建模上,则采取了一些当前最有效的方法,包括但不限于Dynamnic NTK、Log-N attention scaling、window attention等,确保模型表现效果更稳定。

这也是模型虽然只有140亿,但序列长度能达到8192的原因。

之所以能取得不错的效果,也与Qwen-14B的训练数据分不开。

Qwen-14B整体采用了超过3万亿tokens数据训练。

这里面不仅仅包含语数英等基础学科,还包括了理化生政史地等多个其他学科的知识、以及代码知识,直接接受了9年义务教育(手动狗头)。

除此之外,团队还进一步做了不少数据处理工作,包括大规模数据去重、垃圾文本过滤、以及提升高质量数据比例等。

同时,为了让模型更好地学会调用工具、增强记忆能力,团队也在微调样本上进行了优化,建立更全面的自动评估基准来发现Qwen-14B表现不稳定的情況,并针对性地使用Self-Instruct方法扩充了高质量的微调样本。

事实上,这已经是通义千问系列的第三波开源了。

最早在8月初,阿里云就开源了中英文通用模型Qwen-7B和对话模型Qwen-7B-Chat

Qwen-7B支持8K上下文长度,基于超过2.2万亿tokens包含文本、代码等类型的数据集训练,也支持插件调用和开发Agent等AI系统。

项目一开源,就冲上GitHub热榜,目前已经收获4k星热度。

(值得一提的是,这次阿里云除了发布Qwen-14B以外,也顺带升级了一波Qwen-7B)

随后在8月底,阿里云再次推出视觉语言大模型Qwen-VL

Qwen-VL基于Qwen-7B为基座语言模型研发,支持图像、文本、检测框等多种输入,并且在文本之外,也支持检测框输出。

从demo展示中来看,Qwen-VL身兼多种能力,中英文对话、代码图像理解都来得:

对于阿里的一系列Qwen开源大模型感兴趣的,可以去项目主页试玩一波了~

试玩地址:
​ https://modelscope.cn/studios/qwen/Qwen-14B-Chat-Demo/summary​​

参考链接:
[1]https://github.com/QwenLM/Qwen-7B
[2]https://github.com/QwenLM/Qwen-VL
[3]https://benchmarks.llmonitor.com/sally

.....

#TextDiffuser

无惧图像中的文字,TextDiffuser提供更高质量文本渲染

近几年来,Text-to-Image 领域取得了巨大的进展,特别是在 AIGC(Artificial Intelligence Generated Content)的时代。随着 DALL-E 模型的兴起,学术界涌现出越来越多的 Text-to-Image 模型,例如 Imagen,Stable Diffusion,ControlNet 等模型。然而,尽管 Text-to-Image 领域发展迅速,现有模型在稳定地生成包含文本的图像方面仍面临一些挑战。

尝试过现有 sota 文生图模型可以发现,模型生成的文字部分基本上是不可读的,类似于乱码,这非常影响图像的整体美观度。

现有 sota 文生图模型生成的文本信息可读性较差

经过调研,学术界在这方面的研究较少。事实上,包含文本的图像在日常生活中十分常见,例如海报、书籍封面和路牌等。如果 AI 能够有效地生成这类图像,将有助于辅助设计师的工作,激发设计灵感,减轻设计负担。除此之外,用户可能只希望修改文生图模型结果的文字部分,保留其他非文本区域的结果。

因此,研究者希望设计一个全面的模型,既能直接由用户提供的 prompt 生成图像,也能接收用户给定的图像修改其中的文本。目前该研究工作已被NeurIPS 2023接收。

  • 论文地址:https://arxiv.org/abs/2305.10855
  • 项目地址:https://jingyechen.github.io/textdiffuser/
  • 代码地址:https://github.com/microsoft/unilm/tree/master/textdiffuser
  • Demo地址:https://huggingface.co/spaces/microsoft/TextDiffuser

TextDiffuser 的三个功能

本文提出了 TextDiffuser 模型,该模型包含两个阶段,第一阶段生成 Layout,第二阶段生成图像。

TextDiffuser框架图

模型接受一段文本 Prompt,然后根据 Prompt 中的关键词确定每个关键词的 Layout(也就是坐标框)。研究者采用了 Layout Transformer,使用编码器-解码器的形式自回归地输出关键词的坐标框,并用 Python 的 PILLOW 库渲染出文本。在这个过程中,还可以利用 Pillow 现成的 API 得到每个字符的坐标框,相当于得到了字符级别的 Box-level segmentation mask。基于此信息,研究者尝试微调 Stable Diffusion。

他们考虑了两种情况,一种是用户想直接生成整张图片(称为 Whole-Image Generation)。另一种情况是 Part-Image Generation,在论文中也称之为 Text-inpainting,指的是用户给定一张图像,需要修改图里的某些文本区域。

为了实现以上两种目的,研究者重新设计了输入的特征,维度由原先的 4 维变成了 17 维。其中包含 4 维加噪图像的特征,8 维字符信息,1 维图像掩码,还有 4 维未被 mask 图像的特征。如果是 Whole-image generation,研究者将 mask 的区域设为全图,反之,如果是 part-image generation,就只 mask 掉图像的一部分即可。扩散模型的训练过程类似于 LDM,有兴趣的伙伴可以参考原文方法部分的描述。

在 Inference 阶段,TextDiffuser 非常灵活,有三种使用方式:

  • 根据用户给定的指令生成图像。并且,如果用户不大满意第一步 Layout Generation 生成的布局,用户可以更改坐标也可以更改文本的内容,这增加了模型的可控性。
  • 直接从第二个阶段开始。根据模板图像生成最终结果,其中模板图像可以是印刷文本图像,手写文本图像,场景文本图像。研究者专门训练了一个字符集分割网络用于从模板图像中提取 Layout。
  • 同样也是从第二个阶段开始,用户给定图像并指定需要修改的区域与文本内容。并且,这个操作可以多次进行,直到用户对生成的结果感到满意为止。

构造的 MARIO 数据

为了训练 TextDiffuser,研究者搜集了 1000 万张文本图像,如上图所示,包含三个子集:MARIO-LAION, MARIO-TMDB 与 MARIO-OpenLibrary。

研究者在筛选数据时考虑了若干方面:例如在图像经过 OCR 后,只保留文本数量为 [1,8] 的图像。他们筛掉了文本数量超过 8 的文本,因为这些文本往往包含大量密集文本,OCR 的结果一般不太准确,例如报纸或者复杂的设计图纸。除此之外,他们设置文本的区域大于 10%,设置这个规则是为了让文本区域在图像的比重不要太小。

在 MARIO-10M 数据集训练之后,研究者将 TextDiffuser 与现有其他方法做了定量与定性的对比。例如下图所示,在 Whole-Image Generation 任务中,本文的方法生成的图像具有更加清晰可读的文本,并且文本区域与背景区域融合程度较高。

与现有工作比较文本渲染性能研究者还做了定性的实验,如表 1 所示,评估指标有 FID,CLIPScore 与 OCR。尤其是 OCR 指标,本文方法相对于对比方法有很大的提升。

表1:定性实验对于 Part-Image Generation 任务,研究者尝试着在给定的图像上增加或修改字符,实验结果表明 TextDiffuser 生成的结果很自然。

文本修复功能可视化

总的来说,本文提出的 TextDiffuser 模型在文本渲染领域取得了显著的进展,能够生成包含易读文本的高质量图像。未来,研究者将进一步提升 TextDiffuser 的效果。

.....

#Mistral 7B

这是真正的开源。所有基准测试都优于Llama 2 13B,最好的7B模型来了,免费用

在 Llama 2 系列模型发布后的这几个月里,各大公司机构也在不断推出自家产品。
近日,一家法国人工智能初创公司 Mistral AI 发布了一款新模型 Mistral 7B,其在每个基准测试中,都优于 Llama 2 13B,并且在代码、数学和推理方面也优于 LLaMA 1 34B。

Mistral AI 联合创始人兼首席科学家 Guillaume Lample 也在推特上宣布了这一好消息。

在其官方博客中,他们表示,「这是迄今为止最好的 7B 模型,Apache 2.0。」

具体而言,Mistral 7B 具体参数量为 7.3B:

  • 在所有基准测试中均优于 Llama 2 13B;
  • 在多个基准测试中优于 Llama 1 34B;
  • 代码性能接近 CodeLlama 7B,同时对英语任务非常擅长;
  • 使用分组查询注意力(GQA,Grouped-query attention)来加快推理速度;
  • 使用滑动窗口注意力(SWA,Sliding Window Attention)以较小的成本处理较长的序列。

值得一提的是,Mistral 7B 是在 Apache 2.0 许可下发布的,这是一种高度宽松的方案,除了归属之外,对使用或复制没有任何限制。这意味着该模型可以被业余爱好者、价值数十亿美元的公司等使用,只要他们拥有能够在本地运行该模型的系统或愿意支付必要的云资源费用。

  • 下载完以后可以在任何地方使用(包括本地);
  • 使用 vLLM 推理服务器和 skypilot 将其部署在任何云 (AWS/GCP/Azure) 上;
  • 可以在 HuggingFace 上使用它。

用户还可以在不同任务上对 Mistral 7B 进行微调。

性能表现

首先,Mistral AI 将 Mistral 7B 与 Llama 2 家族模型进行了比较:

Mistral 7B 和不同 Llama 版本在各种基准测试中的性能。结果显示 Mistral 7B 在所有指标上都显著优于 Llama 2 13B,并且与 Llama 34B 相当(由于 Llama 2 34B 尚未发布,因而只报告了 Llama 34B 的结果)。

实验中,Mistral AI 将基准测试按照主题进行了分类:

  • 常识推理:Hellaswag、Winogrande、PIQA、SIQA、OpenbookQA、ARC-Easy、ARC-Challenge 和 CommonsenseQA 的 0-shot 平均值;
  • 世界知识:NaturalQuestions 和 TriviaQA 的 5-shot 平均值;
  • 阅读理解:BoolQ 和 QuAC 的 0-shot 平均值;
  • 数学:maj@8 的 8-shot GSM8K 和 maj@4 的 4-shot MATH 的平均值;
  • 代码:0-shot Humaneval 和 3-shot MBPP 的平均值;
  • 热门聚合结果:5-shot MMLU、3-shot BBH 和 3-5-shot AGI Eval(仅限英文多项选择题)。

在推理、理解和 STEM 推理 (MMLU) 方面,Mistral 7B 的性能与大小为其 3 倍以上的 Llama 2 相当。这既节省了内存,又增加了吞吐量。 

Mistral 7B 和 Llama 2 (7B/13/70B) 在 MMLU、常识推理、世界知识和阅读理解上的结果。Mistral 7B 在所有评估中都大大优于 Llama 2 13B,除了在知识基准方面,它们处于同等水平。

在训练方法上,该研究使用 GQA 和 4096 个 token 的滑动窗口对其进行训练,从而获得恒定的缓存大小和线性解码速度。此外该研究对 FlashAttention v2 和 xFormers 支持滑动窗口的更改已向社区开放。

具体到细节,Mistral 7B 采用 SWA 机制,其中每一层都会关注之前的 4096 个隐藏状态,其线性计算成本为 O(sliding_window.seq_len)。在实践中,Mistral AI 对 FlashAttention 和 xFormers 所做的修改使 4k 窗口下 16k 序列长度的速度提高了 2 倍。

SWA 机制利用了 Transformer 的堆叠层来关注过去超出窗口大小的内容:第 k 层的 token i 会关注第 k-1 层的 token [i-sliding_window, i]。这些 token 关注 token [i-2*sliding_window, i]。比起注意力模式所需要的,更高层能够接触到更久远的过去信息。

最后,固定的注意力跨度意味着可以使用旋转缓冲区将缓存的大小限制在 token sliding_window 的范围内。这为 8192 序列长度的推理节省了一半的缓存内存,而不会影响模型质量。

针对聊天微调 Mistral 7B

为了展示 Mistral 7B 的泛化能力,研究者在 HuggingFace 上公开的指令数据集上对其进行了微调。没有技巧,没有专有数据。由此产生的模型 Mistral 7B Instruct 在 MT-Bench 上优于所有 7B 模型,并可与 13B 聊天模型相媲美。

Mistral 7B Instruct 模型证明了微调基础模型以获得非常好性能是容易做到的。

参考链接:

​https://twitter.com/GuillaumeLample/status/1707053786374496726​

​https://mistral.ai/news/announcing-mistral-7b/​

.....

#Low-Rank Adaption

最近几个月,ChatGPT 等一系列大语言模型(LLM)相继出现,随之而来的是算力紧缺日益严重。虽然人人都想打造专属于自己的大模型,但是能负担得起上亿参数模型训练的机构却寥寥无几。

在快速发展的人工智能领域,以高效和有效的方式使用大型语言模型正变得越来越重要。

LoRA(Low-Rank Adaption,低秩自适应) 作为微调 LLMs 一种比较出圈的技术,其额外引入了可训练的低秩分解矩阵,同时固定住预训练权重,从而大大减少了下游任务的可训练参数数量。

本文中,来自 Lightning AI 首席人工智能教育家 Sebastian Raschka 将带你了解如何以高效的方式用 LoRA 来微调 LLM。下面是全文内容。

为什么要进行微调?

预训练大语言模型通常被称为基础模型,这样称呼的原因是:大语言模型在各种任务中表现良好,可以将它们用作对目标任务进行微调的基础。正如 Sebastian Raschka 在上一篇博文《Understanding Parameter-Efficient Finetuning of Large Language Models: From Prefix Tuning to LLaMA-Adapters》中所讨论的,微调能够使模型适应目标域和目标任务。尽管如此,大模型在计算上的成本可能非常昂贵 —— 模型越大,更新其网络层的成本就越高。

如果不想将网络中所有的层都进行更新,可以使用诸如前缀微调和适配器之类的高效调参方法。如今,有一种更流行的微调技术:Hu 等人提出的低秩自适应(LoRA)。

什么是 LoRA?它是如何工作的?它与其他流行的微调方法相比如何?本文将回答所有这些问题。

提高权重更新效率

论文《 LoRA: Low-Rank Adaptation of Large Language Models》提出将权重变化 ΔW 分解为秩较低的表示。(LoRA 不会直接分解矩阵,而是通过反向传播来学习分解的矩阵)。

在仔细研究 LoRA 之前,我们先简要解释一下规则微调期间的训练程序。首先是权重变化 ΔW。假设 W 表示给定神经网络层中的权重矩阵。然后,使用常规反向传播,我们可以获得权重更新 ΔW,它通常被计算为损失乘以学习率的负梯:

然后,得到 ΔW 后,原始权重按如下公式更新:W'=W+ΔW。如下图所示(为了简单起见,省略了偏置矢量)。

或者,我们可以保持权重更新矩阵分离,并按如下公式计算输出:h=Wx+ΔWx:

其中 x 表示输入,如下所示: 

当在神经网络中训练全连接(即 dense)层时,如上所示,权重矩阵通常具有全秩,这意味着矩阵不具有任何线性相关(即冗余)的行或列。相比之下,与全秩相比,低秩意味着矩阵具有冗余的行或列。

因此,尽管预训练模型的权重是满秩矩阵,但根据 Aghajanyan 等人的说法,LoRA 的作者指出预训练的大型语言模型在适应新任务时具有较低的内在维度。

低内在维度意味着数据可以由低维度空间有效地表示或近似,同时保留其大部分基本信息或结构。换句话说,这意味着可以将适应任务的新权重矩阵分解为低维(较小)矩阵,而不会丢失太多重要信息。

例如,假设 ΔW 是 A×B 维权重矩阵的权重更新矩阵,这个权重更新矩阵可以分解为两个较小的矩阵:ΔW=W_A W_B,其中 W_A 是 A×r 维矩阵,W_B 是 r×B 维矩阵。在这里,我们保持原始权重 W 冻结,并且只训练新的矩阵 W_A 和 W_B。如下图所示。

选择秩

上图中的 r 是超参数,指定用于自适应的低秩矩阵的秩。r 越小,低秩矩阵越简单,在自适应过程中需要学习的参数越少,训练就更快,计算需求会相应的减少。然而,r 变小的弊端是,低秩矩阵捕获任务特定信息的能力降低。这可能导致较低的自适应质量,并且与较高的 r 相比,模型在新任务上可能表现不佳。总之,在 LoRA 中确定 r 的取值,需要在模型复杂性、自适应能力和拟合不足或拟合过度的风险之间进行权衡。因此,重要的是用不同的 r 值进行实验,以找到正确的平衡,从而在新任务中满足所需的性能。

 LoRA 实战

LoRA 的使用很直接,可以将其视为 LLM 中全连接层的前向传递修正版。伪代码如下所示:

参数效率

接下来说一说最棘手的问题:如果引入新的权重矩阵,参数如何才能有效?新的矩阵 W_A 和 W_B 可以非常小。例如,假设 A=100,B=500,那么 ΔW 的大小为 100×500=50000。如果将其分解为两个较小的矩阵,一个 100×5 维矩阵 W_A 和一个 5×500 维矩阵 W_B。这两个矩阵总共只有 5×100+5×500=3000 个参数。

 减少推理开销

在实践中,如果在如上所示的训练后保持原始权重 W 以及矩阵 W_A 和 W_B 处于分离状态,推理过程中就会额外产生效率损失,因为引入了额外的计算步骤。相反可以在训练后通过 W’=W+W_A・W_B 更新权重,这类似于前面提到的 W’=W+ΔW。

然而,保持权重矩阵 W_A 和 W_B 分离也可能有些优势。例如,假设希望保留预训练的模型作为各种客户的基础模型,并且希望从基础模型开始为每个客户创建一个微调的 LLM。在这种情况下,就不需要为每个客户存储完整的权重矩阵 W’。不然存储模型的所有权重 W’=W+W_A W_B 对于 LLM 来说可能非常大,因为 LLM 通常具有数十亿到数万亿的权重参数。因此,可以保留原始模型 W,只需要存储新的轻量级矩阵 W_A 和 W_B。

用具体的数字来说明的话,一个完整的 7B LLaMA checkpoint 需要 23 GB 的存储容量,而选择 r=8 的秩,则 LoRA 权重可以小到 8 MB。

 实践效果

LoRA 在实践中有多好,与完全微调和其他参数有效方法相比如何?根据 LoRA 的论文,在几个特定任务的基准测试中,使用 LoRA 的模型的建模性能略好于使用 Adapters、prompt tuning 或 prefix tuning 的模型。通常,LoRA 的性能甚至比微调所有层更好,如下面 LoRA 论文的注释表所示。

值得注意的是,LoRA 与其他微调方法正交,这意味着它也可以与 Adapters 或 prefix tuning 相结合。

LoRA & LLaMA

现在,让我们使用 LoRA 来微调 Meta 提出的 LLaMA 模型。

除了用于训练和运行 LLaMA 本身的代码(使用原始的 Meta-LLaMA 权重)外,还包含用于使用 LLaMA Adapter 和 LoRA 微调 LLaMA 的代码。

作者建议使用以下操作方法文件:

  • 下载预训练的权重:https://github.com/Lightning-AI/lit-llama/blob/main/howto/download_weights.md
  • 使用 LoRA 进行微调:https://github.com/Lightning-AI/lit-llama/blob/main/howto/finetune_lora.md
  • 使用适配器进行微调:https://github.com/Lightning-AI/lit-llama/blob/main/howto/finetune_adapter.md(可选,用于比较研究)

下一节将比较 7B LLaMA 基础模型与使用 LoRA 和 LLaMA Adapter 微调的 7B LLaMA 基础模型。(请注意,需要具有至少 24 GB RAM 的 GPU)。

计算性能基准

本节中,作者将比较 LLaMA 7B 基础模型与使用 LoRA 和 LLaMA Adapter 微调的基础模型的计算性能。

微调数据集是 Alpaca 52k 指令数据集,其结构如下:

数据集是按照 Self-Instruct 论文中描述的方法生成的,由 49759 个训练样本和 2000 个验证样本组成。Self-Instruct 的流程可总结为 4 个步骤:

  • 种子任务池,包含一组人工编写的指令(本例中为 175 条)和样本指令;
  • 使用预训练的 LLM(如 GPT-3)来确定任务类别;
  • 给定新指令,让预训练的 LLM 生成响应结果;
  • 在将响应结果添加到任务池之前,先收集、剪枝和筛选响应结果。

Alpaca 52k 数据集是使用上述 Self-Instruct 程序收集的。但是,也可以使用(或将其与)替代数据集进行比较。例如,一个有趣的候选数据集是最近发布的开源 databricks-doolly-15k 数据集,该数据集包含 databricks 员工编写的约 15k 条指令 / 响应微调记录。Lit LLaMA 存储库包含一个数据集制备脚本,可以选择使用 Dolly 15k 数据集。

给定以下超参数设置(块大小、批大小和 LoRA 的 r),Adapter 和 LoRA 都可以以 bfloat-16 的混合精度,在具有 24 Gb RAM 的单个 GPU 上微调 7B 参数的 LLaMA 基本模型。

LoRA

LaMA Adapter 

如果代码将来发生变化,GitHub 上会同步更新代码(带有超参数设置)。

Adapter 在 A100 上使用了大约 22 Gb 的空间,并在 162 分钟内完成了 62400 次迭代。同样的迭代次数下,LoRA 使用了 21 Gb 的内存,在 192 分钟内完成。总之,同样基于 Lit-LLaMA 的 Adapter 和 LoRA 使用的 RAM 数量大致相同,训练时间大致相同。(请注意,这是在单个 GPU 上进行的,但如果有多个 GPU,只需将设备参数更改为 > 1 即可利用额外的加速!)相比之下,完全微调(LLaMA 7B 由 32 个 Transformer 块和 3 个全连接的输出层组成)需要至少 2 个具有至少 30Gb 的 GPU 和完全分片训练来分配权重。或者,可以使用 4 个 GPU,每个 GPU 的最大内存使用量为 22 Gb。在 4 个 GPU 上进行训练,训练耗时 1956 分钟。在单个 GPU 上至少需要 6000 分钟,这将比参数高效的 LLaMA Adapter 或 LoRA 代价昂贵 30-40 倍。

接下来,本文将介绍应用不同微调策略后的模型输出。

评估建模性能

有几种度量标准可用于评估 LLM 生成的文本。例如,困惑度、BLEU 和 ROUGE 分数是自然语言处理中用来评估 LLM 在各种任务中的表现的一些最常见的评估指标。然而,所有这些指标都有很大的缺点,人工评估仍然是黄金标准 —— 人工评估的缺点是创建成本高,难以自动化。简单起见,本文只关注定性比较,看看各种模型产生的一些输出。

为了对模型进行定性比较,本文将使用 “请解释权重衰减是如何工作的” 作为 prompt。

基准模型

请解释一下权重衰减是如何工作的…… 以及为什么它有时看起来有效,有时无效!

最简单地说,权重衰减从模型中删除特征(作为特征重要性的函数),它也被称为正则化。

推理时间 1:4.18 秒,11.95 个 token / 秒

正如我们所看到的,模型开始通过附加 “… 以及为什么它有时看起来有效,有时不起作用!” 来补全 prompt。这是因为基础模型只在下一个单词预测任务中进行了训练,没有经过专门的训练来处理指令。尽管如此,它试图在下面进一步解释。这种解释对正则化做出了一些正确的解释,但它并没有真正捕捉到权重衰减的关键机制。

LLaMA-Adapter

权重衰减是一种惩罚词条字数的方法。通过选择单词较少的句子,模型更有可能选择那些可能提供重要信息的句子。

推理时间:总计 7.04 秒,14.21 个 tokens/sec。

使用的内存:13.56 GB。

这个解释在语法上是合理的,但也不一定是我们想要的。

LoRA

权重衰减是机器学习模型中用于减少过拟合的算法参数。这是一个调整模型权重的过程,以减少模型中不太重要的特征的影响并防止过拟合。权重衰减是通过向权重添加少量噪声来减少模型对训练数据的影响。这个过程鼓励模型更多地关注对预测任务更重要的特征。

推理时间:总计 3.25 秒,30.76 个 tokens/sec。

使用的内存:27.65 GB。

可以看到离期望的答案越来越近了;放到广义线性模型中,这种解释确实有意义,在广义线性模型中,会强迫模型学习与输入特征相乘结果较小的权重参数。在神经网络中,这通常会应用于模型中的所有权重参数。

上面的 LoRA 方法目前来说使用的内存最多。然而,如前所述,可以通过将 LoRA 权重与预训练的模型权重合并来减少这种内存使用。

由于评估 LLM 本身就是一个大课题,因此这种定性概述只能反应每个模型能力的一小部分。但这里的要点是,LoRA 可以用于以相对经济高效的方式在指令数据集上微调 LLM。

结论

本文讨论了低秩自适应(LoRA),这是一种参数完全微调的有效替代方案。使用 LoRA,可以在几个小时内在单个 GPU 上对 LLaMA 等相对较大的模型进行微调,这使得它对那些不想在 GPU 资源上花费数千美元的人特别有吸引力。LoRA 的特别之处在于,可以选择性地将新的 LoRA 权重矩阵与原始的预训练权重合并,这样在推理过程中就不会产生额外的开销或复杂性。

随着越来越多的 ChatGPT 或 GPT-4 开源替代品的出现,在特定的目标数据集或目标上微调和定制这些 LLM 将在各个研究领域和行业变得越来越有吸引力。而 LoRA 等参数有效的微调技术使微调更具资源效率和可访问性。Lit LLaMA 存储库中提供了诸如 LoRA 和 LLaMA Adapter 之类的参数高效微调技术。

 .....

#Megatron-LLaMA

32卡176%训练加速,开源大模型训练框架Megatron-LLaMA来了

9 月 12 日,淘天集团联合爱橙科技正式对外开源大模型训练框架 ——Megatron-LLaMA,旨在让技术开发者们能够更方便的提升大语言模型训练性能,降低训练成本,并且保持和 LLaMA 社区的兼容性。测试显示,在 32 卡训练上,相比 HuggingFace 上直接获得的代码版本,Megatron-LLaMA 能够取得 176% 的加速;在大规模的训练上,Megatron-LLaMA 相比较 32 卡拥有几乎线性的扩展性,而且对网络不稳定表现出高容忍度。目前 Megatron-LLaMA 已在开源社区上线。

开源地址:https://github.com/alibaba/Megatron-LLaMA

大语言模型的卓越表现一次又一次地超出了人们的想象。过去几个月,LLaMA 及 LLaMA2 向开源社区的全面放开,使想训练一个自己的大语言模型的人们多了一种很好的选择。在开源社区中,已有非常多的基于 LLaMA 产出的模型,包括进行续训 / SFT(如 Alpaca、Vicuna、WizardLM、Platypus、StableBegula、Orca、OpenBuddy、Linly、Ziya 等)和从零开始训练(Baichuan、QWen、InternLM、OpenLLaMA)的工作。这些工作不仅在目前各种大模型能力客观评测榜单上表现卓越,同时展现了在长文本理解、长文本生成、代码编写、数学求解等实用场景的优秀性能。更进一步,业界出现了很多有意思的产品,例如 LLaMA 结合 Whisper 的语音聊天机器人、LLaMA 结合 Stable Diffusion 的绘画软件、医学 / 法律领域的辅助咨询机器人等。

尽管从 HuggingFace 上可以拿到 LLaMA 的模型代码,但用自己的数据训一个 LLaMA 模型对个人用户或中小型组织并不是一件低成本且简单的工作。大模型的体积和数据的规模,使得在普通的计算资源上无法完成有效的训练,算力和成本成为严重的瓶颈。Megatron-LM 社区的用户在这方面的诉求非常急迫。

淘天集团和爱橙科技在大模型应用上有着非常广阔应用场景,在大模型的高效训练上进行了非常多的投入。LLaMA 的问世,在数据处理、模型设计、微调及强化学习反馈调整等方面都给予了包括淘天集团和爱橙科技在内的许多公司非常多的启示,也助力业务应用场景取得了新的突破。因此,为了回馈整个 LLaMA 开源社区、促进中文预训练大模型开源社区的发展,让开发者们能够更方便地提升大语言模型的训练性能,降低训练成本,淘天集团联合爱橙科技将部分内部优化技术开源,发布 Megatron-LLaMA,期望与每一位合作伙伴共建 Megatron 及 LLaMA 生态。

Megatron-LLaMA 提供了一套标准的 Megatron-LM 实现的 LLaMA,并提供了与 HuggingFace 格式自由切换的工具,方便与社区生态工具兼容。Megatron-LLaMA 重新设计了 Megatron-LM 的反向流程,使得无论在节点数较少需要开较大梯度聚合(Gradient Accumulation, GA)、或是节点数较多必须使用小 GA 的场景,都能够取得卓越的训练性能。

  • 在 32 卡训练上,相比 HuggingFace 上直接获得的代码版本,Megatron-LLaMA 能够取得 176% 的加速;即便是采用 DeepSpeed 及 FlashAttention 优化过的版本,Megatron-LLaMA 仍然能减少至少 19% 的训练时间。
  • 在大规模的训练上,Megatron-LLaMA 相比较 32 卡拥有着几乎线性的扩展性。例如使用 512 张 A100 复现 LLaMA-13B 的训练,Megatron-LLaMA 的反向机制相对于原生 Megatron-LM 的 DistributedOptimizer 能够节约至少两天的时间,且没有任何精度损失。
  • Megatron-LLaMA 对网络不稳定表现出高容忍度。即便是在现在性价比较高的 4x200Gbps 通信带宽的 8xA100-80GB 训练集群(这种环境通常是混部环境,网络只能使用一半的带宽,网络带宽是严重的瓶颈,但租用价格相对低廉)上,Megatron-LLaMA 仍然能取得 0.85 的线性扩展能力,然而在这个指标上 Megatron-LM 仅能达到不足 0.7。

Megatron-LM 技术带来的高性能 LLaMA 训练机会

LLaMA 是目前大语言模型开源社区中一项重要工作。LLaMA 在 LLM 的结构中引入了 BPE 字符编码、RoPE 位置编码、SwiGLU 激活函数、RMSNorm 正则化以及 Untied Embedding 等优化技术,在许多客观和主观评测中取得了卓越的效果。LLaMA 提供了 7B、13B、30B、65B/70B 的版本,适用于各类大模型需求的场景,也受到广大开发者的青睐。同诸多开源大模型一样,由于官方只提供了推理版的代码,如何以最低成本开展高效训练,并没有一个标准的范式。

Megatron-LM 是一种优雅的高性能训练解决方案。Megatron-LM 中提供了张量并行(Tensor Parallel,TP,把大乘法分配到多张卡并行计算)、流水线并行(Pipeline Parallel,PP,把模型不同层分配到不同卡处理)、序列并行(Sequence Parallel, SP,序列的不同部分由不同卡处理,节约显存)、DistributedOptimizer 优化(类似 DeepSpeed Zero Stage-2,切分梯度和优化器参数至所有计算节点)等技术,能够显著减少显存占用并提升 GPU 利用率。Megatron-LM 运营着一个活跃的开源社区,持续有新的优化技术、功能设计合并进框架中。

然而,基于 Megatron-LM 进行开发并不简单,在昂贵的多卡机上调试及功能性验证更是十分昂贵的。Megatron-LLaMA 首先提供了一套基于 Megatron-LM 框架实现的 LLaMA 训练代码,支持各种规模的模型版本,并且可以很简单地适配支持 LLaMA 的各类变种,包括对 HuggingFace 格式的 Tokenizer 的直接支持。于是,Megatron-LLaMA 可以很便捷地应用在已有的离线训练链路中,无需进行过多的适配。在中小规模训练 / 微调 LLaMA-7b 和 LLaMA-13b 的场景,Megatron-LLaMA 能够轻松达到业界领先的 54% 及以上的硬件利用率(MFU)。

Megatron-LLaMA 的反向流程优化

图示:DeepSpeed ZeRO Stage-2DeepSpeed ZeRO 是微软推出的一套分布式训练框架,其中提出的技术对很多后来的框架都有非常深远的影响。DeepSpeed ZeRO Stage-2(后文简称 ZeRO-2)是该框架中一项节约显存占用且不增加额外计算量和通信量的技术。如上图所示,由于计算需要,每个 Rank 都需要拥有全部的参数。但对于优化器状态而言,每个 Rank 只负责其中的一部分即可,不必所有 Rank 同时执行完全重复的操作。于是 ZeRO-2 提出将优化器状态均匀地切分在每个 Rank 上(注意,这里并不需要保证每个变量被均分或完整保留在某个 Rank 上),每个 Rank 在训练进程中只负责对应部分的优化器状态和模型参数的更新。在这种设定下,梯度也可以按此方式进行切分。默认情况下,ZeRO-2 在反向时在所有 Rank 间使用 Reduce 方式聚合梯度,而后每个 Rank 只需要保留自身所负责的参数的部分,既消除了冗余的重复计算,又降低了显存占用。

原生 Megatron-LM 通过 DistributedOptimizer 实现了类似 ZeRO-2 的梯度和优化器状态切分,以减少训练中的显存占用。如上图所示,DistributedOptimizer 在每次获得预设的梯度聚合过的所有梯度后,通过 ReduceScatter 算子,将之前累积的全部梯度分发到不同的 Rank。每个 Rank 只获得自己需要处理的部分梯度,而后进行优化器状态的更新和对应参数的更新。最后各个 Rank 通过 AllGather 的方式从其他节点上获取更新过的参数,最终取得全部的参数。实际训练的结果显示,Megatron-LM 的梯度和参数通信与其他计算串行进行,对于大规模预训练任务,为了保证总批数据大小不变,通常无法开启较大的 GA。于是通信占比会伴随机器增加上升,这时候串行通信的特点导致扩展性很弱。在社区内,这方面的需求也很迫切。

Megatron-LLaMA OverlappedDistributedOptimizer为了解决这一问题,Megatron-LLaMA 改进了原生 Megatron-LM 的 DistributedOptimizer,使其梯度通信的算子能够可以和计算相并行。特别的,相比于 ZeRO 的实现,Megatron-LLaMA 在并行的前提下,通过巧妙的优化优化器分区策略,使用了更具有具有扩展性的集合通信方式来提升扩展性。OverlappedDistributedOptimizer 的主要设计保证了如下几点:a) 单一集合通信算子数据量足够大,充分利用通信带宽;b) 新切分方式所需通信数据量应等于数据并行所需的最小通信数据量;c) 完整参数或梯度与切分后的参数或梯度的转化过程中,不能引入过多显存拷贝。

具体而言,Megatron-LLaMA 改进了 DistributedOptimizer 的机制,提出了 OverlappedDistributedOptimizer,用于结合新的切分方式优化训练中的反向流程。如上图所示,在 OverlappedDistributedOptimizer 初始化时,会预先给所有参数分配其所属的 Bucket。Bucket 中的参数是完整的,一个参数仅属于一个 Bucket,一个 Bucket 中可能有多个参数。逻辑上,每个 Bucket 将会被连续等分成 P(P 为数据并行组的数量)等份,数据并行组中的每个 Rank 负责其中的一份。

Bucket 被放置在一个本地队列(Local grad bucket queue)中,从而保证通信顺序。在训练计算的同时,数据并行组间以 Bucket 为单位,通过集合通讯交换各自需要的梯度。Megatron-LLaMA 中 Bucket 的实现尽可能采用了地址索引,只在有需要值更改时才新分配空间,避免了显存浪费。

上述的设计,再结合大量的工程优化,使得在大规模训练时,Megatron-LLaMA 可以很充分地使用硬件,实现了比原生 Megatron-LM 更好的加速。从32张A100卡扩展到512张A100卡的训练,Megatron-LLaMA在常用混部的网络环境中仍然能够取得0.85的扩展比。

Megatron-LLaMA 的未来计划

Megatron-LLaMA 是由淘天集团和爱橙科技共同开源并提供后续维护支持的训练框架,在内部已有广泛的应用。随着越来越多的开发者涌入 LLaMA 的开源社区并贡献可以相互借鉴的经验,相信未来在训练框架层面会有更多的挑战和机会。Megatron-LLaMA 将会紧密关注社区的发展,并与广大开发者共同推进以下方向:

  • 自适应最优配置选择
  • 更多模型结构或局部设计改动的支持
  • 在更多不同类硬件环境下的极致性能训练解决方案

项目地址:https://github.com/alibaba/Megatron-LLaMA

 .....

#Birth of a Transformer

以下是标题由来

更好地理解基于 Transformer 的大型语言模型(LLM)的内部机制,以提高它们的可靠性和可解释性。

随着大型语言模型(LLM)在使用和部署方面的不断增加,打开黑箱并了解它们的内部工作原理变得越来越重要。更好地理解这些模型是如何做出决策的,这对改进模型和减轻其故障(如幻觉或推理错误)至关重要。

众所周知,最近 LLM 成功的一个重要因素是它们能够从上下文中学习和推理。LLM 对这些上下文的学习能力通常归功于 Transformer 架构,特别是自注意力块的使用,其能够仔细选择输入序列,进而推理出可信的下一个 token。此外,预测可能需要全局知识,如语法规则或一般事实,这些可能不会出现在上下文中,需要存储在模型中。

我们不禁会疑问,为什么基于 Transformer 的模型非常擅长使用它们的上下文来预测新的 token,这种能力是如何在训练中产生的?带着这些问题,来自 Meta AI 的研究者进行了深入的研究。他们通过研究合成设置下 Transformer 的学习机制,揭示了其全局和上下文学习的平衡,并将权重矩阵解释为联想记忆,为理解和优化 Transformer 提供了基础。

论文地址:https://arxiv.org/pdf/2306.00802.pdf

首先要了解的是在训练过程中 Transformer 是如何发现这些能力的。为此,该研究引入了一个合成数据集,该数据集由二元语言模型生成的序列组成。然后,模型需要依靠上下文学习来对特定的二元序列进行良好的预测,而全局二元可以根据当前 token 的全局统计数据进行猜测。虽然单层的 Transformer 无法可靠地预测上下文二元,但该研究发现通过开发感应头(induction head)机制的双层 Transformer 取得了成功,即拥有两个注意力头的 circuit,其允许 Transformer 从上下文 [・・・, a, b,・・・, a] 中预测 b,并且在 Transformer 语言模型中似乎无处不在。这种感应头(induction head)机制在 Transformer 语言模型中是普遍存在的,并且取得了成功。

更进一步的,为了更好的了解上下文机制是怎样出现在训练过程中的,该研究在随机初始化时冻结了一些层(包括嵌入和值矩阵)来进一步简化模型架构。这样一来研究重点转移到注意力和前馈机制,同时避免了学习表征的困难。与此同时,这种简化还为单个权重矩阵引入了一个自然模型作为联想记忆。自然模型可以通过它们的外积存储输入 - 输出或键 - 值对嵌入。随机高维嵌入由于其接近正交性而特别适合这种观点。

总结而言,该研究的贡献可概括为:

  • 本文引入了一种新的合成设置来研究全局和上下文学习:序列遵循二元语言模型,其中一些二元在序列中变化,而另一些不会。
  • 本文将 Transformer 的权重矩阵视为学习存储特定嵌入对的联想记忆,并以此为任务推导出一个简化但更可解释的模型。
  • 本文对训练动态进行了细致的实证研究:首先学习全局二元,然后以自上而下的方式学习适当的记忆,形成感应头。
  • 本文给出了训练动力学的理论见解,展示了如何通过在噪声输入中找到信号,在种群损失上进行一些自上而下的梯度步骤来恢复所需的联想记忆。

方法介绍

接着该研究介绍了合成数据设置,这样能够仔细研究感应头机制在训练过程中的发展以及 Transformer 如何学习利用上下文信息的。

双元数据模型:模型序列由一个通用的双元语言模型(即马尔可夫链)组成,每个序列的生成方式如下:

   下图 2 可视化了测试序列上的注意力图,这表明该模型已经学习了感应头机制。 

接着该研究介绍了 Transformer 联想记忆观点:因为几乎正交的嵌入,权重矩阵表现为联想记忆,将成对的嵌入存储为其外积的加权和。研究引入了一个具有固定随机嵌入的简化 Transformer 模型,将用这种想法产生对学习动力学的精确理解。

此外,该研究提出了一个有用的观点,将 Transformer 中的模型权重视为高维嵌入向量的联想记忆。感应头机制可以通过以下外积矩阵作为记忆来获得,而其他所有权重则固定为随机初始化状态:

图 3 研究了在迭代 300 次之前冻结不同层对训练动态的影响。 

全局 vs 上下文学习。从图 4(左 / 右)可以看出,当联合训练所有层时,全局二元统计的学习速度往往比感应头更快,这可以从早期迭代中的 loss 和 KL 的快速下降中看出。

此外,从图 4(左)中看到,数据分布的变化会对上下文机制的学习速度产生重大影响。该研究观察到以下情况可能会使上下文学习减慢:(i) 较少数量的触发器 K, (ii) 仅使用少有的固定触发器,以及 (iii) 使用随机触发器而不是固定触发器。

该研究还在图 4(中间)中显示,在训练时将输出 token 分布更改为二元分布会降低准确率,这表明,使用更多样化的训练分布可以产生具有更好泛化准确率的模型,并且只需少量的额外训练成本。

.....

#DragNUWA

随着 ChatGPT、GPT-4、LLaMa 等模型的问世,人们越来越关注生成式模型的发展。相比于日渐成熟的文本生成和图像生成,视频、语音等模态的 AI 生成还面临着较大的挑战。

现有可控视频生成工作主要存在两个问题:首先,大多数现有工作基于文本、图像或轨迹来控制视频的生成,无法实现视频的细粒度控制;其次,轨迹控制研究仍处于早期阶段,大多数实验都是在 Human3.6M 等简单数据集上进行的,这种约束限制了模型有效处理开放域图像和复杂弯曲轨迹的能力。

基于此,来自中国科学技术大学、微软亚研和北京大学的研究者提出了一种基于开放域扩散的新型视频生成模型 ——DragNUWA。DragNUWA 从语义、空间和时间三个角度实现了对视频内容的细粒度控制。

论文地址:https://arxiv.org/abs/2308.08089

以拖动(drag)的方式给出运动轨迹,DragNUWA 就能让图像中的物体对象按照该轨迹移动位置,并且可以直接生成连贯的视频。例如,让两个滑滑板的小男孩按要求路线滑行:

还可以「变换」静态景物图像的相机位置和角度:

方法简介

该研究认为文本、图像、轨迹这三种类型的控制是缺一不可的,因为它们各自有助于从语义、空间和时间角度控制视频内容。如下图 1 所示,仅文本和图像的组合不足以传达视频中存在的复杂运动细节,这可以用轨迹信息来补充;仅图像和轨迹组合无法充分表征视频中的未来物体,文本控制可以弥补这一点;在表达抽象概念时,仅依赖轨迹和文本可能会导致歧义,图像控制可以提供必要的区别。

为了解决当前研究中有限的开放域轨迹控制问题,该研究重点关注三个方面的轨迹建模: 

  • 使用轨迹采样器(Trajectory Sampler,TS)在训练期间直接从开放域视频流中采样轨迹,用于实现任意轨迹的开放域控制;
  • 使用多尺度融合(Multiscale Fusion,MF)将轨迹下采样到各种尺度,并将其与 UNet 架构每个块内的文本和图像深度集成,用于控制不同粒度的轨迹;
  • 采用自适应训练(Adaptive Training,AT)策略,以密集流为初始条件来稳定视频生成,然后在稀疏轨迹上进行训练以适应模型,最终生成稳定且连贯的视频。

实验及结果

该研究用大量实验来验证 DragNUWA 的有效性,实验结果展示了其在视频合成细粒度控制方面的卓越性能。

与现有专注于文本或图像控制的研究不同,DragNUWA 主要强调建模轨迹控制。为了验证轨迹控制的有效性,该研究从相机运动和复杂轨迹两个方面测试了 DragNUWA。

如下图 4 所示,DragNUWA 虽然没有明确地对相机运动进行建模,但它从开放域轨迹的建模中学习了各种相机运动。

为了评估 DragNUWA 对复杂运动的精确建模能力,该研究使用相同的图像和文本对各种复杂的拖动(drag)轨迹进行了测试。如下图 5 所示,实验结果表明 DragNUWA 能够可靠地控制复杂运动。  

此外,DragNUWA 虽然主要强调轨迹控制建模,但也融合了文本和图像控制。研究团队认为,文本、图像和轨迹分别对应视频的三个基本控制方面:语义、空间和时间。下图 6 通过展示文本(p)、轨迹(g)和图像(s)的不同组合(包括 s2v、p2v、gs2v、ps2v 和 pgs2v)说明了这些控制条件的必要性。

....

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐