LLM生成的关于LLM微调的图像(左边为GPT,中间也为GPT,右边为Gemini)
自2022年11月发布以来,ChatGPT已经引起了关于大型语言模型(LLM)和人工智能能力的广泛讨论。现在,很少有人没听说过ChatGPT或没尝试过使用它。尽管像GPT、Gemini或Claude这样的工具拥有数百亿甚至数千亿的参数,并且已经在庞大的文本语料库上进行了预训练,它们并非万能。这些模型在某些特定任务上会显得力不从心。不过,我们并不缺少解决方法。我们可以利用较小的开源模型,将它们调整以适应我们特定的问题。
这篇博客旨在简单介绍一下几个较小的开源大型语言模型,并讲解两个重要概念:量化和LoRA。此外,我们还将介绍几个最受欢迎的库,并附有代码示例,让您快速应用这些概念。让我们开始吧,一起深入了解微调。
如果你想跳过理论直接看代码,可以点击这里。
目录如下- “小规模”大型语言模型
- 量化技术
- 低秩适应(LoRA)
- Unsloth
- 监督微调训练器(SFT)技术
- 比率偏好优化技术(ORPO)
- 结论。
- 参考文献资料
微调大语言模型可能会非常昂贵,尤其是对于参数量很大的模型。一般来说,参数少于10亿的模型通常可以进行微调而不会遇到显著的基础设施挑战。然而,对于更大的模型,如Llama 3 70亿参数,需要大量的资源。微调一个像Llama 3这样的70亿参数模型大约需要1.5太字节的GPU显存。为了更好地理解这个数量,这相当于大约20个配备80GB显存的Nvidia A100显卡组成的集群。这种设置的成本大约为40万美元(假设硬件可获得)。
或者可以使用像 AWS、Azure 或 GCP 这样的云服务提供商,但这也非常昂贵。例如,在 AWS 上使用 8 个 Nvidia A100 GPU 一小时需要 40 美元。如果你用 20 个 GPU 在 5 天内对 70B 参数的模型进行微调,大约需要花费 12,000 美元。
由于这些成本,大多数实践者主要使用参数少于10亿的小型LLM。这些模型可以更经济地训练它们,只需要16GB到24GB的vRAM(支持更大的批处理和更快的训练)。比如说,我使用AWS上的Nvidia A10将Mistral 7B微调到塞尔维亚语,这花费不到10小时,成本不到20美元。
当然,还是,7B模型仍然无法在不进行4位量化的情况下,在那么多vRAM上训练。
(量化)(量化)指将连续的数值范围转换为离散的数值范围的过程。
即使使用全32位参数,我们仍然需要极其夸张(以凡人标准来看)的大约150GB的显存来训练这个大型语言模型。
把 FP32 转换为 INT8
量化通过将模型参数转换为低精度的数据类型(如8位或4位),显著减少了内存消耗,并且提升了执行速度。其概念很简单:所有可能的32位数值被映射到一个较小的范围,例如8位转换为256个可能的值。这个过程可以被理解为将高精度值分组到几个特定的固定点周围,这些固定点代表了附近值的近似。
下秩适应(LoRA).LoRA 是一种利用矩阵维度减少来更新模型权重的技术。由于变压器在大语言模型中被广泛应用且依赖于矩阵,因此这项技术尤为重要。Jay Alammar 的一篇 博客文章 中详细解释了 LoRA 的底层工作方式。
定期微调
在更新模型权重时,需要调整这些矩阵中的参数,即调整权重。从概念上看,这种调整可以看作是在原矩阵上加上一个权重更新矩阵:W’ = W + ΔW,其中W’表示更新后的权重,W表示原来的权重,ΔW表示权重的更新量。LoRA 通过将权重更新矩阵分解为两个较小的矩阵来引入了一种新的方法,这两个矩阵相乘可以近似原权重更新矩阵。在微调阶段,LoRA 直接生成这两个较小的矩阵,而不是先创建更新矩阵再进行分解,而是直接生成这两个较小的矩阵。
以下图片展示了常规微调与使用LoRA进行微调之间的说明性比较,取自塞巴斯蒂安·拉施卡(Sebastian Raschka)的博客文章。
左边是常规微调的另一种方式,右边是使用LoRA的微调方法。
LoRA 的关键优势在于,虽然近似可能稍微不那么精确,但显著提升了内存和计算效率。例如,考虑一个 1000x1000 的参数矩阵,总共有 100 万个参数。通过使用略微不那么精确的 1000x100 和 100x1000 矩阵的分解版本,参数数量减少到 20 万个,参数量减少了 80%。
量化和LoRA经常被一起使用,形成了所谓的QLoRA,这种组合被称为QLoRA。
Unsloth如果我要重新开始大规模语言模型(LLM)的微调,我会选择 Unsloth。Unsloth 提供了多种针对 LLM 微调优化,并支持包括 Mistral、Llama 3、Gemma 等在内的多种流行的大规模语言模型。例如,他们的免费层级包含了对 Mistral 的 12 种微调优化,加速了近 2.2 倍。
UNSLOTH 精细调优
以下是一些代码片段,展示了如何使用Unsloth库对Llama 3 8B进行调优。以下代码均来自Unsloth GitHub,完整的Llama 3 8B调优笔记本可以在这里查看:此处。Llama 3 8B(一种大型语言模型)。
导入4比特模型:
model, tokenizer = FastLanguageModel.from_pretrained(
model_name = "unsloth/llama-3-8b-bnb-4bit",
max_seq_length = 最大序列长度,
dtype = 数据类型,
load_in_4bit = load_in_4bit,
# 如果使用的是像meta-llama/Llama-2-7b-hf这样的受保护模型,你需要提供一个token
)
配置LoRA参数:
model = FastLanguageModel.get_peft_model(
model,
r = 16, # 选择任何大于0的数!建议选择8、16、32、64或128
target_modules = ["q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj",],
lora_alpha = 16,
lora_dropout = 0, # 支持任何值,但=0是优化过的
bias = "none", # 支持任何值,但='none'是优化过的
# [NEW] "unsloth" 使用30%更少的VRAM,适合两倍大的批次
use_gradient_checkpointing = "unsloth", # True或"unsloth"适用于非常长的上下文
random_state = 3407,
use_rslora = False, # 我们支持秩稳定的LoRA和LoftQ
loftq_config = None,
)
开始使用 Hugging Face 的有监督微调工具:
trainer = SFTTrainer(
model = model,
tokenizer = tokenizer,
train_dataset = dataset,
dataset_text_field = "text",
max_seq_length = max_seq_length,
dataset_num_proc = 2,
packing = False, # 可以将短序列的训练速度提高5倍。
args = TrainingArguments(
per_device_train_batch_size = 2,
gradient_accumulation_steps = 4,
warmup_steps = 5,
max_steps = 60,
learning_rate = 2e-4,
fp16 = not torch.cuda.is_bf16_supported(),
bf16 = torch.cuda.is_bf16_supported(),
logging_steps = 1,
optim = "adamw_8bit",
weight_decay = 0.01,
lr_scheduler_type = "linear",
seed = 3407,
output_dir = "outputs",
),
)
训练模型:
trainer_stats = trainer.train(); // 训练器的统计数据通过调用训练器的训练方法获得。
在经过预训练一个大模型之后,接下来的关键步骤是监督微调。这个过程对于开发一个能够理解和生成连贯的回复的模型至关重要,而不是仅仅完成句子的填充。
像 Hugging Face 提供的 SFT 和 PEFT 这样的工具,以及 Tim Dettmers 开发的 BitsAndBytes,显著简化了将诸如 LoRA、量化以及微调等技术应用到模型中的过程。这些库使得实现这些高级优化方法变得更加简单高效,无论是对开发者还是研究人员来说都更加友好。
下面你会发现Unsloth、SFT和ORPO的代码相当相似。这种相似性是因为这些库的基本理念大体相同,区别主要在于库的具体实现以及可能涉及的一些超参数。
导入4位精度的模型:
# Hugging Face 模型 ID
model_id = "meta-llama/Meta-Llama-3-8B"
model_id = "mistralai/Mistral-7B-v0.1"
# 位宽4配置
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16 if use_flash_attention2 else torch.float16
)
# 加载自回归语言模型和自动分词器
model = AutoModelForCausalLM.from_pretrained(
model_id,
quantization_config=bnb_config,
use_cache=False,
device_map="auto", # 设备分配方式为自动模式
token=os.environ["HF_TOKEN"], # 如果模型是受保护的,比如 llama 或者 Mistral
attn_implementation="flash_attention_2" if use_flash_attention2 else "sdpa"
)
model.config.pretraining_tp = 1
tokenizer = AutoTokenizer.from_pretrained(
model_id,
token=os.environ["HF_TOKEN"], # 如果模型是受保护的,比如 llama 或者 Mistral
)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right" # 分词器的填充方向设置为右边
设置LoRA:
# 基于 QLoRA 论文的 LoRA 配置如下
peft_config = LoraConfig(
lora_alpha=16,
lora_dropout=0.1,
r=64,
bias="none",
task_type="CAUSAL_LM",
target_modules=[
"q_proj",
"k_proj",
"v_proj",
"o_proj",
"gate_proj",
"up_proj",
"down_proj",
]
)
# 准备模型训练
model = prepare_model_for_kbit_training(model)
开始使用 Hugging Face 的监督微调训练程序:
args = TrainingArguments(
output_dir="mistral-int4-alpaca",
num_train_epochs=1,
per_device_train_batch_size=6 if use_flash_attention2 else 2, # 你可以根据你的硬件调整批量大小。
gradient_accumulation_steps=4, # 梯度累积步数
gradient_checkpointing=True,
optim="paged_adamw_8bit", # 注意:paged_adamw_8bit 是一种特定的优化器,可能需要额外解释
logging_steps=10,
save_strategy="epoch",
learning_rate=2e-4,
bf16=use_flash_attention2,
fp16=not use_flash_attention2,
tf32=use_flash_attention2,
max_grad_norm=0.3,
warmup_steps=5,
lr_scheduler_type="linear", # 学习率调度器类型
disable_tqdm=False, # 禁用进度条
report_to="none"
)
model = get_peft_model(model, peft_config)
trainer = SFTTrainer(
model=model,
train_dataset=dataset,
peft_config=peft_config,
max_seq_length=2048,
tokenizer=tokenizer,
packing=True,
formatting_func=format_instruction,
args=args,
)
训练这个模型:
trainer.训练模型() # 开始训练模型
该完整的笔记本可以在这里找到这里。
优势比率偏好优化 ORPO在这篇博客文章中,我们重点介绍了大型语言模型(LLM)的预训练和有监督的微调。然而,所有顶尖的大型语言模型都会经历另一个关键步骤:偏好调整。这一步发生在预训练和微调之后,通过告诉模型哪些生成的输出是可取的,哪些不是。偏好调整的流行方法包括从人类反馈中的强化学习(RLHF)和直接偏好优化法(DPO)。
2024年3月,出现了一种叫做优势比偏好优化法(ORPO)的新方法,结合了监督微调和偏好对齐。
传统LLM调优与ORPO LLM调优对比
关于ORPO的详细解释,包括代码示例和总体概述,可以参阅Maxime Labonne的内容深刻的博客文章。
这里有一段使用ORPO进行微调和调整偏好的代码。完整代码可以在这里找到:here。
导入4位精度的模型:
# 模型
base_model = "meta-llama/Meta-Llama-3-8B"
new_model = "OrpoLlama-3-8B"
# QLoRA 配置
bnb_config = 位和字节配置(
加载4位=True,
4位量化类型="nf4",
4位计算数据类型=torch_dtype,
4位使用双量化=True,
)
# 定义自动分词器
tokenizer = 自动分词器.from_pretrained(base_model)
# 定义自动因果语言模型
model = 自动因果语言模型.from_pretrained(
base_model,
量化配置=bnb_config,
设备映射="自动",
注意力实现方式=attn_implementation
)
设置LoRA环境:
# LoRA
peft_config = LoraConfig(
r=16,
lora_alpha=32,
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM", # 代表因果语言模型 (CAUSAL_LM)
target_modules=['up_proj', 'down_proj', 'gate_proj', 'k_proj', 'q_proj', 'v_proj', 'o_proj'] # 目标模块
)
model = prepare_model_for_kbit_training(model) # 准备模型进行位元训练
初始化 Hugging Face 的 ORPO Trainer, 如下:
orpo_args = ORPOConfig(
learning_rate=8e-6, # 学习率
beta=0.1, # 衰减系数
lr_scheduler_type="linear", # 学习率调度器类型:线性
max_length=1024, # 最大长度
max_prompt_length=512, # 最大提示长度
per_device_train_batch_size=2, # 每设备训练批次大小
per_device_eval_batch_size=2, # 每设备评估批次大小
gradient_accumulation_steps=4, # 梯度累积步数
optim="paged_adamw_8bit", # 优化器:paged_adamw_8bit
num_train_epochs=1, # 训练周期数
evaluation_strategy="steps", # 评估策略:步骤
eval_steps=0.2, # 评估步数
logging_steps=1, # 日志步数
warmup_steps=10, # 预热步数
report_to="wandb", # 报告到wandb
output_dir="./results/", # 输出目录
)
trainer = ORPOTrainer(
model=model,
args=orpo_args,
train_dataset=dataset["train"],
eval_dataset=dataset["test"],
peft_config=peft_config,
tokenizer=tokenizer,
)
让我们来训练模型。
trainer.训练()
结语
虽然像GPT、Gemini或Claude这样的大型语言模型(LLMs)非常强大,但其庞大的规模和资源需求使得它们在许多任务中难以实现。为了解决这个问题,可以使用如量化和低秩适应(LoRA)等技术对较小的开源LLMs进行微调和定制,以满足特定需求的定制。这些技术能够减少内存消耗并提高计算效率,使其更加经济高效,特别是那些参数少于100亿的模型。
像 Unsloth、监督微调(SFT)和几率比偏好优化(ORPO)这样的工具简化了微调过程,使其更加易于使用。例如,Unsloth 就能显著加速训练过程。而 ORPO 结合了监督微调和偏好一致来提升模型性能。
通过利用这些技术和工具,开发人员和研究人员可以定制大语言模型(LLM),以满足他们的特定需求,而无需承担训练大型模型的高昂成本。这种方法使高级语言模型的访问更加民主化,从而使各个领域中广泛应用的程序得以实现。
参考文献[1] 模型内存实用:https://huggingface.co/spaces/hf-accelerate/model-memory-usage
AWS EC2 P4d 实例定价: https://aws.amazon.com/ec2/instance-types/p4/#:~:text=了解更多详情%C2%BB-,实例详情,-实例规格
[4] 使用 NVIDIA TensorRT 进行量化感知训练法以使 INT8 推理达到 FP32 的精度:https://developer.nvidia.com/blog/achieving-fp32-accuracy-for-int8-inference-using-quantization-aware-training-with-tensorrt/
《Transformer 图解》: https://jalammar.github.io/illustrated-transformer/
[6] LoRA:低秩适配:大型语言模型的低秩适配:https://arxiv.org/abs/2106.09685论文
[7] 使用低秩适应(LoRA)进行参数高效的LLM微调:https://sebastianraschka.com/blog/2023/llm-finetuning-lora.html
[8] QLoRA:高效微调量化LLM。点击这里查看详细信息:https://arxiv.org/abs/2305.14314
[9] Unsloth:https://unsloth.ai/ (一个专注于减少工作倦怠的人工智能平台)
[10] 带有监督的微调训练器:https://huggingface.co/docs/trl/sft_trainer
[11] ORPO:不依赖参考模型的一体化偏好优化:https://arxiv.org/abs/2403.07691
[12] 使用 ORPO 微调 Llama 3 模型:https://huggingface.co/blog/mlabonne/orpo-llama-3