链载Ai

标题: LLM微调方法大比拼 [打印本页]

作者: 链载Ai    时间: 5 小时前
标题: LLM微调方法大比拼

本文一共介绍7种方法LLM微调技术吗,一些微调技是可以叠加使用的。


一、量化Quantization

出于节约资源的目的,我们可以在微调中将模型参数转换成低精度的数据类型,比如8位或4位,来大幅减少内存使用并加快计算速度。这个原理其实很简单:它将所有32位的可能值映射到一个更小范围的有限值上(例如,8位可以表示256个不同的值)。这一过程可以想象为将高精度的值围绕若干个固定点进行分组,这些固定点代表了其周围的值。

关于量化的内容,此前文章已经有很多:

LLM量化方法对比: GPTQ VS bitsandbytes

AQLM 2-bit量化Mixtral-8x7B

AWQ量化Llama2


我们思考一个问题:Q-LoRA和现将一个FP-16 LoRA进行训练,训练完毕后再GPTQ进行量化  有啥区别?

Q-LoRA(Quantized Low-Rank Adaptation)和先将模型从FP-16进行LoRA(Low-Rank Adaptation)训练,然后再使用GPTQ进行量化,这两种方法在目标和实现过程中有一些关键的区别。

量化和微调的顺序:

目标和效果:

在HF上,这两种方式训练出来的模型都有。

第一种:

https://huggingface.co/namespace-Pt/Llama-3-8B-Instruct-80K-QLoRA

下文训练模型时用的就是Q-LoRA,请观察推理时的参数设置。

第二种:

https://huggingface.co/TheBloke/gpt4-alpaca-lora-30B-GPTQ


二、LoRA

LoRA是一种通过使用矩阵降维技术来更新模型权重的方法。这种技术特别重要,因为在大型语言模型(LLM)中广泛使用的transformers模型严重依赖于矩阵。关于LoRA在底层工作原理的详细解释,可以在Jay Alammar的博客文章中找到。

常规微调 在更新模型权重时,需要调整这些矩阵内的参数。从概念上讲,这种调整可以被视为向原始矩阵添加一个权重更新矩阵:W’ = W + ΔW。LoRA引入了一种新颖的方法,通过将这个更新矩阵分解为两个较小的矩阵,这两个小矩阵相乘时,可以近似地表示更新矩阵。在微调过程中,LoRA不是创建然后分解更新矩阵,而是直接创建这两个较小的矩阵进行相乘。


LoRA的主要优势在于,虽然其近似结果的精确度略有降低,但它显著提高了内存和计算效率。例如,考虑一个具有1000x1000参数的矩阵,总共有100万个参数。通过使用分解后的(并且精度略低的)1000x100乘以100x1000矩阵,参数数量减少到仅为2*100k,这导致参数减少了80%。

关于LoRA的文章,此前已经有过很多介绍。

使用LoRA微调Llama2@Azure GPU VM

LoRA微调 LLM精义

LoRA还是QLoRA?

量化和LoRA经常被结合使用,形成了所谓的QLoRA。量化和 LoRA 通常结合使用,形成所谓的 QLoRA。QLoRA的实现过程中,首先对原始LLMs进行量化处理。然后,选择模型中的一部分参数进行低秩分解,生成两个较小的矩阵。这些较小的矩阵用于替换原始参数,并在微调过程中进行更新。目前看到的Q-LoRA技术主要使用bnb量化。


