Unsloth大模型微调简明教程

TOOL Dec 16, 2024

在本文中,我们将介绍使用 Unsloth 库训练和微调语言模型的过程。我们将分解所提供代码的每个部分,解释其功能和用途。此外,我们还将提供进一步增强和优化项目的技巧。

1、安装和升级所需的库

在开始项目之前,必须安装和更新必要的库。

!pip install unsloth
!pip uninstall unsloth -y && pip install --upgrade --no-cache-dir --no-deps git+https://github.com/unslothai/unsloth.git

说明:

  • 安装 Unsloth 库:第一个命令安装 unsloth 库。
  • 升级 Unsloth 库:第二个命令卸载现有的 unsloth 安装并直接从 GitHub 存储库重新安装最新版本,确保你拥有最新的更新和功能。

技巧:

  • 版本控制:定期检查和管理库版本以避免兼容性问题。
  • 依赖管理:虽然 --no-deps 选项可以通过跳过依赖项来加快安装速度,但请确保在必要时手动管理所有必需的依赖项。

2、导入必要的模块

我们导入将在整个项目中使用的模块。

from unsloth import FastLanguageModel
import torch
from datasets import load_dataset
import os
import json
import re
import random
from sklearn.model_selection import train_test_split

说明:

  • FastLanguageModel:来自 Unsloth 库的类,用于有效处理语言模型。
  • torch:用于深度学习操作的 PyTorch 库。
  • datasets:用于加载和管理数据集的库。
  • osjsonrerandom:用于系统操作、JSON 处理、正则表达式和随机操作的标准 Python 库。
  • train_test_split:来自 scikit-learn 的函数,用于将数据集拆分为训练集和验证集。

技巧:

  • 模块化代码:在开头导入所有必要的模块可以提高可读性并简化调试。

3、设置配置参数

我们定义训练过程中将使用的配置设置。

# Configuration
max_seq_length = 2048
load_in_4bit = True  # Efficient memory usage
dtype = None

# Directory to store checkpoints
checkpoint_dir = "/content/drive/MyDrive/Defense/outputs_Meta-Llama-3.1-8B-bnb-4bit"

说明:

  • max_seq_length:模型可以处理的最大序列长度。
  • load_in_4bit:启用 4 位加载以高效使用内存。
  • dtype:数据类型配置(当前设置为 None)。
  • checkpoint_dir:将保存模型检查点的目录路径。

技巧:

  • 内存管理:使用 4 位加载可显著减少内存消耗,允许在有限的硬件上训练更大的模型。
  • 动态配置:考虑从外部文件(例如 JSON 或 YAML)加载配置参数,以获得更大的灵活性。

4、检查点管理函数

我们定义函数来管理训练期间的模型检查点。

def is_model_processed(model_name):
    checkpoint_path = os.path.join(checkpoint_dir, model_name.split("/")[-1], "checkpoint-500", "trainer_state.json")
    print(f"Checking checkpoint path: {checkpoint_path}")
    return os.path.exists(checkpoint_path)

def mark_model_as_processed(model_name):
    checkpoint_file = os.path.join(checkpoint_dir, f"{model_name.replace('/', '_')}.done")
    print(f"Marking model as processed: {checkpoint_file}")
    with open(checkpoint_file, 'w') as f:
        f.write("")

说明:

  • is_model_processed:通过验证 trainer_state.json 文件是否存在来检查特定模型的检查点是否存在。
  • mark_model_as_processed:通过创建 .done 文件将模型标记为已处理,表明模型已成功训练并保存。

提示:

  • 状态跟踪:使用检查点文件有助于在发生中断时无缝恢复训练。
  • 文件命名:替换模型名称中的特殊字符以防止文件路径出现问题。

5、数据预处理、验证和增强

我们对数据集进行预处理,以确保其干净、有效且经过增强,从而提高模型性能。

