51c大模型~合集98

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

#Low-Bit Quantization Favors Undertrained LLMs

低精度只适用于未充分训练的LLM?腾讯提出LLM量化的scaling laws

本文来自腾讯 AI Lab,介绍了一套针对于低比特量化的 scaling laws。

  • 论文标题:Low-Bit Quantization Favors Undertrained LLMs: Scaling Laws for Quantized LLMs with 100T Training Tokens
  • 论文链接:https://arxiv.org/abs/2411.17691

低比特量化(low-bit quantization)和低比特大语言模型(low-bit LLM)近期受到了广泛的关注,因为有一些研究发现,它们能够以更小的模型规模、更低的内存占用和更少的计算资源,取得与 fp16 或 bf16 精度相当的性能表现。这一发现让低比特语言模型一度被认为是实现模型高效化的一个非常有前景的方向。

然而,这一观点受到了腾讯 AI Lab 的挑战。他们的研究发现,低比特量化只有在未充分训练的 LLM(训练量通常在 1000 亿 tokens 以内,基本不会超过 5000 亿 tokens:这种 setting 在当前的学术界研究论文中非常常见)上才能取得与 fp16/bf16 相当的性能表现。随着训练的深入和模型逐渐被充分训练,低比特量化与 fp16/bf16 之间的性能差距会显著扩大。

为了更系统地研究这一现象,研究人员量化了超过 1500 个不同大小以及不同训练程度的开源 LLM 检查点。试图观察并建模量化所导致的性能退化(QiD,quantization-induced degradation,即量化后模型与原始 fp16/bf16 模型的性能差距,记作∆qLoss)

图片

最终推演出了一套针对于低比特量化的 scaling laws。通过这套 scaling laws,可以预测出当 7B, 70B 以及 405B 的模型在训练规模达到 100 万亿 tokens 时低比特量化时损失(如下图)。

图片

根据研究人员的描述,这个工作最初是源于 2 个观察(如下图):a) model size 固定的情况下,training tokens 越多,QiD 就会变得越大;b) training token 数固定的情况下,model size 越小,QiD 就会变得越大。考虑到不管是减小 model size 还是增加 training tokens 都会有利于模型更充分的训练,因此研究人员推测在充分训练的模型上进行低比特量化会造成较为严重的 degradation,反之在未充分训练的模型上则不会有太多 degradation.

图片

为了更好地验证这一推测,研究人员选择了 Pythia 系列开源语言模型进行实验,因为 Pythia 系列模型不仅公开了不同尺寸的 LLM,而且还开源了其中间训练过程的检查点。研究人员选取了 160M, 410M, 1B, 2.8B, 6.9B 以及 12B 这 6 种不同尺寸的 LLM。对于每种尺寸的 LLM,又选取了其训练过程中间 20 个检查点。对这 120 个检查点,研究人员用 GPTQ 对它们分别进行了 2-bit, 3-bit, 4-bit 量化,来观察在不同检查点上量化所导致的性能退化(即 QiD)。

通过分别对于 training tokens, model size 以及量化比特数分别的建模分析(分别建模的结果这里就不详述了,感兴趣的可以阅读论文),最终得到一个统一的 scaling laws:

图片

这里 N, D, P 分别表示模型参数量(除掉 embedding 部分),training tokens 数以及精度(比特数)。α, β 和 γ 分别表示它们对应的指数(α, β, γ 均为正数),k 为联合系数。根据这个 scaling law 的公式,我们不难得到当其它变量固定时:

  • N 越大(模型越大),QiD 越小,说明越大的模型,量化掉点越小;
  • D 越大(训练数据量越大),QiD 越大,说明训练越多的模型,量化掉点越多;
  • P 越大(精度越高),QiD 越小,说明量化的精度(比特数)越高,量化掉点越小。

研究人员根据上述函数形式拟合观测到的数据点,得到在 Pythia 系列 LLM 的低比特量化的 scaling law 公式:

图片

研究人员根据这个公式绘制出曲线,发现能够很好地拟合观测到的数据点:

图片

另外,研究人员对不同测试数据,不同量化方法以及不同的基础模型都进行了评测,发现所得到的 scaling laws 的函数形式大概率是普适成立的:

图片

图片

图片

如下图所示,我们现在知道了充分训练的 LLMs 会遭受更大的 QiD,而训练不足的 LLMs 则更容易实现近乎无损的低比特量化。那这个现象是怎么造成的呢?

图片

研究人员从训练时权重变化幅度这一角度给出了一些见解:未经充分训练的 LLMs 往往会经历较大幅度的权重变化,在训练过程中的这种大起大落式的变化会让模型对 weight variation 变得更为鲁棒 —— 即便进行了低比特量化,量化所造成的偏离往往也要小于它在训练过程中经历的偏移;而充分训练的 LLM 在训练过程中的权重变化就会非常小了,往往在小数点后几位变化,这个时候模型如果遭度更大幅度的权重变化 (如低比特量化带来的权重变化),就非常容易造成严重的 degradation.

图片

除此之外,研究人员还开创性地将 QiD 视为一个衡量 LLM 是否充分训练的指标。如果低比特量化的 QiD≈0,那说明这个 LLM 还远远没有充分训练,还没有将参数高精度的潜力发挥出来。那么根据前文所得到的 scaling laws,就可以推算出不同尺寸的 LLM 达到指定 QiD 所需要的 training tokens 数,如下表:

图片

我们以 4-bit 量化造成 QiD=0.2 为例,7B 模型达到这个程度需要近 17.3 万亿 tokens,而 405b 模型则需要将近 50 万亿 tokens. 考虑到近 4 年模型的训练数据量增长了近 50 倍,可以预见未来模型的训练量会更大(例如,未来几年可能会达到 100 万亿 token)。随着模型训练变得更加充分,低比特量化在未来的应用前景则会变得并不明朗。

除此之外,研究人员也对于原生的(native)低比特 LLM(例如BitNet-b1.58)进行了评测,发现其规律与低比特量化近乎一致,但相比于量化,原生的低比特LLM可能会在更后期才会明显暴露这个问题——因为原生的低精度训练能够让模型一直保持在低精度权重下工作的能力。尽管有一些研究声称原生的低比特LLM可以媲美fp16/bf16精度下的表现,但这些研究普遍都是在未充分语言模型上得到的结果从而推出的结论,研究人员认为在充分训练的情况下进行比较的话,低比特LLM也将很难匹敌其在fp16/bf16精度下对应的模型。 

图片

考虑到学术界算力的限制,在未充分训练的 LLM 上进行实验、评测,从而得到一些结论,并试图将这些结论推广为普遍适用,这一现象已经越来越普遍,这也引发了研究人员的担扰,因为在未充分训练的 LLM 上得到的结论并不一定能够普遍适用。研究人员也希望社区能重新审视那些在未充分训练的 LLM 上得到的结论,从而引出更深入的思考与讨论。

最后的最后,研究人员用了一组插画来形象地概括了一下他们的发现:

1. 如果把模型类比成水瓶,那水瓶里的装水量就可以反映模型的训练充分程度。小模型更容易被装满,大模型则需要更多的水才能装满。

图片

2. 量化就相当于用手去挤压瓶身。对于装满水的瓶子,水会溢出(performance degradation);而没装满水的瓶子则不会有水溢出。

图片

3.量化的精度可以类比成挤压瓶身的力量大小。越低比特的量化挤压得越狠,越容易造成大量的水被挤出(significant degradation)。

图片

#谷歌劈柴立军令状

必斩OpenAI,夺回第一!

谷歌CEO劈柴在年度战略会议上放出狠话,2025年将是AI领域生死存亡之年。一场足以改变AI未来走向的巅峰对决,即将在2025年全面打响 

2025年还未开始,硅谷的战鼓已经擂响。

在这场AI巨头之间的角力中,谷歌CEO劈柴放出了一记重磅炸弹。

在谷歌山景城总部举行的年度战略会议上,身着节日毛衣的劈柴语气凝重地对员工们说道——

2025年将是关键的一年!

我们必须认识到当下时刻的紧迫性,公司需要以更快的速度前进。这是一个颠覆性时刻,赌注从未如此之高。

这番话背后,是谷歌与微软OpenAI之间愈演愈烈的较量。

自ChatGPT横空出世以来,这家搜索引擎巨头一直在努力追赶。过去两年,谷歌的努力几乎总被爱抢风头的OpenAI盖了过去。

直到12月,Gemini 2.0 Flash、Veo 2、「谷歌版o1」Gemini 2.0 Flash Thinking等轮番轰炸,一展超越领先模型的潜力。仅用了90天的时间,谷歌终于逆风翻盘。

这意味着,这场AI竞赛终于迎来了转折点。

会议现场,劈柴展示了一张大模型的对比图,Gemini 1.5傲然领先于OpenAI等其他竞争对手的模型。

当被问及ChatGPT正成为AI的代名词时,劈柴坦言道,「在历史场长河中,你不必总是第一个,但必须出类拔萃,必须在同类产品中保持第一梯队。这就是2025年的意义所在」。

谷歌开发者关系负责人Logan Kilpatrick多次暗示,明年谷歌真的要发力了。

——预计1月份的目标,完整版Gemini 2.0正式上线。

甚至,在谷歌DeepMind CEO Hassabis年度总结下面,他称这些都只是开胃菜,好戏将在2025年开始。

有网友预测道,明年谷歌将会把很多强大模型免费推向所有人,而且还会发布直击o3的竞争模型。

为了在这场AI革命中占据制高点,谷歌正全力以赴。

Gemini,下一个5亿级用户爆款应用

对谷歌而言, 可能更糟糕的是竞争对手OpenAI在搜索业务的挑战。

虽然谷歌仍主导搜索市场,但GenAI为人们提供了各种访问在线信息的新方式。

而OpenAI正在被更多人看作是AI的代名词,如同国外把谷歌看作搜索的代名词一样。

谷歌背负着巨大的压力,正通过重金投资Gemini,来巩固期在AI领域的领先优势。

Gemini应用程序允许用户访问许多工具,包括谷歌的聊天机器人。

劈柴表示,「建立大型新业务」是重中之重。

谷歌目前拥有15个用户超过五亿的应用, 而高管们普遍认为Gemini应用将是下一个。

劈柴认为Gemini应用具有「强劲势头」,但也不得不承认「在2025年还有一些工作要做,以缩小差距并建立领导地位」。

劈柴后来补充道:「明年最大的重点是在消费者方面扩展Gemini。」

此外, 谷歌在美国还被法律缠身,包括其在垄断搜索的法律裁定,以及非法主导在线广告技术的指控。

英国监管机构则暂时认定谷歌的广告技术行为影响了该国的竞争力。

对此,劈柴表示:「这是我们的规模和成功所带来的。这是科技正在大规模影响社会的大趋势的一部分。因此,此时此刻,我们要比以往任何时刻都要确保自己不会分心。」

谷歌,「后来者」居上?

OpenRouterAI的数据显示,谷歌旗下的Gemini在开发者中的市场份额从9月份的约5%,直接飙升至>50%市场份额,遥遥领先,连带股价也上涨了14%。

而此时,距离OpenAI发布ChatGPT的2022年11月30日,已经整整过去了2年。

早在2017年,Sam Altman在一次邮件交流中就明确表示,谷歌DeepMind是在AGI竞争中最强大的对手,但令他没想到的是,曾经公认的「AI巨头」谷歌却是个「花架子」。

ChatGPT发布以来,几乎成了「AI的代名词」,不仅迅速征服了市场,还连带着微软起飞,在Edge浏览器中嵌入Copilot AI搜索助手,直接断崖式领先。

被寄予厚望的谷歌,反倒是昏招频出,先是Bard难产,又经历AI Overview总结能力大翻车,建议孕妇吸烟、自杀跳桥等等。

在AI顺风车下,股价不涨反降,谷歌一时间沦为笑柄,被OpenAI打的毫无还手之力,更准确的说,连同台竞技的资格都没有。

让人费解的是,谷歌和DeepMind拥有世界上最好的硬件、最多的训练数据、最顶尖的人才,也发过很多具有划时代意义的论文,如AlphaFold、GenCast等,但为什么就连Anthropic这样的创业公司都打不过?

网友分析的细节原因不胜枚举,一句话来说,就是「船大难掉头」。

从安全性上考虑,谷歌作为世界级独一档的科技巨头,不论发布什么产品,其用户群体都不会小,会涉及到各个种族、不同的意识形态,一点小问题就会被无限放大,而文本生成又是非常主观的,很容易受到训练材料的偏见影响,所以谷歌需要更长的时间进行合规检查。

23年初,谷歌受舆论裹挟,急急忙忙发布Bard,在发布会上关于问题「关于詹姆斯·韦伯太空望远镜,我可以告诉我9岁的孩子它有哪些新发现?」,Bard给出错误答案。

这直接导致公司股价暴跌8%,市值缩水1000多亿美元,让投资群体和用户大失所望。

但其实OpenAI的产品也经常胡说八道、张冠李戴,不过因为是创业公司,所以大众的容忍度会高很多,产品标注为beta测试,还可以不断迭代修改。

作为大公司的谷歌,也受到更多法律上的限制,比如用户数据的版权问题。

谷歌曾因在AI训练过程中违反欧盟版权法被罚款2.5亿欧元,也是全球首个因AI训练数据被罚款的案例,坐拥金山,却无法使用,无异于自断双臂。

谷歌的产品线庞大,想要在所有产品中都加入AI驱动,需要非常细致的产品管理能力,而去年末发布的Gemini 1.0,因其过度「多样化」的策略,再次导致股价下跌。

不过,「成功者说什么都是对的」,谷歌重新以王者之姿横扫AI,未来还计划继续引领「智能体」发展,携手浏览器和手机端,全自动实现用户任务。

谷歌年度AI大事件

谷歌并非没有看到AI市场的巨大潜力, 但2024年才算是开始「发力」的一年。

很多有名气的产品基本都是在今年发布的,比如各种版本的Gemini,NotebookLM,Pixel手机上的AI功能等等。

传送门:https://blog.google/technology/ai/google-ai-news-recap-2024/

谷歌宣称在2024年有「60条重大AI发布」, 不妨看看其中几条主要的基础能力。​

Gemini模型

去年12月,谷歌推出首个原生多模态模型Gemini 1.0,打响了谷歌的AI反击战。

它可以同时处理文本、视频、图像、音频和代码等数据,结合了包括数学、物理、历史、法律、医学和伦理在内的 57 个学科,也是第一个在MMLU(大规模多任务语言理解)基准上超越人类专家的模型。

今年2月,谷歌将Gemini 升级到1.5,把上下文窗口从32k提升到100万个token,超越了同时期所有大模型。

在推理性能上也有大幅提升,Bard也正式更名为Gemini

7月,免费版Gemini 1.5 Flash发布,支持40多种语言,覆盖230多个国家和地区,质量和延迟都有大幅提升,尤其是在推理和图像理解方面。

12月推出的Gemini 2.0 Flash集成了多模态和原生工具使用能力,标志着大模型正式迈入「智能体」时代。

基于Gemini 2.0, 谷歌构建了原型项目Mariner,从浏览器出发探索全新的人机交互方式:训练Gemini来理解并推理浏览器屏幕上的信息,包括像素和文本、代码、图像和表单等元素,然后通过实验性的Chrome扩展程序自主完成复杂任务。

在产品方面,谷歌于今年5月基于Gemini推出Ask Photos功能,用户可以通过输入关键词,如地点、人物和日期,或是类似「主题生日派对」等自然语言概念对照片库进行检索。​

NotebookLM

去年7月,谷歌推出了一款AI驱动的科研和写作助手NotebookLM。

一年后,其底层模型切换为Gemini 1.5 Pro,用户可以上传研究笔记、访谈记录或公司文件,然后提出相关问题以理解和探索复杂材料,支持幻灯片、pdf等多种格式。

9月,NotebookLM发布重磅Audio Overview功能,模型可以针对用户材料生成两位AI主持人互相讨论的音频,可以帮助用户对材料进行「总结」和「深入讨论」。

不过该功能目前仍然处于实验阶段,比如只会英语,无法打断等问题。​

AlphaFold 3

今年5月,谷歌的AlphaFold 3论文在Nature上发表,在预测蛋白质与其他分子类型相互作用上性能提升了至少50%,在某些重要的相互作用类别,其准确率甚至能翻倍。

论文链接:https://www.nature.com/articles/s41586-024-07487-w

2020年发布的AlphaFold 2实现了蛋白质结构预测的根本性突破,在包括疟疾疫苗、癌症治疗和酶设计等领域辅助数百万科研人员进行新发现,引用超过2万次。

AlphaFold 3则跳出蛋白质,进入广泛的生物分子领域,有可能解锁更多变革性科学成果,比如开发生物可再生材料和更具弹性的作物,加速药物设计和基因组学研究。

11月,研究人员发布了AlphaFold 3模型代码和权重,以供学术使用。​

Pixel移动端

1月份,谷歌宣布为新款Galaxy S24系列的录音、笔记等软件基于Gemini Pro提供摘要能力;基于文生图模型Imagen 2,为Galaxy S24图库提供生成式照片编辑功能。

八月,谷歌发布自家手机Pixel 9,使用全新的定制芯片Tensor G4提供AI计算能力,整合了大量AI能力,包括生成定制天气报告、整理截图信息、本地文生图等多种功能。

其中最重要的Gemini Live,能够以更直观、自然的方式帮助用户计划旅行攻略、解决家庭维修问题、构思礼物等等。

期待明年谷歌即将带来的礼物。

参考资料:

​https://x.com/tsarnick/status/1872927162757726475​

​https://www.cnbc.com/2024/12/27/google-ceo-pichai-tells-employees-the-stakes-are-high-for-2025.html​

​https://techcrunch.com/2024/12/28/google-ceo-says-ai-model-gemini-will-the-companys-biggest-focus-in-2025/​

​https://www.cnbc.com/2024/12/27/how-googles-sundar-pichai-navigated-a-pressure-filled-year.html​

#HuggingFace

「源神」稚晖君又双叒叕开源,这一次机器人直接进入人类生活!

近期开源的 Deepseek V3,让国产 MoE 大模型在全球圈粉无数,一跃成为中国 AI 圈的顶流担当。

而作为中国具身智能的领军企业,智元也在2024年底放了个大招,携手上海人工智能实验室等单位重磅开源了AgiBot World,具身智能领域也迎来了「ImageNet」时刻!

AgiBot World(智元世界)—— 一个汇集百万真实机器人数据的开源数据集。在这个具身数据的世界里,机器人不再只是进行简单的桌面任务,而是全方位融入我们的日常生活。机器人和人类和谐相处世界的大幕,正在徐徐拉开。

2024,哪个场景最符合你对未来机器人的想象?

作你的「家务管家」,洗衣、做饭都交给它?

还是化身「打工人」在超市拣货、收银、整理货架?

亦或是工厂里的「永动机」,搬运、打包,不断电不下班?

这些「科幻片」里的场景已经在路上了!

欢迎来到 AgiBot World,一个汇集百万真实机器人数据的开源数据集。具身应用,数据先行,作为具身领域的 ImageNet,智元世界有望成为引领我们进入具身智能新时代的「通关密码」,让机器人的世界更加真实,从此告别「NPC」人生!

  • 项目开源地址

HuggingFace:https://huggingface.co/agibot-world

Github:https://github.com/OpenDriveLab/agibot-world

  • 项目主页:https://agibot-world.com/

是的,你没看错,加持了 AgiBot World 的百万真机数据,机器人的控制已经如此精细。现在就能在你家客厅优雅地插花了。

整套动作行云流水,用金属制的机械手抓取,娇嫩的花材也完好无损。

图片

刷马桶这事,等到机器人「出师」后也能放心交给他,再也不用全家抽签确定谁去刷了。

图片

图片

具身智能领域的 ImageNet 何时到来?

图片

Open X-Embodiment, ICRA 2024

谷歌 DeepMind 通过整合来自 22 种不同本体机器人的数据构建了 Open X-Embodiment数据集,但大部分数据缺乏统一标准化的采集流程,且许多机器人构型已经过时,数据质量格式参差不齐,在机器人策略学习的过程中甚至会带来副作用。

为实现规范化的数据采集,来自斯坦福、伯克利、谷歌等构建了 DROID 数据集,尽管涵盖了相对丰富的场景与技能,然而作者团队在后续研究中指出 DROID 存在大量低质量数据,从而给机器人的学习过程造成「困惑」。

一些具身大模型初创公司基于自采集的大规模高质量双臂机器人数据训练的模型展现出了整理、分拣、洗衣等执行复杂动作的能力,这进一步印证了高质量数据在当前具身智能领域研究阶段的重要性,但相关数据集目前仅在公司内部使用,并未开源。

AgiBot World

让高质量机器人数据触手可及

为了进一步推动通用具身智能领域研究进展,让高质量机器人数据触手可及,作为上海模塑申城语料普惠计划中的一份子,智元机器人携手上海人工智能实验室、国家地方共建人形机器人创新中心以及上海库帕思,重磅发布全球首个基于全域真实场景、全能硬件平台、全程质量把控的百万真机数据集开源项目 AgiBot World。

这一里程碑式的开源项目,旨在构建国际领先的开源技术底座,标志着具身智能领域 「ImageNet 时刻」已到来。

通过汇聚顶尖资源与技术力量,各方将共同推动具身智能发展新范式,加速人类迈向通用人工智能的新时代,在全球范围内奠定中国在这一前沿领域的领导地位。

AgiBot World 是全球首个基于全域真实场景、全能硬件平台、全程质量把控的大规模机器人数据集。

相比于 Google 开源的 Open X-Embodiment 数据集,AgiBot World 的长程数据规模高出 10 倍,场景范围覆盖面扩大 100 倍,数据质量从实验室级上升到工业级标准。

多样任务,十八般武艺样样精通

  • 插内存条的过程需要毫米级精细控制,稍有不慎可能导致设备损坏,如神经纤维般灵敏的末端触觉传感器助力机器人精准对接。
  • 饭后勺筷碗盘层层堆叠在洗碗池中,在这条数据中,机器人将杂乱的餐具一一准确无误地整理至洗碗机相应卡槽中,整理洗碗机的操作流程长、动作繁琐。
  • 熨衣服是个「精细活儿」,只见机器人双手协作,一只手稳稳抓住衬衫的一角,另一只手精准控制挂烫机与衣物的距离,细致地熨烫每一道褶皱,让衣物焕发平整光泽。
  • 对于大件物体搬运,单机器人难以完成。两个机器人分工协作,可以分担重量,也能实时调整位置与角度,以确保物体搬运过程中稳定安全。

AgiBot World 数据集收录了八十余种日常生活中的多样化技能,从抓取、放置、推、拉等基础操作,到搅拌、折叠、熨烫等精细长程、双臂协同复杂交互,几乎涵盖了日常生活所需的绝大多数动作需求。

图片

全域场景,上得厅堂下得厨房

AgiBot World 诞生于智元自建的大规模数据采集工厂与应用实验基地,空间总面积超过 4000 平方米,包含 3000 多种真实物品,真实复刻了家居、餐饮、工业、商超和办公五大核心场景。

全面覆盖了机器人在生产、生活中的典型应用需求,为机器人提供了一个高度真实的生产生活环境。

通过多场景的高度还原与任务设计,AgiBot World 为机器人研发和测试构建了实现具身智能的必要条件。

  • 家居场景再现真实住宅布局,包括卧室、客厅、厨房、卫生间等核心空间,可以实现家务清洁、物品整理和厨房任务等
  • 超市场景高度还原超市货架布局与收银区设计,包含生鲜、日用、冷冻等多个品类区域,可以模拟物品上架、货物盘点、顾客引导、无人结算等
  • 餐厅场景实现智能服务体验,模拟前厅、后厨与用餐区域,包括点餐台、备餐区、餐桌等,可以实现餐厅服务(点餐、上菜、清理餐桌)、食材传递、后厨协作等
  • 工业场景模拟分拣与物流自动化,复刻工业仓库与生产线,包括分拣系统、打包设备、传输带等,可以实现物料分拣、包装打包、物流搬运等

客厅、书房、餐厅、卫生间、厨房和工厂等都是机器人大显身手的舞台。

在客厅,它可以使用工具工具给地面来个全面大扫除,碎屑、灰尘和液体都「无处可逃」。