三、Unsloth(https://github.com/unslothai/unsloth)

Unsloth有三个版本,其中Free版本仅仅针对一个GPU。后面讨论的也是针对这个Free的开源版本。


Unsloth 是一个 Python 库,它专为大型语言模型(LLM)的微调过程设计,提供了各种优化。它支持多种流行的 LLM,包括 Mistral、Llama 3、Gemma 等。

Unsloth 的主要特点包括:

from unsloth import FastLanguageModelimport torchmax_seq_length = 2048 # Choose any! We auto support RoPE Scaling internally!dtype = None # None for auto detection. Float16 for Tesla T4, V100, Bfloat16 for Ampere+load_in_4bit = True # Use 4bit quantization to reduce memory usage. Can be False.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,)

此时显存开销:

设置LoRA配置,我们看到比普通的LoRA多了use_gradient_checkpointing = "unsloth"。

model = FastLanguageModel.get_peft_model(    model,    r = 16, # Choose any number > 0 ! Suggested 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, # Supports any, but = 0 is optimized    bias = "none",    # Supports any, but = "none" is optimized# [NEW] "unsloth" uses 30% less VRAM, fits 2x larger batch sizes!    use_gradient_checkpointing = "unsloth", # True or "unsloth" for very long context    random_state = 3407,    use_rslora = False,  # We support rank stabilized LoRA    loftq_config = None, # And LoftQ)

准备数据:

alpaca_prompt = """Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.

### Instruction:{}

### Input:{}

### Response:{}"""

EOS_TOKEN = tokenizer.eos_token # Must add EOS_TOKENdef formatting_prompts_func(examples): instructions = examples["instruction"] inputs = examples["input"] outputs = examples["output"] texts = []for instruction, input, output in zip(instructions, inputs, outputs):# Must add EOS_TOKEN, otherwise your generation will go on forever! text = alpaca_prompt.format(instruction, input, output) + EOS_TOKEN texts.append(text)return { "text" : texts, }pass

from datasets import load_datasetdataset = load_dataset("yahma/alpaca-cleaned", split = "train")dataset = dataset.map(formatting_prompts_func, batched = True,)

定义trainer:

from trl import SFTTrainerfrom transformers import TrainingArguments

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, # Can make training 5x faster for short sequences.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",),)

查看内存占用:


#@title Show current memory statsgpu_stats = torch.cuda.get_device_properties(0)start_gpu_memory = round(torch.cuda.max_memory_reserved() / 1024 / 1024 / 1024, 3)max_memory = round(gpu_stats.total_memory / 1024 / 1024 / 1024, 3)print(f"GPU = {gpu_stats.name}. Max memory = {max_memory} GB.")print(f"{start_gpu_memory} GB of memory reserved.")

开始训练:

trainer_stats = trainer.train()

54秒训练完毕:

查看训练中的资源开销:

保存模型并进行推理验证。

model.save_pretrained("lora_model") # Local savingtokenizer.save_pretrained("lora_model")# model.push_to_hub("your_name/lora_model", token = "...") # Online saving# tokenizer.push_to_hub("your_name/lora_model", token = "...") # Online saving

if True:from unsloth import FastLanguageModel    model, tokenizer = FastLanguageModel.from_pretrained(        model_name = "lora_model", # YOUR MODEL YOU USED FOR TRAINING        max_seq_length = max_seq_length,        dtype = dtype,        load_in_4bit = load_in_4bit,    )    FastLanguageModel.for_inference(model) # Enable native 2x faster inference

# alpaca_prompt = You MUST copy from above!

inputs = tokenizer([ alpaca_prompt.format("Answer question base on truth", # instruction"Introducing the history of Beijing", # input"", # output - leave this blank for generation! )], return_tensors = "pt").to("cuda")

outputs = model.generate(**inputs, max_new_tokens = 1000, use_cache = True)tokenizer.batch_decode(outputs)

查看推理结果,我将其机器翻译成中文,并且以图片展示,因为太长了。我们看到不仅回答准确,而且详细。需要指出的是,如果用中文问问题,其准确性比用英文问要低。

四、SFT

在深度学习和自然语言处理(NLP)领域,有监督微调(Supervised Fine-Tuning, SFT)是一个广泛的概念,它包括了多种微调模型的方法和策略,旨在优化模型在特定任务上的表现。这些方法和策略包括但不限于:

  1. 全量微调:这是一种最直接的微调方法,涉及对预训练模型的所有参数进行调整,以适应新的任务或数据集。这种方法通常需要大量的计算资源和时间,但可以在新任务上实现最佳性能。

  2. 参数高效微调(Parameter-Efficient Fine-Tuning, PEFT):这是一组旨在减少微调过程中所需计算资源和时间的策略,同时尽量保持或提高模型在特定任务上的性能。PEFT包括多种技术,如Adapter模块、Prompt Tuning、BitFit、差分学习率(Differential Learning Rates)、重参数化(Reparameterization)和低秩适应(LoRA)等。

因此,SFT是一个包含全量微调和PEFT的更广泛框架。在实际应用中,开发者和研究人员可以根据特定任务的需求、可用的计算资源以及模型性能的目标,选择最适合的微调策略。全量微调提供了性能上的上限,而PEFT提供了在资源受限情况下进行微调的灵活性和效率。

我们用大白话来聊聊PEFT中Adapter和LoRA之间的区别。

想象一下,你有一辆车(这里的车比喻为我们的预训练模型),现在你想让这辆车适应不同的赛道(不同的任务或数据集)。Adapter和LoRA就是两种让车适应新赛道的方法,但它们的做法不同。

Adapter:

LoRA(Low-Rank Adaptation):

总结一下:

五、比值比偏好优化 (ORPO)

2024 年 3 月出现了一种称为优势比偏好优化 (ORPO) 的新方法,它将监督微调和偏好调整结合起来。

篇幅有限,具体内容后面文章介绍。

六、DPO

参考文章:DPO微调Mistral 7B

在LLM(例如聊天模型)的训练中,人类反馈强化学习(RLHF)通常采用近端策略优化(Proximal Policy OptimizationPO)方法。这种方法能够有效地使模型的行为与人类偏好保持一致。然而,RLHF的过程既不稳定也复杂。更简单、更稳定地训练LLM模型,使其更好地符合人类的偏好,而无需复杂的强化学习过程。

RLHF(Reinforcement Learning from Human Feedback)实际上在整个过程中涉及到四个不同的模型:

  1. 参考模型(reference model),通过监督式精调(SFT)在指令数据集上训练得到。

  2. 奖励模型(reward model),训练用来预测人类偏好的排名。

  3. 价值模型(value model),通常由奖励模型初始化。

  4. 我们希望通过RLHF训练的模型(policy),通常由参考模型初始化。

    DPO 直接偏好优化(Direct Preference Optimization)则需要两个模型:

1.参考模型,同样是使用SFT在指令数据集上精调得到的。

2.我们希望通过DPO训练的基础模型(base model)。

七、GA-LoRA

参考文章:GA-LoRA实现语言模型全量微调并解决过拟合


GaLore旨在通过低秩投影来提高训练过程中的内存效率,同时允许对模型的全参数进行学习和更新。这种方法通过减少训练过程中所需的内存占用,同时保持模型训练的完整性和效果,从而在一定程度上介于全量微调和参数高效微调(PEFT)之间。

GaLore的特点是它专注于提高内存效率,这是PEFT策略的一个重要目标。然而,与典型的PEFT方法(如只微调模型的一小部分参数)不同,GaLore通过对梯度进行低秩投影来实现内存节省,同时仍然涉及到模型的全参数更新。因此,虽然GaLore采用了与PEFT相似的内存优化目标,但它通过一种独特的方式实现,即允许全参数学习的同时提高内存效率。

综上所述,GaLore可以被视为一种特殊的、介于全量微调和PEFT之间的策略,它通过低秩投影技术来优化内存使用,而不是限制模型参数的更新范围。这使得GaLore在提高训练内存效率的同时,保持了对模型全参数的更新能力。


GaLore(Gradient Low-Rank)支持全量微调(Full Fine-tuning)。全量微调指的是在微调过程中调整模型所有参数,而不是只调整模型的一小部分参数。这与参数效率微调(Parameter-Efficient Fine-Tuning, PEFT)方法如LoRA(低秩适应)相对,后者只调整或优化模型参数的一个子集。


GaLore通过其创新的梯度低秩投影技术,使得即使是在内存限制较大的情况下,也能够进行大型模型的全量微调。它识别出训练过程中梯度的低秩结构,并利用这一特性压缩梯度,从而大幅降低了存储这些梯度所需的内存量。这意味着即便是对那些拥有大量参数的庞大模型,GaLore也能在普通消费级硬件上实现全量微调,而不需要牺牲模型性能或显著增加所需的计算资源。

GaLore提供了一种高效的方式来在资源有限的设置下实现大型语言模型的全量微调,打破了传统上对高端硬件的依赖。在实践中,GaLore 可以在消费级硬件上实现 7B LLM 的预训练和全面微调:






欢迎光临 链载Ai (https://www.lianzai.com/) Powered by Discuz! X3.5