Серия генеративного искусственного интеллекта

Точная настройка LLM: Эффективная точная настройка параметров (PEFT) — LoRA и QLoRA — Часть 2

Эффективная точная настройка параметров — LoRA, QLoRA — практический опыт

В этом блоге мы реализуем LoRA идею, лежащую в основе эффективной точной настройки параметров (PEFT), и исследуем LoRA и QLoRA, два наиболее важных метода PEFT. Мы также будем изучать «Весы и смещения» для сбора показателей обучения. Мы будем дорабатывать небольшую модель параметров Salesforce codegen с 350 м, чтобы повысить эффективность генерации кода Python.

В части 1 мы обсуждали, как LoRA обеспечивает модульность и сокращает время обучения, позволяя нам улучшить базовую модель с помощью модуля адаптера значительно меньших размеров. QLoRA развивает этот подход еще больше, уменьшая размеры базовой модели. Это достигается за счет квантования, которое включает преобразование формата 32 с плавающей запятой в меньшие типы данных, такие как 8-битные или 4-битные.

В этом сообщении блога мы возьмем модель Salesforce codegen 350m и настроим ее для генерации сложного кода Python. Чтобы улучшить его производительность, мы будем использовать набор инструкций Alpaca для более точной настройки и более эффективной генерации кода Python.

Начнем с настройки учетных записей «Обнимающее лицо» и «Вес и предвзятость». Мы будем использовать веса и смещения для сбора показателей обучения.

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

Мы будем использовать веса и смещения для сбора показателей нашего обучения и экспериментов. Для создания учетной записи перейдите по ссылке https://wandb.ai/abvijay/projects. Добавьте новый проект (я добавил python-fine-tuning (https://wandb.ai/abvijay/python-fine-tuning)). Вы найдете ключ API в рабочей области базовой панели управления или в разделе Быстрая помощь в правом верхнем углу.

Теперь приступим к кодированию.

Установка зависимостей

Следующий код показывает различные зависимости и импорт, которые нам понадобятся.

!pip install -q -U trl transformers accelerate git+https://github.com/huggingface/peft.git
!pip install -q datasets bitsandbytes einops 
!pip install -q wandb

from datasets import load_dataset
from random import randrange

import torch
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig, TrainingArguments
from peft import LoraConfig, prepare_model_for_kbit_training, get_peft_model, AutoPeftModelForCausalLM

from trl import SFTTrainer

from huggingface_hub import login

import wandb

Давайте поймем, зачем нам нужны эти различные зависимости

  • trl: Этот пакет Python «Обучение с подкреплением трансформатора» используется для точной настройки модели трансформатора с использованием обучения с подкреплением. Мы будем использовать наш набор данных инструкций для выполнения этого обучения с подкреплением и точной настройки модели. Мы будем использовать объект SFTrainer для точной настройки.
  • 🤗 transformers: Этот пакет предоставляет все API для загрузки и работы с различными предварительно обученными моделями, которые находятся в центре моделей HuggingFace. В нашем примере мы будем загружать Salesforce/codegen-350M-mono. Мы также будем использовать библиотеку битов и байтов из преобразователей для квантования и автотокенизаторы для создания токенизатора для предварительно обученной модели.
  • 🤗 accelerate: Это еще один очень мощный пакет Huggingface, который скрывает сложность разработчика, пытающегося писать/управлять кодом, необходимым для использования нескольких графических процессоров/TPU/fp16.
  • 🤗 peft: Этот пакет предоставляет все API, которые нам понадобятся для выполнения техники LoRA.
  • 🤗 datasets: Этот пакет Huggingface предоставляет доступ к различным наборам данных в хабе Huggingface.
  • wandb: Эта библиотека обеспечивает доступ к библиотеке весов и смещений для сбора различных показателей в процессе тонкой настройки.

В следующем коде мы устанавливаем значения модели, имени набора данных и карты устройства.

model_name = "Salesforce/codegen-350M-mono"
dataset_name = "iamtarun/python_code_instructions_18k_alpaca"
device_map = {"": 0}

Конфигурация ЛоРА