电视机表面容易积灰,交给机器人吧!它可以精确控制掸子掸灰,或者用软布来清理桌面的污垢,同时避免划伤表面。

厨房也可以交给机器人承包了。给它食材和配方,分分钟就能给你变出一盘精致沙拉,切菜、拌料、装盘全都会,也可以控制清洁工具对瓶内外进行刷洗。

瓶子脏了?碗要洗?躺平吧,这些都交给它们!

在超市,机器人可以精准控制扫码抢扫描、结算货物,还附赠装袋一条龙服务。

在工厂,机器人在流水线上利用机械臂精准控制将物品放入指定的包装盒中,实现物品自动打包。

AgiBot World 包含超过 100 种真实场景,按家居(40%)、餐饮(20%)、工业(20%)、商超(10%)和办公(10%)进行分布。

此外,80% 的任务为长程任务,时长集中在 60s-150s 之间,且涵盖多个原子技能,是 DROID 和 OpenX-Embodiment 工作的 5 倍。该数据集包含了 3000 多种物品,基本涵盖了这五大场景,并且仍在不断扩展和丰富中。

图片

场景和任务分布

图片

各个场景物品分类

图片

数据集时长分布

全能硬件,没有金刚钻难揽瓷器活

,时长00:20

  • 360° 感知:8 个摄像头环绕式布局,能够实时全方位感知周围环境的动态变化。
  • 灵巧操作:可配备具有 6 个主动自由度的灵巧手,保障动作精准且灵活,能够完成熨衣服等多种复杂操作。
  • 末端精细感知:标配末端六维力传感器,并可配备高精度视触觉传感器,能够感知力的微小变化,做到「拿捏有度」,从容完成各种精细操作。
  • 高自由度:全身最高 32 个自由度,灵活应对洗衣、做饭、分拣、搬运等复杂任务。

图片

质量把控,严师出高徒

AgiBot World 对数据质量要求极高,通过专业培训、多级质量把控、全程人工在环,在做到超大规模真实数据采集的前提下,严格精细化控制数据质量。

  • 任务设计:从设计初稿和设计迭代流程中,邀请了学术界、工业界、消费者多视角进行任务把关。这样设计出的任务更加贴近真实的工作和生活场景。
  • 数据采集:从采集员培训到采集质量把控,由完善的管理体系和专业的管理团队进行全程保障。
  • 审核标注:对于采集的数据,首先会经过端云两侧的严格筛选,自动剔除不符合要求的数据。此外,专业的审核员会对全量数据进行逐帧审核,确保每一个动作都符合任务标准,并对关键帧和数据特性进行多维度标注。
  • 算法验证:通过人工审核的数据还会进一步通过算法进行验证。对于未能通过验证的数据,会重新设计任务进行数据补采,确保数据可用性。

图片

据智元透露,他们还为即将到来的 2025 年攒了一波「大招」,小小地剧透一下:

1. 百万真机全量数据将陆续开源

2. 千万仿真数据同步推送,支持更泛化和更通用的大模型训练

3. 发布具身基座大模型,支持模型微调,赋能千行百业

4. 发布全套工具链,实现采集、训练和评测完美闭环

5. 举办一系列 AgiBot World Challenge

……

那我们先一把期待住了,且看明年,智元又会带来哪些惊喜?

#PixVerse V3.5

拿下近3亿元融资后,爱诗上线新模型,AI视频生成速度杀入10秒大关

我们实测16个Prompt:生成速度比Sora还快,动漫效果行业SOTA。

前段时间,AI 毒液特效爆火,迅速攻占抖音小红书。

而想出这个「鬼点子」的正是爱诗科技。

他们把自家视频模型 PixVerse V3 和电影《毒液:最后一舞》进行联动,搞出了这一热门玩法。

图片

此外,在 SuperCLUE 12 月文生视频测评结果中,PixVerse V3 综合能力更是吊打 Sora。

图片

图片来源:https://mp.weixin.qq.com/s/yOkK5jG3D9d5xllqbUFDRA

短短一个月,火爆全球的 PixVerse 母公司又发布了新一代视频模型 ——PixVerse V3.5。

该模型主打一个生成速度快、运动控制强,动漫和动画效果还拿下行业第一。

爱诗科技AIsphere

,赞151

先奉上几个视频给大家搂一眼。

,时长00:13

视频来自 X 博主 @aziz4ai:

​https://x.com/aziz4ai/status/1872614428598014298​

还有网友称这绝对是市场上最好的「图片转视频」模型。

图片

,时长00:05

高清视频来自 X 博主 @vladimircherner:

​https://x.com/vladimircherner/status/1872978258444120224​

自今年 1 月 PixVerse V1 版本上线以来,爱诗科技平均每 2-3 个月就有大的模型升级。

其中既有底层模型能力的提升,也有新功能的增加和用户界面的优化。

接下来,我们就来个全方位测评,看看 PixVerse V3.5 究竟进化到何种程度了。

PixVerse 官网链接:https://app.pixverse.ai/

10 秒跑出一个视频

在这个用户体验至上的时代,AI 视频缓慢的生成速度无疑是让用户抓狂的「罪魁祸首」之一。

PixVerse V3.5 就在速度上卷出了新高度,据说是全球首个接近实时生成的高质量 AI 视频模型。

官方称,使用 turbo 模式和分辨率较小的图片进行视频生成,等待时间不超过 10 秒,如果各方面条件给力,它能达到最快 5 秒的生成速度。

为了测评这一点,我们专门掐了下秒表。

以外国老头喝茶看电脑这张梗图为例。我们选择 turbo 模式并使用「圣诞战袍」特效,PixVerse 仅需 7 秒左右的时间即可搞定。

图片

头发花白、一本正经的老头立马换上清凉圣诞装,秒变肌肉男。

图片

AI 视频的生成速度是衡量性能的关键指标之一,因为它直接影响着用户体验和创作效率。

试想当你打开 AI 视频生成应用,急需快速处理任务时,却不得不面对漫长的排队等待,以及像乌龟爬一样的生成速度,这无疑会大大影响创作热情。而 PixVerse V3.5 的出现,恰好解决了这两大痛点。

此外,不同的应用场景对生成速度也有着不同要求。

在直播、游戏等实时互动场景中,快速生成是必不可少的刚需;影视制作虽然对速度要求不那么苛刻,但提升生成效率依然能显著改善工作流程。

因此,PixVerse V3.5 此番提速,势必会为内容创作开启更多可能性。

运动控制强

一直以来,精确的运动控制都是 AI 视频的短板。

只要幅度变大,动作就会扭曲变形,即使是 Sora、Veo2 也免不了翻车。

Veo2 的生成效果

PixVerse V3.5 在运动控制方面下了大功夫,虽然遇到体操运动仍容易出 bug,但与上一代相比已经有了长足的进步。

比如我们让它生成一段啦啦队成员进行蹦床运动的视频。

女孩们跳跃的同时双手开合,动作整齐划一,没有出现不自然的扭曲或变形。女孩飘扬的头发、蹦床的凹陷等细节 PixVerse V3.5 也处理得很逼真。

要知道,蹦床运动是一个技术、艺术和物理规律的综合体。

它涉及重力、弹力等物理现象,还涉及多主体动作的协调性,这对于 AI 说来并非易事。

,时长00:05

Prompt:一群啦啦队成员在蹦床上跳。

在下面这则小男孩奔跑的视频中,虽然生成的是慢镜头,但运动幅度大,小男孩也没有出现左右腿不分、动作诡异的情况。

虚化的背景、荡起的发丝、泛起的尘土也都让画面更加真实。

图片

Prompt:一个小男孩正在操场跑步。

我们使用它的图生视频功能,试试经典的吃面镜头。

上传一张「皮衣刀客」黄仁勋的图像,输入 Prompt:这名男子正拿着叉子吃意大利面。

PixVerse V3.5 完美刻画了从叉子夹面到入口、咀嚼、吞咽的连贯动作,同时精准呈现了颈部的自然筋纹和皮衣在灯光下的细腻光泽变化。

图片

画质高清细腻

PixVerse V3.5 还是个细节控,很擅长处理光影、纹理以及人物的面部表情等。

同时,它支持多分辨率,720P、1080P,甚至还可以升级为 4K 分辨率。

比如一名时髦的女士走在繁华的大街上,画面很有电影感。

女人微笑的表情、行走的动作以及衣服的褶皱都很自然逼真,就连背景中的行人也几乎找不出什么问题。

,时长00:05

Prompt:一名中年妇女行走在纽约的大街上。

PixVerse V3.5 不仅能完美还原真实场景,在非现实画面的创作中也同样表现惊艳。

西装革履的金毛煞有介事地敲着打字机,那蔫头耷脑的模样,很有当代打工人的牛马感。

,时长00:05

Prompt:一只穿着西装的狗,正在老式打字机上打字。

穿着厨师服装的小狗手拿铲子摊煎饼,虚化的厨房背景中摆着各式各样的厨具、炉灶,细节满分。

,时长00:05

Prompt:一只穿着厨师服装的狗在做煎饼。

不得不说,它生成的怪物史莱克简直和动画电影中一模一样,毛衣的针织纹理也清晰可见。为了体现麦当劳这一场景,咖啡杯上特意印着大大的 Logo。

,时长00:05

Prompt:怪物史莱克在麦当劳喝咖啡。

我们再来看看它对于复杂提示词的理解能力。

Prompt:low-angle tracking shot, 18mm lens. The car drifts, leaving trails of light and tire smoke, creating a visually striking and abstract composition. The camera tracks low, capturing the sleek, olive green muscle car as it approaches a corner. As the car executes a dramatic drift, the shot becomes more stylized. The spinning wheels and billowing tire smoke, illuminated by the surrounding city lights and lens flare, create streaks of light and color against the dark asphalt. The cityscape – yellow cabs, neon signs, and pedestrians – becomes a blurred, abstract backdrop. Volumetric lighting adds depth and atmosphere, transforming the scene into a visually striking composition of motion, light, and urban energy.

(低角度跟踪拍摄,18 毫米镜头,汽车漂移,留下光线和轮胎烟雾的痕迹,创造出视觉冲击力极强的抽象构图,摄像机低角度追踪拍摄,捕捉这辆橄榄绿色的时尚肌肉车驶近弯道的瞬间,随着汽车急速漂移,镜头变得更加风格化,在周围城市灯光和镜头光晕的照射下,旋转的车轮和滚滚的轮胎烟雾在漆黑的沥青路面上形成了光与色的交错,城市景观 —— 黄色出租车、霓虹灯和行人 —— 成为一个模糊的抽象背景。体积光增加了画面的深度和氛围,将这一场景转化为一个由运动、光线和城市活力构成的视觉冲击。)

,时长00:05

面对这一长串的文本描述,PixVerse V3.5 几乎实现了每一个细节:低角度跟踪镜头、轮胎烟雾,橄榄绿的车身、城市中闪烁的霓虹灯以及穿梭的黄色出租车…… 即便在高速运动场景中,画面依然清晰稳定。

其电影级的镜头语言、光影效果和动态表现,不禁让人想到《速度与激情》的经典画面。

Prompt:A massive, terrifying monster appears among the skyscrapers in a dystopian city under a stormy night sky. The creature has glowing red eyes, sharp claws, and reptilian scales. Lightning flashes illuminate the dark cityscape, Helicopters circle in the background, while panicked citizens watch from the streets below. The atmosphere is tense, with a cinematic mix of chaos and awe .

(在狂风暴雨的夜空下,一个巨大、恐怖的怪物出现在一个乌托邦城市的摩天大楼之间。这只怪物有一双闪着红光的眼睛、锋利的爪子和爬行动物般的鳞片。闪电照亮了黑暗的城市景观,直升机在背景中盘旋,而惊慌失措的市民则在下面的街道上观望。气氛紧张,混乱与敬畏交织在一起。)

,时长00:05

PixVerse V3.5 生成的画面极具张力,怪物可怕的形象让恐怖氛围拉满,远处的闪电划破夜空照亮整个城市,形成强烈的光影对比。

动画效果一流

PixVerse V3.5 不仅擅长写实风格,在动画创作上表现也很出色。

只需输入提示词,就能直出日漫、3D 动画等多种风格,画面精美,丝毫不输专业的动画制作软件。换句话说,其动画效果已跻身行业顶尖水平。

Prompt:A cinematic shot, old Hollywood era musical. A group of tap dancing hamsters dance across a busy Christmas high street. light mist, light snow falling, 3D Animation.

(电影镜头,老好莱坞时代的音乐剧,一群仓鼠在繁忙的圣诞大街上跳着踢踏舞,轻微的薄雾,轻微的雪花飘落,3D 动画。)

生成的画面中,大街上张灯结彩,各种圣诞元素拉满。拟人化的小仓鼠有节奏地跳着舞。

即使在多主体的情况下,PixVerse V3.5 仍能保持动作协调一致,画面没有出现虚化闪帧变形等问题。

Prompt: A meeting of a lion, a bear and a giraffe, all of them wearing suits,Disney style(穿着西装的狮子、熊和长颈鹿开会,迪士尼风格。)

图片

PixVerse V3.5 巧妙地抓住了迪士尼动画的特点,角色表情丰富且夸张,毛发、服装以及环境的光影效果,都力求逼真和生动,配色也很舒服。

更有意思的是,角色边指手画脚边侃侃而谈的场景可太像打工人开会了。

我们再来试试图生视频的风格化。

上传一张美漫风格的图片,输入 Prompt: Two cars are engaged in a chase.

图片

PixVerse V3.5 延续画面风格特征,让静态图片瞬间动起来。两辆汽车在街道上演追逐大戏,一路火花带闪电,镜头也能随着汽车而移动,变换不同的城市场景。

它还能生成黑白动漫风。这种风格强调线条和对比,PixVerse V3.5 使用粗犷的线条勾勒出人物和环境,并利用黑白对比增强视觉冲击力。

天空飘落的雨丝、男人六亲不认的步伐都让画面更加生动。

图生视频。Prompt:The man walks to a car in a rainy night.

此外,它还能在画面字体不出现乱码的情况下,让四个角色同时动起来,而且姿势各异。

图生视频。Prompt:Four assassins take up fighting stances.

进阶玩法

除了模型升级外,PixVerse 还有更多进阶玩法。

比如新增首尾帧功能。我们上传两张 AI 生成的写实照片作为视频的首帧和尾帧,PixVerse 以此生成一段连贯的视频。

图片

图片

上效果:

它还赶在圣诞节前夕上线了一堆节日特效。

比如「圣诞礼物盲盒」特效,只需上传一张图片或视频,输入「我想要……」的提示词即可。

图片

来看看效果:

以及「万物皆可羊毛卷」特效,随便上传一张梗图,就能秒变卷发。

截至目前,它已经上线了 27 种特效,其中比较出圈的就有「成为肌肉猛男」、「爱的抱抱」、「扫射一切」等。

图片

一番实测下来,我们可以清晰地看到 PixVerse V3.5 的进步。秒级生成速度为内容创作带来前所未有的流畅体验,精准的运动控制让高难度动作不再「变形」,细腻的画质表现更是将每一帧都打磨成了电影级画面。

更令人惊喜的是,它在写实与动画两个领域都展现出强大的驾驭能力,再配合丰富的特效玩法,极大地降低了影视创作门槛。

大家也快去体验一波吧。

#llama98.c

26年前老年机跑Llama2,每秒39个token:你的AI PC,也可以是Windows 98

让 Llama 2 在 Windows 98 奔腾 2(Pentium II)机器上运行,不但成功了,输出达到 39.31 tok / 秒。

这台 PC 可能比你的年龄还大,要知道它已经是 26 年前的硬件了,配备英特尔奔腾 2 CPU 和 128MB 的内存。

该项目是一个名为 EXO Labs 组织的一次大胆尝试,其证明了如果 Llama 模型能在 26 年前的硬件上跑通,那么它可以在任何地方运行。

图片

为了证明这是真实发生的,EXO Labs 还放出了一段视频。视频显示一台古老的 350 MHz Elonex 奔腾 2 电脑启动 Windows 98 后,然后启动了基于 Andrej Karpathy 开发的 Llama2.c 定制的纯 C 推理引擎,并要求 LLM 生成一个关于 Sleepy Joe 的故事。

令人惊讶的是,它成功了,故事的生成速度非常快。

,时长00:49

不过,上述令人大开眼界的壮举远不是 EXO Labs 的最终目标。这个有点神秘的组织于 9 月正式成立,其使命是「让人工智能普及大众」。该组织由牛津大学的研究人员和工程师组成。简而言之,EXO 认为少数几家大公司控制人工智能对文化、真理和我们社会的其他基本方面来说是一件非常糟糕的事情。因此,EXO 希望建立开放的基础设施来训练前沿模型,并使任何人都可以在任何地方运行它们。通过这种方式,普通人几乎可以在任何设备上训练和运行人工智能模型 —— 而这个疯狂的 Windows 98 运行大模型只是一个演示,展示了在(严重)有限的资源下我们可以做些什么。

正如读者所料,EXO 轻而易举地从 eBay 上买到了一台旧的 Windows 98 PC 作为该项目的基础,硬件有了,但仍有许多障碍需要克服。EXO 解释说,将数据导入旧硬盘就是一项巨大的挑战,因此他们只能使用老式 FTP(good old FTP)通过这台古老机器的以太网端口进行文件传输。

然而,更大的挑战是让 Windows 98 编译现代代码。好在 EXO 找到了 Andrej Karpathy 的 llama2.c 项目。借助此资源和旧的 Borland C++ 5.02 IDE 和编译器(加上一些小调整),可以将代码制作成与 Windows 98 兼容的可执行文件并运行。

,时长00:17

代码已经开源。

项目地址:https://github.com/exo-explore/llama98.c

为了让大家更好的了解这项研究,EXO Labs 还专门写了一篇博客,通过这篇博客,我们看看具体实现过程。

硬件设置

首先,需要机器本身。我们在 eBay 上以 118.88 英镑的价格找到了一台 Windows 98 奔腾 II 电脑。

图片

我在 eBay 上发现了这个泛黄的主机,一台 Windows 98 奔腾 II,128MB 内存,售价 118.88 英镑。

让它与现代外围设备一起工作是第一个挑战,我们的 USB 键盘或鼠标都无法工作。解决办法是重新使用传说中的 PS/2 接口,但有一个问题要注意:鼠标必须放在端口 1,键盘放在端口 2。反向配置则行不通。

图片

背板展示了 PS/2 端口、串行端口,以及至关重要的、后来被证明是必不可少的以太网端口。

文件传输:回归 FTP

下一个挑战是将文件传输到机器上。我们需要传输模型权重、tokenizer 配置和推理代码。现代解决方案都失败了:

  • 系统无法识别 RW 磁盘
  • 我们的 4TB USB 驱动器对于 FAT32 格式来说太大了

最后成功的是古老的 FTP。事实证明,FTP 多年来一直保持向后兼容。我们在 M4 MacBook Pro 上运行 FileZilla FTP 服务器,通过以太网(使用 USB-C 转以太网适配器)连接到 Windows 98 机器,设置静态 IP,然后就可以直接从命令行传输文件了。

图片

使用手动 IP(192.168.1.1)配置 MacBook 的 USB-C 以太网适配器,以便与 Windows 98 通信。

设置网络配置后,我们需要验证连接。一个简单的 ping 测试确认了可以相互通信:

图片

成功!Windows 98 机器与 MacBook 的连接延迟小于 1 毫秒。

建立网络连接后,我们终于可以使用 FTP 传输文件了。一个关键问题是:可执行文件无法运行,直到我们发现它们需要以二进制模式传输。解决方法很简单,只需在 FTP CLI 中输入「二进制」即可:

图片

通过 FTP 以二进制模式传输 stories260K.bin 模型文件。

编译挑战

在 Windows 98 下编译现代代码非常棘手。我们首先尝试了 mingw,据说它可以为 Windows 98/Pentium II 编译现代 C++。结果却走入了死胡同 — 可能是由于 CMOV 指令不支持奔腾 Pro 之前的版本。

于是,我们采用了老式的方法:Borland C++ 5.02 是一款有 26 年历史的集成开发环境和编译器,可直接在 Windows 98 上运行。唯一的问题是它支持的是非常老的 C/C++ 版本。现代 C++ 是不可能的,但几十年来 C 语言的变化却少得惊人。C 语言最大的变化是在 1999 年(C99),所以很遗憾我们错过了。旧版 C 的主要限制是不能「随处声明」变量,所有变量都必须在函数的开始部分声明。

图片

Borland C++ 5.02,一款有 26 年历史的集成开发环境,仍然能够完成任务。

Karpathy 救场

这让我们想到了 Andrej Karpathy 的 llama2.c:700 行纯 C 语言,可以在具有 Llama 2 架构的模型上运行推理。非常完美,但为了在奔腾 II 上运行 Win98,它仍然需要一些调整:

  • 用「DLONGWORD」代替「long long」(使用类型定义);
  • 将所有变量声明移至函数起始处;
  • 简化了从磁盘到内存的加载(内存映射会导致 SEGFAULTS);
  • 通过用 GetTickCount () 代替 clock_gettime 修复了时间戳问题。

本项目的代码可在 GitHub 上的 llama98.c 获取。

图片

成功!在 Windows 98 上运行的 260K 参数 Llama 模型生成了一个关于 Sleepy Joe 的故事。

结果

终于让它工作了!以下是我们实现的结果,完全在 Pentium II CPU 上运行,无需 GPU:

图片

Llama 3.2 1B 的结果是基于运行适合内存的模型碎片的基准测试和磁盘读取基准测试得出的。Llama98.c 正在通过卸载功能进行扩展,以运行更大的模型来进行实际测试。

虽然没有达到 ChatGPT 的速度,但让任何现代人工智能模型在二十多年前的 CPU 硬件上运行,都是朝着我们的使命迈出的重要一步。在此特别感谢 Chris Wellons 关于如何在 Windows 98 上运行 C++ 的精彩博文。

未来:BitNet 及其他

BitNet 是前沿模型真正可以在任何硬件上运行的一个前景广阔的方向。BitNet 是一种使用三元权重的 transformer 架构,每个权重只能是 0、-1 或 1,每个权重只需要 1.58 比特(log₂(3) ≈ 1.58)。这一简单的变化具有重大意义:

图片

Matmul 变成了具有三元权重的加法(Ma et al. 2024)。

由于乘以 0 是省略,乘以 1 是加法,而乘以 - 1 是减法,因此所有常见的矩阵乘法都变成了加法和减法。

这样做的好处是显而易见的:

  • 一个 7B 参数的 BitNet 模型只需要 1.38GB 的存储空间,小到足以安装在大多数硬件上,甚至是几十年前的 PC(我们使用的 PC 只有 1.6GB 的硬盘驱动器);
  • CPU 优先:微软的 BitCPP 在 M2 Ultra CPU 上每秒可生成 52 个 token,在英特尔 i7 上每秒可生成 18 个 token;
  • 更令人印象深刻的是:100B 参数的 BitNet 可在单个 CPU 上以人类读取速度(5-7 个 token / 秒)运行;
  • 节能:比全精度模型节能 50% 以上。

在 EXO,我们研究三元模型已经有一段时间了。2024 年 4 月,我们发布了在苹果芯片上高效运行的 MLX-BitNet。在 2024 年的 ICML 会议上,我们首次展示了用于蛋白质语言建模的 BitNet 实现,并正在开发用于蛋白质建模的更大 BitNet 模型。

虽然目前还没有大型开源 BitNet 模型,但我们相信三元模型是人工智能的未来。我们计划在 2025 年训练一个三元模型。

然后呢?

我们希望看到更多的努力集中于在旧硬件上运行人工智能模型。从优化内存使用到探索能在有限硬件上高效运行的新架构,都有大量工程工作要做。

如果你对在旧硬件(如旧 Mac、Gameboy、摩托罗拉手机,甚至旧 Raspberry Pi)上运行模型感兴趣,请查看代码并加入我们的 Discord #retro 频道。人工智能的未来不一定要被锁在庞大的数据中心里,它可以在你已有的硬件上运行。

参考链接:https://blog.exolabs.net/day-4/

#VideoVAE+

港科大开源VideoVAE+,视频重建质量全面超越最新模型

