微调大型语言模型:根据你的需求定制Llama3-8B

2024年05月06日 由 alex 发表 985 0

自 2022 年 11 月发布以来,ChatGPT 引发了关于大型语言模型(LLM)和人工智能能力的广泛讨论。现在,很少有人没听说过 ChatGPT 或用它做过实验了。虽然像 GPT、Gemini 或 Claude 这样的工具都非常强大,拥有数千亿(甚至上万)个参数,并在大量文本库中进行了预训练,但它们并不是万能的。在一些特定的任务中,这些模型存在不足。但是,我们并非没有解决这些任务的办法。我们可以利用较小的开源模型来发挥 LLM 的威力,使其适应我们的特定问题。


本文旨在简要介绍几个较小的开源 LLM,并解释 LLM 微调的两个关键概念: 量化和 LoRA。此外,我们还将介绍几个最流行的微调库以及代码示例,以便你可以快速将这些概念应用到你的使用案例中。让我们深入了解微调。


小 "大语言模型


7


对 LLM 进行微调的成本可能高得令人望而却步,尤其是对于参数数量较多的模型。根据经验,100 亿参数以下的模型通常可以进行微调,而不会遇到重大的基础设施挑战。然而,对于像 Llama 3 70B 这样的大型模型,则需要大量资源。微调像 Llama 3 这样的 70B 参数模型需要大约 1.5 TB 的 GPU vRAM。从这个角度来看,这个数量的 vRAM 相当于一个由大约 20 个 Nvidia A100 组成的集群,每个集群拥有 80GB 的 vRAM。假设硬件可用,这样一个集群的成本约为 40 万美元。


另外,也可以使用 AWS、Azure 或 GCP 等云提供商,但这种方法的成本也很高。例如,在 AWS 上使用 Nvidia A100 GPU 一小时的成本为 40 美元。如果要在 20 个 GPU 上对 70B 模型进行 5 天的微调,则需要花费约 10 万美元。


由于这些成本,大多数从业者主要使用参数少于 100 亿的小型 LLM。这些模型的训练成本更低,只需要 16GB 到 24GB 的 vRAM(用于更大的批量和更快的训练)。例如,我在 AWS 上使用 Nvidia A10 对 Mistral 7B 进行了塞尔维亚语的微调,用时不到 10 个小时,花费不到 20 美元。


当然,如果不进行量化,特别是量化到 4 位,7B 模型仍然无法在这么多的 vRAM 上适应和训练。


量化

如果使用完整的 32 位参数,我们仍然需要大量的 vRAM(按照凡人的标准)来训练 LLM,大约需要 150GB 左右。


8


量化技术通过将模型参数转换为低精度数据类型(如 8 位或 4 位)提供了一种解决方案,从而大大减少了内存消耗并提高了执行速度。这个概念很简单:将所有可能的 32 位数值映射到更小范围的有限数值(例如,8 位转换为 256)。这一过程可以形象地理解为将高精度数值围绕几个固定点进行分组,这些固定点代表其附近的数值。


低库自适应 (LoRA)

LoRA 是一种通过矩阵降维来更新模型权重的技术。由于在 LLM 中广泛使用的变换器在很大程度上依赖于矩阵,因此这种技术尤为重要。


9


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


下图是普通微调与 LoRA 微调的对比示意图,改编自 Sebastian Raschka 的博文。


10


LoRA 的主要优势在于,虽然近似的精确度略低,但却能显著提高内存和计算效率。例如,假设一个矩阵有 1000x1000 个参数,总计 100 万个参数。通过使用 1000x100 乘以 100x1000 矩阵的分解版本(精确度稍低),参数数只需 2*100k,从而减少了 80% 的参数。


量化和 LoRA 经常结合使用,形成所谓的 QLoRA。


无缝

如果要重新开始 LLM 微调,我会选择 Unsloth Python 库。Unsloth 为 LLM 微调量身定制了各种优化,并支持各种流行的 LLM,包括 Mistral、Llama 3、Gemma 等。例如,他们的免费层为 Mistral 提供了 12 种不同的微调优化,显著提高了 2.2 倍的速度。


11


下面的代码片段演示了如何使用 Unsloth 库对 Llama 3 8B 进行微调。


导入 4 位模型:


model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "unsloth/llama-3-8b-bnb-4bit","unsloth/llama-3-8b-bnb-4bit",
    max_seq_length = max_seq_length,
    dtype = dtype,
    load_in_4bit = load_in_4bit,
    # token = "hf_...", # use one if using gated models like meta-llama/Llama-2-7b-hf
)


设置 LoRA:


model = FastLanguageModel.get_peft_model(
    model,
    r = 16, # Choose any number > 0 ! Suggested 8, 16, 32, 64, 12816, # 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
)


