低成本微调LLM
最近在微调不同量级上的大模型,包括Llama-2-7b,Llama-2-13b,Llama-2-70b,Yi-34b,Qwen-14b,Qwen-72b等大模型。在有限的资源上微调大模型,节约显存,可以考虑使用
LoRA这个算法,来自论文《LoRA: Low-Rank Adaptation of Large Language Models》,目前可以用的包有两个,分别是loralib 和peft这两个包,其中peft 和huggingface 中的transformers结合一起使用非常方便大家的使用。
LoRA 原理
LLM模型中有一部分层是线性层,LoRA是对线性层的参数增加两个低维度的矩阵进行线性运算,从而降低了模型微调的参数量和显存消耗。通常低维度的参数rank 可以自行设置,相比于LLM的hidden_size的值要小很多。下面介绍LoRA的数学表达式:
y
=
(
w
T
+
α
l
o
r
a
_
A
T
l
o
r
a
_
B
T
)
x
=
w
T
x
+
α
l
o
r
a
_
A
T
l
o
r
a
_
B
T
x
y = (w^{T} + \alpha lora\_{A}^{T}lora\_{B}^{T}) x\\ =w^{T} x+ \alpha lora\_{A}^{T}lora\_{B}^{T} x
y=(wT+αlora_ATlora_BT)x=wTx+αlora_ATlora_BTx
其中
w
∈
R
m
×
h
w \in R^{m\times h}
w∈Rm×h,
l
o
r
a
_
A
∈
R
r
×
h
lora\_{A} \in R^{r\times h}
lora_A∈Rr×h,
l
o
r
a
_
B
∈
R
m
×
r
lora\_{B} \in R^{m\times r}
lora_B∈Rm×r,
r
r
r通常可以自行设置的值比较小,而且远远小于m,
l
o
r
a
_
A
∈
R
r
×
h
,
l
o
r
a
_
B
∈
R
m
×
r
lora\_{A} \in R^{r\times h},lora\_{B} \in R^{m\times r}
lora_A∈Rr×h,lora_B∈Rm×r的参数量远小于
w
∈
R
m
×
h
w \in R^{m\times h}
w∈Rm×h。在采用LoRA的方式微调LLM,梯度更新的参数为每一层的线性层相应的lora参数,例如表达式中的
l
o
r
a
_
A
lora\_{A}
lora_A和
l
o
r
a
_
B
lora\_{B}
lora_B,原模型的参数不进行梯度更新,这样做的目的是训练参数量减少,节约显存,加快训练速度。在显卡资源不足的情况下,可以选择LoRA的方式进行微调LLM。而且
l
o
r
a
_
A
T
l
o
r
a
_
B
T
∈
R
h
×
m
lora\_{A}^{T}lora\_{B}^{T}\in R^{h\times m}
lora_ATlora_BT∈Rh×m与
w
T
w^{T}
wT的大小是一致的,方便将
l
o
r
a
_
A
T
l
o
r
a
_
B
T
lora\_{A}^{T}lora\_{B}^{T}
lora_ATlora_BT参数合并到原始模型参数
w
T
w^{T}
wT上,并未对原始模型增加新的参数,从而采用原始模型的推理方式进行推理。有时候微调的时候需要训练embedding层和lm head 这一层,在采用LoRA训练的时候也可以训练这两层,在peft中可以实现这两层的训练。
LoRA微调LLM
下面将给出Qwen采用LoRA的方式进行微调,是基于peft和transformers实现的。下面将给出示例代码。注意这里使用的是Qwen1不是Qwen2。
from peft import PeftModel
from peft import LoraConfig
from peft import get_peft_model
from modeling_qwen import QWenLMHeadModel
def load_qwen_model_lora(pretrain_model_path, use_gradient_checkpointing, lora_r, lora_alpha,
lora_dropout, bf16=False, fp16=False, checkpoint_dir=None
):
model = QWenLMHeadModel.from_pretrained(pretrain_model_path,
torch_dtype=torch.bfloat16 if bf16 else torch.float16 if fp16 else torch.float32)
print("loadding model")
model.config.torch_dtype = (torch.float16 if fp16 else (torch.bfloat16 if bf16 else torch.float32))
if use_gradient_checkpointing:
model.gradient_checkpointing_enable()
print("using gradient_checkpointing_enable")
model.config.use_cache = False
target_modules = find_all_linear_names(model)
print("模型中linear层的名称集合:", target_modules)
config = LoraConfig(
r=lora_r,
lora_alpha=lora_alpha,
target_modules=target_modules,
lora_dropout=lora_dropout,
bias="none",
task_type="CAUSAL_LM",
)
if checkpoint_dir is not None:
print("加载模型继续训练.")
model = PeftModel.from_pretrained(model, checkpoint_dir)
for name, param in model.named_parameters():
if 'lora_' in name:
param.requires_grad = True
else:
print('添加 LoRA 网络...')
model = get_peft_model(model, config)
for name, module in model.named_modules():
if isinstance(module, LoraLayer):
if bf16:
module = module.to(torch.bfloat16)
if 'ln' in name:
module = module.to(torch.float32)
if 'lm_head' in name or 'wte' in name:
if hasattr(module, 'weight'):
if bf16 and module.weight.dtype == torch.float32:
module = module.to(torch.bfloat16)
return model
def find_all_linear_names(model):
lora_module_names = set()
for name, module in model.named_modules():
if isinstance(module, torch.nn.Linear):
names = name.split('.')
lora_module_names.add(names[0] if len(names) == 1 else names[-1])
print("lora name: " , lora_module_names)
if 'lm_head' in lora_module_names: # needed for 16-bit
lora_module_names.remove('lm_head')
return list(lora_module_names)
采用transformers 的Trainer 进行模型训练,这里也可以采用deepspeed ddp 进行模型训练。
下面将介绍在采用LoRA进行微调的时候训练embedding 和lm header 这两层。如果模型字典进行延扩,embedding 和lm header 这两个层需要训练。只需要在LoraConfig指定就可以。
def load_qwen_model_lora(pretrain_model_path, use_gradient_checkpointing, lora_r, lora_alpha,
lora_dropout, bf16=False, fp16=False, checkpoint_dir=None,
finetuning_embedding_and_lm_head=True
):
model = QWenLMHeadModel.from_pretrained(pretrain_model_path,
torch_dtype=torch.bfloat16 if bf16 else torch.float16 if fp16 else torch.float32)
print("loadding model")
model.config.torch_dtype = (torch.float16 if fp16 else (torch.bfloat16 if bf16 else torch.float32))
if use_gradient_checkpointing:
model.gradient_checkpointing_enable()
print("using gradient_checkpointing_enable")
model.config.use_cache = False
target_modules = find_all_linear_names(model)
config = LoraConfig(
r=lora_r,
lora_alpha=lora_alpha,
target_modules=target_modules,
lora_dropout=lora_dropout,
bias="none",
task_type="CAUSAL_LM",
modules_to_save=['base_model.model.lm_head', 'base_model.model.transformer.wte']
)
if checkpoint_dir is not None:
print("从 checkpoint 加载 adapters.")
model = PeftModel.from_pretrained(model, checkpoint_dir)
for name, param in model.named_parameters():
if 'lora_' in name:
param.requires_grad = True
else:
print('添加 LoRA 网络...')
model = get_peft_model(model, config)
for name, module in model.named_modules():
if isinstance(module, LoraLayer):
if bf16:
module = module.to(torch.bfloat16)
if 'ln' in name:
module = module.to(torch.float32)
if 'lm_head' in name or 'wte' in name:
if hasattr(module, 'weight'):
if bf16 and module.weight.dtype == torch.float32:
module = module.to(torch.bfloat16)
# 设置lm head 和 embedding是否训练
if finetuning_embedding_and_lm_head:
for name, param in model.named_parameters():
if 'lm_head' in name or 'wte' in name:
param.requires_grad = True
return model
QLoRA 微调LLM
QLoRA是模型量化(Quantilization) 和LoRA结合起来使得降低显存并加快训练效率,可以看作是对LoRA的优化。下面将给出千问的示例。在实际中使用的是4bit进行微调的。
import os
import torch
from peft import PeftModel
from peft import LoraConfig
from peft import get_peft_model
from peft import prepare_model_for_kbit_training
from modeling_qwen import QWenLMHeadModel
def load_qwen_model_qlora(pretrain_model_path, use_gradient_checkpointing,
bits,
double_quant,
quant_type,
lora_r, lora_alpha, lora_dropout,
checkpoint_dir=None,
bf16=False, fp16=False,
finetuning_embedding_and_lm_head=False):
## lora 微调embedding和lm head 的话,有两种方式,一种是下面两行代码取消注释,一种是LoraConfig中modules_to_save添加要训练的层名
if finetuning_embedding_and_lm_head:
replace_peft_save_pretrined()
model_dict = load_model_state_dict(pretrain_model_path)
if checkpoint_dir is not None:
fintuning_checkpoint_file = os.path.join(checkpoint_dir, 'embedding_and_lm_head.pt')
if os.path.exists(fintuning_checkpoint_file):
# 加载保存点的变化的权重
finetuning_state = torch.load(fintuning_checkpoint_file, map_location=lambda storage, loc: storage)
#
for n, p in finetuning_state.items():
n = n.replace('base_model.model.', '')
if n in model_dict:
model_dict[n] = p
else:
raise NameError
weight_dtype = torch.bfloat16 if bf16 else torch.float16 if fp16 else torch.float32
model = QWenLMHeadModel.from_pretrained(pretrain_model_path,
torch_dtype=weight_dtype,
state_dict=model_dict,
load_in_4bit=bits == 4,
load_in_8bit=bits == 8,
quantization_config=BitsAndBytesConfig(
load_in_4bit=bits == 4,
load_in_8bit=bits == 8,
bnb_4bit_compute_dtype=weight_dtype,
bnb_4bit_use_double_quant=double_quant,
bnb_4bit_quant_type=quant_type
),)
print("loadding model")
model = prepare_model_for_kbit_training(model, use_gradient_checkpointing=use_gradient_checkpointing)
target_modules = find_all_linear_names(bits, model)
print("模型中linear层的名称集合:", target_modules)
config = LoraConfig(
r=lora_r,
lora_alpha=lora_alpha,
target_modules=target_modules,
lora_dropout=lora_dropout,
bias="none",
task_type="CAUSAL_LM",
# modules_to_save=['base_model.model.lm_head', 'base_model.model.transformer.wte'] # ,
)
if checkpoint_dir is not None:
print("从 checkpoint 加载 adapters.")
model = PeftModel.from_pretrained(model, checkpoint_dir)
else:
print('添加 LoRA 网络...')
model = get_peft_model(model, config)
for name, param in model.named_parameters():
if finetuning_embedding_and_lm_head:
if 'lm_head' in name or 'wte' in name:
param.requires_grad = True
continue
return model
以上是对LoRA和QLoRA的使用介绍,如有理解错误,欢迎指证。
欢迎使用Markdown编辑器
你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。
新的改变
我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:
- 全新的界面设计 ,将会带来全新的写作体验;
- 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
- 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
- 全新的 KaTeX数学公式 语法;
- 增加了支持甘特图的mermaid语法1 功能;
- 增加了 多屏幕编辑 Markdown文章功能;
- 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
- 增加了 检查列表 功能。
功能快捷键
撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G
查找:Ctrl/Command + F
替换:Ctrl/Command + G
合理的创建标题,有助于目录的生成
直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC
语法后生成一个完美的目录。
如何改变文本的样式
强调文本 强调文本
加粗文本 加粗文本
标记文本
删除文本
引用文本
H2O is是液体。
210 运算结果是 1024.
插入链接与图片
链接: link.
图片:
带尺寸的图片:
居中的图片:
居中并且带尺寸的图片:
当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。
如何插入一段漂亮的代码片
去博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片
.
// An highlighted block
var foo = 'bar';
生成一个适合你的列表
- 项目
- 项目
- 项目
- 项目
- 项目1
- 项目2
- 项目3
- 计划任务
- 完成任务
创建一个表格
一个简单的表格是这么创建的:
项目 | Value |
---|---|
电脑 | $1600 |
手机 | $12 |
导管 | $1 |
设定内容居中、居左、居右
使用:---------:
居中
使用:----------
居左
使用----------:
居右
第一列 | 第二列 | 第三列 |
---|---|---|
第一列文本居中 | 第二列文本居右 | 第三列文本居左 |
SmartyPants
SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:
TYPE | ASCII | HTML |
---|---|---|
Single backticks | 'Isn't this fun?' | ‘Isn’t this fun?’ |
Quotes | "Isn't this fun?" | “Isn’t this fun?” |
Dashes | -- is en-dash, --- is em-dash | – is en-dash, — is em-dash |
创建一个自定义列表
-
Markdown
- Text-to- HTML conversion tool Authors
- John
- Luke
如何创建一个注脚
一个具有注脚的文本。2
注释也是必不可少的
Markdown将文本转换为 HTML。
KaTeX数学公式
您可以使用渲染LaTeX数学表达式 KaTeX:
Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n−1)!∀n∈N 是通过欧拉积分
Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=∫0∞tz−1e−tdt.
你可以找到更多关于的信息 LaTeX 数学表达式here.
新的甘特图功能,丰富你的文章
- 关于 甘特图 语法,参考 这儿,
UML 图表
可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图:
这将产生一个流程图。:
- 关于 Mermaid 语法,参考 这儿,
FLowchart流程图
我们依旧会支持flowchart的流程图:
- 关于 Flowchart流程图 语法,参考 这儿.
导出与导入
导出
如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。
导入
如果你想加载一篇你写过的.md文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。
mermaid语法说明 ↩︎
注脚的解释 ↩︎