港科大团队重磅开源 VideoVAE+,提出了一种强大的跨模态的视频变分自编码器(Video VAE),通过提出新的时空分离的压缩机制和创新性引入文本指导,实现了对大幅运动视频的高效压缩与精准重建,同时保持很好的时间一致性和运动恢复。

  • 论文地址:https://arxiv.org/abs/2412.17805
  • 代码已开源:https://github.com/VideoVerses/VideoVAEPlus

VideoVAE + 模型大幅超过最新模型包括英伟达在 2024.11 发布的 Cosmos Tokenizer,同时也超越一众方法包括腾讯在 2024.12 发布的 Hunyuan Video,CogvideoX VAE,WF-VAE,CV-VAE,Open Sora,Open Sora Plan, Easy Animate-VAE。

,时长00:10

什么是 VideoVAE 模型

VideoVAE 模型(Video Variational Autoencoder)是一种基于深度学习的生成模型,用于对视频数据进行压缩、重建和生成,讲视频从 RGB 像素空间投影到低维度的 latent 空间。常用于结合 Diffusion 生成模型组成两阶段的模型结构:即先通过 VideoVAE 的编码器(Encoder)实现压缩数据维度,去除冗余信息,再在低维 latent 空间用 diffusion 模型进行 latent 生成,最后通过 VideoVAE 的解码器(Decoder)将 latent 解码到 RGB 视频,从而实现降低计算资源,更加高效的生成。

目前方法

一些方法直接采用图像 VAE 进行逐帧压缩,忽略了帧与帧之间的时间关联性,导致视频生成过程中出现严重的时序闪烁问题。此外,时间维度上的冗余信息未被充分压缩,使得后续的扩散模型训练效率低下,成本高昂。

另外,最近很多方法开始使用 VideoVAE,考虑时间维度,但仍存在诸多问题。包括细节模糊和失真(比如面部、手部、边缘和文本),以及重建大幅运动的视频时,出现运动卡顿(缺乏连贯及合理的时序过渡)和伪影等问题。

图片

图 1:该研究将该研究的方法和一众优秀工作包括 Open Sora Plan, Open Sora, CV-VAE, CogVideoX-VAE, Easy Animate-VAE 进行了视觉对比,VideoVAE + 能够准确重建大幅运动的视频,并且有效解决了运动卡顿,重建模糊,细节缺失等问题。

方法

为了解决上述问题,VideoVAE + 提出了一种新的 cross-modal Video VAE 架构,其设计的关键点包括:

1. 时空分离的压缩机制:提出一种时序感知的空间压缩方法,有效分离空间和时间信息处理,避免因时空耦合而导致的运动伪影。

2. 轻量级运动压缩模型:专门设计了一个模型用于时序压缩,高效捕获视频中的运动动态。

3. 文本信息融合:利用文本到视频数据集中的文本信息作为指导,提高视频细节的保留能力和时间稳定性。

4. 图像和视频的联合训练:通过在图像和视频数据上的联合训练,增强了模型在多任务上的重建性能和适应性。

图片

文章对比了三种时空建模方法:同步建模、顺序建模以及该研究提出的最优时空建模方案。

  • 方式 1 同时建模:通过将预训练的 2D 空间 VAE 扩展为 3D VAE 实现,进行时间和空间的同步压缩。然而,这种方法在捕捉时间动态时容易导致信息混淆,影响生成质量。
  • 方式 2 顺序建模:先通过空间编码器压缩空间维度,再用时间编码器压缩时间信息。但这种方式对时序细节的处理较为有限,容易出现时序一致性问题。
  • 该研究的方法:该研究结合两种方法的优势,提出最优的时空建模策略。

该研究的方案具体包括时序感知的空间自编码器(Temporal-aware Spatial AutoEncoder)和时序自编码器(Temporal Autoencoder):

在第一阶段,该研究将 2D 卷积扩展为核大小为 (1,3,3) 的 3D 卷积,并与同时建模类似,同时添加了额外的 3D 卷积层来建模时序维度,该研究将第一阶段的模型称为时序感知的空间自编码器。但与方式 1 同时建模不同的是,在第一阶段中,该研究仅压缩空间信息,而不压缩时序信息。

在第二阶段中,该研究引入了另一个时序自编码器(Temporal Autoencoder)来进一步编码和压缩时间维度信息,这作为第二阶段的压缩部分。该研究遵循方式 2 的时间编码器和解码器设计。通过这种方式该研究同时实现了更好的细节恢复能力和运动恢复能力。

同时,该研究提出引入跨模态信息,通过文本指导进一步增强视频生成的细节保留和时间一致性。

该技术的主要特点有:

1. 智能特征分块

将视频的视觉特征图分割成小块(patch),并将它们作为 token 进行处理,不同层采用多种尺寸(8×8、4×4、2×2、1×1),确保每层特征的细节追踪到位。

2. 跨模态注意力机制

首次在 Video VAE 任务上引入文本信息作为语义指导,让视觉 token(作为 Query)与文本嵌入(作为 Key 和 Value)计算跨模态注意力,提升细节重建质量。

3. 强大的文本嵌入器

采用先进的 Flan-T5 模型,将文字转化为语义向量,为视频生成提供坚实的语义基础。

其次,该研究采用了图像与视频的联合训练。模型能够同时接受图像和视频作为训练数据,既学习图像压缩能力,又提升视频压缩性能。在训练中该研究观察到,加入更多高质量的图像数据,能进一步增强视频自编码性能。

结果

该研究提供了 latent 在 16 channel 和 4 channel 两个版本的模型,以及在三个不同的数据集上对效果进行了全面评测。

该研究的 VideoVAE + 模型大幅超过最新模型包括英伟达在 2024.11 发布的 Cosmos Tokenizer,同时也超越一众方法包括腾讯在 2024.12 发布的 Hunyuan Video,CogvideoX VAE, WF-VAE,CV-VAE,Open Sora,Open Sora Plan, Easy Animate-VAE。

图片

同时该研究提供了 Demo video 可以更直观地查看模型的视觉效果。

,时长01:29

#用于韦伯区位问题的去奇异性次梯度方法

暨南大学通用机器学习课题组由网络空间安全学院和信息科学技术学院的多名青年教师、博士生、硕士生和本科生共同组成,研究方向包括通用逼近理论、分布外泛化、非凸优化、稀疏学习、深度学习框架的基础模块开发、优化器开发、隐私保护与增强等。自 2024 年 4 月至 12 月,课题组作为第一单位已获得所有 CCF A 机器学习国际顶级会议 ICML(2 篇)、NeurIPS 和人工智能国际顶级会议 IJCAI、AAAI 录用论文共 5 篇。本文第一作者为课题组负责人赖兆荣,通讯作者为博士生李程,其他合作作者为课题组教师吴小天、方良达、陈子良。

问题背景

韦伯区位问题源自一个经典的运筹优化问题,它首先由著名数学家皮耶・德・费马提出,后被著名经济学家阿尔弗雷德・韦伯(著名社会学家马克斯・韦伯的弟弟)扩展,在机器学习、人工智能、金融工程及计算机视觉等众多领域均有广泛应用。在一般定义下,该问题的目标在于找到一个「中心点」x_*,使得这个中心点到 m 个给定数据点 x_i 的加权距离之和最小 [1][2]:

图片

这里有两个重要参数:用作距离的 l_p 范数中的 p 值,以及距离的幂次 q。一般考虑 p>=1 且 1<=q<=p。p=2 表示常用的欧氏距离;p=1 表示曼哈顿距离,代表一种重要的非欧几何。允许这两个变化参数有助于增强韦伯区位问题的表达力和对更广泛任务的适应性。

为直入主题,计算 (1) 式中损失函数的梯度如下:

图片

其中上标 t 表示第 t 维,并假设数据点 x_i 属于 d 维实空间(1<=t<=d)。容易看出,当 q<p 或 p<2 时,若 y 刚好击中如下奇异集,则梯度不存在:

图片

其中 1<=q<p,p=2 的情形相对比较简单,每个数据点即为奇异点,所以总共只有有限个奇异点,如下图所示。该情形已由本课题组的 IJCAI 2024 论文解决 [3]。

图片

而 1<=q<=p,1<=p<2 的情形就要复杂很多了。由于 p=2 的情形只有有限个奇异点(如下图左的红点所示),所以只要成功设计出一个能保持损失函数下降性质的算法,则可以保证最多只经过每个奇异点一次并脱离奇异集。但对于 1<=p<2 的情形,奇异集是一个包含无限个点的连续点集合(如下图右的红色虚线及红点所示),所以算法可能重新访问奇异集无限次并最终不会逃离奇异集。

图片

该奇异性问题经常且意外地发生。造成奇异性的初始或中间迭代点可在 d>=2 维实空间中的一个开集中稠密,甚至充满整个 d 维实空间 [4]。更为严重的是,该问题无法依靠简单直观的手段来回避,例如随机扰动迭代点使其离开奇异集,或者重选一个随机初始点,等等。事实上,只要采用本文提出的去奇异性次梯度方法,即可在不增加计算复杂度(与一般梯度法相比)的有利条件下解决该奇异性问题,因此完全不需要再借助其他回避手段。

图片

  • 论文标题:De-singularity Subgradient for the q-th-Powered L_p-Norm Weber Location Problem
  • 论文链接:http://arxiv.org/abs/2412.15546
  • 项目地址:https://github.com/laizhr/qPpNWAWS

去奇异性次梯度法

本文提出一种解决奇异性问题的直观方法:识别出引发奇异性的数据点及维度,然后把相应的分量去除掉。首先是识别出引发奇异性的数据点及维度,分别用集合 V_t (y) 和 U_i (y) 来表示。

图片

下图是 V_t (y) 和 U_i (y) 的一个直观示意图。

图片

然后使用定义 5 来定义去奇异性次梯度 D_{p,q}(y)。

图片

接着,我们需要验证这个去奇异性次梯度 D_{p,q}(y) 具有与普通梯度类似的良好性质。例如,它要能够刻画最小值点(定理 6)和下降方向(定理 7)。这些刻画的关键技术在于引入 p 范数的共轭范数,即使得 1/r+1/p =1 成立的 r 范数。

图片

图片

基于 q 次方 p 范数的去奇异性 Weiszfeld 算法

获得可行的去奇异性次梯度 D_{p,q}(y) 后,下一步就是建立可行的求解算法。本文基于求解该问题常用的 Weiszfeld 算法 [5][2],建立一种基于 q 次方 p 范数的去奇异性 Weiszfeld 算法(简记为 qPpNWAWS,如 18 式所示)。它在非奇异性情形下使用 (9) 式的常规 Weiszfeld 更新迭代,在奇异性情形下使用 (17) 式的沿下降方向线性搜索法。

图片

图片

图片

通过这种方式,qPpNWAWS 算法可自由来回多次(甚至包括无限次)游走于非奇异集与奇异集之间或之内,同时保证损失函数随迭代下降,并最终收敛。在 1<p<2 这一严格凸情形下,qPpNWAWS 算法甚至能进一步获得更强的收敛性质,如迭代序列收敛到全局最小值点,等等。具体算法流程较为繁琐复杂,请参阅论文附录 A。算法的收敛性定理、其他性质定理以及详细证明也请参阅论文。

实验结果

我们以 CSI300 数据集 [3] 为例简单介绍实验结果,其他数据集以及详细实验结果请参阅论文。运行实验的机器配置为:Intel Core i9-14900KF 中央处理器 1 个,64-GB DDR5 6000-MHz 内存,带 16-GB 独立显存的 Nvidia RTX 4080 SUPER 显卡 1 张。

实验一:

该实验用于记录 qPpNWAWS 算法在奇异点需要几次线性搜索才能使损失函数下降。结果表明在绝大多数情形下只需不超过 3 次线性搜索。

图片

实验二:

该实验用于记录 qPpNWAWS 算法完整运行一次所需的总迭代次数以及总时间。结果表明在绝大多数情形下只需不超过约 15 次迭代以及 0.02 秒的总时间。

图片

实验三:

该实验用于记录 qPpNWAWS 算法的实际计算收敛率。结果表明在绝大多数情形下收敛率均远小于 1,即达到线性收敛速度。

图片

实验四:

该实验主要测试不同 (q,p) 情形下使用 qPpNWAWS 算法进行在线资产配置实验 [6][7] 所得到的投资得分 —— 累计财富(CW)和夏普比率(SR)。结果表明一定数目的其他 (q,p) 情形(例如 (q,p)=(1,1.6))的得分要比原始版本 (q,p)=(1,2) 的得分高。因此解决 1<=q<=p,1<=p<2 情形下的奇异性问题有着非常重要的现实意义。

图片

关于通用机器学习

通用机器学习是一个由多个研究方向有机结合而成的整体领域。其往往需要融会贯通多个数学类和计算机类学科的知识,攻关通用人工智能中最为基础的科学与技术难题。本文属于该领域中的基础模块开发与优化器开发研究方向。以下是近期本课题组在该领域的一些主要论文与主攻方向,欢迎阅读并与我们交流讨论。

#我与vLLM的2024:清华大佬的vLLM开发之路

也许二十年后再回首,我们会发现,现在我们就站在下一个“互联网级奇迹”的起点上。​

楔子

我与 vLLM 的缘分,还得从五年前的那个暑假说起。

2019 年,我在UC Berkeley的RISELab跟随Michael Jordan教授进行暑期研修。某天,我偶然遇到一位新入学的博士生,厚着脸皮加了他的微信。当时的我怎么也不会想到,这一“社交冒险”会在五年后改变我的人生轨迹。​

ChatGPT之变

时间快进到 2022 年年底,ChatGPT 横空出世。曾经和我一起玩泥巴的青苹果同学已经成为ChatGPT训练师,而我还在AI顶会与随机分配的审稿人展开“鸡同鸭讲”式的争论。顶会的内卷气氛让我无比焦虑,也让我意识到:我需要做出改变。

彼时,我正专注于研究一种能加速Conv-BN模块微调效率的优化方法。算法虽然不复杂,但需要深入修改模型的计算图。一番调研后,我接触到了PyTorch的​​torch.fx​​​模块,顺势了解了PyTorch 2.刚发布的​​torch.compile​​。我对这些内容越看越感兴趣,一口气看完了2022年PyTorch大会的全部视频。

学习过程中,我结识了PyTorch编译器团队的Jason Ansel。他不但教我 Dynamo 的原理,还让我对机器学习系统(MLSys)这个领域有了初步了解。就这样,我从优化Conv-BN的训练效率,一路探索到机器学习编译器的核心原理。与此同时,我拜读了计算机体系结构的泰斗David Patterson和编译器大师Chris Lattner的经典讲座,他们都提到:在后摩尔定律时代,算力增长的唯一途径是专用芯片,而机器学习编译器会成为重要的研究方向。

2023 年年底,Michael Jordan教授再次访问清华。我向他诉说了对机器学习研究现状的困惑,表达了转向机器学习系统研究的兴趣。巧合的是,他的老朋友Ion Stoica——机器学习系统领域的顶级专家——也一同前来访问。Jordan教授一听,直接就把我推荐给了Ion。在和Ion的交流中,我“现学现卖”,详细阐述领域专用硬件和机器学习编译器的未来。他听完非常认同,还告诉我,他正在主导一个项目,涉及处理多种芯片,比如MI300X、Inferentia等(老实说,当时这些芯片名字我连听都没听过)。聊到最后,我们一拍即合,在实验室和导师的鼎力支持下,我幸运地拿到了再次访问UC Berkeley的机会。

2024 年,我又一次拖着行李箱,站在了伯克利的土地上。五年过去了,这里的一切似乎都没变。Ion Stoica的实验室,还是五年前熟悉的地点,连实验室管理员都还是当年的Kattt。唯一的变化,是实验室的名字从 RISELab 改成了 SkyLab。而Ion提到的那个需要处理多种芯片的项目,正是由五年前我遇到的那位博士生和另一位韩国小哥共同创立的vLLM项目。一切仿佛命中注定,我和vLLM的故事,正式拉开了序幕。

图片

SkyLab充满历史沧桑感的门牌,从spark,到ray,再到vLLM,这个实验室盛产大型开源软件​

上手:初入江湖

2024年3月,vLLM已经在社区内小有名气,但项目管理还略显原始。我加入项目后,首先将PyTorch的一些成熟开源管理经验移植到vLLM,例如issue模板(要求每个提 issue 的人必须提供完整的运行环境信息,从而方便我们定位问题)、PR模板等等。

为了快速了解项目,我订阅了vLLM的所有GitHub消息。每天早晨,睁眼后的第一件事就是查看新增的issue和PR,了解项目动态,并解答我能解答的问题。就这样,我的“vLLM oncall”模式持续了大半年,直到基本掌握了项目的全貌,才结束了“oncall”状态。​

下马威:PyTorch 2.2 的阴影

我接手的第一个任务,乍一看再简单不过:将vLLM依赖的PyTorch从2.1升级到2.2。然而,这个看似“入门级”的活儿,差点让我怀疑人生。

经过连续几周的调试,我最终发现,当以下四个条件同时满足时:曲线救国

  1. 使用 L4 机器(没有 NVLink)。
  2. 开启 多卡并行推理。
  3. 启用 cudagraph。
  4. NCCL 版本为 2.19 或以上。

vLLM 的显存占用会神秘地增加 2 GiB。

为了解决这个问题,我硬着头皮一行行代码排查,一头扎进NVIDIA的CUDA编程文档、驱动文档和运行时文档中。最终,我发现问题出在NCCL的一个实验特性上,解决方案仅需设置一个环境变量以禁用该功能(一行代码即可搞定)。但为了找到这“一行代码”,我整整折腾了三个月。

在彻底解决问题之前,我们只能采取权宜之计,先强制安装一个独立于PyTorch的NCCL 2.18版本。这又让我不得不参与vLLM的CI流程、发布流程等一系列开源项目管理事务,顺便接手了 vLLM 的分布式推理,并创建了​​vllm.distributed​​子模块。

没想到,半年后,几乎相同的问题在vLLM的RLHF流程中再次出现,并且惊动了John Schulman。是的,就是那个OpenAI的John Schulman,发明了PPO的那个男人。得知他也用vLLM,我非常激动,以最高优先级解决了问题,把RLHF的权重更新时间从3分钟压缩到了4秒钟,还和他一起完成了一个PR。​

苦日子:从 GPU Poor 到 GPU Rich

四五月间,我们陆续收到一些反馈,vLLM在H100上的性能表现不尽人意。然而问题来了——我们自己连一台H100都没有!当时,我还在用实验室的V100开发代码,我们的CI流程也捉襟见肘,只能验证代码的基本正确性,却完全无法跟踪性能变化。

有一次,一个贡献者提交了一个看似无害的改动,我审了一眼,觉得没问题,就痛快地同意合并了。谁知道第二天就有人反馈,这个PR把整体速度拖慢了好几倍!当时听了,真是哭笑不得——身为项目开发者,我们居然连代码的性能都搞不清楚,反倒是社区的一些有钱用户,在长期跟踪测试每个commit的性能。

究其根本,大模型推理项目的性能测试需要高端GPU,而我们CI的资金早已捉襟见肘。整个项目陷入了青黄不接的窘境,我们一度怀疑:这个项目怎么维护下去?还要不要维护?毕竟,vLLM是个“烧钱机器”,它消耗的资源远远超过我们几个学生的生活费,而未来还需要更多投入。我们要从哪里找到这些钱?

幸运的是,社区的热情与支持在关键时刻给予了我们极大帮助。在我们四处求援之后,NVIDIA 送来了一台满血H100和一台H200;AWS和Google Cloud等云厂商,捐赠了大量计算资源;真格、红杉等创投机构,也慷慨解囊,帮助我们解决了燃眉之急。

与NVIDIA送的H200机器合影

可以说,vLLM虽然诞生于伯克利,但它的成长靠的是“百家饭”。我们深深感激这一份支持,也因此坚定了一个信念:vLLM的今天离不开社区的帮助,未来我们一定要更好地回馈社区。​

迎战 LLaMA 3.1 405B

vLLM 支持了许多开源模型,且通常在模型发布当天就能推出相关支持,我们管这叫“day 1 support”。但这种“神速”,背后隐藏着大量看不见的 “day -1 support”——在模型发布前,我们已经悄悄做了大量的准备工作。这其中,令我印象最深刻的,就是迎战LlaMA 3.1 405B。

今年四月,Meta发布了LLaMA 3系列模型。乍看之下,它与LLaMA和LLaMA 2在架构上并没有太大变化,也不需要vLLM提供额外支持。然而,Meta的发布博客中提到还有一个400B+的模型正在训练。这消息一下子让我们坐不住了。

要知道,405B模型仅权重就需要800GiB的显存。即便是最顶级的H100机器也撑不住这种规模。于是,我们立刻着手开发多机分布式推理功能,包括针对非RDMA机器的流水线并行推理、单机测试的CPU offloading等等,最终成功支持了LLaMA 3.1 405B模型的推理。

有趣的是,后来 Meta 告诉我们,他们在和一些合作伙伴测试405B模型时,发现这些厂商根本不知道如何部署。无奈之下,Meta只能紧急为405B模型开发FP8量化版本。vLLM对满血非量化版405B模型的多机部署解决方案,使得Meta的十个官方发布合作伙伴中,有八个选择了 vLLM。

模型架构的微创新一直在路上,vLLM团队也一直在努力,增加对各种模型的支持。毫不夸张地说,vLLM是支持开源模型类型最广泛的推理框架。​

重构与优化:永恒的话题

vLLM 一经推出,就凭借数十倍于HuggingFace Transformers的推理速度吸引了广泛关注。然而,随着社区越来越大、功能越来越多,早期缺乏性能跟踪机制的问题逐渐显现,尤其是在高端H100 GPU上,小模型的推理性能不佳。

从四五月份开始,随着性能测试逐渐完善,我们便一直在讨论重构的必要性。通过参考类似框架如LMDeploy、LightLLM和TRT-LLM的经验,我们为vLLM增加了基于ZMQ的API服务器、多步调度(multi-step scheduling)等大幅提升性能的特性。然而,由于 vLLM 的功能非常多,这些优化措施有时会与某些小众功能发生冲突,导致代码中出现了不少分支逻辑。

为了彻底解决这一问题,我们正在准备一次大版本重构。这次重构将以性能优化为核心,优先支持常用功能,然后逐步改造那些小众功能,最终实现整个框架的全面升级。一些早期用户已经部署了新版本的尝鲜版,获得了 2~3 倍的性能提升。

此外,多样化的硬件支持也是需要重构的重点。尽管 NVIDIA 是市场上的头号玩家,但其他巨头公司也纷纷推出了自家的芯片。如何兼容多种加速硬件,是我们必须面对的挑战。为此,我创建了​​vllm.platforms​​子模块,将硬件相关的细节集中管理,减少主干代码中的分支逻辑。有趣的是,我发现PyTorch在硬件支持上也面临类似的挑战。vLLM与PyTorch,在这方面可以说是殊途同归。正因如此,后来我推动vLLM加入PyTorch生态系统就显得顺理成章。通过更紧密地融入PyTorch,我们能够从其发展过程中吸取更多经验与教训,同时为PyTorch社区作出我们的贡献。

​torch.compile​​集成

在出发前往伯克利之前,我给 Ion Stoica 画的大饼,是利用​​torch.compile​​​来支持多种硬件。然而vLLM的开源项目事情很多,我不得不将与开源相关的工作置于优先位置,只能在闲暇时间“兼职”探索​​torch.compile​​的集成。

一次偶然的机会,我在为Command-R模型增加支持时,发现​​torch.compile​​​的guard系统存在缺陷,会导致重复编译。我向Jason Ansel报告了这个问题,他建议我直接联系PyTorch Compiler团队的经理。结果,这个问题竟让我有机会在PyTorch团队的例会上做了一次报告,深入分析了​​torch.compile​​在大模型推理中遇到的挑战和潜在解决方案。

这次报告直接促使PyTorch团队将vLLM的​​torch.compile​​​集成列为下半年的重点目标。经过长达半年的协作,我们在PyTorch Compiler核心成员的帮助下,基于PyTorch提供的底层能力,开发了vLLM专属的推理优化​​torch.compile​​技术栈,也将在不久后正式发布。

有趣的是,​​torch.compile​​集成过程中用到的一个关键功能,正是我去年研究PyTorch Compiler时为其新增的bytecode hook。​

群英荟萃的 PyTorch Conference 与 Meetup