def preprocess_dataset(input_path, output_path, train_path, val_path, augmentation_factor=3):
    print("Preprocessing, validating, and augmenting dataset...")
    valid_entries = 0

    def clean_text(text):
        """Normalize and clean text."""
        text = re.sub(r"[^a-zA-Z0-9ğüşıöçĞÜŞİÖÇ.,!?\\-]", " ", text)  # Remove unwanted characters
        text = re.sub(r"\s+", " ", text).strip()  # Remove extra spaces
        return text.lower()  # Normalize to lowercase

    def augment_text(text):
        """Create variations of text for augmentation."""
        synonyms = {
            "highlight": ["emphasize", "focus on", "spotlight"],
            "identify": ["detect", "recognize", "pinpoint"],
            "discuss": ["elaborate on", "examine", "analyze"],
            "important": ["crucial", "key", "essential"]
        }
        for word, replacements in synonyms.items():
            if word in text:
                text = text.replace(word, random.choice(replacements))
        return text

    augmented_data = []
    with open(input_path, 'r', encoding='utf-8') as infile:
        for line in infile:
            try:
                data = json.loads(line)
                if 'instruction' in data and 'input' in data and 'output' in data:
                    cleaned_data = {
                        "instruction": clean_text(data.get("instruction", "")),
                        "input": clean_text(data.get("input", "")),
                        "output": clean_text(data.get("output", ""))
                    }
                    augmented_data.append(cleaned_data)
                    valid_entries += 1

                    for _ in range(augmentation_factor):
                        augmented_entry = {
                            "instruction": augment_text(cleaned_data['instruction']),
                            "input": augment_text(cleaned_data['input']),
                            "output": augment_text(cleaned_data['output'])
                        }
                        augmented_data.append(augmented_entry)
            except json.JSONDecodeError:
                print("Skipping invalid JSON line.")

    print(f"Dataset preprocessing complete. Valid entries: {valid_entries}")

    # Split into train and validation
    train_data, val_data = train_test_split(augmented_data, test_size=0.2, random_state=42)

    # Save datasets
    with open(output_path, 'w', encoding='utf-8') as outfile:
        for entry in augmented_data:
            outfile.write(json.dumps(entry, ensure_ascii=False) + '\n')
    with open(train_path, 'w', encoding='utf-8') as trainfile:
        for entry in train_data:
            trainfile.write(json.dumps(entry, ensure_ascii=False) + '\n')
    with open(val_path, 'w', encoding='utf-8') as valfile:
        for entry in val_data:
            valfile.write(json.dumps(entry, ensure_ascii=False) + '\n')

    print(f"Enhanced dataset saved to {output_path}. Train and validation sets saved to {train_path} and {val_path}.")

说明:

  • clean_text:通过删除不需要的字符、多余的空格并将文本转换为小写来清理和规范化文本。
  • augment_text:通过用同义词替换特定单词来创建变体,从而增强数据集。
  • preprocess_dataset:读取输入数据集,清理和增强数据,将其拆分为训练和验证集,并保存处理后的数据。

提示:

  • 数据清理:调整正则表达式以适应不同的语言或特定的数据集要求。
  • 数据增强:结合更复杂的增强技术,例如释义或反向翻译,以增加数据多样性。
  • 错误处理:增强错误日志记录以捕获有关有问题的数据条目的更多详细信息。

6、定义数据集路径和预处理

我们指定数据集的路径并执行预处理函数。

# Paths to dataset
dataset_input_path = "/content/drive/MyDrive/output.jsonl"
dataset_cleaned_path = "/content/drive/MyDrive/cleaned_dataset.jsonl"
train_dataset_path = "/content/drive/MyDrive/train_dataset.jsonl"
val_dataset_path = "/content/drive/MyDrive/val_dataset.jsonl"

# Preprocess the dataset
preprocess_dataset(dataset_input_path, dataset_cleaned_path, train_dataset_path, val_dataset_path, augmentation_factor=3)

说明:

  • dataset_input_path:原始数据集路径。
  • dataset_cleaned_pa​​th:保存清理和增强数据集的路径。
  • train_dataset_path & val_dataset_path:保存训练和验证子集的路径。
  • preprocess_dataset:使用指定路径和增强因子调用预处理函数。

提示:

  • 数据管理:利用 Google Drive 等云存储解决方案高效处理大型数据集。
  • 文件命名:使用描述性文件名来简化数据集跟踪和管理。

7、列出要进行微调的模型

