Подробное пошаговое руководство по тонкой настройке любого LLM.

В этом блоге я проведу вас через процесс тонкой настройки модели Meta Llama 2 7B для категоризации новостных статей по 18 различным категориям. Я буду использовать набор данных инструкций по классификации новостей, созданный ранее с помощью GPT 3.5. Если вам интересно, как я создал этот набор данных и мотивация этого мини-проекта, вы можете обратиться к моему предыдущему блогу или блокноту, где я обсуждаю детали.

Цель этой записной книжки — предоставить всеобъемлющее пошаговое руководство по тонкой настройке любой LLM (большой языковой модели). В отличие от многих доступных руководств, я подробно объясню каждый шаг, охватывая все используемые классы, функции и параметры.

Это руководство будет разделено на две части:

Часть 1. Настройка и подготовка к тонкой настройке

  1. Установка и загрузка необходимых модулей
  2. Шаги, чтобы получить одобрение для семейства моделей Meta Llama 2
  3. Настройка интерфейса командной строки Hugging Face и аутентификации пользователей
  4. Загрузка предварительно обученной модели и связанного с ней токенизатора
  5. Загрузка обучающего набора данных
  6. Предварительная обработка обучающего набора данных для тонкой настройки модели

Часть 2. Тонкая настройка и открытый исходный код [Этот блог]

  1. Настройка метода PEFT (эффективной тонкой настройки параметров) QLoRA для эффективной тонкой настройки
  2. Тонкая настройка предварительно обученной модели
  3. Сохранение настроенной модели и связанного с ней токенизатора
  4. Отправка доработанной модели в Hugging Face Hub для публичного использования.

Обратите внимание, что запустить это на процессоре практически невозможно. Если вы работаете в Google Colab, перейдите в раздел «Среда выполнения» > «Изменить тип среды выполнения». Измените аппаратный ускоритель на GPU. Измените тип графического процессора на T4. Измените форму среды выполнения на High-RAM.

Давайте начнем!

Создание конфигурации PEFT

Точная настройка предварительно обученных LLM на нисходящих наборах данных приводит к огромному приросту производительности по сравнению с использованием готовых предварительно обученных LLM. Однако по мере того, как модели становятся все больше и больше, полная точная настройка становится невозможной для обучения на потребительском оборудовании. Кроме того, хранение и развертывание точно настроенных моделей независимо для каждой последующей задачи становится очень дорогим, поскольку точно настроенные модели имеют тот же размер, что и исходная предварительно обученная модель. Подходы Parameter-Efficient Fine-tuning (PEFT) предназначены для решения обеих проблем!

Подходы PEFT точно настраивают только небольшое количество (дополнительных) параметров модели, при этом замораживая большинство параметров предварительно обученных LLM, тем самым значительно снижая затраты на вычисления и хранение. Это также помогает в переносимости, поскольку пользователи могут настраивать модели с помощью методов PEFT, чтобы получить крошечные контрольные точки стоимостью в несколько МБ по сравнению с большими контрольными точками полной тонкой настройки.

Короче говоря, подходы PEFT позволяют получить производительность, сравнимую с полной тонкой настройкой, при наличии лишь небольшого количества обучаемых параметров.

Hugging Face предоставляет библиотеку PEFT, в которой реализованы новейшие методы параметрически эффективной точной настройки, плавно интегрированные с Hugging Face Transformers и Hugging Face Accelerate.

Существует несколько методов PEFT. В следующей ячейке мы будем использовать QLoRA, один из последних методов, который уменьшает использование памяти при тонкой настройке LLM без компромиссов в производительности, используя класс LoraConfig из библиотеки peft.

QLoRA использует 4-битное квантование для сжатия предварительно обученной языковой модели. Затем параметры LM замораживаются, и к модели добавляется относительно небольшое количество обучаемых параметров в виде низкоранговых адаптеров. Во время тонкой настройки QLoRA передает обратно градиенты через замороженную 4-битную квантованную предварительно обученную языковую модель в адаптеры низкого ранга. Слои LoRA — единственные параметры, которые обновляются во время обучения.

def create_peft_config(r, lora_alpha, target_modules, lora_dropout, bias, task_type):
    """
    Creates Parameter-Efficient Fine-Tuning configuration for the model

    :param r: LoRA attention dimension
    :param lora_alpha: Alpha parameter for LoRA scaling
    :param modules: Names of the modules to apply LoRA to
    :param lora_dropout: Dropout Probability for LoRA layers
    :param bias: Specifies if the bias parameters should be trained
    """
    config = LoraConfig(
        r = r,
        lora_alpha = lora_alpha,
        target_modules = target_modules,
        lora_dropout = lora_dropout,
        bias = bias,
        task_type = task_type,
    )

    return config

Поиск модулей для приложения LoRA

В следующей ячейке мы определим функцию find_all_linear_names, чтобы найти модуль, к которому нужно применить LoRA. Эта функция получит имена модулей из model.named_modules() и сохранит их в наборе, чтобы сохранить разные имена модулей.