九月中旬,我因获得社区创新奖,受邀参加PyTorch 2024大会。初次踏入会场,我就被现场惊人的“人才密度”震撼到了。在随机游走的过程中,我偶遇了Flash Attention的作者Tri Dao、LLVM的作者Chris Lattner,以及PyTorch的创始人Soumith等重量级人物。更令人惊叹的是,他们都非常技术导向,乐于探讨具体的技术细节。那种思想碰撞的火花,让人深刻体会到硅谷之所以成为创新沃土,绝非偶然。

与Chris Lattner谈笑风生

除了PyTorch Conference这样的年度盛会,vLLM社区也会定期组织线下Meetup,类似开发者见面会。在这些活动中,我有机会与诸多技术专家探讨前沿技术,获得了不少宝贵的经验。广受欢迎的社区课程CUDA Mode也举办了首次线下Meetup,我更是亲眼见到了Andrej Karparthy、CUDA编程入门课的主讲老师——UIUC胡文美教授等人,成功实现线下追星。

如果你来湾区,千万不要错过各种Meetup!比如通过 lu.ma 网站,你可以轻松订阅大量AI相关的技术 meetup,感受硅谷的开放与活力。​

vLLM:智能时代的Linux雏形

自2023年6月开源以来,vLLM已经走过了一年半的历程。我很荣幸能够参与到这个项目的发展中,为其成长贡献我的一份力量。

随着智能时代的浪潮席卷而来,我们正见证一场深刻的技术革命。从云端服务器到端侧智能设备,再到自动驾驶汽车,各个领域都在积极探索大模型的应用场景,而vLLM已经在这些场景中实现了广泛的生产部署。我相信,vLLM将逐步发展成为智能时代的“Linux”——一个高效、稳定且开源的系统软件,支撑着智能时代的基础架构。

为更好地践行开源与开放的精神,vLLM正在加入Linux基金会。我们希望借助这一平台,进一步壮大社区,与更多开发者和企业携手,共同建设和完善智能时代的生态系统。正如Linux曾经推动了操作系统的普及,vLLM也有望在智能时代留下浓墨重彩的一笔。​

硬件彩票:与其抽奖,不如和庄家合作

访问快要结束时,我斗胆去请教David Patterson教授,问他AI硬件的未来会怎么发展。这位计算机体系结构的泰斗只是淡淡一笑,没有直接回答,而是推荐我去读一篇论文:《The Hardware Lottery》。

与David Patterson教授(以及他那满墙的荣誉证书)远程合影

论文中提出了一个发人深省的观点:在摩尔定律仍然有效的时代,软件和硬件的发展基本是各自为战。写软件的人几乎不用考虑硬件的特性,因为硬件性能每隔两年翻一倍,软件的性能自然也会跟着变快。然而,摩尔定律的黄金年代已经结束。虽然NVIDIA的“黄氏定律”仍在推动硬件性能增长,但这种增长更多体现在专用领域。如果算法没有利用那些特化的硬件特性,就像没抽中“硬件彩票”——注定很难跑赢时代的红利。例如,Hinton提出的capsule network,由于对GPU不友好,目前尚未得到广泛应用。

这让我联想到一个有趣的传闻:NVIDIA在P100上首次推出FP16数值格式时,芯片量产后却发现训练无法收敛,算法研究人员拒绝使用P100,这几乎让他们的数值格式征途“出师未捷身先死”。后来,是混合精度训练让P100化险为夷,从而开启了V100、A100等后续芯片的辉煌。再后来,NVIDIA在开发FP8数值格式时,更是未雨绸缪,先通过大量的软件仿真验证了可行性,才敢造芯片。

这一系列故事让我感到深受启发:硬件亲和性,已经成为决定算法成功的关键。作为一名算法研究人员,与其天马行空地研究算法(抽奖),期待着未来的硬件会对算法亲和,不如直接学习理解硬件,设计对当前硬件亲和的算法,直接与庄家合作,岂不是必然抽中彩票?或许,最理想的情况,就像那个影响了FP8的男人那样,必须算法、软件、硬件协同设计。​

展望:泡沫与奇迹

vLLM能受到如此广泛的关注,离不开大模型这两年飞速发展的浪潮。发展得快,泡沫自然也就多。泡沫会不会破灭?什么时候破灭?这些问题没人能预测。但当我向许多经验丰富的“过来人”请教时,他们最常提到的参考是互联网。

回望互联网历史,确实在2000年左右经历了泡沫的破灭。但二十年后再看,即便是当年泡沫顶峰时期那些天马行空的设想,都远远不及互联网如今对世界的深远影响。很多人二十年前吹过的“牛”,不仅已经实现,而且还远超当时的想象。

硅谷先锋Roy Amara曾言道:“对突破性技术,人们往往在短期内高估其影响,但在长期内低估其潜力。” 历史总是在不断重复着螺旋式上升,AI或许就走在类似当年互联网的道路上。也许二十年后再回首,我们会发现,现在我们就站在下一个“互联网级奇迹”的起点上。

#大模型轻量化解读系列 (五)

QuaRot:基于 Rotation 的 4-bit LLM 量化

4-bit 量化 LLaMA2-70B 模型的 WikiText-2 困惑度损失最多为 0.47,并保留 99% 的 Zero-Shot 性能。QuaRot 可以使用 Round-To-Nearest (RTN) 量化提供无损 6-bit 和 8-bit LLaMA-2 模型,且无需任何校准数据。

采用旋转矩阵解决 4-bit LLM 量化困难。

量化方案:

Weight: Per-channel Symmetric,Activation:Per-token Symmetric

QuaRot 是一种基于旋转 (Rotation) 的新量化方案,它能够以 4-bit 端到端量化 LLM,包括所有的 weight、activation 和 KV cache。QuaRot 通过旋转的方式,在不改变输出的情况下从隐藏状态中去除异常值,进而使量化更容易。这种模式应用于 LLM 的 hidden state,FFN 的激活值,attention 和 KV cache。QuaRot 量化之后,所有的矩阵乘法都使用 4-bit 执行,没有任何 channel 保持更高的精度。

4-bit 量化 LLaMA2-70B 模型的 WikiText-2 困惑度损失最多为 0.47,并保留 99% 的 Zero-Shot 性能。QuaRot 可以使用 Round-To-Nearest (RTN) 量化提供无损 6-bit 和 8-bit LLaMA-2 模型,且无需任何校准数据。

下面是对本文的详细介绍。

1 QuaRot:基于 Rotation 的 4-bit LLM 量化

论文名称:QuaRot: Outlier-Free 4-Bit Inference in Rotated LLMs (NeurIPS 2024)

论文地址:​http://arxiv.org/pdf/2404.00456​

代码链接:​http://github.com/spcl/QuaRot​

1.1 QuaRot 研究背景

大型语言模型 (LLM) 变得越来越重要。然而,实际使用,即推理时,需要大量的计算、显存和能量,特别是在 LLM 的预填充 (Prefill) 阶段,模型应该处理大的 Prompt 并在每一层 cache 它们。量化是改进内存和计算问题的最重要技术之一,通过在前向传递期间以较低的精度保持数据类型。

由于预填充阶段是 Compute Bound 的,联合量化旨在减少参数和 KV cache 的精度 (使得显存的使用量较低) 以及 activation 为的精度。但是量化 activation 很困难,因为它们具有较大的异常值 (示例见图1),使得激活 activation 比量化 weight 更难,尤其是对于 4-bit 的情况。之前的工作[1][2]依赖于使用校准集来表征异常值特征,并将它们保持在更高的精度进行推理。

本文通过使用随机 Hadamard 变换旋转模型的输入来解决异常值特征的问题。作者借助计算不变性思想[3]做到这一点,并将 Hadamard 变换融合到权重矩阵中,从而得到一个没有异常值特征的等价网络。这使得 weight、activation 和 KV cache 能够在精度损失最小的情况下量化为 4-bit。主要贡献是:

  • 本文表明,随机 Hadamard 变换可以应用于权重矩阵,而无需额外的模型修改。反过来,这完全消除了异常值特征,并使激活易于量化,而无需更改模型的输出。这可以看作是在结构化修剪的背景下在 SliceGPT 中提出的计算不变性思想的扩展。
  • 本文扩展了这种方法,将在线 Hadamard 变换应用于 Attention 以消除 Key 和 Value 中的异常值特征,实现 KV cache 的量化。
  • 使用上述修改,QuaRot 实现了所有 weight、activation 和 KV cache 的 4-bit 量化。作者为 QuaRot 提供了有效的 Kernel 支持:在 LLaMA2-70B 模型上,QuaRot 实现了高达 3.33× 的 prefill 加速,解码阶段节省 3.89 倍的显存,WikiText-2 困惑度损失 0.47。QuaRot 保留了 Zero-Shot 任务的 99% 的精度,本文也实现了 6-bit 和 8-bit 量化通过简单的 RTN 量化无损。

1.2 正交矩阵,旋转矩阵和 Hadamard 矩阵简单介绍

正交矩阵   是一个方阵,使得  。在这项工作中,作者只考虑正交矩阵。

旋转矩阵是一个正交矩阵。

Hadamard 矩阵是一个正交矩阵,其内部值为 。

Walsh-Hadamard 矩阵是大小为  的方阵,其中:

图片

遵循[4]的做法,作者利用随机 Hadamard 矩阵, 方便。设  是一个包含从  和  中随机抽取的向量。很容易看出  也是一个正交矩阵。

矩阵的不一致性

QuIP[5]在 Weight-only LLM 量化中引入了不一致性处理。定义 -incoherent 的权重矩阵 W 为:

图片

其中,  是矩阵的元素最大值,  是元素的数量。具有高不一致性的权重矩阵很难量化:最大元素相对于平均元素的大小是异常值。QuIP 表明,将权重矩阵左乘,右乘正交矩阵可以减少不一致性,使矩阵更容易量化。

QuaRot 用了类似的技术,将权重矩阵乘以正交矩阵,来改善不一致性。还对 activation 应用了针对不一致性的处理,从而改善了 weight 和 activation 的量化。图 1 中显示了将不一致性处理应用于 LLaMA-2 对于 activation 的影响。

图片

图1:第 10 层中,LLaMA2--7B 模型输入到 FFN 块的激活分布。左:使用从 Hugging Face 下载的默认配置。右图:在使用 QuaRot 处理后。处理后的分布没有异常值,更适合量化

Transformer 架构

图 2 和图 3 (更详细的版本) 介绍了本文针对的 LLM 架构,网络是 "pre-norm" 的,其中每个 Block 前面都有一个 LayerNorm 或 RMSNorm 操作。尽管本文的方法可以直接应用于 MLP 架构,作者还是假设 FFN 使用门控架构,如 LLaMA-2。

图片

图2:LM 中使用的门控前馈网络,包括 RMSNorm

图片

图3:LM 中使用的 Self-attention 的流程,包括 RMSNorm。实线箭头表示训练期间的流量、每个 token 的填充和推理。虚线箭头显示访问和从生成时使用的 KV cache

计算不变性

SliceGPT[3]的计算不变性定理 (Theorem1) 表明,可以使用正交矩阵转换 Transformer 中的 weight 和 Block 之间的 activation,而不更改模型输出。

主要思想是:如果  是一个权重矩阵,出现在 Block 的左侧 (即图1中的  或图2中的  ), 那么可以将左侧乘以正交矩阵 , 并通过将输出矩阵 (  )乘以  来消除这种影响。

上面这个计算不变性的思想尽管两个 Block 之间有 RMSNorm 也不影响。这是因为从概念上讲, RMSNorm 将 activation 除以其范数, 并将正交矩阵  应用于 activation 则不会影响范数。有:

图片

这里假设的是 RMSNorm 会作用于输入  的每一行: 。 。

那么 RMS 的这个性质就意味着一件事:给输出矩阵乘以  使得线性层的输出本该是  但变为了  。这个  被归一化之后送入下一个 Block。此 Block 的输入权重现在是  。因此, 这个线性层可以输出原始的 activation。

QuaRot 包括 2 个阶段。

  1. 在第 1 阶段,对模型 weight 进行操作 (以全精度),并将两个额外的 Hadamard 操作插入到模型的前向传递中。
  2. 在第 2 阶段,使用一些现有方法对 weight 进行量化,并将量化操作添加到前向传递中,以实现 activation (和cache) 的在线量化。默认情况下,使用 GPTQ[6]来量化权重,而 activation 使用简单的 RTN 方案即时量化。图 4 和图 5 显示了带有 QuaRot 修改的前向传递的更新框图。

1.3 QuaRot 阶段 1:Hadamard 变换阶段 1a:权重调节:利用计算不变性将每个权重矩阵乘以正交矩阵作者首先利用计算不变性将每个权重矩阵乘以正交矩阵。为了实现这一点, LayerNorm 或 RMSNorm 的线性部分被融合到相邻的权重矩阵中。图4显示了如何通过从 RMSNorm(  ) 中移除缩放操作并吸收到后续权重矩阵。作者选择了一个大小与模型隐藏维度相匹配的随机 Hadamard 矩阵, 并乘在每个权重矩阵上面。在图 4 和图 5 中, 该矩阵表示为  。例如, Key 投影权重矩阵  修改为:

图片

对于其他权重矩阵也是如此。出现在 Block 输出端的矩阵右乘Q 。

根据计算不变性定理[3],这种权重修改不会影响模型的输出。作者注意到修改后的权重类似于 QuIP#中使用的修改,减少了权重的不一致性,尽管本文的修改在运行时不需要任何额外的处理。此外,在 Transformer Block 之间传递的激活矩阵变成 。图 1 显示了这种处理的结果:处理后的激活不再包含任何异常值。

阶段 1b:FFN 输出旋转:在 FFN 中插入在线 Hadamard 操作

对矩阵权重做了左乘  的修改之后, 作者再在 FFN 中插入在线 Hadamard 操作, 如图4中的下采样投影之前的 hadamard 所示。然后这个操作也需要补偿,方式就是将另一个 Hadamard 矩阵融合到下采样投影矩阵中:  。再加上阶段 1 a 中为每个 Block 输出端右乘 , 因此下采样投影矩阵就变为:  。

阶段 1c:注意力值投影:对每个注意块应用额外的 Hadamard

作者对每个注意块应用额外的 Hadamard 操作。这种修改一部分在线计算,一部分融合到权重矩阵中。首先,在计算注意力时,  和  矩阵在每个头部内隐式相乘。

图片

其中,  是方阵, 维度为序列长度。  是每个 head 的 Value 矩阵。上式提供了一个使用 Hadamard matrix 对  和  矩阵进行处理的机会。

图片

将式 6 的修改代入式 5,计算的结果保持不变。式 6 可以等效为 对 和  矩阵执行单个 Kronecker 结构化乘法:

图片

作者利用下面的特性:

图片

当头数  和每个 head 的维数  都是 2 的幂时成立。

因为式 7 ,所以:

  • 图5 中的  先右乘  之后, 再进行了一次 hadamard heads, 即: , 相当于是给  右乘了 。
  • 图5 中的  先左乘  之后, 再左乘 , 相当于是左乘了 , 即  。

图片

图4:QuaRot 应用于 LLaMa 风格的 FFN。RMSNorm 缩放 (α) 已被吸收到权重矩阵 ((α) 是具有 RMSNorm 参数的对角矩阵)

图片

图5:QuaRot 应用于注意力。RMSNorm 缩放 α 被吸收到输入权重矩阵中,隐藏状态按照与 FFN 相同的方式旋转

阶段 1d:Key 的旋转:对注意块的 Key 应用额外的 Hadamard

注意力模块中的 Key 向量也被认为会受到异常值的影响[7]。因此,本文再使用 Hadamard 旋转来缓解这个问题,允许完全量化 KV cache。注意力分数计算如下:

图片

其中,  是 Softmax 尺度通常设置为  是 Mask (比如 Causal Mask), Pos 表示位置编码。位置嵌入通常仅在第 1 层到输入之前添加, 在这种情况下 Pos 是恒等函数。然而, 最近的方法, 比如 RoPE, 直接向 Key 向量和 Query 向量添加位置信息。

可以观察到  和  之间的相同交互,这点和  和  矩阵之间的计算过程很类似。然而, Pos 的存在阻止了将 Hadamard 矩阵直接融合到  和  中。因此, 作者使用在线头部 Hadamard 旋转来旋转 Query 和Key。因此, Query 和 Key 矩阵的计算更改如下:

图片

由于 Query 和 Key 都被旋转,最终的注意力分数  保持不变。

总体而言,本文前向传播的修改,包括插入特殊的 Hadamard 变换并对权重进行调整不会改变模型的前向传播过程。效果是 Block 之间的 activation 乘以 Hadamard 矩阵,Block 内的 activation 使用 Hadamard 变换在线处理,通过相应的权重矩阵修改完成。现在可以开始量化 weight 和 activation 了。

1.4 QuaRot 阶段 2:权重和激活值的量化

在做好 Hadamard 变换之后,就可以开始量化 weight 和 activation 了。

阶段 2a:Weight 的量化

作者应用 GPTQ[6]来量化网络的权重。在上述前向传递修改之后,可以应用任何量化方法。在随后的部分中,作者也展示了可以以牺牲一些准确性为代价应用简单的 RTN 方案。

阶段 2b:Activation 的量化

在 activation 的量化期间,保留 RMSNorm (without scaling) 的精度为 FP32,使用对称 per-token 量化线性层的输入。

阶段 2c:Attention 的量化

对于更长的序列和更大的 Batch Size,注意力存在显著的显存限制。在旋转 Key 和 Value 后,可以成功地将 cache 量化为低位宽。这减少了所需的 IO 操作的数量。作者将 Query 保留为 FP16,并使用类似于 Flash Attention[8]的在线 softmax 计算。在从内存中加载一段 KV 向量后,以 FP16 精度解量化并计算点积。

1.5 实验设置

在 PyTorch 上使用 Hugging Face 实现 QuaRot。

Input 量化: 所有实验中使用 per-token 对称量化,固定 clipping ratio 为 0.9。

KV cache 量化: 使用 group size 为 128 的非对称量化对 KV 缓存进行量化,clipping ratio 为 0.95。

Weight 量化: 使用 RTN 和 GPTQ,per-channel 对称量化。

使用来自 WikiText-2 训练集的 128 个样本,序列长度为 2048,作为 GPTQ 量化期间的校准集。在单个 NVIDIA A100 GPU 上,使用 QuaRot 修改 LLAMA2-70B 需要 5 分钟,并使用 GPTQ 量化模型需要 2 小时。

模型、任务和 GPU

作者在语言生成和零样本任务,在 LLaMA-2 系列上评估 QuaRot。作者实现了 CUDA kernel,使用 CUTLASS 库执行 4-bit 矩阵乘法。使用 FlashInfer 库来实现 KV cache 量化。由于针对消费者类型的 GPU,作者在 NVIDIA RTX 3090 GPU 上对所有实验进行评估。

1.6 精度结果

语言生成任务

作者首先评估了 QuaRot 在语言生成任务上的准确性。图 6 显示了使用 GPTQ 量化权重时 WikiText-2 上 LLaMA-2 模型的困惑度。作者与 4-bit SmoothQuant 和 OmniQuant 进行比较。QuaRot 在不需要任何重新训练 (比如 OmniQuant) 或更高的精度异常值特征和非对称量化 (比如 QUIK) 的情况下,最多优于所有以前的工作 0.63 的 Perplexity。作者还应用分组量化,对 weight 和 activation 应用相同数量的 group,与 Atom 进行比较。在这种情况下,QuaRot 不需要保留任何更高的精度特征和相关的操作 (比如重新排序)。QuaRot 在 7B 模型中优于 Atom 0.1 的困惑度,在 13B 模型中得到与 Atom 相同的困惑度。

图片

图6:LLaMA-2 模型的 4-bit 量化的 WikiText-2 困惑度结果 ,序列长度为 2048

Zero-Shot 任务

作者接下来专注于在 6 个重要的零样本任务上评估 QuaRot:PIQA、WinoGrande、HellaSwag、LAMBADA (OpenAI) 和 Arc (Easy and Challenge)。使用默认参数的 LM Evaluation Harness 进行实验。图 7 显示了本文的方案在上述任务和平均分数上的精度。在 LLaMA-2 模型家族上,QuaRot 以最多 4.18% 的平均分数损失来保持精度。

图片

图7:PIQA (PQ)、Winogrande (WG)、HellaSwag (HS)、Arc-Easy (A-e)、Arc-Challenge (A-c) 和 LAMBADA (LA) 上的 4 -bit (A4W4KV4) QuaRot 的 LLAMA-2 模型的 Zero-Shot 精度

1.7 性能分析

作者在 PyTorch 之上使用 CUDA/12.1 实现 QuaRot,并使用 CUTLASS 在 TensorCore 上执行 INT-4 矩阵乘法 (结果将保存在 INT32 累加器中)。本节中作者评估了本文 Kernel 在 NVIDIA RTX 3090 GPU 上预填充和解码步骤的性能。作者在单个 Transformer Block 上提供了所有的实验,因为用的 GPU 集群装不下整个模型。

Prefill 阶段性能增提升

对于 compute-bound 的 Prefill 阶段,作者在图 8 中展示了在 2048 的序列长度上使用 QuaRot 的加速。在 LLaMA2-7B 模型上,使用 QuaRot Kernel 获得了比 FP16 实现的版本 1.97-2.16 倍的加速。在 LLaMA2-70B 模型上获得了高达 3.33 倍的加速。注意可以通过优化 Kernel (例如将量化操作融合到 MatMul) 来提高性能结果。

Decoding 阶段显存节约

最后,作者评估了节约的显存,这是 Decoding 阶段的主要瓶颈。图 8 显示了 LLaMA-2 模型的峰值显存节省。作者提供了 LLAMA2-7B 和 LLaMA2-70B 模型的结果。在这两个模型中,在 Decoding 过程中获得了与 FP16 相比至少 3.63 倍的峰值显存节约。注意 LLaMA2--7B 模型中 KV cache 更大,因为 LLaMA2-70B 使用分组查询注意力。在 LLAMA2--7B 模型中,显存节约随着序列长度的增加而增加,显存节约高达 3.75 倍。在 LLaMA2-70B 模型上,几乎在所有情况下都节约了 3.89 倍。作者认为这些值对于整个模型来说更大,因为随着层数的增加,恒定大小对象在显存中的影响变得不那么显著。

图片

图8:使用 NVIDIA RTX 3090 GPU 在 LLaMA-2 模型的单个 Transformer Block 上的性能。左:对于加速结果,使用不同 Batch Size,序列长度 2048 进行评估。右:使用 Batch Size 16 对不同预填充序列长度的 50 个 token 的解码期间的峰值显存节约

#当红炸子鸡 LoRA,是当代微调 LLMs 的正确姿势?

对炼丹界的当红炸子鸡LoRA的「大拷问」!结合源码解析深入了解LoRA。

自 ChatGPT 掀起了大模型(LLM)风潮后,一大波 LLMs(GPT-4, LLaMa, BLOOM, Alpaca, Vicuna, MPT ..) 百花齐放。知识问答、文章撰写、代码编写和纠错、报告策划等等,它们都会,也能够交互式地和你玩文字游戏,甚至还有些很有才的朋友将 LLM 作为交互的接口,同时连接到其它各种模态(e.g. 视觉 & 语音)的模型,从而创造了炸裂的多模态效果,炫~!

这么炫,难免人人都想打造一个自己专属的 LLM(怎么有种回到了小时候玩宠物驯养游戏的赶脚..)。但是,大多数像 CW 这种“平民”玩家,并没有能够玩得起 LLM 的资源(主要是 GPU),别说成百上千亿参数量的模型了,就算是几十亿级别的模型,玩得起的朋友可能也不多。

大多数人对于 LLM 的“亲密度”,可能最多就是拉个开源的 demo 跑下推理过程,得到个“意料之中”的结果,然后很讽刺地自 high 一把:WOW~ 好腻害哟!我们离 AGI 又更近了一步!至于你让他训一个?他会说:呵呵.. 别想太多,洗洗睡吧!

一项技术通常都不会在它诞生之初就得以被广泛使用,和人一样,它也需要机遇。正是现在这种背景,加剧了我们大多数平民在大模型时代炼丹的矛盾。于是,本文的主角 LoRA(Low-Rank Adaptation of Large Language Models) ,一个于2021年就出生的家伙,顺势成为炼丹界的当红炸子鸡,成功出圈。