Мы можем определить конфигурацию LoRA, используя LoraConfig. Библиотека Hugginface PEFT поддерживает различные другие методы PEFT, такие как настройка префикса, P-настройка и быстрая настройка. и т. д. Поскольку мы используем метод LoRA, мы используем класс LoraConfig. Код ниже показывает конфигурацию, которую мы будем использовать

peft_config = LoraConfig(
      lora_alpha=16,
      lora_dropout=0.1,
      r=64,
      bias="none",
      task_type="CAUSAL_LM",
)

LoraConfig имеет следующие атрибуты.

  • lora_alpha: коэффициент масштабирования весовых матриц. alpha — это коэффициент масштабирования, который регулирует величину объединенного результата (выходные данные базовой модели + адаптация низкого ранга). Мы установили его равным 16. Более подробную информацию об этом можно найти в документе LoRA здесь.
  • lora_dropout: вероятность выпадения слоев LoRA. Этот параметр используется во избежание переобучения. Этот метод по сути исключает некоторые нейроны как во время прямого, так и обратного распространения, это поможет устранить зависимость от одной единицы нейронов. Мы устанавливаем это значение равным 0,1 (что составляет 10%), что означает, что каждый нейрон имеет вероятность выпадения 10%.
  • r: Это размер матрицы низкого ранга. Более подробную информацию можно найти в Части 1 этого блога. В данном случае мы устанавливаем значение 64 (что фактически означает, что в нашем адаптере LoRA будут параметры 512x64 и 64x512).
  • bias: В этом примере мы не будем тренировать смещение, поэтому устанавливаем значение «none». Если нам нужно обучить смещения, мы можем установить для этого параметра значение «all», или, если мы хотим обучать только смещения LORA, мы можем использовать «lora_only».
  • task_type: Поскольку мы используем причинную языковую модель, тип задачи мы установили на CAUSAL_LM.

Эта конфигурация используется для создания адаптера LoRA поверх базовой модели.

Теперь нам нужно определить конфигурацию QLoRA. Мы определяем его с помощью BitsAndBytesConfig. Следующий код показывает конфигурации QLoRA.

bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_use_double_quant=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype="float16"
)

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

  • load_in_4bit: мы загружаем базовую модель с 4-битным квантованием, поэтому устанавливаем для этого значения значение True.
  • bnb_4bit_use_double_quant: Нам также нужно двойное квантование, чтобы квантовалась даже константа квантования. Поэтому мы устанавливаем для этого параметра значение True.
  • bnb_4bit_quant_type: Мы устанавливаем значение nf4.
  • bnb_4bit_compute_dtype: и тип вычислительных данных, который мы устанавливаем на float16.
from huggingface_hub import notebook_login
# Log in to HF Hub
notebook_login()

wandb.login()
%env WANDB_PROJECT=python-fine-tuning

Давайте теперь посмотрим на набор инструкций, который мы будем использовать для точной настройки модели. Мы будем использовать набор инструкций Python из альпаки 18 тыс. на HuggingFace.

iamtarun/python_code_instructions_18k_alpaca

На следующем снимке экрана показана структура набора инструкций.

Нам нужно будет определить метод для обработки этих наборов инструкций, и этот метод будет вызываться контролируемым тренером точной настройки для преобразования набора данных инструкций в требуемую инструкцию и подсказку. Следующий метод определяет, как преобразовать каждую строку в этом наборе данных в инструкцию/подсказку и вывести ее.

def prompt_instruction_format(sample):
  return f"""### Instruction:
    Use the Task below and the Input given to write the Response, which is a programming code that can solve the following Task:

    ### Task:
    {sample['instruction']}

    ### Input:
    {sample['input']}

    ### Response:
    {sample['output']}
    """

Давайте теперь загрузим набор данных, вызвав load_dataset().

dataset = load_dataset(dataset_name, split=split)

Мы можем загрузить модель с помощью AutoModelForCausalLM и передать конфигурацию QLoRA, чтобы модель загружалась в квантованном виде.

model = AutoModelForCausalLM.from_pretrained(model_name, 
          quantization_config=bnb_config, 
          use_cache = False, 
          device_map=device_map)
model.config.pretraining_tp = 1