def find_all_linear_names(model):
    """
    Find modules to apply LoRA to.

    :param model: PEFT model
    """

    cls = bnb.nn.Linear4bit
    lora_module_names = set()
    for name, module in model.named_modules():
        if isinstance(module, cls):
            names = name.split('.')
            lora_module_names.add(names[0] if len(names) == 1 else names[-1])

    if 'lm_head' in lora_module_names:
        lora_module_names.remove('lm_head')
    print(f"LoRA module names: {list(lora_module_names)}")
    return list(lora_module_names)

Расчет обучаемых параметров

Мы можем использовать функцию print_trainable_parameters, чтобы узнать количество и процент параметров обучаемой модели. Эта функция рассчитает общее количество параметров в model.named_parameters(), а затем те, которые будут обновлены.

def print_trainable_parameters(model, use_4bit = False):
    """
    Prints the number of trainable parameters in the model.

    :param model: PEFT model
    """

    trainable_params = 0
    all_param = 0

    for _, param in model.named_parameters():
        num_params = param.numel()
        if num_params == 0 and hasattr(param, "ds_numel"):
            num_params = param.ds_numel
        all_param += num_params
        if param.requires_grad:
            trainable_params += num_params

    if use_4bit:
        trainable_params /= 2

    print(
        f"All Parameters: {all_param:,d} || Trainable Parameters: {trainable_params:,d} || Trainable Parameters %: {100 * trainable_params / all_param}"
    )

Тонкая настройка предварительно обученной модели