初始化Hugging Face 的监督微调训练器:


trainer = SFTTrainer(
    model = model,
    tokenizer = tokenizer,
    train_dataset = dataset,
    dataset_text_field = "text","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",
    ),
)


训练模型


trainer_stats = trainer.train()


监督微调训练器 (SFT)

在对 LLM 进行预训练后,下一个关键步骤就是监督微调。这一过程对于开发能理解并生成连贯反应的模型至关重要,而不仅仅是完成句子。


Hugging Face 的 SFT(监督微调训练器)和 PEFT(参数高效微调)等工具,以及 Tim Dettmers 的 BitsAndBytes,大大简化了对模型应用 LoRA、量化和微调等技术的过程。这些库简化了高级优化方法的实施,使开发人员和研究人员都能更方便、更高效地使用这些方法。


下面,你会发现 Unsloth、SFT 和 ORPO 的代码非常相似。这种相似性源于这些库背后的基本思想大体相同,不同之处主要在于库本身,也可能在于某些超参数。


导入 4 位模型


# Hugging Face model id
model_id = "meta-llama/Meta-Llama-3-8B"
model_id = "mistralai/Mistral-7B-v0.1"
# BitsAndBytesConfig int-4 config 
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
)
# Load model and tokenizer
model = AutoModelForCausalLM.from_pretrained(
    model_id, 
    quantization_config=bnb_config, 
    use_cache=False, 
    device_map="auto",
    token = os.environ["HF_TOKEN"], # if model is gated like llama or 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"], # if model is gated like llama or mistral
)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"


设置 LoRA:


# LoRA config based on QLoRA paper
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",
        ]
)
# Prepare model for training
model = prepare_model_for_kbit_training(model)


初始化Hugging Face 的监督微调训练器:


args = TrainingArguments(
    output_dir="mistral-int4-alpaca","mistral-int4-alpaca",
    num_train_epochs=1,
    per_device_train_batch_size=6 if use_flash_attention2 else 2, # you can play with the batch size depending on your hardware
    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,2048,
    tokenizer=tokenizer,
    packing=True,
    formatting_func=format_instruction, 
    args=args,
)


训练模型


trainer.train()


赔率偏好优化 (ORPO)

在本文中,我们重点介绍了 LLM 的预训练和监督微调。然而,所有 SOTA LLM 都要经历另一个关键步骤:偏好调整。这一步发生在预训练和微调之后,在这一步中,你要告知模型哪些生成的输出是可取的,哪些是不可取的。偏好调整的常用方法包括从人类反馈中强化学习(RLHF)和直接偏好优化(DPO)。


2024 年 3 月出现了一种名为 "赔率偏好优化"(ORPO)的新方法,它将监督微调和偏好调整结合在一起。


12


导入 4 位模型:


# Model
base_model = "meta-llama/Meta-Llama-3-8B"
new_model = "OrpoLlama-3-8B"
# QLoRA config
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch_dtype,
    bnb_4bit_use_double_quant=True,
)
# Load tokenizer
tokenizer = AutoTokenizer.from_pretrained(base_model)
# Load model
model = AutoModelForCausalLM.from_pretrained(
    base_model,
    quantization_config=bnb_config,
    device_map="auto",
    attn_implementation=attn_implementation
)


设置 LoRA:


# LoRA config
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,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"],"train"],
    eval_dataset=dataset["test"],
    peft_config=peft_config,
    tokenizer=tokenizer,
)


训练模型


trainer.train()


结论

虽然 GPT、Gemini 或 Claude 等大型语言模型 (LLM) 功能强大,但其庞大的规模和资源需求使它们在许多任务中都不实用。为了解决这个问题,可以使用量化和低级自适应(LoRA)等技术对较小的开源 LLM 进行微调和定制,以满足特定需求。这些技术降低了内存消耗,提高了计算效率,使训练模型的成本更低,尤其是参数少于 10B 的模型。


Unsloth、Supervised Finetuning Trainer(SFT)和Odds Ratio Preference Optimization(ORPO)等工具简化了微调过程,使其更易于使用。例如,Unsloth 提供的优化可以显著加快训练速度,而 ORPO 则将监督微调与偏好调整相结合,以提高模型性能。


通过利用这些技术和工具,开发人员和研究人员可以根据自己的特定需求定制LLM,而无需支付与训练大型模型相关的高昂成本。这种方法使高级语言模型的访问民主化,并支持跨不同领域的广泛应用。


文章来源:https://medium.com/@miloszivic99/finetuning-large-language-models-customize-llama-3-8b-for-your-needs-bfe0f43cd239
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
写评论取消
回复取消