本文会先介绍 LoRA 的概念与优势、讲述其 motivation 和 以往方法存在的问题,然后以提问的形式从七个方面切入去认识与理解 LoRA(结合源码解析),接着进一步深入思考 LoRA 的一些方面,最后给出一个应用 LoRA 进行微调的例子。对 LoRA 已经有基本了解的帅哥靓女们,可以直接跳到“LoRA 七问”与“进击的 LoRA”这两章。

LoRA 是什么

如今快节奏生活下的人们都比较浮躁,你们看我吹水那么多水还没讲 LoRA 到底是什么,肯定已经饥渴难耐。哦?你说你不会,那很好,CW 为你点赞!不过,我也不磨叽,该进入正题了。

LoRA 的全称是 "Low-Rank Adaption", 看到 "low-rank",线性代数玩家们应该会神经反射性地联系到低秩矩阵。Binggo! 这里就是这个意思。你问我 LoRA 的中文名?Em.. 就叫它“低秩(自)适应”吧,虽然英文里没有 "self", 但根据 LoRA 的思想和做法及其带来的效果,它就是自适应的意思。

概括地来说,LoRA 是一项主要用于微调 LLMs 的技术,它额外引入了可训练的低秩分解矩阵,同时固定住预训练权重。这个玩法的重点在于:预训练权重不需训练,因此没有梯度,仅训练低秩矩阵那部分的参数。

有一点 CW 一定要告诉你们:引入的低秩矩阵那部分的参数量比起预训练权重来说,少炒鸡多! 这就意味着,比起全量 fine-tune 的玩法,可训练的参数量少了很多,于是就不需要那么多显存(GPU)资源了。这对于我等平(贫)民来说,简直不要太香了~!

利用 LoRA,我们可以享受到诸多福利,比如以下几点:

  1. 在面对不同的下游任务时,仅需训练参数量很少的低秩矩阵,而预训练权重可以在这些任务之间共享
  2. 省去了预训练权重的梯度和相关的 optimizer states,大大增加了训练效率降低了硬件要求
  3. 训练好的低秩矩阵可以合并(merge)到预训练权重中,多分支结构变为单分支,从而达到没有推理延时的效果;
  4. 与之前的一些参数高效的微调方法(如 Adapter, Prefix-Tuning 等)互不影响,并且可以相互结合

注:参数高效的微调方法 即 PEFT(Parameter-Efficient Fine-Tuning),这类方法仅需微调少量参数(可以是额外引入的),而无需微调预训练模型的所有参数,从而能够降低计算和存储资源。

灵感来源

对于一项技术,CW 往往会好奇它是基于怎样的想法被发明出来的。也就是,发明者的灵感来源究竟源自于哪里。可惜无法亲自采访作者,不然我肯定让他“口若悬河”hhh!没办法咯,我只能通过 paper 来为自己找答案。

CW 发现,作者在 paper 中提到:以往的一些工作表明,模型通常是“过参数化”(over-parametrized)的,它们在优化过程中参数更新的部分通常“驻扎”(reside)在低维子空间中。基于此,作者就顺理成章地提出假设:预训练模型在下游任务中微调而更新参数时,也符合这样的规律。

另外,以往的 PEFT 方法存在一系列问题,如:加大了推理延时、增加了模型深度、限制了输入句长 等,更重要的是,它们大多数都打不过全量 fine-tune,也就是最终训完的模型性能没有全量 fine-tune 来得好。

结合自己的假设与时代背景,作者就搞出了 LoRA,在这种玩法下训出的模型,最终在性能上能和全量 fine-tune 对飙,甚至在一些任务上还更加出色。

以往的 PEFT 跪在哪里

在上一章,CW 浅浅地提到了以往的 PEFT 方法存在的一些问题,如今在本章,我再稍稍展开来谈一下。

在 LoRA 出生之前,比较有代表性的 PEFT 方法主要有额外引入适配下游任务的 adapter layers 和 优化靠近模型输入层的激活(activations) 两种。对于后者,比较有代表性的有 prefix tuning 等。其实,降低下要求,这些方法也算是 good 的,毕竟在一定程度上算是 work 了,只是不 good enough ..

对于引入 adapter layers 这类方法,它们的不足在于:

  1. 新增的 adapter layers 必须串行处理,从而增加了推理延时
  2. 在分布式训练时需要更多的 GPU 同步操作(如 All-Reduce & Broadcast)

至于另一类方法,以 prefix tuning 为例,它们则跪在了:

  1. 该方法本身很难优化
  2. 这种方法需要在模型的输入序列中预留一部分用作可微调的 prompt,从而限制了原始输入文本的句长

LoRA 七问

这一章,CW 会向大家详细说明 LoRA 的玩法,主要从七个方面切入,分别对应以下每一小节的标题,这其实也是我自己在刚接触 LoRA 时所产生的疑问。可以把它们当作一个个 target 去攻破,待全部攻破之后,对 LoRA 应该就算是有一定的理解了。

前四节主要是理论分析,结合了 paper 中的公式和实验结果。后三节的内容则会结合源码解析,这样会有更深刻的认识。

为何可以引入低秩矩阵

作者说他之前看到了一篇 paper: Intrinsic Dimensionality Explains the Effectiveness of Language Model Fine-Tuning,这篇 paper 的结论是:预训练语言模型在下游任务微调后,权重矩阵其实具有很低的 "intrinsic rank"F-。

【关于 intrinsic rank 的理解】
"intrinsic" 直译是“内在的”、“固有的”,因此我看到有人直接喊 instrinsic rank 为“内在秩”、“固有秩”。(⊙o⊙)… 对于这种叫法,我是觉得很别扭,而且有点不明所以。
CW 觉得,在这里,"intrinsic" 应该理解为“本质上的”、“最具代表性的”会比较恰当。于是,"intrinsic rank" 就应当理解为最能体现数据本质的维度(特征)数目,我们也因此可以美其名曰:“本征秩”。其实,在信号处理里也有相应的概念——intrinsic dimension,它代表能够表示信号的最少特征数,它们所对应的特征是最能体现信号本质的特征。

也就是说,模型在适配下游任务后,权重矩阵的本征秩变得很低,这就代表着其实并不需要这么高的维度数就能够进行表征了,在高维度的权重矩阵中存在冗余。

基于此,作者就自信地认为:模型在微调过程中,权重更新的那部分(参数矩阵)肯定也低秩(low rank)的。

Inspired by this, we hypothesize the updates to the weights also have a low “intrinsic rank” during adaptation.

你问:“权重更新的那部分”具体指什么?

CW 答:模型在微调过程中,权重的变化可以表示为  。其中 就是更新前的权重(在一开始就是预训练权重),而  就是更新的那部分,也就是经过反向传播得到梯度后计算出来的需要更新的量

LoRA 改变了什么

假设 , 由于梯度与权重参数是一对一的, 因此  。如今, 既然认为  的本征秩很低,那么不妨对其做低秩分解:

, 其中 , 并且  。

这个  就是所谓的 low rank,因为它远小于  和  。

由此可知, 经过低秩分解后,  这部分的参数量是远小于预训练权重  的。

按理来说,  是在反向传播阶段才会出现的, 但我们可以将其 “提前拿出来” :让它和  一起做好朋友参与前向过程。这样一来, 反向传播时梯度就只会传导到  这部分, 因其本身就是待更新量, 而  初始是预训练权重, 被固定了, 无需接收梯度。

经过 LoRA 的“洗礼”, 现在如果你喂给模型一个输入 , 前向过程就会表示为:

另外,这里还有两点需要提一下:

  • 低秩矩阵 B,AB,A 的初始化

经过  式的低秩分解后, 为了在一开始让模型的输出保持为原来预训练模型的输出(也就是没有  那部分), 于是将  初始化为全0, 而  则采用随机高斯初始化。

  • 对低秩部分的输出  进行 scale

作者在 paper 中还提到, 对于  这部分, 会乘上一个 scale 系数  。其中,  相对于  保持一个常数倍的关系。作者认为调节这个  大致相当于调节学习率, 于是干脆固定为常数(这样就可以偷懒了 )。

降低了哪部分的显存需求

由于  的参数量远小于 , 因此, 相比于全量 fine-tune 的玩法, LoRA 降低了 optimizer states 这部分对于显存资源的需求。

这是因为 optimizer 对于需要更新的模型参数会保存一份副本, 在全量 fine-tune 的玩法下,  要全量更新, 于是 optimizer 保存的副本参数量为 ; 而我们的小可爱 LoRA 仅需更新  这部分, 所以 optimizer 保存的副本参数量仅为 , 其中  是远小于  的。

另外, 我们可能很容易理所当然地认为 LoRA 对于梯度部分的显存需求也远小于全量 fine-tune, 实际真的是这样吗? 嘿嘿不妨一起来分析下  的梯度是如何计算的。

假设模型经过如公式  所示的前向过程后得到了输出 , 并且我们进一步计算出了损失  ,现在我们是来求  的梯度。根据链式求导法则,易得:

注意  这部分, 它和全量 fine-tune 时是一样的, 这部分梯度的 shape 和权重矩阵的 shape 一致,都是  。

OMG!这就是说, 实际在计算  的梯度过程中, 所需的显存并没有比全量 fine-tune 来得少, 样也需要算出 shape 为  的梯度矩阵。更尴尬的是, 由于  的存在, 因此在梯度的计算过程中, 所需的显存和计算量甚至比全量 fine-tune 还来得多.. 幸运的是, 在计算完成后,  这个中间状态量所占的显存会被释放掉, 仅需存储  这部分 shape 为  的梯度矩阵。

所以说,对于梯度部分,LoRA 在严格意义上并不能降低其对于显存资源的需求,甚至比起全量 fine-tune 来说计算量还更大了,只不过降低了最终存储的需求。

对模型的哪些部分做低秩分解

可是,在如今的 202x 年代,模型通常有 N 个权重矩阵,那么应该对其中的哪些做低秩分解呢?还是说,应该暴力地、一个不拉地通杀?

对于这个问题,作者选择了偷懒,他仅将 LoRA 应用于 self-attention 层中的 projection matrices(如),而其中的 MLP 模块以及 self-attention 层以外的结构均“不受待见”。

In the Transformer architecture, there are four weight matrices in the self-attention module (Wq, Wk, Wv, Wo) and two in the MLP module.
We limit our study to only adapting the attention weights for downstream tasks and freeze the MLP modules.
We leave the empirical investigation of adapting the MLP layers, LayerNorm layers, and biases to a future work.

作者可能也猜到了你们可能会打破砂锅问到底:应该对 self-attention 层中的哪个或哪几个 projection matrices 应用 LoRA 呢?于是,对于这个问题,他倒是下了番功夫去做实验进行探究。

在实验中,作者以 175B 的 GPT-3 为研究对象,并设置了参数量为 18M 的 budget,也就是应用了 LoRA 部分的可微调参数量不能超过 18M。在这种设置下,当每层仅对  的其中一个应用 LoRA 时,rank  则等于8;而如果每层都对  的其中两个应用 LoRA,则 rank  等于4。

通过上表可以看出,模型更倾向于我们对更多类型的 projection matrices 应用 LoRA(如上表显示,对4个 projection matrices 都应用 LoRA 时效果是最好的),尽管 rank 很低(如上表中最右一列  ), 也足够让  捕获足够的信息。

在代码中如何实现

假设 是一个线性层(Linear Layer),我们一起来看看对其应用 LoRA 是如何实现的。

(麻烦认真看下代码中的注释,谢谢~)

class MergedLinear(nn.Linear, LoraLayer):
    # Lora implemented in a dense layer
    def __init__(
        self,
        in_features: int,
        out_features: int,
        r: int = 0,
        lora_alpha: int = 1,
        lora_dropout: float = 0.0,
        enable_lora: List[bool] = [False],
        fan_in_fan_out: bool = False,
        merge_weights: bool = True,
        **kwargs,
    ):
        nn.Linear.__init__(self, in_features, out_features, **kwargs)
        LoraLayer.__init__(self, r=r, lora_alpha=lora_alpha, lora_dropout=lora_dropout, merge_weights=merge_weights)

        # enable_lora 是一个布尔类型的列表,用于指示对权重矩阵的哪些“子部分”做低秩分解。
        # 比如 W 是 shape 为 (out_features, in_features) 的矩阵,
        # 那么 enable_lora = [True, False, True] 就表示将 W 在 out_features 这个维度上按序均分成三部分 W1, W2, W3,
        # shape 均为 (out_features // 3, in_features),然后仅对 W1 和 W3 做低秩分解。
        # 其中 W1 的第一个维度取值范围是 [0, out_features // 3),W3 则是 [2 * out_features // 3, out_features)。
        # 同理,若 enable_lora = [True],就表示对整个 W 都做低秩分解。
        if out_features % len(enable_lora) != 0:
            raise ValueError("The length of enable_lora must divide out_features")
        
        self.enable_lora = enable_lora
        self.fan_in_fan_out = fan_in_fan_out

        # Actual trainable parameters
        if r > 0 and any(enable_lora):
            # 仅 enable_lora = True 的部分应用低秩分解,每部分的 low-rank 是 r
            self.lora_A = nn.Linear(in_features, r * sum(enable_lora), bias=False)
            # 注意下这里 B 是用一维的分组卷积实现的
            self.lora_B = nn.Conv1d(
                r * sum(enable_lora),
                out_features // len(enable_lora) * sum(enable_lora),
                kernel_size=1,
                groups=2,
                bias=False,
            )

            # scale 系数,对低秩矩阵的输出(BAx)做缩放
            self.scaling = self.lora_alpha / self.r
            # Freezing the pre-trained weight matrix
            # 固定住预训练权重
            self.weight.requires_grad = False

            # Compute the indices
            # 记录权重矩阵中,做了低秩分解的是哪些“子矩阵”
            self.lora_ind = self.weight.new_zeros((out_features,), dtype=torch.bool).view(len(enable_lora), -1)
            self.lora_ind[enable_lora, :] = True
            self.lora_ind = self.lora_ind.view(-1)

        self.reset_parameters()
        if fan_in_fan_out:
            # fan_in_fan_out 是针对 GPT-2 的 Conv1D 模块的,
            # 该模块和 Linear 的区别就是维度互为转置
            self.weight.data = self.weight.data.T

    def reset_parameters(self):
        nn.Linear.reset_parameters(self)
        if hasattr(self, "lora_A"):
            # initialize A the same way as the default for nn.Linear and B to zero
            nn.init.kaiming_uniform_(self.lora_A.weight, a=math.sqrt(5))
            nn.init.zeros_(self.lora_B.weight)

以上这个类叫作 MergedLinear, 顾名思义就是低秩分解的部分  可以合并到原来的预训练权重  中。

以上代码中, 比较绕的是与 enable_lora 这个参数相关的内容, 该参数可以用来灵活地指定预训练权重  中,哪些部分要做低秩分解。

关于这个参数的设计由来,  猜想这是因为在某些模型的实现中, Attention 层中的  projection matrix 是用一个共享的线性层实现的(比如 GPT-2, BLOOM, etc.), 而有了这个 enable_lora 参数, 就可以灵活地指定要对这三者中的哪一个做低秩分解。

所有需要进行低秩分解的层都会继承 LoraLayer 这个父类,这个类没有什么特别,也就是设置一些 LoRA 该有的属性:

class LoraLayer:
    def __init__(
        self,
        r: int,
        lora_alpha: int,
        lora_dropout: float,
        merge_weights: bool,
    ):
        self.r = r
        self.lora_alpha = lora_alpha

        # Optional dropout
        if lora_dropout > 0.0:
            self.lora_dropout = nn.Dropout(p=lora_dropout)
        else:
            self.lora_dropout = lambda x: x

        # Mark the weight as unmerged
        # 标记低秩分解部分是否已经合并至预训练权重
        self.merged = False
        # 指定是否要将低秩分解部分合并至预训练权重中
        self.merge_weights = merge_weights

        # 是否要禁用低秩分解的部分,如果是,则仅使用预训练权重部分
        self.disable_adapters = False

现在来介绍下 MergedLinear 这个层的前向过程:

  1. 先用预训练权重  对输入  实施前向过程, 得到 ;
  2. 再将输入  喂给低秩分解矩阵 , 得到输出 ;
  3. 接着对  这部分作 "零填充"使其与  的 shape 一致, 并且进行缩放(scale);
  4. 最后再将这部分的结果加回至  中, 如前面的公式  。
def forward(self, x: torch.Tensor):
    result = F.linear(x, transpose(self.weight, self.fan_in_fan_out), bias=self.bias)
    if self.r > 0:
        after_A = self.lora_A(self.lora_dropout(x))
        after_B = self.lora_B(after_A.transpose(-2, -1)).transpose(-2, -1)
        result += self.zero_pad(after_B) * self.scaling

        return result

第3点中的“零填充"对应以上代码中的 zero_pad(), 前面 CW 在介绍 enable_lora 这个参数时说过, 由于不一定会对整个预训练权重矩阵做低秩分解, 于是  的 shape 不一定等同于 , 因此需要对前者进行 padding, 使其与后者的 shape 一致, 从而才可让两者进行 element-wise add 。

现在放出这个填充的逻辑:

