
当红炸子鸡 LoRA,是当代微调 LLMs 的正确姿势?
自 ChatGPT 掀起了大模型(LLM)风潮后,一大波 LLMs(GPT-4, LLaMa, BLOOM, Alpaca, Vicuna, MPT …) 百花齐放。知识问答、文章撰写、代码编写和纠错、报告策划等等,它们都会,也能够交互式地和你玩文字游戏,甚至还有些很有才的朋友将 LLM 作为交互的接口,同时连接到其它各种模态(e.g. 视觉 & 语音)的模型,从而创造了炸裂的多模态效果,炫~!这
前言
自 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”。
【关于 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 答:模型在微调过程中,权重的变化可以表示为 。其中 就是更新前的权重(在一开始就是预训练权重),而 就是更新的那部分,也就是经过反向传播得到梯度后计算出来的需要更新的量。
在代码中如何实现
假设
是一个线性层(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)
End
LoRA 作为当今大模型时代最火的技术之一,是否算得上是微调 LLMs 的正确姿势由你们决定。比起正确与否,合不合适才是最重要的。于我而言,只是觉得它好玩而不是无聊的风格而已~
如何系统的去学习大模型LLM ?
作为一名热心肠的互联网老兵,我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。
但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的 AI大模型资料
包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
所有资料 ⚡️ ,朋友们如果有需要全套 《LLM大模型入门+进阶学习资源包》,扫码获取~
👉[CSDN大礼包🎁:全网最全《LLM大模型入门+进阶学习资源包》免费分享]👈
一、全套AGI大模型学习路线
AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!
二、640套AI大模型报告合集
这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。
三、AI大模型经典PDF籍
随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。
四、AI大模型商业化落地方案
阶段1:AI大模型时代的基础理解
- 目标:了解AI大模型的基本概念、发展历程和核心原理。
- 内容:
- L1.1 人工智能简述与大模型起源
- L1.2 大模型与通用人工智能
- L1.3 GPT模型的发展历程
- L1.4 模型工程
- L1.4.1 知识大模型
- L1.4.2 生产大模型
- L1.4.3 模型工程方法论
- L1.4.4 模型工程实践 - L1.5 GPT应用案例
阶段2:AI大模型API应用开发工程
- 目标:掌握AI大模型API的使用和开发,以及相关的编程技能。
- 内容:
- L2.1 API接口
- L2.1.1 OpenAI API接口
- L2.1.2 Python接口接入
- L2.1.3 BOT工具类框架
- L2.1.4 代码示例 - L2.2 Prompt框架
- L2.2.1 什么是Prompt
- L2.2.2 Prompt框架应用现状
- L2.2.3 基于GPTAS的Prompt框架
- L2.2.4 Prompt框架与Thought
- L2.2.5 Prompt框架与提示词 - L2.3 流水线工程
- L2.3.1 流水线工程的概念
- L2.3.2 流水线工程的优点
- L2.3.3 流水线工程的应用 - L2.4 总结与展望
- L2.1 API接口
阶段3:AI大模型应用架构实践
- 目标:深入理解AI大模型的应用架构,并能够进行私有化部署。
- 内容:
- L3.1 Agent模型框架
- L3.1.1 Agent模型框架的设计理念
- L3.1.2 Agent模型框架的核心组件
- L3.1.3 Agent模型框架的实现细节 - L3.2 MetaGPT
- L3.2.1 MetaGPT的基本概念
- L3.2.2 MetaGPT的工作原理
- L3.2.3 MetaGPT的应用场景 - L3.3 ChatGLM
- L3.3.1 ChatGLM的特点
- L3.3.2 ChatGLM的开发环境
- L3.3.3 ChatGLM的使用示例 - L3.4 LLAMA
- L3.4.1 LLAMA的特点
- L3.4.2 LLAMA的开发环境
- L3.4.3 LLAMA的使用示例 - L3.5 其他大模型介绍
- L3.1 Agent模型框架
阶段4:AI大模型私有化部署
- 目标:掌握多种AI大模型的私有化部署,包括多模态和特定领域模型。
- 内容:
- L4.1 模型私有化部署概述
- L4.2 模型私有化部署的关键技术
- L4.3 模型私有化部署的实施步骤
- L4.4 模型私有化部署的应用场景
学习计划:
- 阶段1:1-2个月,建立AI大模型的基础知识体系。
- 阶段2:2-3个月,专注于API应用开发能力的提升。
- 阶段3:3-4个月,深入实践AI大模型的应用架构和私有化部署。
- 阶段4:4-5个月,专注于高级模型的应用和部署。
这份完整版的所有 ⚡️ 大模型 LLM 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费
】
全套 《LLM大模型入门+进阶学习资源包》↓↓↓ 获取~
👉[CSDN大礼包🎁:全网最全《LLM大模型入门+进阶学习资源包》免费分享👈
更多推荐
所有评论(0)