我们定义了将进行微调的模型列表。

# List of models to try for fine-tuning
fourbit_models = [
    "unsloth/Meta-Llama-3.1-8B-bnb-4bit",
    "unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit",
    "unsloth/Meta-Llama-3.1-70B-bnb-4bit",
    "unsloth/Meta-Llama-3.1-405B-bnb-4bit",
    "unsloth/Mistral-Nemo-Base-2407-bnb-4bit",
    "unsloth/Mistral-Nemo-Instruct-2407-bnb-4bit",
    "unsloth/mistral-7b-v0.3-bnb-4bit",
    "unsloth/mistral-7b-instruct-v0.3-bnb-4bit",
    "unsloth/Phi-3.5-mini-instruct",
    "unsloth/Phi-3-medium-4k-instruct",
    "unsloth/gemma-2-9b-bnb-4bit",
    "unsloth/gemma-2-27b-bnb-4bit",
]

说明:

此列表包含来自 Unsloth 的各种 4 位模型,这些模型将进行微调。这些模型的大小和配置各不相同,针对不同的任务进行了优化。

提示:

  • 模型选择:根据任务的具体要求选择模型,在性能和计算资源之间取得平衡。
  • 多样性:尝试不同的架构,以确定哪种架构最适合你的使用案例。

8、顺序加载和测试模型

我们遍历列表中的每个模型,加载它,并检查现有检查点以在可用的情况下恢复训练。

# Load and test models sequentially
for model_name in fourbit_models:
    print(f"Processing model: {model_name}")
    model_dir = os.path.join(checkpoint_dir)
    print(f"Model directory: {model_dir}")

    checkpoint_path = os.path.join(model_dir, "checkpoint-500", "trainer_state.json")
    print(f"Checkpoint path: {checkpoint_path}")

    if os.path.exists(checkpoint_path):
        print(f"Resuming from checkpoint: {checkpoint_path}")
        print(f"Files in checkpoint directory: {os.listdir(os.path.dirname(checkpoint_path))}")
        model, tokenizer = FastLanguageModel.from_pretrained(
            os.path.dirname(checkpoint_path),
            max_seq_length=max_seq_length,
            dtype=dtype,
            load_in_4bit=load_in_4bit,
        )
    else:
        print(f"Starting training for model: {model_name}")
        model, tokenizer = FastLanguageModel.from_pretrained(
            model_name=model_name,
            max_seq_length=max_seq_length,
            dtype=dtype,
            load_in_4bit=load_in_4bit,
        )

    print(f"Loaded model: {model_name}")

说明:

  • 模型处理循环:遍历 fourbit_models 列表中的每个模型。
  • 检查点检查:验证当前模型是否存在检查点以恢复训练;否则,从头开始训练。
  • 模型和标记器加载:使用 FastLanguageModel.from_pretrained 加载模型和标记器,可以从检查点加载,也可以直接从模型名称加载。

提示:

  • 并行处理:考虑并行处理多个模型以节省时间,前提是有足够的计算资源。
  • 检查点策略:实施强大的检查点策略,以最大限度地减少数据丢失并促进无缝训练恢复。

9、配置 LoRA 进行微调

我们将低秩自适应 (LoRA) 应用于模型以实现高效微调。

# LoRA Configuration for fine-tuning
    model = FastLanguageModel.get_peft_model(
        model,
        r=16,
        target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
                        "gate_proj", "up_proj", "down_proj"],
        lora_alpha=16,
        lora_dropout=0,
        bias="none",
        use_gradient_checkpointing="unsloth",
        random_state=42,
    )
    print(f"LoRA configuration done for model: {model_name}")

说明:

PEFT 模型:将 LoRA 应用于模型的选定模块,以实现高效微调。参数如下:

  • r:LoRA 等级值,较低的值可节省内存。
  • target_modules:指定要适应哪些层。
  • lora_alpha:学习率的缩放因子。
  • lora_dropout:正则化的 Dropout 率。
  • bias:偏差配置设置。
  • use_gradient_checkpointing:启用梯度检查点以提高内存效率。
  • random_state:通过设置随机种子确保可重复性。