Давайте определим токенизатор для модели, используя Huggingface AutoTokenizer.

tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"

Давайте теперь определим различные аргументы обучения. Эти аргументы будут использоваться тренером для точной настройки модели. Давайте подробно рассмотрим каждый из этих аргументов.

trainingArgs = TrainingArguments(
    output_dir=finetunes_model_name,
    num_train_epochs=3,
    per_device_train_batch_size=4,
    gradient_accumulation_steps=2,
    gradient_checkpointing=True,
    optim="paged_adamw_32bit",
    logging_steps=5,
    save_strategy="epoch",
    learning_rate=2e-4,
    weight_decay=0.001,
    max_grad_norm=0.3,
    warmup_ratio=0.03,
    group_by_length=False,
    lr_scheduler_type="cosine",
    disable_tqdm=True,
    report_to="wandb",
    seed=42
)
  • output_dir: Выходной каталог, в котором будут храниться прогнозы модели и контрольные точки.
  • num_train_epochs=3: Количество эпох обучения
  • per_device_train_batch_size=4: Размер пакета на каждый графический процессор для обучения
  • gradient_accumulation_steps=2: Количество шагов обновления для накопления градиентов.
  • gradient_checkpointing=True: Включить контрольную точку градиента. Градиентная контрольная точка — это метод, используемый для уменьшения потребления памяти во время обучения глубоких нейронных сетей, особенно в ситуациях, когда использование памяти является ограничивающим фактором. Градиентная контрольная точка выборочно пересчитывает промежуточные активации во время обратного прохода вместо того, чтобы сохранять их все, тем самым выполняя некоторые дополнительные вычисления для уменьшения использования памяти.
  • optim=”paged_adamw_32bit”: Используемый оптимизатор. Мы будем использовать paged_adamw_32bit.
  • logging_steps=5: Заходите в консоль о прогрессе каждые 5 шагов.
  • save_strategy=”epoch”: сохраняться после каждой эпохи
  • learning_rate=2e-4: Скорость обучения
  • weight_decay=0.001: Снижение веса — это метод регуляризации, используемый при обучении моделей для предотвращения переобучения путем добавления штрафного члена к функции потерь. Уменьшение веса происходит путем добавления члена к функции потерь, который штрафует большие значения весов модели.
  • max_grad_norm=0.3: Этот параметр устанавливает максимальную норму градиента для отсечения градиента.
  • warmup_ratio=0.03: Коэффициент разминки — это значение, которое определяет, какая часть общего количества шагов или эпох тренировки будет использоваться для фазы разминки. В данном случае мы устанавливаем его на 3%. Разминка относится к конкретной стратегии планирования скорости обучения, которая постепенно увеличивает скорость обучения от ее первоначального значения до полного значения в течение определенного количества шагов или эпох обучения.
  • lr_scheduler_type=”cosine”: Планировщики скорости обучения используются для динамической регулировки скорости обучения во время обучения, чтобы улучшить сходимость и производительность модели. Мы будем использовать тип косинуса для планировщика скорости обучения.
  • report_to=”wandb”: Мы хотим сообщить о наших показателях в Weights and Bias.
  • seed=42: Это случайное начальное число, которое задается в начале обучения.

Давайте теперь создадим объект тренера. Здесь мы будем передавать конфигурации LoRA, чтобы обучение проводилось на адаптере низкого ранга, а не на базовой модели.

# Create the trainer
trainer = SFTTrainer(
    model=model,
    train_dataset=dataset,
    peft_config=peft_config,
    max_seq_length=2048,
    tokenizer=tokenizer,
    packing=True,
    formatting_func=prompt_instruction_format,
    args=trainingArgs,
)

При инициализации класса SFTTrainer мы передаем базовую модель для обучения, данные обучения устанавливают конфигурации PEFT, токенизированные, и метод, который необходимо использовать для преобразования данных обучения в «подсказку», как мы указали. упаковка как «Правда». Мы также передаем аргументы обучения, которые мы инициализировали на предыдущем шаге.

Мы можем начать обучение, вызвав метод train().

trainer.train() 