def zero_pad(self, x):
        """ 将低秩矩阵的输出 BAx 与原权重矩阵输出 Wx 在维度上对应起来, 维度不足的部分用0填充 """
        result = x.new_zeros((*x.shape[:-1], self.out_features))
        result = result.view(-1, self.out_features)
        # 将 BAx “塞到”与 Wx 相对应的正确位置
        result[:, self.lora_ind] = x.reshape(-1, self.out_features // len(self.enable_lora) * sum(self.enable_lora))

        return result.view((*x.shape[:-1], self.out_features))

怎么做到无推理延时

CW 在前面提到过,LoRA 的优势之一就是推理无延时(相比预训练模型),这是因为低秩分解的部分可以合并至原预训练权重中。比如,这时候模型需要推理,那么你会先调用 model.eval(),这时等同于调用了 model.train(mode=False),接着再将低秩分解部分合并至预训练权重中,过程如下:

def train(self, mode: bool = True):
        nn.Linear.train(self, mode)
        self.lora_A.train(mode)
        self.lora_B.train(mode)

        # 注:当调用 model.eval() 时就会调用 train(mode=False)
        # 将低秩矩阵 A, B 合并至原权重矩阵 W
        if not mode and self.merge_weights and not self.merged:
            # Merge the weights and mark it
            if self.r > 0 and any(self.enable_lora):
                # \delta_W = BA
                delta_w = (
                    # 这里使用1维卷积将低秩矩阵 A, B 进行“融合”:
                    # A(r * k) 作为输入,r 看作是其 channel,k 看作是空间维度上的大小;
                    # B(d * r * 1) 作为卷积权重,d 是 output channel, r 是 input channel, 1 是 kernel size(注意B本身就是用1维分组卷积实现的)。
                    # 由于是卷积,因此二维的 A 需要增加一维给 mini-batch:r * k -> 1 * r * k。
                    # 卷积后,输入(1 * r * k) -> 输出(1 * d * k)
                    F.conv1d(
                        self.lora_A.weight.data.unsqueeze(0),
                        self.lora_B.weight.data,
                        groups=sum(self.enable_lora),
                    )
                    .squeeze(0)  # 1 * d * k -> d * k
                    .transpose(-2, -1)  # d * k -> k * d
                )
                # zero_pad() 是对低秩分解矩阵 \delta_W 进行0填充,因为原权重矩阵 W 中可能有些部分没有进行低秩分解,
                # 从而得到一个和原权重矩阵 W 的 shape 对齐的结果,以便进行加和。k * d -> k * D(假设 D 是原权重矩阵 W 的 out features)
                # 对于原权重矩阵 W 是 Linear 层的情况,fan_in_fan_out = False,于是这里会进行 transpose: k * D -> D * k;
                # 而对于原权重矩阵 W 是 GPT-2 的 Conv1D 的情况,fan_in_fan_out=True,于是不需要 transpose,它的 out features 就是放在第二维的
                # W = W + # \delta_W
                self.weight.data += transpose(self.zero_pad(delta_w * self.scaling), not self.fan_in_fan_out)
        elif xxx:
            ...

merge 完之后,在进行前向过程时就无需再像上一节展示的那样分步进行,而是一步到位(见以下第二个分支):

def forward(self, x: torch.Tensor):
    # 此部分先省略,下一节再介绍
    if xxx:
        ...
    # 低秩分解部分已合并
    elif self.merged:
        return F.linear(x, transpose(self.weight, self.fan_in_fan_out), bias=self.bias)
    # 低秩分解部分未合并
    else:
        result = F.linear(x, transpose(self.weight, self.fan_in_fan_out), bias=self.bias)
        if self.r > 0:
            after_A = self.lora_A(self.lora_dropout(x))
            after_B = self.lora_B(after_A.transpose(-2, -1)).transpose(-2, -1)
            result += self.zero_pad(after_B) * self.scaling

        return result

如何在下游任务中灵活地切换

LoRA 还有一点很吸引人,就是模型在某个下游任务 A 微调后,可以将低秩矩阵那部分的参数解耦出来,还原出预训练权重,从而继续在另一个下游任务 B 中进行微调。

def train(self, mode: bool = True):
        nn.Linear.train(self, mode)
        self.lora_A.train(mode)
        self.lora_B.train(mode)

        if xxx:
            ...
        # 前一个分支是代表 mode=False,进入该分支说明 mode=True,即调用了 model.train(),
        # 那么当低秩矩阵 A, B 已经合并至原权重矩阵 W 中时,就需要将它们分解出来,以便进行训练(预训练权重 W 无需训练)。
        elif self.merge_weights and self.merged:
            # Make sure that the weights are not merged
            if self.r > 0 and any(self.enable_lora):
                # \delta_W = BA
                delta_w = (
                    F.conv1d(
                        self.lora_A.weight.data.unsqueeze(0),
                        self.lora_B.weight.data,
                        groups=sum(self.enable_lora),
                    )
                    .squeeze(0)
                    .transpose(-2, -1)
                )
                # W = W - \delta_W
                self.weight.data -= transpose(self.zero_pad(delta_w * self.scaling), not self.fan_in_fan_out)
            self.merged = False

还原了预训练权重后,如果你不想使用低秩矩阵那部分的参数,也可以(见以下第一个分支):

def forward(self, x: torch.Tensor):
        # 当指定不需要使用 adapters 部分(在这里即低秩分解矩阵 \delta_W=BA 这部分),
        # 则将已经合并到预训练权重 W 中的 \delta_W 解耦出来,仅用预训练权重 W 对输入 x 进行前向操作
        if self.disable_adapters:
            if self.r > 0 and self.merged and any(self.enable_lora):
                delta_w = (
                    F.conv1d(
                        self.lora_A.weight.data.unsqueeze(0),
                        self.lora_B.weight.data,
                        groups=sum(self.enable_lora),
                    )
                    .squeeze(0)
                    .transpose(-2, -1)
                )
                # W = W - \delta_W
                self.weight.data -= transpose(self.zero_pad(delta_w * self.scaling), not self.fan_in_fan_out)
                self.merged = False
            return F.linear(x, transpose(self.weight, self.fan_in_fan_out), bias=self.bias)
        # 当使用 adapters 并且低秩分解矩阵 \delta_W=BA 已经合并至预训练权重 W 中,则直接进行前向过程即可。
        elif self.merged:
            ...
        # 当使用 adapters 但低秩分解矩阵 \delta_W=BA 未合并到预训练权重 W 中,则“分步”进行前向过程:
        # 先用预训练权重 W 对输入 x 实施前向过程,得到 Wx;
        # 再将输入 x 喂给低秩分解矩阵 \delta_W=BA,得到 adapters 的输出 (\delta_W)x;
        # 接着对 adapters 部分的输出作零填充使得其与 Wx 的 shape 一致,并且进行缩放(scale);
        # 最后再将这部分的结果加回至 Wx 中。
        else:
            ...

进击的 LoRA

攻破了 LoRA 七问之后,是时候来些更深层次的思想活动了。

Rank r 的设置

一个很直接的问题就是:在实践中,rank  应该设为多少比较合适呢?

作者做了几组实验进行比较,结果发现 rank  可以很低,不超过8就很 OK 了,甚至是1也挺好..

Low Rank 的有效性

看到前面这个实验现象,作者“忍不住”认为: 拥有很低的本征秩(intrinsic rank),增加 rr 并不能使其覆盖到更多有意义的子空间,low rank 万岁!

不过, 他并非是个嘴炮, 对于这一直觉, 他还是仔仔细细地做了个实验去验证的。具体做法就是: 以同样的预训练模型, 分别用  和  两种 rank 设置去应用 LoRA 进行微调, 然后将训好的低秩矩阵  拿出来做奇异值分解, 得到它们的右奇异单位矩阵 , 最后去比较它们的 top 奇异值向量所张成(span)的子空间的重合程度(使用 Grassmann Distance 度量), 公式表示为(对应 paper 中的公式4):

其中  对应于  的 top-i 奇异值向量的列,  同理。 的取值范围是 , 越大代表两个子空间重合度越高。

根据上图的实验结果显示,两者在最 top 的那些奇异值向量所张成的空间重合度最高,特别是在 top-1 处,这也对前面一节“  效果也不错”提供了一种解释。

由于在两种 rank 的设置下使用的是同一个预训练模型,并且经过同样的下游训练后,两者在 top 奇异值向量的方向上比较一致(其余方向上则相关度较小),因此这说明 top 奇异值向量所指的方向对于下游任务i 来说是最有用的,而其它方向可能更多地是一些随机噪声的方向,这可能是训练过程中被潜在地积累下来的。

于是乎,low rank 对于  才是正解。

低秩矩阵与预训练权重矩阵的关系

刚接触 LoRA 时, CW 就很好奇训出来的这个  与原来的预训练权重  到底有没有“血缘关系", 它与  的相关度是怎么样的呢?

作者也对这个问题进行了探究, 他将  映射到  的  维子空间, 得到  ,其中  分别是  左、右奇异向量矩阵。然后计算  的 Frobenius 范数 (后续简称为  范数, 即所有元素的平方和再开方)  。作为对照组, 作者还将  分别映射到了自身和一个随机矩阵的 top-r 奇异值向量空间。

由实验结果可以看出, 预训练权重矩阵  与低秩矩阵  还算是“近亲":比起随机矩阵,  映射到  的子空间后的数值会大一些。

低秩矩阵的优化方向

以上实验结果还揭示出两点:

  • 低秩矩阵在经过下游训练后放大了那些在预训练时没有被强调的向量方向
  • 对于上一点,  较低时放大程度更强 (

咋一看实验结果,可能无法 get 到以上第一点,那么该如何理解呢?

还是得看回上面那张实验结果图, 无论是  还是  的情况,  映射到  的  维子空间后其 F2 范数都非常小(0.32 & 1.90), 说明这些子空间的向量方向并非是  中重要程度比较高的方向, 而是在预训练时显得不那么重要的、没有被强调的方向。但可以看到,  却不那么小(6.91 & 3.57), 说明在经过下游微调后, 那些原本存在感不强的方向被重视起来了。

由此可知, 在下游训练过程中, 低秩矩阵  并非简单地重复预训练权重矩阵的 top 奇异值向量方向,而是去放大原本在预训练中没有被强调的方向。

至于第二点, 我们分别在  和  两种情况下计算 , 其中  取  那一列的值, 这个计算结果衡量了低秩矩阵在第2点中的放大效应。通过计算, 我们可以发现在秩比较的低的情况下  放大效应更强, 那么这又意味着什么呢?

我们有理由认为, 低秩矩阵  包含着大部分与下游任务相关的向量方向(毕竟是朝着下游最优的方向而进行优化的), 于是以上计算结果就意味着适配下游任务的矩阵的本征秩是低秩的。

巧了! 不小心再次证明了 low rank 对于  才是正解~

低秩是万能的吗

CW 在以上多次喊道“low rank 对于  是正解”其实有点过于夸张了。首先,作者的实验场景十分有限,没有在更广泛的 case 上进行验证;其次,我们也不应该无脑地认为以一个很小的  值就能够在所有任务和数据集上 work。

想象下,当下游任务与预训练任务的差异(gap)巨大时(比如在英文上预训练、而在中文上微调),使用很小的  值应该不会有好效果,这时候去微调模型所有的参数(可以令 )应该会得到更好的效果,毕竟中英文的向量空间重合度应该不那么高,我们需要让更多的参数“调头”,转向到适配中文的空间中去。

Example: 在单卡 12G 左右的显存下微调 BLOOM-7B

最后这部分给一个利用 LoRA 进行微调的例子,这个 demo 基于 Huggingface 的 PEFT 库,使用了 LoRA + 8bit 训练,其中 8bit 训练需要安装 bitsandbytes。在单卡的条件下,12G 左右的显存即可玩起 7B 的 BLOOM。

一些杂碎

先来做一些琐碎小事:导入模块、设置数据集相关参数、训练参数 以及 随机种子等。

import gc
import os
import sys
import psutil
import argparse
import threading

import torch
import torch.nn as nn

import numpy as np

from tqdm import tqdm

from torch.utils.data import DataLoader

from datasets import load_dataset
from accelerate import Accelerator
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    default_data_collator,
    get_linear_schedule_with_warmup,
    set_seed,
)

from peft import LoraConfig, TaskType, get_peft_model, prepare_model_for_int8_training


def set_seed(seed: int):
    """
    Helper function for reproducible behavior to set the seed in `random`, `numpy`, `torch` and/or `tf` (if installed).

    Args:
        seed (`int`): The seed to set.
    """

    random.seed(seed)
    np.random.seed(seed)
    
    if is_torch_available():
        torch.manual_seed(seed)
        torch.cuda.manual_seed_all(seed)
        # ^^ safe to call this function even if cuda is not available
    
    if is_tf_available():
        tf.random.set_seed(seed)


def main():
    accelerator = Accelerator()

    dataset_name = "twitter_complaints"
    text_column = "Tweet text"
    label_column = "text_label"
    
    # 样本的最大句长
    max_length = 64

    lr = 1e-3
    batch_size = 8
    num_epochs = 20

    # 设置随机种子(42 yyds!)
    seed = 42
    set_seed(seed)

数据加载和预处理

这里使用的数据集是 RAFT(The Real-world Annotated Few-shot Tasks) 任务中的 Twitter Complaints,有50个训练样本和3399个测试样本。

下面给出数据加载和预处理的逻辑,代码本身简单明了,无需啰嗦。

''' Datset and Dataloader '''

    dataset = load_dataset("ought/raft", dataset_name, cache_dir=args.data_cache_dir)
    classes = [k.replace("_", " ") for k in dataset["train"].features["Label"].names]
    dataset = dataset.map(
        lambda x: {"text_label": [classes[label] for label in x["Label"]]},
        batched=True,
        num_proc=4
    )

    # Preprocessing

    tokenizer = AutoTokenizer.from_pretrained(args.model_name_or_path, cache_dir=args.model_cache_dir)

    def preprocess_function(examples):
        # 注:这里的 batch size 并非训练时的 batch size,
        # 在这个数据预处理的过程中,也是批处理的,所以这里也有个 batch size 的概念
        batch_size = len(examples[text_column])

        # Add prompt 'Label' to input text
        inputs = [f"{text_column} : {x} Label : " for x in examples[text_column]]
        targets = [str(x) for x in examples[label_column]]

        model_inputs = tokenizer(inputs)
        labels = tokenizer(targets)

        # 依次处理每个样本
        for i in range(batch_size):
            sample_input_ids = model_inputs["input_ids"][i]
            label_input_ids = labels["input_ids"][i] + [tokenizer.pad_token_id]

            # 将输入文本(model_inputs)与标签(labels)“对齐”(设置成一样),然后将标签中对应输入文本的部分设为 -100,
            # 这样在计算 loss 时就不会计算这部分,仅计算真实标签文本的那部分。
            # Add label text to input text
            model_inputs["input_ids"][i] = sample_input_ids + label_input_ids
            # Let the label value which correspond to the input text word to be -100
            labels["input_ids"][i] = [-100] * len(sample_input_ids) + label_input_ids
            model_inputs["attention_mask"][i] = [1] * len(model_inputs["input_ids"][i])

        # Put pad tokens at the front of the inputs, and truncate to 'max_length'
        for i in range(batch_size):
            sample_input_ids = model_inputs["input_ids"][i]
            label_input_ids = labels["input_ids"][i]

            pad_length = max_length - len(sample_input_ids)
            labels["input_ids"][i] = [-100] * pad_length + label_input_ids
            model_inputs["input_ids"][i] = [tokenizer.pad_token_id] * pad_length + sample_input_ids
            model_inputs["attention_mask"][i] = [0] * pad_length + model_inputs["attention_mask"][i]

            # To tensor
            model_inputs["input_ids"][i] = torch.tensor(model_inputs["input_ids"][i][:max_length])
            model_inputs["attention_mask"][i] = torch.tensor(model_inputs["attention_mask"][i][:max_length])
            labels["input_ids"][i] = torch.tensor(labels["input_ids"][i][:max_length])

        model_inputs["labels"] = labels["input_ids"]
        return model_inputs

    def test_preprocess_function(examples):
        batch_size = len(examples[text_column])

        inputs = [f"{text_column} : {x} Label : " for x in examples[text_column]]
        model_inputs = tokenizer(inputs)

        for i in range(batch_size):
            sample_input_ids = model_inputs["input_ids"][i]

            pad_length = max_length - len(sample_input_ids)
            model_inputs["input_ids"][i] = [tokenizer.pad_token_id] * pad_length + sample_input_ids
            model_inputs["attention_mask"][i] = [0] * pad_length + model_inputs["attention_mask"][i]

            # To tensor
            model_inputs["input_ids"][i] = torch.tensor(model_inputs["input_ids"][i][:max_length])
            model_inputs["attention_mask"][i] = torch.tensor(model_inputs["attention_mask"][i][:max_length])

        return model_inputs

    with accelerator.main_process_first():
        processed_datasets = dataset.map(
            preprocess_function,
            batched=True,
            num_proc=4,
            remove_columns=dataset["train"].column_names,
            load_from_cache_file=True,
            desc="Running tokenizer on dataset",
        )
    accelerator.wait_for_everyone()
    train_dataset = processed_datasets["train"]

    with accelerator.main_process_first():
        processed_datasets = dataset.map(
            test_preprocess_function,
            batched=True,
            num_proc=4,
            remove_columns=dataset["train"].column_names,
            load_from_cache_file=False,
            desc="Running tokenizer on dataset",
        )
    eval_dataset = processed_datasets["train"]
    test_dataset = processed_datasets["test"]

    # Dataloaders
    train_dataloader = DataLoader(
        train_dataset, shuffle=True, collate_fn=default_data_collator,
        batch_size=batch_size, pin_memory=True, num_workers=4
    )
    eval_dataloader = DataLoader(
        eval_dataset, collate_fn=default_data_collator,
        batch_size=batch_size, pin_memory=True, num_workers=4
    )
    test_dataloader = DataLoader(
        test_dataset, collate_fn=default_data_collator,
        batch_size=batch_size, pin_memory=True, num_workers=4
    )

    print(f"The 1st train batch sample: {next(iter(train_dataloader))}\n")

Model, Optimizer & Lr scheduler

必备套装:模型、优化器、学习率的调度。

''' Model, Optimizer, Lr Scheduler '''

    # creating model
    model = AutoModelForCausalLM.from_pretrained(
        args.model_name_or_path,
        cache_dir=args.model_cache_dir,
        load_in_8bit=args.load_in_8bit,
        device_map='auto'  # A device map needs to be passed to run convert models into mixed-int8 format
    )

    ''' Post-processing on the model, includes:
        1- Cast the layernorm in fp32;
        2- making output embedding layer require grads;
        3- Anable gradient checkpointing for memory efficiency;
        4- Add the upcasting of the lm head to fp32
    '''
    model = prepare_model_for_int8_training(model)

    # 配置 LoRA 的一些参数
    peft_config = LoraConfig(task_type=TaskType.CAUSAL_LM, inference_mode=False, r=8, lora_alpha=32, lora_dropout=0.1)
    # 对模型应用 LoRA
    model = get_peft_model(model, peft_config)

    # 打印出可训练的参数个数
    model.print_trainable_parameters()

    # optimizer
    optimizer = torch.optim.AdamW(model.parameters(), lr=args.lr)

    # lr scheduler
    lr_scheduler = get_linear_schedule_with_warmup(
        optimizer=optimizer,
        num_warmup_steps=0,
        num_training_steps=(len(train_dataloader) * num_epochs),
    )

    model, train_dataloader, eval_dataloader, test_dataloader, optimizer, lr_scheduler = accelerator.prepare(
        model, train_dataloader, eval_dataloader, test_dataloader, optimizer, lr_scheduler
    )
    accelerator.print(f"Model: {model}\n")

Preparation for 8bit Training

这一节进一步解析上一节中的 model = prepare_model_for_int8_training(model) 这部分,它是为了让训练过程更稳定,以便得到更好的效果,下面来看看其具体做了些什么。

def prepare_model_for_int8_training(
    model, output_embedding_layer_name="lm_head", use_gradient_checkpointing=True, layer_norm_names=["layer_norm"]
):
    r"""
    This method wrapps the entire protocol for preparing a model before running a training. This includes:
        1- Cast the layernorm in fp32 2- making output embedding layer require grads 3- Add the upcasting of the lm
        head to fp32

    Args:
        model, (`transformers.PreTrainedModel`):
            The loaded model from `transformers`
    """
    loaded_in_8bit = getattr(model, "is_loaded_in_8bit", False)

    # 1. 固定预训练的权重;
    # 2. 将 Layer Norm 的参数转换为 fp32,这是为了训练的稳定性  
    for name, param in model.named_parameters():
        # freeze base model's layers
        param.requires_grad = False

        if loaded_in_8bit:
            # cast layer norm in fp32 for stability for 8bit models
            if param.ndim == 1 and any(layer_norm_name in name for layer_norm_name in layer_norm_names):
                param.data = param.data.to(torch.float32)

    # 让 Embedding 层接受梯度,通过对 Embedding 层注册 forward hook 实现,
    # forward hook 的内容会在模型前向过程完成后被调用。
    # 通过以下可以看到,这里 hook 的内容是使 Embedding 层的输出接受梯度,
    # 从而梯度可以传导到 Embedding 层。
    if loaded_in_8bit and use_gradient_checkpointing:
        # For backward compatibility
        if hasattr(model, "enable_input_require_grads"):
            model.enable_input_require_grads()
        else:

            def make_inputs_require_grad(module, input, output):
                output.requires_grad_(True)

            model.get_input_embeddings().register_forward_hook(make_inputs_require_grad)

        # enable gradient checkpointing for memory efficiency
        # 对前向过程的中间激活部分的梯度做优化,以优化内存所需。
        model.gradient_checkpointing_enable()

    # 将模型头部的输出转换为 fp32 以稳定训练
    if hasattr(model, output_embedding_layer_name):
        output_embedding_layer = getattr(model, output_embedding_layer_name)
        input_dtype = output_embedding_layer.weight.dtype

        class CastOutputToFloat(torch.nn.Sequential):
            r"""
            Manually cast to the expected dtype of the lm_head as sometimes there is a final layer norm that is casted
            in fp32

            """

            def forward(self, x):
                # 这里之所以要先将输入(x)转换成该层参数的精度(dtype)是因为上一层可能是 Layer Norm,
                # 而由上可知,我们对 Layer Norm 的输出精度转换成了 fp32,因此在这种情况下,就需要先将
                # 上一层的输出(也就是该层的输入 x)先转成与该层参数同样的精度。
                return super().forward(x.to(input_dtype)).to(torch.float32)

        setattr(model, output_embedding_layer_name, CastOutputToFloat(output_embedding_layer))

    return model

通过以上的源码实现并结合 CW 的注释,可以知道,这部分主要做了以下四件事情:

  1. 将 LayerNorm 的参数转换成 fp32 类型;
  2. 令 Embedding 层的输出接收梯度,从而使得梯度得以传递至该层;
  3. 优化前向过程产生的中间激活部分的梯度,以减少显存消耗;
  4. 将模型头部(Head)的输出精度转换成 fp32 类型

PEFT Model

在这一节,CW 引领大家来看看从普通的 model 转换成 peft model:_model = get_peft_model(model, peft_config)_ 是怎么做的,以下针对 BLOOM 模型的情况进行解析。

def get_peft_model(model, peft_config):
    """
    Returns a Peft model object from a model and a config.

    Args:
        model ([`transformers.PreTrainedModel`]): Model to be wrapped.
        peft_config ([`PeftConfig`]): Configuration object containing the parameters of the Peft model.
    """

    model_config = model.config.to_dict()
    peft_config.base_model_name_or_path = model.__dict__.get("name_or_path", None)
    
    if peft_config.task_type not in MODEL_TYPE_TO_PEFT_MODEL_MAPPING.keys():
        peft_config = _prepare_lora_config(peft_config, model_config)
        return PeftModel(model, peft_config)
    
    if not isinstance(peft_config, PromptLearningConfig):
        # BLOOM 会进入到这个分支
        peft_config = _prepare_lora_config(peft_config, model_config)
    else:
        peft_config = _prepare_prompt_learning_config(peft_config, model_config)

    # 在我们这个例子里,peft_config.task_type 是 CAUSAL_LM,
    # MODEL_TYPE_TO_PEFT_MODEL_MAPPING[peft_config.task_type] 则是 PeftModelForCausalLM,
    # 它是 PeftModel 的子类,它就是在原模型基础上对目标模块做了 LoRA 转换的结果
    return MODEL_TYPE_TO_PEFT_MODEL_MAPPING[peft_config.task_type](model, peft_config)

进一步来看看 peft_config = _prepare_lora_config(peft_config, model_config) 这里面的实现,它决定了要对模型的哪些模块应用 LoRA 。

def _prepare_lora_config(peft_config, model_config):
    if peft_config.target_modules is None:
        if model_config["model_type"] not in TRANSFORMERS_MODELS_TO_LORA_TARGET_MODULES_MAPPING:
            raise ValueError("Please specify `target_modules` in `peft_config`")
        # 设置需要进行 LoRA 转换的目标模块,通常是 Attention 层中的一个或几个映射矩阵(Linear Layer)
        # 对于 BLOOM,这里返回的是 ["query_key_value"],对应的是其模型实现中 BloomAttention 中的 QKV 映射矩阵
        peft_config.target_modules = TRANSFORMERS_MODELS_TO_LORA_TARGET_MODULES_MAPPING[model_config["model_type"]]

    if len(peft_config.target_modules) == 1:
        # 这个仅对 GPT-2 里用到的 Conv1D 有效
        peft_config.fan_in_fan_out = True
        # 这三个值分别代表 Q,K,V 映射矩阵是否要应用 LoRA
        # 对于 BLOOM 来说,这里仅对 Q, V 的映射矩阵做转换,而 K 不做。
        peft_config.enable_lora = [True, False, True]
    
    if peft_config.inference_mode:
        # 如果是推理模式,则将低秩矩阵 A, B 合并到 Linear 层原来的权重 W 中
        peft_config.merge_weights = True
        
    return peft_config

结合 CW 在上面代码中的注释可知,对于 BLOOM,peft_config.target_modules 是 ["query_key_value"],这对应的是其子模块 BloomAttention 中的 Q, K, V 映射矩阵:

class BloomAttention(nn.Module):
    def __init__(self, config: BloomConfig):
        super().__init__()
        
        # 省略部分
        ...

        self.hidden_size = config.hidden_size
        self.num_heads = config.n_head
        self.head_dim = self.hidden_size // self.num_heads
        self.split_size = self.hidden_size
        self.hidden_dropout = config.hidden_dropout

        # 省略部分
        ...

        # ["query_key_value"] 指的就是这个模块
        self.query_key_value = nn.Linear(self.hidden_size, 3 * self.hidden_size, bias=True)
        self.dense = nn.Linear(self.hidden_size, self.hidden_size)
        self.attention_dropout = nn.Dropout(config.attention_dropout)

这个 target_modules 也支持自定义,只要和模型实现里的关键字匹配得上就行。

训练

其实就是常规的训练迭代,只不过这里的特色是利用了 TorchTracemalloc 上下文管理器,它可以方便地计算出 GPU 和 CPU 的消耗(以 MB 计)。

for epoch in range(num_epochs):
        with TorchTracemalloc() as tracemalloc:
            model.train()

            total_loss = 0
            for step, batch in enumerate(tqdm(train_dataloader)):
                # Forward
                outputs = model(**batch)
                loss = outputs.loss
                total_loss += loss.detach().float()

                # Backward
                accelerator.backward(loss)
                optimizer.step()
                lr_scheduler.step()

                optimizer.zero_grad()

                if step % 3 == 0:
                    accelerator.print(f"epoch {epoch + 1}\t step {step + 1}\t loss {loss.item()}")
            
            epoch_loss = total_loss / len(train_dataloader)
            epoch_ppl = torch.exp(epoch_loss)
            accelerator.print(f"[Epoch{epoch + 1}]\t total loss: {epoch_loss}\t perplexity: {epoch_ppl}\n")
                
        # Printing the GPU memory usage details such as allocated memory, peak memory, and total memory usage
        accelerator.print("GPU Memory before entering the train : {}".format(b2mb(tracemalloc.begin)))
        accelerator.print("GPU Memory consumed at the end of the train (end-begin): {}".format(tracemalloc.used))
        accelerator.print("GPU Peak Memory consumed during the train (max-begin): {}".format(tracemalloc.peaked))
        accelerator.print(
            "GPU Total Peak Memory consumed during the train (max): {}\n".format(
                tracemalloc.peaked + b2mb(tracemalloc.begin)
            )
        )

        accelerator.print("CPU Memory before entering the train : {}".format(b2mb(tracemalloc.cpu_begin)))
        accelerator.print("CPU Memory consumed at the end of the train (end-begin): {}".format(tracemalloc.cpu_used))
        accelerator.print("CPU Peak Memory consumed during the train (max-begin): {}".format(tracemalloc.cpu_peaked))
        accelerator.print(
            "CPU Total Peak Memory consumed during the train (max): {}\n".format(
                tracemalloc.cpu_peaked + b2mb(tracemalloc.cpu_begin)
            )
        )

        train_epoch_loss = total_loss / len(eval_dataloader)
        train_ppl = torch.exp(train_epoch_loss)
        accelerator.print(f"{epoch=}: {train_ppl=} {train_epoch_loss=}\n")

顺便秀一波训练期间 GPU 和 CPU 的资源消耗情况(以下单位均为 MB):

图片

某个 epoch 的训练资源消耗

哦?你说你好奇 TorchTracemalloc 是如何实现的?OK,CW 也不吝啬,这就为您献上:

def b2mb(x):
    """ Converting Bytes to Megabytes. """
    return int(x / 2**20)


class TorchTracemalloc:
    """ This context manager is used to track the peak memory usage of the process. """

    def __enter__(self):
        gc.collect()
        torch.cuda.empty_cache()
        # Reset the peak gauge to zero
        torch.cuda.reset_max_memory_allocated()

        # 返回当前的显存占用
        self.begin = torch.cuda.memory_allocated()
        self.process = psutil.Process()

        self.cpu_begin = self.cpu_mem_used()

        self.peak_monitoring = True
        peak_monitor_thread = threading.Thread(target=self.peak_monitor_func)
        peak_monitor_thread.daemon = True
        peak_monitor_thread.start()

        return self

    def cpu_mem_used(self):
        """Get resident set size memory for the current process"""
        return self.process.memory_info().rss

    def peak_monitor_func(self):
        self.cpu_peak = -1

        while True:
            self.cpu_peak = max(self.cpu_mem_used(), self.cpu_peak)

            # can't sleep or will not catch the peak right (this comment is here on purpose)
            # time.sleep(0.001) # 1msec

            if not self.peak_monitoring:
                break

    def __exit__(self, *exc):
        self.peak_monitoring = False

        gc.collect()
        torch.cuda.empty_cache()

        self.end = torch.cuda.memory_allocated()
        self.peak = torch.cuda.max_memory_allocated()

        self.used = b2mb(self.end - self.begin)
        self.peaked = b2mb(self.peak - self.begin)

        self.cpu_end = self.cpu_mem_used()
        self.cpu_used = b2mb(self.cpu_end - self.cpu_begin)
        self.cpu_peaked = b2mb(self.cpu_peak - self.cpu_begin)

评估

评估与训练的玩法基本类似,只不过前向过程需要调用的是模型的 generate() 方法,而非 forward(),前者是 auto-regressive 的方式。

model.eval()

        eval_preds = []
        with TorchTracemalloc() as tracemalloc:
            for batch in tqdm(eval_dataloader):
                batch = {k: v for k, v in batch.items() if k != "labels"}

                with torch.no_grad():
                    # 注:推理过程用的是 auto-regressive 的方式,调用的是模型的 generate() 方法
                    outputs = accelerator.unwrap_model(model).generate(**batch, max_new_tokens=10)
                outputs = accelerator.pad_across_processes(outputs, dim=1, pad_index=tokenizer.pad_token_id)
                
                preds = accelerator.gather(outputs)
                # The part before 'max_length' belongs to prompts
                preds = preds[:, max_length:].detach().cpu().numpy()
                # 'skip_special_tokens=True' will ignore thoses special tokens(e.g. pad token)
                eval_preds.extend(tokenizer.batch_decode(preds, skip_special_tokens=True))

        # Printing the GPU memory usage details such as allocated memory, peak memory, and total memory usage
        accelerator.print("GPU Memory before entering the eval : {}".format(b2mb(tracemalloc.begin)))
        accelerator.print("GPU Memory consumed at the end of the eval(end-begin): {}".format(tracemalloc.used))
        accelerator.print("GPU Peak Memory consumed during the eval(max-begin): {}".format(tracemalloc.peaked))
        accelerator.print(
            "GPU Total Peak Memory consumed during the eval(max): {}\n".format(
                tracemalloc.peaked + b2mb(tracemalloc.begin)
            )
        )

        accelerator.print("CPU Memory before entering the eval : {}".format(b2mb(tracemalloc.cpu_begin)))
        accelerator.print("CPU Memory consumed at the end of the eval(end-begin): {}".format(tracemalloc.cpu_used))
        accelerator.print("CPU Peak Memory consumed during the eval(max-begin): {}".format(tracemalloc.cpu_peaked))
        accelerator.print(
            "CPU Total Peak Memory consumed during the eval(max): {}\n".format(
                tracemalloc.cpu_peaked + b2mb(tracemalloc.cpu_begin)
            )
        )

        assert len(eval_preds) == len(dataset["train"][label_column]), \
            f"{len(eval_preds)} != {len(dataset['train'][label_column])}"

        correct = total = 0        
        for pred, true in zip(eval_preds, dataset["train"][label_column]):
            if pred.strip() == true.strip():
                correct += 1
            total += 1

        accuracy = correct / total * 100
        accelerator.print(f"{accuracy=}\n")
        accelerator.print(f"Pred of the first 10 samples:\n {eval_preds[:10]=}\n")
        accelerator.print(f"Truth of the first 10 samples:\n {dataset['train'][label_column][:10]=}\n")

推理期间,GPU 和 CPU 的消耗情况如下(以下单位均为 MB):

图片

某个 epoch 训练后,推理的资源消耗

End

LoRA 作为当今大模型时代最火的技术之一,是否算得上是微调 LLMs 的正确姿势由你们决定。比起正确与否,合不合适才是最重要的。于我而言,只是觉得它好玩而不是无聊的风格而已~

#Reasoning Model的精巧实现

从ReFT, Kimi K1.5到DeepSeek R1

本文介绍了国内三款推理模型(ReFT、Kimi K1.5 和 DeepSeek R1)的实现方法,指出它们均通过强化学习(RL)在后训练阶段提升模型推理能力,展现了简洁高效的复现方案。文章还对比了早期猜想中使用的 PRM 和 MCTS 方法,强调了 RL 在推理模型中的核心作用。

最近 Reasoning Model(推理模型)异常火爆,Kimi 和 DeepSeek 陆续推出自家的产品 K1.5 和 R1,效果追评甚至超过 o1,也引起了大家的关注,甚至 OpenAI 也慌了。

我也第一时间体验了下产品的效果,推理能力确实惊艳。也非常好奇到底用了什么技术。国内的 LLM 开源玩家算是比较良心的,模型开源的同时,一些技术细节也都发表出来,也能进一步解答大家的好奇心。

过年期间正好忙里偷闲,可以静下来好好整理下这块内容。我个人认为主要有三篇工作比较清晰的讲述了 Reasoning Model 的探索过程,分别是:字节的 ReFT、Kimi 的 K1.5 和 DeepSeek 的 R1。

看完总结下来:大家方法趋同,核心都是在 Post-Training 阶段通过 RL(Reinforcement learning)提升模型的推理能力。这也不禁让人感叹,Reasoning Model 看似 o1 放出的杀手锏,"国产之光"的复现竟可以做到如此精巧、简洁。

在介绍三篇工作前,我也想按自己的理解先来介绍一些早期的 o1 的猜想。也方便与本文要介绍的工作做些对比,也好理解为什么说复现的工作是精巧、简洁的。

01 Reasoning Model的早期猜想

自从 OpenAI 发布 o1 模型后,让我们体验到 LLM 在复杂问题的推理能力上的进步。Reasoning Model(推理模型)的复现之路也成为各家大模型追捧的热点。在猜想和复现的过程中,试图从 OpenAI、Google、微软的近期的研究中找到一些蛛丝马迹,其中主流的一些猜测集中在使用 PRM 和 MCTS 方法,在 Post-training 和 Inference 阶段提升推理性能。

我们简单看下使用 PRM 和 MCTS 方法是如何提升推理性能的?

1.1 PRM增强推理能力

PRM(Process-supervised Reward Model)是 OpenAI 在 Let’s Verify Step by Step [1] 一文中首次提出的概念。与之相对应的是ORM(Outcome-supervised Reward Model)。

PRM 和 ORM 都是奖励模型,两者区别:

  • PRM: 过程奖励模型,是在生成过程中,分步骤,对每一步进行打分,是更细粒度的奖励模型。
  • ORM: 结果奖励模型,是不管推理有多少步,对完整的生成结果进行一次打分,是一个反馈更稀疏的奖励模型。

使用 PRM 可以在 Post-Training 和 Inference 两阶段提升模型的推理性能。

Post-Training 阶段: 在偏好对齐阶段,通过在 RL 过程中增加 PRM,对采样的结果按步骤输出奖励值,为模型提供更精细的监督信号,来指导策略模型优化,提升模型按步推理的能力。

Inference 阶段: 对于一个训练好的 PRM,可以在 Inference 阶段来筛选优质生成结果。具体来说。对 generator 模型做 N 次采样(如 Beam Search 方法等),并通过 PRM 对每个采样的每步推理进行打分,最终拟合一个整体过程打分,并选取打分最高的结果作为最终的答案。

这里我们假设基础的 generator 模型在 pretrain 后做了指令微调(SFT),有基本的推理能力(能按步骤生成答案,但推理准确性可能较差)。

1.2 MCTS增强推理能力

MCTS(Monte Carlo Tree Search)是强化学习领域提出的方法,通过采样方式预估当前动作或状态的价值。具体操作步骤:使用已有的策略与环境做仿真交互,进行多次 rollout 采样,最终构成了一个从当前节点出发的一颗 Tree(每个 rollout 表示从当前节点到最终结束状态的多次与环境仿真交互的过程)。

这颗 Tree 的所有叶子节点都是结束状态,结束状态是能量化收益的(量化收益的方法:比如方法 1:答案错误收益-1,答案正确收益 +3;再比如方法 2:叶子节点的收益是到达叶子节点路径数/总路径数的概率,这是一种根据投票机制预估的价值,越多路径到达叶子节点,说明这个叶子节点越置信,那么这个叶子节点就有更高的奖励)。

一颗 Tree 的叶子节点有了奖励值,就可通过反向传播,计算每个中间节点的奖励值,最终计算出整个 Tree 所有节点的奖励值。MCTS 一次 rollout 包括:select,expand,simulate,backprop 四个步骤。我们展开描述下四个步骤的具体工作。

Sample(采样):选择一个未被探索的节点,在 Reasoning Model 中节点表示一个打了特定 tag 的推理步骤(如:planning 节点,reflection 节点等)。初始情况,Tree 只有一个表示原始问题的节点(如下图1的  )。

expand(扩展):从未被选择的节点出发(如初始从  ),展开所有可能的子节点(如下图 1 中的  )。当然对于文本生成模型不可能穷举所有的子节点,需要设置个最大生成次数,在有限生成次数内的所有的不同的输出,认为是子节点的集合。

simulate(模拟):从展开的子节点里,再随机选择一个节点,再展开它的子节点,重复做 expand 过程。直到最终到达叶子节点(生成答案)。当然这里也会控制最大树深度,模拟会进行 N 次。

backprop(回传):通过多次模拟我们得到了一个从根节点(原始问题)到叶子节点(最终生成答案)的 Tree,如下图 1 所示。

我们通过计算(从当前节点出发到正确答案的路径数/从当前节点出发总路径数)的比值作为节点的奖励值。这个奖励值隐含表示的是从当前节点出发能得到正确答案的潜在的可能性。

比如以  节点为例,从  出发共有 4 条路径,分别是:$,,,其中有条路径都能走到正确答案。所以S_{2,1}的奖励值为1 / 2$ 。我们通过从后往前回溯,能计算出 Tree 中所有节点的奖励值。

▲ 图1. MCTS生成Search Tree过程

▲ 图1. MCTS生成Search Tree过程

使用 MCTS 提升模型的推理能力,也可在 Post-Training 和 inference 两阶段来实现。

Post-Traing 阶段:对于每个 problem 通过上述方法构造一个搜索 Tree,然后进行 Tree 的游走遍历采样,再用采样的样本 SFT 或 RL 训练模型。

Inference阶段:在推理阶段,也是对一个 problem 探索多节点构造一颗搜索 Tree,对于到达正确答案的路径,根据节点路径的置信度打分,贪心选取最优路径作为最终的推理结果。

使用 PRM 和 MCTS 训练推理模型的大致框图,如图 2 所示,主要是在 Post Training 和 Inference 阶段使用来提升模型的推理能力。

▲ 图2. 基于PRM和MCTS的推理模型

▲ 图2. 基于PRM和MCTS的推理模型

注:这里对 PRM 和 MCTS 在 Reasoning Model 上的使用,是个人参考 paper 和网上的一些资料的总结,可能有不准确的地方。如有错误,欢迎指正

1.3 PRM和MCTS方法存在的问题

PRM 和 MCTS 的方法理论上都有自身的优势。对于复杂的推理过程,PRM 可以按步骤做细粒度的监督,MCTS 可以自动探索解空间。两者配合可以在探索(Exploration)和利用(Exploitation)上做平衡,以提升复杂问题的推理能力。

但在实践中这两种方法存在明显的局限性:

PRM 的局限: 对于一般的推理任务,很难定义一个精细的执行步骤。对于语言模型判断一个中间步骤是否正确是一项艰巨的任务。另外对于 PRM 训练样本的质量要求较高,使用模型进行自动标注可能无法取得令人满意的结果,而手动标注则不利于扩展规模。

一旦引入基于模型的 PRM,就不可避免地会导致 Reward Hacking 问题。此外从头训练奖励一个奖励模型需要额外的训练资源,也使得整个模型训练流程复杂化。

MCTS 的局限: MCTS 方法核心是需要建搜索 Tree,在生成模型任务中,需要提前定义好 Tree 的节点空间(如 Planning,Reflection 等类型节点)。

这个定义是非常难的:因为一方面生成模型面向的场景是多领域、多任务的,很难定义一个有限的节点集合来覆盖所有任务,而且就算提前定义好了一个集合,随着任务的新增,整个集合又要更新,模型要重新训练,这样就增加了维护和迭代的复杂性。

另一方面 token 生成的搜索空间是指数级增长,在全空间做搜索是不可行的。为了解决搜索空间爆炸的问题,通常会做节点扩展限制的搜索,这样可能导致陷入局部最优解。另外 MCTS 方法一般依赖一个价值度量模型(如上述的 PRM)来衡量节点的价值,引入价值模型也进一步增加了模型训练的复杂度。

PRM 和 MCTS 方法,都会引入模型训练和推理的复杂性。在实际的复现 Reasoning Model 工作中,大家并没有应用这些技术,而是不约而同的选择了更轻量、更直接的方案。下面我们来看看国内三篇有价值的 Reasoning Model 的技术报告。

02 Reasoning Model三篇有价值的工作

三篇工作附原文链接,如下:

字节ReFT:​https://arxiv.org/pdf/2401.08967​KIMI K1.5:​ https://arxiv.org/pdf/2501.12599​DeepSeek-R1: https://arxiv.org/pdf/2501.12948

注1:这三篇 Paper 的核心的工作都是卷 RL 阶段来提升模型的推理能力。所以要更好的理解上述工作,要具备基本的 RL 的基础知识,本人之前整理过 RL 核心算法(PPO 训练的源码阶段),如需要提前了解,可以适当参考:

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

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

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

注2:本文并不是对三篇工作做从头到尾的翻译,二是主要讲解核心的实现思路,不会对效果、实验等细节展开讲解。如需要了解更多细节,请参考原文阅读。

下面我们来看看 ReFT、K1.5,R1 的核心实现。

03 ReFT

ReFT (Reinforced Fine-Turning) 是字节 24 年初的一篇工作,ReFT 方法包括两个阶段:SFT 冷启阶段和强化学习训练阶段。

3.1 ReFT SFT冷启阶段(warm-up stage)

SFT 阶段通过构造一批带推理过程的数据,来精调 Base LLM 模型,这个阶段主要是让模型有基本的 CoT 推理能力。ReFT 的做法也非常简单,就是用一批开源的数据,通过 Prompt 工程来发压 GPT-3.5t 来收集样本,再 SFT 微调自己的小模型。

具体实现细节上,样本数据主要来源于 GSM8K,SVAMP 和 MathQA 三个数据集,通过 GPT-3.5-turbo few-shot prompting 的方法收集的训练数据。数据集有两种推理格式:N-CoT,P-CoT。

作者在实验中并没有人工标注训练数据,而是完全通过 self-instruct 方式,基于 GPT-3.5dump 的训练样本。

▲ 图3. ReFT的SFT阶段样本样例数据

▲ 图3. ReFT的SFT阶段样本样例数据

通过上述方法,收集了 SFT 数据集: 62K 的指令集样本,分布如下:

▲ 图4. ReFT的SFT阶段样本分布

▲ 图4. ReFT的SFT阶段样本分布

模型训练: 这个阶段模型训练就是常规的 SFT,训练了 40 个 Epoch(作者说这个训练步数是保证模型收敛的一个比较大的设置了)。

3.2 强化学习训练阶段

ReFT 使用 PPO 的方法做强化学习,我们先回顾下 PPO 的具体方法,如下图 5 所示,参考:

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

▲ 图5. PPO的训练流程图

▲ 图5. PPO的训练流程图

3.2.1 PPO训练四阶段

阶段1: 先基于Pretrain model,训练一个精调模型(SFT Model)和一个奖励模型(Reward Model)。Reward model 一般可以基于SFT model 热启或基于 Pretrain model 热启训练。

阶段2: 模型初始化,PPO 过程,在线同时有四个模型,分别为:

  • Actor Model: 是我们要优化学习的策略模型,同时用于做数据采样,用 SFT Model 热启;
  • Reference Model: 是为了控制 Actor 模型学习的分布与原始模型的分布相差不会太远的参考模型,通过 loss 中增加 KL 项,来达到这个效果。训练过程中该模型不更新;
  • Critic Model: 是对每个状态做打分的价值模型,衡量当前 token 到生成结束的整体价值打分,一般可用 Reward Model 热启;
  • Reward Model: 这里是 ORM(Outcome Reward Model),对整个生成的结果打分,是事先训练好的 Reward Model。训练过程中该模型不更新。

阶段3: 采样 Experience 数据,流程为:

  • 首先采样一批随机指令集(Prompt);
  • 调用 Actor 模型的 generate() 方法,采样 1 条或多条结果(sequences);
  • 四个模型一起参与组装Experience的多个Tensor域,用于后续模型训练。

阶段4: 用 Experience 样本,训练 Actor Model 和 Critic Model。

重复 3-4 阶段,循环采样 Experience 数据-> 模型训练,直到 loss 收敛。

由上述过程可知,做 PPO 训练,我们需要预先准备两个训练好的模型:Base LLM Generator 和 Reward Model。

ReFT 是如何准备这两个模型的?首先 Base LLM Generator 就是 warm-up 阶段 SFT 的模型,对于 Reward Model 本文作者并没有训练一个模型,而是通过定义一个规则函数,设置了一个 Rule-Base RM,下面我们来看看细节。

3.2.2 Reward Model的设计:Rule-Base RM

ReFT 主要是针对数学场景富集样本来训练 Reasoning Model,数学计算的问题是可简单判断答案正确与否的。所以作者设置了一个判别函数作为奖励。具体如下:

图片

这里的 Reward Model 是个 ORM,对于模型生成的中间状态,奖励都为 0; 对于终止状态判别有 3 种情况:

  1. 如果通过规则能抽取出正确答案则奖励为 1;
  2. 如果抽取不出正确答案,但能解析出一个结果,奖励值设置为 0.1;
  3. 如果最终结果无法解析,奖励制设置为 0。

这里对错误答案的推理路径,设置了一个弱奖励的机制(赋值 0.1),主要是为了减少奖励反馈稀疏的问题。如果能解析出一个答案,证明生成过程是在做一个推理的过程,虽然答案错了,但推理的执行过程对模型是有帮助的,所以设置个小的奖励值,激励模型按推理逻辑输出结果。

3.2.3 Critic Model的设计

Critic Model 是对每个状态做打分的价值模型,衡量当前 token 到生成结束的整体价值打分,模型的结构一般跟 Reward Model 一致,通常也会用 Reward 热启。但本文中,并没有 Reward Model,那么 Critic Model 如何设计的呢?

作者对 Critic Model 的设计还是遵从 Reward Model 的设计方式,在 Base Model 之上,增加一个回归头(regresion head)对每个生成的状态进行打分。ReFT 也做了些优化,为了减少训练时模型的计算量和显存占用,Critic Model 的参数与 Actor Model (Policy Model) 的参数共享。如下图所示:

图片

3.2.4 self-training实验设置

为了证明 ReFT 的有效性,作者也实现两个 self-training 的方法。这两个方法虽然被证明没有 ReFT 效果好,但这两种方法实际工作,是经常被使用的方法。所以也详细展开介绍下。有助于在实际工作中,根据自己的业务特点,选择合适的优化方案。

所谓 self-training 就是不采用额外的人工标注的数据集,而是通过模型自己产出的数据再来迭代优化模型的方法。作者设置的两个 self-training 的实验如下:

  1. Offline Self-Training (Offline- ST):用初始的 SFT 的模型作为 generator,对于训练集的每个问题通过生成多次来采集多个样本。然后将采集的样本的答案跟 ground Truth 对比,筛选答案正确的样本,然后跟原始样本混合,再训练初始模型。**这就是我们通常使用的拒绝采样训练模型的方法。 **
  2. Onlline Self-Training (Online-ST):跟 ReFT 类似,用 SFT 的模型 warm-up,之后在每个训练步,我们通过当前版本的模型做即时采样,然后保留答案正确的 Sample,再通过 SFT 训练当前版本模型,得到下一个版本的模型。重复上面的迭代过程,直到达到预期的训练步数。

为了清晰对比 Offline-ST,Online-ST 和 ReFT,如下图所示:

图片

相较于上面两种 Self-Training,**ReFT 优势主要有如下两方面: **

样本充分利用: 在 ReST 中是基于 RL 的优化过程,对于采样的正负样本都参与模型训练。而上述两种 Offline-ST 和 Online-ST 两种方法都是基于 SFT 训练模型,SFT 是只能使用正样本做模型训练的,样本使用上是不够充分的。

模型训练稳定: Offline-ST 采样模型直接使用初始模型,而 Online-ST 采样模型是随着 Policy 模型的更新实时更新的,导致 Online 的方式可能使模型的分布大大偏离原始模型的分布。导致模型在采样的数据集上 overfitting,最终实际应用中效果达不到预期。而 ReST 采用 PPO 的方法训练,模型更新过程通过 KL 限制 Policy 模型与初始 Model 分布不会偏离太远。保证了模型的效果稳定。

上述的 Self-Training 方法,在实际的工作中,都是值得借鉴的,虽然 ReFT 理论上效果更好,但基于 PPO 的训练更复杂。可以结合自己业务的特点,考虑合适的方法驱动提升模型效果。

3.3 总结ReFT

ReFT 核心使用 PPO 算法来提升 Reasoning 的能力,相对于传统的 PPO 算法,主要做了两方面优化:

1)简化 Reward Model,使用的是 Rule-Base Reward 而非训练一个模型。