提示:

  • LoRA 超参数:尝试不同的 r 和 lora_alpha 值,以找到性能和资源使用之间的最佳平衡。
  • 层选择:谨慎选择要适应的层,因为这会显著影响性能和训练时间。

10、定义格式化提示

我们创建一个模板,以模型可以理解的方式格式化数据集示例。

    # Define the formatting prompt
    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  # Ensures proper sequence termination
    def formatting_prompts_func(examples):
        print("Formatting dataset prompts...")
        instructions = examples.get("instruction", "")
        inputs = examples.get("input", "")
        outputs = examples.get("output", "")
        texts = []
        for instruction, input, output in zip(instructions, inputs, outputs):
            text = alpaca_prompt.format(instruction, input, output) + EOS_TOKEN
            texts.append(text)
        return {"text": texts}

说明:

  • alpaca_prompt:一个模板,它使用指令、输入和预期响应构造每个数据集示例。
  • formatting_prompts_func:将 alpaca_prompt 应用于数据集中的每个示例,附加一个序列结束 (EOS) 标记以确保正确终止。

提示:

  • 提示工程:尝试不同的提示结构,以确定哪种格式可产生最佳模型性能。
  • 语言一致性:确保提示语言与数据集语言匹配,以保持一致性并提高模型理解。

11、加载和格式化训练和验证数据集

我们加载训练和验证数据集并应用格式化函数。

# Load the train and validation datasets
    print(f"Loading train dataset from: {train_dataset_path}")
    train_dataset = load_dataset("json", data_files=train_dataset_path, split="train")
    print(f"Loading validation dataset from: {val_dataset_path}")
    val_dataset = load_dataset("json", data_files=val_dataset_path, split="train")
    
    train_dataset = train_dataset.map(formatting_prompts_func, batched=True)
    val_dataset = val_dataset.map(formatting_prompts_func, batched=True)
    print("Datasets loaded and formatted.")

说明:

  • load_dataset:从 JSON 文件加载训练和验证数据集。
  • map:将 formatting_prompts_func 分批应用于每个数据集,根据定义的提示格式化数据。

提示:

  • 批处理:使用 batched=True 可加快大型数据集的处理速度。
  • 数据验证:格式化后,进行快速检查以确保提示结构正确。

12、配置 SFTTrainer 进行训练

我们使用 SFTTrainer 设置训练配置来管理训练过程。

    from trl import SFTTrainer
    from transformers import TrainingArguments
    from unsloth import is_bfloat16_supported

    resume_checkpoint_dir = os.path.join(model_dir, "checkpoint-500")
    print(f"Before Resuming from checkpoint: {resume_checkpoint_dir}")
    if os.path.exists(os.path.join(resume_checkpoint_dir, "trainer_state.json")):
        print(f"Resuming from checkpoint: {resume_checkpoint_dir}")
        print(f"Checkpoint Files: {os.listdir(resume_checkpoint_dir)}")
        resume_from_checkpoint = resume_checkpoint_dir
    else:
        print("No valid checkpoint found, starting from scratch.")
        resume_from_checkpoint = None

    trainer = SFTTrainer(
        model=model,
        tokenizer=tokenizer,
        train_dataset=train_dataset,
        eval_dataset=val_dataset,
        dataset_text_field="text",
        max_seq_length=max_seq_length,
        dataset_num_proc=2,
        packing=False,
        args=TrainingArguments(
            per_device_train_batch_size=4,
            gradient_accumulation_steps=8,
            warmup_steps=50,
            max_steps=1000,
            save_steps=500,
            save_total_limit=2,
            learning_rate=3e-4,
            fp16=not is_bfloat16_supported(),
            bf16=is_bfloat16_supported(),
            logging_steps=10,
            optim="adamw_8bit",
            weight_decay=0.01,
            lr_scheduler_type="linear",
            seed=42,
            output_dir=model_dir,
            report_to="none",
            resume_from_checkpoint=resume_from_checkpoint,  # Pass checkpoint path
        ),
    )

