由LLM生成的关于LLM微调的图像(GPT:左和中,Gemini:右)
自2022年11月发布以来,ChatGPT引发了关于大型语言模型(LLMs)和AI能力的广泛讨论。现在很少能找到没听说过ChatGPT或没尝试过它的人了。虽然像GPT、Gemini或Claude这样的工具拥有数百亿(甚至数千亿)的参数,并且在庞大的文本语料库上进行了预训练,非常强大,但它们并非无所不能。在某些特定任务上,这些模型会表现出不足。然而,我们并非没有解决这些问题的方法。我们可以利用较小的开源模型的力量,将它们适应到我们特定的问题上。
这篇博客旨在简要介绍几个较小的开源LLM,并解释LLM微调中的两个重要概念:量化和LoRA。此外,我们还将介绍一些最流行的微调库,并附上代码示例,以便您能够快速将这些概念应用于您的用例。让我们开始微调之旅。
如果你想要跳过理论部分直接查看代码,请点击这里。
Llama 3与其他模型的比较
微调大型语言模型(LLMs)可能会非常昂贵,特别是对于参数数量较多的模型。一般来说,参数少于100亿的模型通常可以进行微调而不会遇到显著的基础设施挑战。然而,对于更大的模型,如Llama 3 70B,需要大量的资源。微调一个像Llama 3这样的700亿参数模型大约需要1.5太字节的GPU虚拟内存(vRAM)。从另一个角度来看,这相当于大约20个Nvidia A100 GPU的集群,每个GPU有80GB的vRAM。这样的配置成本大约为40万美元,假设硬件甚至可以买到。
或者,也可以使用像 AWS、Azure 或 GCP 这样的云提供商,但这种方法也很昂贵。例如,在 AWS 上使用 8 个 Nvidia A100 GPU 一小时的成本为 40 美元。如果你要用 20 个 GPU 对 70B 参数的模型进行微调 5 天,大约需要花费 12,000 美元。
由于这些成本,大多数从业者主要使用参数少于100亿的较小的LLM。这些模型可以更经济地训练,只需要16GB到24GB的vRAM(对于更大的批次大小和更快的训练)。例如,我使用Nvidia A10在AWS上将Mistral 7B微调为塞尔维亚语,这花费不到10小时且成本不到20美元。
当然,仍然需要的是,一个7B模型在没有量化的情况下无法适应并训练在那么多的vRAM上,具体来说是量化到4位。
使用完整的32位参数,我们仍然需要大量的vRAM(以普通人的标准来看是荒谬的)来训练LLM——大约需要150GB。
将 FP32 转换为 INT8
量化通过将模型参数转换为低精度的数据类型(如8位或4位),显著减少了内存消耗并提高了执行速度。其概念很简单:所有可能的32位值都被映射到一个较小范围的有限值(例如,8位转换为256个值)。这个过程可以被可视化为将高精度值围绕几个固定点进行分组,这些固定点代表其附近的值。
LoRA 是一种通过使用矩阵降维来更新模型权重的技术。这项技术特别相关,因为广泛用于大规模语言模型(LLM)的变压器高度依赖于矩阵。LoRA 工作原理的详细解释可以在 Jay Alammar 的一篇博客文章中找到。
常规微调
当更新模型的权重时,需要调整这些矩阵中的参数。从概念上讲,这种调整可以看作是将一个权重更新矩阵添加到原始矩阵中:W’ = W + ΔW。LoRA 通过将这个更新矩阵分解为两个较小的矩阵来引入了一种新的方法,这两个较小的矩阵相乘可以近似于更新矩阵。在微调过程中,LoRA 直接创建这两个较小的矩阵进行相乘,而不是先创建再分解更新矩阵。
一个关于常规微调和使用LoRA微调之间的对比示例可以在下面的图片中看到,这些图片改编自Sebastian Raschka的博客文章。
与使用LoRA进行微调(右)相比的常规微调(左)的另一种表述形式
LoRA 的关键优势在于,虽然近似值略显不精确,但它显著提高了内存和计算效率。例如,考虑一个包含 1000x1000 参数的矩阵,总共有 100 万个参数。通过使用 1000x100 和 100x1000 矩阵的分解(且略显不精确)版本,参数数量减少到仅 20 万个,从而减少了 80% 的参数。
量化和LoRA通常结合使用,形成所谓的QLoRA。
如果我要重新开始大规模语言模型的微调,我会选择 Unsloth Python 库。Unsloth 提供了多种针对大规模语言模型微调的优化,并支持包括 Mistral、Llama 3、Gemma 等在内的多种流行的大规模语言模型。例如,他们的免费层级包含了针对 Mistral 的 12 种不同的微调优化,提供了显著的 2.2 倍加速。
Unsloth 微调优化
以下是一些代码片段,展示了如何使用 Unsloth 库对 Llama 3 8B 进行微调。所有这些代码块均来自 Unsloth GitHub,完整的微调笔记本可以在这里找到:此处。
导入4位模型:
model, tokenizer = FastLanguageModel.from_pretrained( model_name = "unsloth/llama-3-8b-bnb-4bit", max_seq_length = max_seq_length, dtype = dtype, load_in_4bit = load_in_4bit, # token = "hf_...", # 如果使用带有门控的模型(如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,适合2倍大的批次大小! use_gradient_checkpointing = "unsloth", # True或"unsloth"适用于非常长的上下文 random_state = 3407, use_rslora = False, # 我们支持秩稳定的LoRA loftq_config = None, # 以及LoftQ )
初始化Hugging Face的Supervised Finetuning Trainer:
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(Supervised Finetuning Trainer)和 PEFT(Parameter Efficient Finetuning)这样的工具,以及 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" # BitsAndBytesConfig 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的Supervised Finetuning Trainer:
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", 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.train()
完整的笔记本可以在这里找到:here。
在这篇博客文章中,我们重点介绍了大型语言模型(LLMs)的预训练和监督微调。然而,所有最先进的大型语言模型都会经历另一个关键步骤:偏好对齐。这个步骤发生在预训练和微调之后,通过告知模型哪些生成的输出是可取的,哪些不是。偏好对齐的流行方法包括从人类反馈中学习的强化学习(RLHF)和直接偏好优化(DPO)。
一种名为 Odds Ratio Preference Optimization (ORPO) 的新方法于2024年3月出现,结合了监督微调和偏好对齐。
传统的大语言模型微调 vs ORPO 大语言模型微调
对于 ORPO 的详细解释,包括代码示例和概述,参见 Maxime Labonne 的见解深刻的 博客文章。
这里有一部分使用ORPO进行微调和偏好对齐的代码。完整代码可在此处获取 here。
导入4位模型:
# 模型 base_model = "meta-llama/Meta-Llama-3-8B" new_model = "OrpoLlama-3-8B" # QLoRA 配置 bnb_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch_dtype, bnb_4bit_use_double_quant=True, ) # 加载分词器 tokenizer = AutoTokenizer.from_pretrained(base_model) # 加载模型 model = AutoModelForCausalLM.from_pretrained( base_model, quantization_config=bnb_config, device_map="auto", attn_implementation=attn_implementation )
设置LoRA:
# LoRA配置 peft_config = LoraConfig( r=16, lora_alpha=32, lora_dropout=0.05, bias="none", task_type="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", num_train_epochs=1, evaluation_strategy="steps", eval_steps=0.2, logging_steps=1, warmup_steps=10, report_to="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.train()
虽然像GPT、Gemini或Claude这样的大型语言模型(LLMs)非常强大,但它们的庞大体积和资源需求使得它们在许多任务中不太实用。为了解决这个问题,可以使用量化和低秩适应(LoRA)等技术对较小的开源LLMs进行微调和定制,以满足特定需求。这些技术可以减少内存消耗并提高计算效率,使得训练模型更加经济,尤其是对于参数少于100亿的模型。
像 Unsloth、Supervised Finetuning Trainer (SFT) 和 Odds Ratio Preference Optimization (ORPO) 这样的工具简化了微调过程,使其更加易于访问。例如,Unsloth 提供了可以显著加速训练的优化,而 ORPO 结合了监督微调与偏好对齐,以提升模型性能。
通过利用这些技术和工具,开发人员和研究人员可以将大型语言模型(LLMs)调整到他们特定的需求上,而无需承担训练大型模型的高昂成本。这种方法使高级语言模型的访问更加民主化,并且能够支持不同领域的广泛应用。
[1] 模型内存实用工具: https://huggingface.co/spaces/hf-accelerate/model-memory-usage
[2] AWS EC2 P4d 实例定价: https://aws.amazon.com/ec2/instance-types/p4/#:~:text=to%20learn%20more%20%C2%BB-,产品详情,-实例大小
[3] 量化详细解释: https://huggingface.co/docs/optimum/concept_guides/quantization
[4] 使用 NVIDIA TensorRT 进行量化感知训练以实现 INT8 推理的 FP32 准确性:https://developer.nvidia.com/blog/achieving-fp32-accuracy-for-int8-inference-using-quantization-aware-training-with-tensorrt/
[5] 图解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