Мы создадим fine_tune, нашу последнюю функцию, чтобы обернуть все, что мы сделали до сих пор, и начать процесс тонкой настройки. Эта функция будет выполнять следующие операции предварительной обработки модели, чтобы подготовить ее к обучению:

  1. Включите контрольные точки градиента, чтобы уменьшить использование памяти во время тонкой настройки.
  2. Используйте функцию prepare_model_for_kbit_training из PEFT, чтобы подготовить модель к точной настройке.
  3. Вызовите find_all_linear_names`, чтобы получить имена модулей, к которым нужно применить LoRA.
  4. Создайте конфигурацию LoRA, вызвав функцию create_peft_config.
  5. Оберните базовую модель Hugging Face для точной настройки PEFT с помощью функции get_peft_model.
  6. Распечатайте обучаемые параметры.

Для обучения мы создадим экземпляр объекта Trainer() в функции fine_tune. Для этого класса требуется модель, предварительно обработанный набор данных и обучающие аргументы, перечисленные ниже.

per_device_train_batch_size: Размер пакета на GPU/TPU/CPU для обучения.

gradient_accumulation_steps: количество шагов обновления для накопления градиентов перед выполнением прохода назад/обновления.

warmup_steps: Количество шагов, используемых для линейного прогрева, от 0 до learning_rate.

max_steps: если установлено положительное число, общее количество шагов обучения, которое необходимо выполнить.

learning_rate: начальная скорость обучения для Адама.

fp16: Использовать ли 16-битное (смешанное) обучение точности (через NVIDIA apex) вместо 32-битного обучения.

logging_steps: количество шагов обновления между двумя журналами.

output_dir: выходной каталог, в который будут записаны прогнозы модели и контрольные точки.

optim: Оптимизатор для обучения.

Далее мы будем использовать метод train для объекта тренера, чтобы начать обучение, зарегистрировать и сохранить показатели модели в наборе обучающих данных. Наконец, мы сохраним контрольную точку модели (веса модели, файл конфигурации и токенизатор) в выходном каталоге и удалим модель, чтобы освободить память. Вы можете загрузить модель для вывода позже, используя ее сохраненную контрольную точку.

def fine_tune(model,
          tokenizer,
          dataset,
          lora_r,
          lora_alpha,
          lora_dropout,
          bias,
          task_type,
          per_device_train_batch_size,
          gradient_accumulation_steps,
          warmup_steps,
          max_steps,
          learning_rate,
          fp16,
          logging_steps,
          output_dir,
          optim):
    """
    Prepares and fine-tune the pre-trained model.

    :param model: Pre-trained Hugging Face model
    :param tokenizer: Model tokenizer
    :param dataset: Preprocessed training dataset
    """

    # Enable gradient checkpointing to reduce memory usage during fine-tuning
    model.gradient_checkpointing_enable()

    # Prepare the model for training 
    model = prepare_model_for_kbit_training(model)

    # Get LoRA module names
    target_modules = find_all_linear_names(model)

    # Create PEFT configuration for these modules and wrap the model to PEFT
    peft_config = create_peft_config(lora_r, lora_alpha, target_modules, lora_dropout, bias, task_type)
    model = get_peft_model(model, peft_config)

    # Print information about the percentage of trainable parameters
    print_trainable_parameters(model)

    # Training parameters
    trainer = Trainer(
        model = model,
        train_dataset = dataset,
        args = TrainingArguments(
            per_device_train_batch_size = per_device_train_batch_size,
            gradient_accumulation_steps = gradient_accumulation_steps,
            warmup_steps = warmup_steps,
            max_steps = max_steps,
            learning_rate = learning_rate,
            fp16 = fp16,
            logging_steps = logging_steps,
            output_dir = output_dir,
            optim = optim,
        ),
        data_collator = DataCollatorForLanguageModeling(tokenizer, mlm = False)
    )

    model.config.use_cache = False

    do_train = True

    # Launch training and log metrics
    print("Training...")

    if do_train:
        train_result = trainer.train()
        metrics = train_result.metrics
        trainer.log_metrics("train", metrics)
        trainer.save_metrics("train", metrics)
        trainer.save_state()
        print(metrics)

    # Save model
    print("Saving last checkpoint of the model...")
    os.makedirs(output_dir, exist_ok = True)
    trainer.model.save_pretrained(output_dir)

    # Free memory for merging weights
    del model
    del trainer
    torch.cuda.empty_cache()

Инициализация параметров QLoRA и TrainingArguments ниже для обучения.

################################################################################
# QLoRA parameters
################################################################################

# LoRA attention dimension
lora_r = 16

# Alpha parameter for LoRA scaling
lora_alpha = 64

# Dropout probability for LoRA layers
lora_dropout = 0.1

# Bias
bias = "none"

# Task type
task_type = "CAUSAL_LM"

################################################################################
# TrainingArguments parameters
################################################################################

# Output directory where the model predictions and checkpoints will be stored
output_dir = "./results"

# Batch size per GPU for training
per_device_train_batch_size = 1

# Number of update steps to accumulate the gradients for
gradient_accumulation_steps = 4

# Initial learning rate (AdamW optimizer)
learning_rate = 2e-4

# Optimizer to use
optim = "paged_adamw_32bit"

# Number of training steps (overrides num_train_epochs)
max_steps = 20

# Linear warmup steps from 0 to learning_rate
warmup_steps = 2

# Enable fp16/bf16 training (set bf16 to True with an A100)
fp16 = True

# Log every X updates steps
logging_steps = 1

Вызов функции fine_tune ниже для точной настройки или настройки инструкций предварительно обученной модели в нашем предварительно обработанном наборе данных инструкций по классификации новостей.

fine_tune(model, tokenizer, preprocessed_dataset, lora_r, lora_alpha, lora_dropout, bias, task_type, per_device_train_batch_size, gradient_accumulation_steps, warmup_steps, max_steps, learning_rate, fp16, logging_steps, output_dir, optim)

С помощью этих шагов мы точно настроили популярную предварительно обученную модель с открытым исходным кодом Llama-2–7B на наборе данных инструкций, который мы создали для классификации новостей!

Из лога видно, что в модели 3 540 389 888 параметров, из которых 39 976 960 поддаются обучению. Это примерно 1% от всех параметров. Модель обучалась на 20 шагах и сошлась при значении потерь 1,4. Возможно, что сходящиеся веса не являются лучшими весами. Мы можем исправить это, добавив EarlyStoppingCallback к trainer, что позволит регулярно оценивать модель в наборе данных проверки и сохранять только лучшие веса.

Слияние весов и отталкивание, чтобы обнять лицо

После сохранения точно настроенных весов мы можем создать нашу точную модель, объединив точно настроенные веса и сохранив ее в новом каталоге с его токенизатором. Выполнив этот шаг, мы можем получить точно настроенную модель с эффективным использованием памяти и токенизатор для вывода. Мы также отправим доработанную модель и связанный с ней токенизатор в Hugging Face Hub для публичного использования.

# Load fine-tuned weights
model = AutoPeftModelForCausalLM.from_pretrained(output_dir, device_map = "auto", torch_dtype = torch.bfloat16)
# Merge the LoRA layers with the base model
model = model.merge_and_unload()

# Save fine-tuned model at a new location
output_merged_dir = "results/news_classification_llama2_7b/final_merged_checkpoint"
os.makedirs(output_merged_dir, exist_ok = True)
model.save_pretrained(output_merged_dir, safe_serialization = True)

# Save tokenizer for easy inference
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.save_pretrained(output_merged_dir)

# Fine-tuned model name on Hugging Face Hub
new_model = "sahayk/news-classification-18-llama-2-7b"

# Push fine-tuned model and tokenizer to Hugging Face Hub
model.push_to_hub(new_model, use_auth_token = True)
tokenizer.push_to_hub(new_model, use_auth_token = True)

Посмотрите доработанную модель Hugging Face: https://huggingface.co/sahayk/news-classification-18-llama-2-7b.

Проверьте мой Блокнот Google Colab для получения полного кода.

Заключение

С помощью этих шагов мы точно настроили Llama 2 7B для решения конкретной задачи: классификации новостных статей по 18 категориям. Фактически, вы должны иметь возможность точно настроить любой LLM на любом общедоступном или пользовательском наборе данных и открыть его исходный код на Hugging Face Hub, выполнив следующие шаги.

Спасибо за чтение!

Отсылки: Обнимающее лицо, Филипп Шмид, OVHcloud