说明:

  • SFTTrainer:来自 trl 库的训练器类,专为监督微调而设计。
  • TrainingArguments:配置各种训练参数,如批量大小、学习率、优化器等。
  • per_device_train_batch_size:每个设备(GPU/CPU)的批量大小。
  • gradient_accumulation_steps:更新前累积梯度的步骤数。
  • warmup_steps:学习率调度程序的预热步骤数。
  • max_steps:总训练步骤数。
  • save_steps:保存检查点的频率。
  • learning_rate:优化器的学习率。
  • fp16bf16:混合精度训练选项,可加快计算速度并减少内存使用量。
  • optim:优化器类型(adamw_8bit 用于提高内存效率)。
  • weight_decay:正则化的权重衰减。
  • lr_scheduler_type:学习率调度程序的类型。
  • seed:用于重现性的随机种子。
  • output_dir:保存训练输出的目录。
  • resume_from_checkpoint:如果可用,从检查点恢复训练的路径。

提示:

  • 超参数调整:调整学习率、批量大小和梯度累积步骤等超参数会显著影响模型性能。
  • 混合精度训练:利用 FP16 或 BF16 可以加速训练并减少内存使用量,而不会牺牲模型性能。
  • 检查点管理:定期保存检查点可以在发生中断时恢复训练并促进实验。

13、开始和完成模型训练

我们启动训练过程并监控其完成情况。

print("Starting training...")
    trainer_stats = trainer.train(resume_from_checkpoint=resume_from_checkpoint)
    print("Training completed.")

说明:

  • trainer.train:开始训练过程。如果有检查点可用,则从该点恢复训练。
  • trainer_stats:包含与训练过程相关的统计数据和日志。

提示:

  • 监控:使用 TensorBoard 或 Weights & Biases 等工具实时监控训练指标。
  • 提前停止:当验证集上的性能停止改善时,通过停止训练来实现提前停止以防止过度拟合。

14、保存微调模型

训练后,我们保存微调模型和标记器以供将来使用。

# Save the fine-tuned model
    print(f"Saving model to: {model_dir}")
    model.save_pretrained(model_dir)
    tokenizer.save_pretrained(model_dir)
    print(f"Model {model_name} fine-tuned and saved to {model_dir}")
    mark_model_as_processed(model_name)
    print("----------------------------------------")

说明

  • save_pretrained:将微调后的模型和 tokenizer 保存到指定目录。
  • mark_model_as_processed:创建一个 .done 文件,表示模型已成功处理并保存。

提示:

  • 模型版本控制:实现版本控制系统,以跟踪不同的微调模型版本。
  • 存储优化:使用压缩技术节省存储空间,尤其是对于大型模型。

15、增强项目的提示

要进一步改进你的项目,请考虑以下提示:

a) 扩展数据增强技术

通过结合更多样化的增强方法来增强数据集,例如改变句子结构或使用高级同义词替换。

b) 超参数优化

使用网格搜索或随机搜索等技术来找到超参数的最佳组合,这可以显著提高模型性能。

c) 模型并行化
对于训练大型模型,实现模型并行化技术以减少训练时间。这可以包括分布式训练或模型分片。

d) 性能监控和分析
集成 TensorBoard 或 Weights & Biases 等工具来监控和分析训练期间的模型性能。这有助于了解训练动态并尽早发现问题。

e) 尝试更多模型
尝试不同的模型架构和大小,以确定哪种最适合您的特定任务。随时了解最新模型以利用新进展。

f) 自动化微调过程
创建脚本或管道以自动化训练和微调过程。自动化可减少重复任务并最大限度地降低出错风险。

16、结束语

在本文中,我们彻底探讨了如何使用 Unsloth 库训练和微调语言模型。我们介绍了数据预处理、模型加载、LoRA 配置和训练过程。此外,我们还提供了各种技巧来增强和优化您的深度学习项目。我们希望本指南能成为你自己项目的宝贵资源。

深度学习项目可能很复杂,但通过采取循序渐进的方法并利用正确的工具,你可以取得成功的结果。像 Unsloth 这样的库简化了模型训练和微调的过程,使其更容易使用。我们祝愿你的项目取得成功!


原文链接:Deep Learning Project: Training and Fine-Tuning a Language Model with Unsloth

汇智网翻译整理,转载请标明出处

Tags