На приведенном выше снимке экрана показаны результаты обучения. Вы также можете увидеть связь с весами и смещениями, а также различные показатели, фиксируемые при потерях и обучении.

На следующих снимках экрана показаны информационные панели на сайте весов и смещений, на которых показаны различные показатели обучения и показатели производительности — использование графического процессора, использование памяти и т. д.

После завершения обучения мы сохраним модель и прекратим сбор метрик.

#stop reporting to wandb
wandb.finish()
# save model
trainer.save_model()

Теперь, когда у нас есть обученная модель, нам нужно объединить обученную модель с базовой моделью. Мы можем сделать это, позвонив по номеру merge_and_unload(). Этот метод объединит слои LORA с базовой моделью. Следующий код показывает объединение и последующую загрузку объединенной модели в хаб HuggingFace.

# Merge LoRA with the base model and save the merged model
merged = trained_model.merge_and_unload()
merged.save_pretrained("merged",safe_serialization=True)
tokenizer.save_pretrained("merged")

#push merged model to the hub
merged.push_to_hub("codegen-350M-mono-python-18k-alpaca")
tokenizer.push_to_hub("codegen-350M-mono-python-18k-alpaca")

На следующем снимке экрана показаны результаты слияния и загрузки объединенной модели в хаб HuggingFace.

Как только он будет загружен, вы сможете найти его в своей учетной записи Huggingface. Ниже приведен скриншот моей модели на хабе HuggingFace.

Давайте теперь сделаем некоторые выводы на основе этих обученных моделей и проверим, действительно ли это повысило эффективность.

instruction="Write a Python program to generate a Markov chain given a text input."
input="Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do: once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it, `and what is the use of a book,' thought Alice `without pictures or conversation?'"

prompt = f"""### Instruction:
Use the Task below and the Input given to write the Response, which is a programming code that can solve the Task.

### Task:
{instruction}

### Input:
{input}

### Response:
"""

input_ids = tokenizer(prompt, return_tensors="pt", truncation=True).input_ids.cuda()
# with torch.inference_mode():

print(f"-------------------------\n\n")
print(f"Prompt:\n{prompt}\n")
print(f"-------------------------\n\n")

print(f"Before Training Response :")
output_before = model.generate(input_ids=input_ids, max_new_tokens=100, do_sample=True, top_p=0.9,temperature=0.5)
print(f"Generated instruction:\n{tokenizer.batch_decode(output_before.detach().cpu().numpy(), skip_special_tokens=True)[0][len(prompt):]}")
print(f"-------------------------\n\n")

print(f"After Training Response :")
outputs = merged.generate(input_ids=input_ids, max_new_tokens=100, do_sample=True, top_p=0.9,temperature=0.5)

print(f"-------------------------\n\n")
print(f"Generated instruction:\n{tokenizer.batch_decode(outputs.detach().cpu().numpy(), skip_special_tokens=True)[0][len(prompt):]}")
print(f"-------------------------\n\n")

Мы запустим одно и то же приглашение как для базовой модели, так и для обученной и объединенной модели. На следующем снимке экрана ясно показано, насколько значительно улучшился вывод кода Python после тонкой настройки.

Давайте теперь очистим память и выполним сборку мусора. На следующем снимке экрана показано использование оперативной памяти графического процессора до сборки мусора.

import gc
# clear the VRAM
import gc
del base_model
del trained_model
del lora_merged_model
del trainer
torch.cuda.empty_cache()
gc.collect()

На следующем снимке экрана показаны выходные данные и оперативная память графического процессора после выполнения кода.

Как видите, нам удалось очистить оперативную память. Обычно рекомендуется делать это до того, как мы сделаем выводы после обучения.

Итак, мы можем использовать методы PEFT LoRA и QLoRA для точной настройки модели с помощью простого графического процессора T4 в Google Bollab. Вы можете получить полный исходный код по моей ссылке GitHub здесь.

Надеюсь, это было полезно. Мне нравилось учиться и делиться своими знаниями. Пожалуйста, не стесняйтесь делиться своими отзывами или ошибками/комментариями.

Тем временем я вернусь с новыми техниками. Веселиться...

До скорой встречи ;-)