2)Critic Model 参数与 Policy Model 共享,压缩训练阶段模型的参数的存储空间,也进一步降低模型训练的复杂度。

04 Kimi-K1.5

Kimi K1.5 是个多模态的 Reasoning Model,论文中对模型训练过程描述的比较详细。主要包括:预训练、监督微调和强化学习(RL)三个阶段,其中 RL 阶段仍然是 Kimi 重点优化的阶段。我们先来快速看看预训练和 SFT 阶段的一些细节,之后再重点看下 RL 阶段。

4.1 预训练&监督微调

4.1.1 预训练

预训练阶段比较常规,数据集包括文本和图像多领域多模态高质量数据集,训练包括三个阶段:

  1. Vision-language 预训练阶段:首先基于文本语料训练语言模型,然后进行多模态融合训练;
  2. 退火阶段:筛选公开的和合成的高质量数据来进一步提升模型的基础能力,特别富集了针对推理和知识型任务的高质量数据集,做模型训练;
  3. Long-context 训练阶段:这也是当前模型扩展长文能力的主要方法,在预训练的最后阶段,通过 feed 长文数据集提升长文理解和生成能力,Kimi 最终将长文能力扩展到 128K。

对于 SFT 精调阶段,Kimi 做了两个阶段,分别是覆盖通用能力的基础监督微调强化推理能力的 long-CoT 的监督微调

4.1.2 基础监督微调(SFT)

Kimi 富集了 200 万的监督微调数据集,其中包括 100 万的文本任务数据集(包括问答,编程,写作等)和 100 万的文本-视觉数据集(包括 OCR,视觉推理等),样本数据主要通过人工标注和拒绝采样的方式富集。模型训练阶段首先以 32K 的序列长度训练一个 epoch,然后又扩展到 128K 继续训练一个 epoch。

4.1.3 长思维链(long-CoT)监督微调

这一阶段重点通过 Prompt 方式生成长思维链的推理路径的小规模数据集,来做 SFT 训练。目的是让模型能够先内置一些推理的知识,学会基本的 long-CoT 的生成模式,能对推理过程的必要动作如:planning,evaluation,reflection,exploration 等步骤做正确的、连贯的响应。

注:该步骤的数据集是基于 RL 阶段的数据集做后处理得到的,后面 RL 阶段会详细讲述数据集的富集过程。

经过上述几步,模型除了具备了通用的能力,同时也有了基础的推理能力,下面就是 KIMI 重点优化的 RL 阶段,进一步提升模型的推理性能。

4.2 RL强化模型推理能力

4.2.1 策略优化算法

Kimi 的 RL 训练过程,并没有采用 PPO 的方法,而是采用了一种更轻量的类 Policy Gradient 的方法。具体方法如下:

首先定义一个初始的目标:找到最优的,使得输出的结果让 Reward Model 获得最大期望奖励,如下公式:

图片

其中:

  •  是要优化的策略, 是策略的参数;
  •  表示问题, 表示问题  对应的答案;
  •  是  预估的答案, 是  预估的推理的中间步骤;
  •  是针对问题  ,参照 ground truth  ,评估模型输出的结果  的奖励值,  通过奖励模型预估或规则函数计算得到。

接着,作者为了模型训练更加稳定,类似 PPO,在公式(1)基础上,增加了 KL 的正则项,来限制优化的模型与初始模型分布相差不要太远。

其中  是控制正则项的大于 0 的参数。

对于公式(2),可以进一步做推导,当取最大值时,可以推导出一个解析解  ,如下:

图片

对于上述公式,两边同时取 可以转化为:

图片

其中: 是一个归一化的加和项。

有了上面的转换,进一步我们通过最小二乘法 的回归损失来重写loss(将真实解  替换成待优化函数  如下:

对于实际采样的一批数据: 项可近似通过计算经验均值  得到,如下推导:

图片

最终我们用  替换  项,loss 函数可重写为公式(4)如下。这个 Loss 是 Kimi 最终使用的版本。

图片

这是一个 Policy Gradient 方法的变体版本,该方法移除了 Critic 网络计算 Advantage 的过程。减少的策略优化的复杂度。

注:上述公式省略了严谨的数学推导过程,只是从直观上给出了必要的公式结果。其实这里我们重点要理解的并不是数学上的证明,而是重点要 get 到 Kimi 做 RL 过程用的是一个类 Policy Gradient 的简化版本,在计算 loss 的时候没有依赖 Critic Model。对公式感兴趣同学,可以自行推导。

4.2.2 Reward Model设计

K1.5 中对于 reward 的设计还是比较精细的。对可直接规则判别对错的问题,用 Rule-Base Reward 简化打分过程。对于开放问答类问题,用 Model-Base Reward。同时对于超长的 CoT 过程做了惩罚处理。

具体几种 Reward 设计如下:

Rule-Base Reward:对于能简单判断对错的数学问题,直接通过规则函数来计算 Reward,对于编程问题通过评估是否通过测试用例来直接判断 Reward 打分。

Model-Base Reward:对于开放的问答类问题,训练一个 Reward Model,通过模型打分。

Length Penalty Reward:Kimi 做了一个 warmup 的设置,在训练初始阶段不增加这个惩罚因子,让模型能学习生成 long CoT,在训练后面阶段,为了防止生成过长的 CoT,增加了生成长度的惩罚因子,鼓励模型进行适当思考,而不是生成过于冗长的内容。

4.2.3 RL Prompt和采样策略的精心设计

kimi 通过实验表明 ,强化学习的 Prompt 的质量和多样性对 RL 学习是至关重要的,精心设计的 Prompt 不仅能引导模型稳健推理,还能降低模型 Reward hacking 和过拟合。好的数据集特点:领域分布多样,难度分布多样,可准确评估性。

为了确保收集到好的数据集,作者做了一系列数据筛选策略:

多样性筛选策略:通过开发一些过滤器和分类器,选择有丰富推理路径且易于评估的问题。同时通过分类器,平衡不同领域的数据分布。

难度分级筛选策略:使用一个相对较高的采样温度,让 SFT 模型生成十次答案,然后计算通过率,并将其作为 Prompt 难度的指标。通过率越低,表示 Prompt 的难度越高。利用这种方法,可以预先过滤掉大多数简单样本,并在强化学习训练期间根据问题难度探索不同的采样策略。

筛选可准确评估的问题:为了避免 Reward Hacking,确保每个 Prompt 的推理过程和答案都能被准确验证,需要排除一些容易作弊的问题。作者过滤掉了一些依赖复杂推理且容易作弊的问题,包括选择题,判断题和基于证明的问题。同时对于通过简单的 Prompt 多次采样能有概率回答正确问题做了过滤。

Kimi 也对 RL 训练过程的采样策略做了精心设计,主要通过两个方法来提高训练效率:

课程采样(Curriculum Sampling):作者设计先从训练较简单的任务开始,逐渐过渡到更具挑战性的任务。主要考虑是初始阶段,强化学习模型性能有限,将有限的计算预算花在非常困难的问题上往往只能产生很少的正确样本,从而导致训练效率降低。

优先采样(Prioritized Sampling):关注模型表现不佳的问题。跟踪每个问题的成功率,对成功率低的问题进行更大概率采样,引导模型将精力集中在最薄弱的环节,从而实现更快的学习和更好的整体性能。

4.3 总结Kimi K1.5

Kimi 打磨 K1.5 的过程非常精细,从报告中可详细了解从 PreTrain,到 SFT 精调,再到 RL 阶段的每一步的细节。

核心工作还是集中在 RL 阶段。对于 RL Kimi 采用了一种类 Policy Gradient 的方法,模型训练裁剪掉了 Critic Model 以减少训练的复杂度;对于 Reward 设计比较精细,对于不同问题,不同训练阶段都有细致调整 Reward 策略。同时对于采样做了课程采样和优先采样的精心设计,来提升训练效率。

05 DeepSeek-R1

DeepSeek 做了两阶段探索:DeepSeek-R1-Zero 和 DeepSeek-R1

DeepSeek-R1-Zero: 是个纯做 RL 的阶段,验证 RL 对推理性能的提升的有效性。

DeepSeek-R1: 由于 DeepSeek-R1-Zero 训练的模型可读性是比较差的,通常有多语言混合输出的问题,通用能力也较差。为了解决这些问题,并产出一个实际可用的模型。DeepSeek 在 R1 阶段,做了多阶段的模型训练,并通过混合多任务数据,同时提升模型的通用能力和复杂问题推理能力。

我们先看看看硬卷 RL 的 R1-Zero 的一些核心细节:

5.1 DeepSeek-R1-Zero

这是一个纯 RL 来探索模型推理能力的过程,具体 RL 优化过程和奖励模型设置如下:

5.1.1 基于GRPO的RL优化过程

DeepSeek 一如既往地使用自研的 GRPO (Group Relative Policy Optimization) 来做 RL 阶段的训练。GRPO 原理比较简单,相对于 PPO 同时在线四个模型,GRPO 做了简化,裁剪掉了 Critic Model。GRPO 中计算Advantage 的过程,是一种基于组内相对优势的计算方式。

具体来说,对于每个问题  ,通过 Reference Model 采样一组结果  ,再通过 Reward Model 计算一组结果的打分  。

GRPO 计算每个采样  的 Advantage  就是对一组 Reward Score 做规范化处理(对每个采样的奖励  减去一组值的均值,再比上一组值的方差),如下公式:

图片

GRPO 与 PPO 的对比如下图所示。在 GRPO 训练过程中在线会有三个模型,其中 Reference Model 和 Reward Model 是 Frozen 的,最终只有 Policy Model 迭代更新的。

图片

5.1.2 Reward Model的设计:Rule-Base RM

R1-Zero 阶段只关注数学、程序类推理问题,都是能简单通过规则判别答案对错的,所以奖励模型采用的是纯 Rule-Base 的设计,主要包括 2 类 Reward:

正确性校验 Reward(Accuracy rewards): 数学问题通过简单的规则抽取答案与 ground truth 对比校验。对于程序题,通过编译生成的程序,校验是否能通过测试用例,产生一致的答案。

格式校验 Reward(Format rewards): 校验是否 thought 内容是包含在 ‘’ 和 ‘’tags 之间。

5.1.3 模型训练Prompt模板

DeepSeek 训练 Reasoning 的指令模板相当简单,如下图,除了做些角色和格式描述,对于 Reasoning 的能力描述没有做过多限制,主要也是希望能激发模型的自主能力,防止过多人为设计引入 Bias,干扰 RL 阶段模型的推理路径。(这种简单的指令集设计,确实也需要有 DeepSeek-V3 这样强大的基模能力才行)。

▲ R1-Zero Prompt模板

▲ R1-Zero Prompt模板

DeepSeek-R1-Zero 的核心过程就是如此,论文中着实没有暴露更多细节。

5.2 DeepSeek-R1

DeepSeek-R1-Zero 是个纯 RL 驱动模型训练过程,问题推理能力显著提升,但模型的通用能力有很多瑕疵,比如会输出可读性非常差的混合语言的结果。

为了进一步提升模型的可用性,在 R1-Zero 基础上,DeepSeek 又做了多阶段细致的优化过程,即 DeepSeek-R1。主要的优化包括四个阶段:SFT -> RL -> 增强 SFT -> 增强 RL(有点左脚踩右脚,然后直接起飞的架势)。

阶段 1:SFT Cold Start 阶段

SFT 的样本通过两种方式获取:1)拒绝采样:通过 few-shot prompt 方式,基于已有的生成模型直接生成,来富集 long-CoT 的样本;2)人工标注:获取  R1-Zero 可读的样本,然后通过人工方式精编样本。

在冷启阶段,对样本也设计了可读性更好的 pattern,通过 |special_token| 将推理过程圈定起来,为了让人更便捷阅读,对样本做了 summary 处理。如下:

|special_token|<reasoning_process>|special_token|<summary>

最终产出数千条样本,然后训练 DeepSeek-V3-Base,作为 RL 阶段的初始 Policy Model。

阶段 2:Reasoning-oriented RL 阶段

这个阶段基本就是 R1-Zero 的过程,为了解决多语言混合输出的问题,在训练 R1 过程,对 Reward Model 增加了语言一致性的奖励设置。

具体来说,增加了 Language Consistency Reward,它通过计算推理 CoT 过程的字符与目标语言一致的字符比例,来作为奖励打分,一致率越高奖励越高。这个奖励对模型性能有轻微的影响,但趋向于更便于人可读性的优化,是一个有用的偏好奖励的设置。

阶段 3:增强 SFT 阶段(Rejection Sampling and Supervised Fine-Tuning)

这个阶段主要是提升模型的通用能力,包括:创作,角色扮演和其他一些通用任务。对于 Reasoning  和  Non-Reasoning 的样本通过不同方式富集:

Reasoning data:通过拒绝采样获取。这个阶段引入了一批新的 Prompt 数据,基于上一步得到的模型,生成多结果,最终通过 Rule-Base Reward 和强大的 DeepSeek-V3 作为裁判模型,精选样本。同时根据一些规则,对于混合语言的,冗长的推理 CoT 样本做规则过滤。最终筛选了 600K 的 Reasoning 样本。

Non-Reasoning data:引入训练 DeepSeek-V3 的通用高质量 SFT 数据,包括: 创作、事实问答、自我认知和翻译。样本处理上,通过 prompt 方式调用 DeepSeek-V3,在回答问题前,先生成一个思维链,保证与 Reasoning Data 的样本格式一致。最后收集了 200K 的 Non-Reasoning 样本。

最终用这 800K 样本 SFT DeepSeek-V3-base 模型,产出了 Reasoning 和非 Reasoning 能力兼顾的新的模型(注:这里并没有基于上个阶段的模型继续微调,而是在基模上微调的,主要是为了保证更好的通用能力,然后进一步通过过滤后的样本继续微调,保留refine后的推理能力)。

阶段 4:增强 RL 阶段(Reinforcement Learning for all Scenarios)

这阶段其实跟 K1.5 的工作差不多,对于多样的数据,采用多种奖励方式来做精细化的奖励反馈。复用了 R1-Zero 和 DeepSeek-V3 的 Reward Model 的设置。其他并没有太多可关注的地方。

5.3 DeepSeek-R1 总结

R1-Zero 采用了非常激进的纯 RL 方式来提升模型的问题推理能力,虽然模型有瑕疵,但 R1-Zero 的做法把 Reasoning Model 的探索推向了一个极简方式,我个人觉得是非常有价值的尝试。

但话又说回来,之所以 DeepSeek 能这么玩,还是要依赖于他自己有较强大的 DeepSeek-V3 支撑,换做其他玩家,未必可行。如果我们再详细对比 R1 和 K1.5 训练过程,都是 SFT 冷启,在做 RL 或 SFT 增强训练,基本的做法是趋同的。而显然 Kimi 的报告要更详细,DeepSeek 的报告感觉是有所保留的。

06 总结

本文主要介绍了「国产之光」的三篇做 Reasoning Model 的工作,对于复杂问题的推理能力的探索,大家都不约而同的采用了精巧、简洁的复现方案。通过设定清晰的目标,减少过多的人为设定,基于 RL 端到端的自驱探索能力上限。我觉得本文最终可以用一句话总结:Reasoning Model,RL is all your need!

参考文献

[1] https://arxiv.org/pdf/2305.20050

#微软20年精炼,全球首个拓扑量子芯片出炉

巴掌大芯片碾压全球超算

就在刚刚,微软全球首个拓扑架构量子芯片Majorana 1横空出世,一枚芯片将集成百万量子比特,胜过地球上全部超算,从此,物质有了第四种形态,量子计算机可能会在几年内实现!微软苦研20年的成果,从此将彻底改变世界。

物质有三态,固液气。但从今天起,物理学教科书要彻底被改写了!

凌晨,微软团队重磅发布全球首个基于拓扑架构的量子芯片——Majorana 1。

这块巴掌大的芯片,未来将容纳一百万个量子比特。

它不仅仅是一块芯片,更是一种超越固态、液态、气态的全新物质形态,标志着量子计算迈入一个全新的时代。

最新研究已于19日发表在了顶刊Nature上。

论文地址:https://www.nature.com/articles/s41586-024-08445-2

与传统量子计算相比,Majorana 1具有更高容错能力、更强的抗干扰性,可以在复杂环境中运行。

拓扑导体制造的量子比特,更快速、更可靠、更小尺寸的优势,每个量子比特尺寸仅为0.01mm。

这意味着,未来就可以轻易打造出拥有100万个量子比特的处理器。

纳德拉表示,「一块可以轻松握在掌心的芯片,能够解决当今地球上所有超级计算都无法突破的难题」。

就比如,分解塑料、设计自我修复的材料、药物发现等等化学、生命科学、生物学中的问题,未来都能被攻克。

纳德拉预测,如果将AI与量子计算结合,量子计算可以用来生成合成数据;然后,AI可以利用这些数据训练更好的模型,应用于化学、物理等复杂领域

更值得一提的是,开发出全新的拓扑量子比特,是微软团队近20年磨一剑的成果。

上个月,老黄曾预言量子计算还有20年才能实用,如今这一观点要被颠覆了。

Majorana 1横空出世,意味着人类能够在几年内,而不是几十年后,打造出具有现实意义的量子计算机。

纳德拉激动表示:这不是在炒作技术,而是在创造真正能服务于世界的科技。

上下滑动查看

就连马斯克也激动转发,量子计算的突破越来越多!

网友惊呼,原来物质世界还有另一种状态。

量子时代,需要全新「晶体管」

Majorana 1量子芯片,是全球首款采用新型「拓扑计算核心」(Topological Core)架构的量子芯片(QPU)。

它采用了全球首创的「拓扑导体」。

拓扑导体(也称拓扑超导体)是一种特殊类别的材料,能够创造出全新的物质状态——这既不是固态、液态或气态,而是拓扑量子态。

这种突破性材料,能够观察和控制马约拉纳粒子(Majorana particles),从而制造出更可靠、更具扩展性的量子比特——量子计算机的基本构建单元。

研究人员利用这一特性,能够产生更稳定的量子比特。

这种量子比特不仅运算速度快、微型化、数字化可控,而且无需像现有方案那样作出巨大的取舍。

正如半导体的发明,使当今的智能手机、计算机和电子设备成为可能一样,拓扑导体加持的新型量子芯片,将会开辟全新的应用途径。

量子时代,需要全新的晶体管。

用于开发Majorana 1量子芯片的这种新架构,为在一个可以放入手掌的单个芯片上集成百万量子比特提供了明确的技术路径。

需要明确的是,微软已经在一枚可扩展至百万量子比特规模的芯片上,成功集成了8个拓扑量子比特。

从单个量子比特到自动纠错的量子计算阵列,是一条实现可靠量子计算的必经之路

100万量子比特,是量子计算机实现变革性实际应用所需的关键阈值。

比如,将微塑料分解为无害的副产品,或者发明应用于建筑、制造或医疗保健领域的自修复材料。

即便将当今世界上所有计算机的算力集中在一起,也无法完成未来一台百万量子比特计算机所能完成的任务。

也就是说,这一系统扩展至百万量子比特规模后,在解决最复杂的工业和社会问题上,也可以得心应手。

微软预计,这一突破将使量子计算机能在未来几年内,而非几十年后,解决具有实际意义的工业级问题。

要知道,商业上重要的应用需要数万亿吨的操作,此前依赖每个量子微调模拟控制的方法,几乎不可能完成。但如今,不可能已经变为可能。

目前,微软内部正在构建世界首个基于拓扑量子比特的容错原型机(FTP),计划将在未来几年内问世。它也是美国DARPA「实用规模量子计算未充分探索系统」(US2QC)项目最后阶段的一部分。

搭载在Majorana 1上的全球首个拓扑计算核心的可靠性与生俱来,这是因为它在硬件层面上集成了容错能力,因而稳定性更高。

20多年前,微软决定攻克拓扑量子比特设计这个项目,充满挑战,但潜力巨大。

他们采取的这种独特方法,面临着陡峭的学习曲线,需要前所未有的科学和工程突破。然而,也只有这种方法,是通往可扩展、可控量子比特最有希望的道路。

今日的重大进展证明,微软多年前的战略选择没有错!

微软研究员Matthias Troyer表示:「从一开始,我们就希望打造一台能产生商业影响的量子计算机,而不仅仅是引领思想潮流。我们知道,我们需要一种全新的量子比特,我们必须实现规模化」

拓扑导体,量子计算新基石

世界首个拓扑导体,一种此前仅存在于理论中的新物质状态,如何打造的?

据介绍,一种由砷化铟(一种半导体)和铝(一种超导体)构成的全新的材料体系,即栅控器件(gate-defined devices),才得以实现。

其中大部分材料,都是由微软通过逐个「原子级精度」设计和制造。

当冷却到接近绝对零度并通过磁场调节时,这些器件形成拓扑超导纳米线,在纳米线的两端具有「马约拉纳零模」(Majorana Zero Modes,简称MZMs)。

接下来的研究目标是,诱导一种称为马约拉纳(Majorana)的新型量子粒子产生。

近一个世纪以来,这些准粒子仅存在于教科书中。现在,人类可以在拓扑导体中按需创建、控制它们。

MZMs是量子比特的基本构建块,通过「宇称」(parity),即纳米线中包含偶数还是奇数个电子来存储量子信息。

在传统超导体中,电子结合成库珀对(Cooper pair)并无阻力地移动。任何未配对的电子都可以被检测到,因为它的存在需要额外的能量。

而拓扑导体则不同:在这里,一个未配对的电子在一对MZMs之间共享,使其对环境不可见。

正是这种独特的特性,保护了量子信息。

虽然这使拓扑导体成为量子比特的理想候选者,但也带来了一个挑战:我们如何读取被如此好地隐藏起来的量子信息?

我们如何区分,比如说,1,000,000,000和1,000,000,001个电子?

图1:拓扑量子比特状态的读取

解决这个测量挑战的方案如下:

  • 使用数字开关将纳米线的两端连接到量子点。
  • 这种连接增加了量子点存储电荷的能力。关键是,具体增加多少取决于纳米线的「宇称」状态。
  • 使用微波来测量这种变化。量子点存储电荷的能力决定了微波如何从量子点反射。因此,返回的微波携带着纳米线量子态的印记。

实验结果显示,微软设计的新器件使这些变化足够大,可以在单次读取中可靠地测量,并且展现出令人印象深刻的稳定性。

目前,初始测量的错误率为1%,但微软表示,已经找到了显著降低这一比率的明确途径。

外部能量(如电磁辐射)可能会打断「库珀对」,产生未配对电子,这可能使量子比特的状态从偶数「宇称」翻转到奇数「宇称」。

然而,最新结果表明这种情况很少发生,平均每毫秒只发生一次。

这表明包裹处理器的屏蔽层能有效地阻挡这种辐射吗,目前,微软正在探索进一步降低这种情况发生频率的方法。​

超精确读出技术,数字化可控

如果希望应用更有商业价值,就需要在百万量子比特上执行万亿量级的运算,但现有的方法都是依赖于对每个量子比特进行精细的模拟控制,因而难以实现。

但现在有了微软的新方法,就可以直接实现量子比特的数字化控制了!

这就从根本上重新定义了量子计算的运作方式,而且将其极大简化。

这种读出技术实现了一种从根本上不同的量子计算方法,即利用测量来执行计算。

传统量子计算需要通过精确角度旋转量子态,这就要求为每个量子比特定制复杂的模拟控制信号。这使量子纠错(QEC)变得复杂,因为它必须依赖这些相同的敏感操作来检测和纠正错误。

而微软基于测量的方法大大简化了量子纠错。

通过测量来执行错误纠正,这些测量由简单的数字脉冲激活,用于连接和断开量子点与纳米线的连接。这种数字控制方式使管理实际应用所需的大量量子比特变得切实可行。​

从物理学到工程学

现在,核心构建模块已经得到证实——量子信息编码在MZMs中,受拓扑保护,并通过测量进行处理。

现在,微软已准备好从物理突破,走向实际应用。

下一步是,将围绕一个称为四端子量子比特(tetron)的单量子比特器件构建可扩展架构(见图2)。

其中一个基本操作——测量tetron中一个拓扑纳米线的宇称。另一个关键操作是使量子比特处于「宇称态」的叠加态。

后者同样是通过量子点的微波反射测量来实现的,但在不同的测量配置中,微软将第一个量子点从纳米线解耦,并在器件一端将不同的量子点连接到两条纳米线。

通过执行这两个正交的泡利测量(Pauli measurements,Z和X),他们已经证明了基于测量的控制——这是一个关键里程碑,为路线图上的下一步铺平了道路。

图2:使用四子结构实现容错量子计算的路线图。第一个子图展示了单量子比特装置:四子结构由两条平行的拓扑导线(蓝色)组成,每个末端具有一个马约拉纳零模(橙色点),并由一条垂直的普通超导导线(浅蓝色)连接。第二个子图展示了支持基于测量的编织变换的双量子比特装置。第三个子图显示了一个4×2的四子结构阵列,用于演示两个逻辑量子比特的量子错误检测。这些演示为量子纠错技术铺平道路,如右侧子图所示的装置(一个27×13的四子结构阵列)

微软路线图现在系统地指向可扩展的量子纠错,下一步将涉及4×2 tetron阵列。

团队首先将使用两个量子比特子集来演示量子纠缠和基于测量的编织变换(braiding transformations)。随后,使用全部八个量子比特阵列,再对两个逻辑量子比特实施量子错误检测。

拓扑量子比特的内置错误保护简化了量子纠错。

此外,与之前最先进的方法相比,微软的定制量子纠错码(QEC codes)将开销降低了约十倍。

这种显著的减少意味着,可扩展系统不仅可以用更少的物理量子比特构建,而且有潜力实现更高的时钟频率。

开启量子计算的无限可能

一年半前,微软制定了实现量子超级计算机的路线图。

而今天,他们达到了第二个重要里程碑,成功展示了世界上第一个拓扑量子比特。

不仅如此,微软已经在一个可容纳百万量子比特的芯片设计中,成功集成了八个拓扑量子比特。

可以说,一台百万量子比特的量子计算机不仅仅是一个里程碑——它更是解决世界上一些最棘手问题的关键。

  • 量子计算可以帮助破解材料腐蚀或开裂的复杂化学机理。这一突破可能催生出能够自动修复桥梁构件、飞机零件、碎裂手机屏幕甚至汽车划痕的智能材料。
  • 由于塑料种类繁多,目前还无法找到一种通用的催化剂来分解各类塑料,这对于治理微塑料污染和应对碳排放尤为重要。量子计算有望通过精确计算催化剂特性,既可以将污染物转化为有价值的副产品,也可以从源头开发无毒替代材料。
  • 通过量子计算提供的精确计算能力,人类可以更有效地利用酶在医疗卫生和农业领域的应用。这可能带来消除全球饥饿的重大突破:提升土壤肥力以增加农作物产量,或促进作物在恶劣气候条件下的可持续生长。

,时长12:23

,时长12:23

更令人激动的是,量子计算可以让工程师、科学家简单地设计一切,从医疗保健到产品开发。

当量子计算的力量与AI相结合,人们就可以通过语言创建新材料或分子,直接得到答案,无需猜测或实验。

即使最强大的超算,也无法准确预测些决定未来关键材料性质的量子过程。

但如今,我们有望迎来革命性创新,或许人类即将发现能修复桥梁裂缝的自修复材料、可持续农业技术,以及更安全的化学物质发现方法。

现在许多需要投入巨额资金的实验研究,很可能用量子计算机就能直接获得结果。

总之,通往实用量子计算的道路已经清晰可见。

参考资料:

​https://news.microsoft.com/source/features/ai/microsofts-majorana-1-chip-carves-new-path-for-quantum-computing/?ocid=FY25_soc_omc_br_x_QuantumMajorana​​​

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值