QLoRA Обучение большой языковой модели на GPU емкостью 16 ГБ.

Обучение большой языковой модели QLoRA на GPU с емкостью 16 ГБ.

Давайте изучим, как работает квантование, и представим пример улучшения 7-миллиардного параметрической модели Блума на T4 16GB GPU в Google Colab.

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

Изображение, сгенерированное автором с использованием Dall-E2

Мы собираемся комбинировать технику снижения веса моделей, такую как квантование, с техникой эффективного настройки параметров, например LoRA. Результатом этой комбинации является QLoRA, позволяющий нам эффективно настраивать большие модели с минимальным использованием ресурсов.

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

Эффективная настройка параметров с LoRA. Оптимальная обучающая среда для больших языковых моделей.

LoRA является одной из самых эффективных и эффективных техник настройки параметров, применимых к большим языковым моделям. В посте…

levelup.gitconnected.com

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

Мы сможем настраивать модели с 7 миллиардами параметров, такие как Lllama-2 7B или Bloom 7B, на видеокарте с всего 16 ГБ памяти. Это делает QLoRA одним из самых эффективных методов настройки моделей, которые могут реально использоваться.

И мы сможем достичь этого с минимальными усилиями, воспользовавшись фантастическими и замечательными ребятами из Hugging Face. Которые предоставляют библиотеку, подобную PEFT, позволяющую нам наслаждаться всеми этими преимуществами всего несколькими строками кода.

Как работает квантование?

Основная идея проста: мы собираемся снизить точность чисел с плавающей запятой, которые обычно занимают 32 бита, до целых чисел с 8 или даже 4 битами.

Это снижение происходит в параметрах модели, весах нейронных слоев, и в значениях активации, которые проходят через слои модели.

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

Естественно, есть потеря точности, но особенно в случае 8-битового квантования, эта потеря минимальна.

Давайте взглянем на небольшой пример.

Я создам функцию для квантования и другую для обратного преобразования квантования (или как оно там называется).

На самом деле, я хочу увидеть потерю точности, которая происходит при переходе от числа с плавающей запятой 32 бита к квантованному числу с 8/4 битами и затем возвращению к исходному значению с 32 битами.

#Импорт необходимых библиотек
import numpy as np
import math
import matplotlib.pyplot as plt

#Функции для квантования и обратного преобразования
def quantize(value, bits=4):
    quantized_value = np.round(value * (2**(bits - 1) - 1))
    return int(quantized_value)

def unquantize(quantized_value, bits=4):
    value = quantized_value / (2**(bits - 1) - 1)
    return float(value)

quant_4 = quantize(0.622, 4)
print (quant_4)

quant_8 = quantize(0.622, 8)
print(quant_8)

При квантовании значения 0.622 мы получаем следующие результаты:

  • 4 бита: 4.
  • 8 бит: 79

Восстановим эти значения до их исходной точности и посмотрим, что получится.

unquant_4 = unquantize(quant_4, 4)print(unquant_4)unquant_8 = unquantize(quant_8, 8)print(unquant_8)
  • 4 бита без квантования: 0.57142
  • 8 бит без квантования: 0.62204

Если мы учтем, что исходное число было 0.622, можно сказать, что квантование в 8 бит едва ли теряет точность, а потери от квантования в 4 бита управляемы.

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

Давайте построим кривую с неквантованными значениями косинуса.

x = np.linspace(-1, 1, 50)y = [math.cos(val) for val in x]y_quant_8bit = np.array([quantize(val, bits=8) for val in y])y_unquant_8bit = np.array([unquantize(val, bits=8) for val in y_quant_8bit])y_quant_4bit = np.array([quantize(val, bits=4) for val in y])y_unquant_4bit = np.array([unquantize(val, bits=4) for val in y_quant_4bit])

plt.figure(figsize=(10, 12))plt.subplot(4, 1, 1)plt.plot(x, y, label="Оригинал")plt.plot(x, y_unquant_8bit, label="Без квантования 8 бит")plt.plot(x, y_unquant_4bit, label="Без квантования 4 бита")plt.legend()plt.title("Сравнение графика")plt.grid(True)

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

Сказав это, мы собираемся использовать квантование в 4 бита, потому что, как уже упоминалось, для генерации текста мы не заметим большой разницы, и нам необходимо загрузить модель на одну 16-гигабайтную видеокарту.

QLoRA: Настройка модели с квантованием в 4 бита с использованием LoRA.

Чтобы следовать за записной книжкой вместе с статьей, она доступна на Github:

Large-Language-Model-Notebooks-Course/5-Fine Tuning/QLoRA_Tuning_PEFT.ipynb at main ·…

Практический курс по большим языковым моделям. . Сотрудничество с peremartra/Large-Language-Model-Notebooks-Course…

github.com

Модель, которую я собираюсь использовать, это Bloom 7B. Это одна из хорошо установленных моделей в Hugging Face, очень мощная и работает на уровне LLAMA. Это модель, которую мы не могли загрузить на 16-гигабайтную видеокарту без квантования.

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

Мы можем начать загрузку необходимых библиотек.

!pip -q install accelerate!pip -q install datasets!pip -q install trl

Библиотеки trl и accelerate являются частью экосистемы HuggingFace и позволяют нам выполнить настройку модели.

Библиотека datasets содержит множество предобработанных наборов данных, включая тот, который мы собираемся использовать в нашем примере.

Вы могли заметить, что отсутствуют две основные библиотеки: transformers и peft. Первая служит основным интерфейсом для моделей Hugging Face, а вторая содержит реализацию различных техник настройки модели. PEFT означает Parameter Efficient Fine Tuning.

Давайте установим эти библиотеки особым способом.

# Установите последние версии peft & transformers library# если вы хотите работать с самыми новыми моделями!pip install -q git+https://github.com/huggingface/peft.git!pip install -q git+https://github.com/huggingface/transformers.git

Таким образом, мы устанавливаем последние версии этих библиотек напрямую из репозитория проекта на GitHub, который включает реализации последних моделей, таких как Mistral или LLAMA-2. В нашем случае это может быть не обязательно, потому что семейство моделей Bloom уже некоторое время поддерживается в доступной версии этих библиотек.

Импортируем необходимые классы.

from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfigfrom trl import SFTTrainerimport torch

Загрузка модели.

# Используйте любую модель, если вы хотите сделать некоторые быстрые тесты, просто используйте самую маленькую.#model_name = "bigscience/bloomz-560m"#model_name="bigscience/bloom-1b1"model_name = "bigscience/bloom-7b1"target_modules = ["query_key_value"]

Выбранная модель – это Bloom 7B, но если вы проводите тесты, я рекомендую использовать одну из меньших моделей, чтобы минимизировать время обучения и использование ресурсов. Когда вы будете удовлетворены результатами, вы можете попробовать модель 7B и посмотреть результаты.

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

bnb_config = BitsAndBytesConfig(    load_in_4bit=True,    bnb_4bit_use_double_quant=True,    bnb_4bit_quant_type="nf4",    bnb_4bit_compute_dtype=torch.bfloat16)

Мы указываем использование 4-битного квантования и также включаем двойное квантование для уменьшения потери точности.

Для параметра bnb_4bit_quant_type я использовал рекомендуемое значение в статье “QLoRA: Efficient Finetuning of Quantized LLMs.

Теперь мы можем перейти к загрузке модели.

device_map = {"": 0}foundation_model = AutoModelForCausalLM.from_pretrained(model_name,                    quantization_config=bnb_config,                    device_map=device_map,                    use_cache = False)

Таким образом, у нас будет разными количествами бит модели в памяти. Если хотите, можете попытаться загрузить модель без квантования, просто удалив параметр quantization. Вероятно, вы не сможете ее загрузить из-за ограничений памяти.

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

tokenizer = AutoTokenizer.from_pretrained(model_name)tokenizer.pad_token = tokenizer.eos_token

Тестирование модели без настройки.

Чтобы определить, имеет ли настройка модели положительный эффект, лучше всего выполнить тест с только что загруженной моделью перед внесением каких-либо изменений.

С этой целью я создам функцию, которая принимает модель, ввод пользователя и максимальную длину ответа.

# Эта функция возвращает результаты от полученной модели и входов.def get_outputs(model, inputs, max_new_tokens=100):    outputs = model.generate(        input_ids=inputs["input_ids"],        attention_mask=inputs["attention_mask"],        max_new_tokens=max_new_tokens,        repetition_penalty=1.5, #Избегайте повторений.        early_stopping=False, #Модель может остановиться до достижения max_length        eos_token_id=tokenizer.eos_token_id,    )    return outputs

Теперь мы можем отправить запрос модели и проверить ее ответ, что позволит нам сравнить его с ответом, который она предоставит после донастройки.

#Выполнение оригинальной моделиinput_sentences = tokenizer("Хочу, чтобы вы выступали в роли мотивационного тренера. ", return_tensors="pt").to('cuda')foundational_outputs_sentence = get_outputs(foundation_model, input_sentences, max_new_tokens=50)print(tokenizer.batch_decode(foundational_outputs_sentence, skip_special_tokens=True))

[«Хочу, чтобы вы выступали в роли мотивационного тренера. Я не имею в виду, что вы должны говорить людям, что им следует делать, а скорее поощрять и помогать мотивировать их собственные действия.
Можно начать с таких вопросов:

Каковы ваши цели?
Как это поможет их достижению?

Затем»]

Ответ довольно хорош. Bloom – это хорошо подготовленная модель, способная предоставлять точные ответы в различных ситуациях. Я провел тот же тест с 560M Bloom, и ответ был действительно другим: [«Хочу, чтобы вы выступали в роли мотивационного тренера. Не бойтесь вызовов.»].

Подготовка набора данных.

В качестве используемого набора данных используется один из наборов данных, доступных в библиотеке datasets: fka/awesome-chatgpt-prompts.

Давайте взглянем на некоторые примеры, содержащиеся в наборе данных:

  • Я хочу, чтобы вы выступали в качестве консоли javascript. Я буду печатать команды, а вы отвечаете на них тем, что должно отображаться в консоли javascript. Отвечайте только терминальным выводом внутри одного уникального блока кода и ничего больше. Не пишите объяснения. Не вводите команды, если я не дам указания. Когда мне нужно что-то сказать на английском, я сделаю это, поместив текст в фигурные скобки {вот так}. Моя первая команда – console.log(“Привет, мир”);
  • Я хочу, чтобы вы выступали в качестве туристического гида. Я укажу свое местоположение, а вы предложите мне место для посещения рядом с моим местоположением. В некоторых случаях я также дам вам знать о типе мест, которые я хочу посетить. Вы также можете предложить мне места, похожие на указанное место, которые находятся рядом с ним. Мой первый запрос: “Я нахожусь в Стамбуле/Бейоглу и хочу посетить только музеи.”
  • Я хочу, чтобы вы выступали в качестве сценариста. Вам нужно разработать увлекательный и творческий сценарий для полнометражного фильма или веб-сериала, который может завлечь зрителей. Начните с разработки интересных персонажей, задания их в окружение и диалогов между персонажами и т.д. Когда ваша разработка персонажей завершена, создайте захватывающий сюжет, полный поворотов и неожиданностей, который будет держать зрителей в напряжении до самого конца. Мой первый запрос: “Мне нужно написать романтическую драму, действие которой происходит в Париже.”

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

from datasets import load_datasetdataset = "fka/awesome-chatgpt-prompts"#Создание набора данных для создания подсказок.data = load_dataset(dataset)data = data.map(lambda samples: tokenizer(samples["prompt"]), batched=True)train_sample = data["train"].select(range(50))del datatrain_sample = train_sample.remove_columns('act')display(train_sample)

Dataset({ features: [‘prompt’, ‘input_ids’, ‘attention_mask’], num_rows: 50 })

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

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

Донастройка с помощью QLoRA.

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

Мы можем начать процесс донастройки, используя QLoRA для создания новой модели, способной генерировать подсказки, подобные тем, которые содержатся в наборе данных.

Первым шагом будет создание объекта конфигурации LoRA, в котором мы установим переменные, определяющие характеристики процесса донастройки.

# TARGET_MODULES# https://github.com/huggingface/peft/blob/39ef2546d5d9b8f5f8a7016ec10657887a867041/src/peft/utils/other.py#L220import peftfrom peft import LoraConfig, get_peft_modellora_config = LoraConfig(    r=16, #Чем больше R, тем больше параметров для обучения.    lora_alpha=16, #масштабный коэффициент, который регулирует величину весовой матрицы. Как выше, тем больше вес тренировки.    target_modules=target_modules,    lora_dropout=0.05, #Помогает избежать переобучения.    bias="none", #указывает, должен ли обучаться параметр смещения.    task_type="CAUSAL_LM")

Давайте рассмотрим установленные значения:

  • r: это указывает на размер перепараметризации. Имейте в виду, что чем меньше значение, тем меньше параметров обучается. Обучение большего количества параметров дает больший шанс выучить связь между входными и выходными данными, но это также более вычислительно затратно. Значение 16 является разумным компромиссом, позволяющим контролировать параметры и достигать правильного результата.
  • lora_alpha: этот коэффициент регулирует величину матрицы весов. В небольших моделях он обычно не оказывает существенного влияния, но в более крупных моделях он помогает придать больший вес донастройке по сравнению с остальными неизмененными весами.
  • target_modules: это указывает, какие модули мы хотим обучать. На первый взгляд это может показаться сложным решением, прежде всего потому, что вам нужно знать внутренние имена модулей в модели. К счастью, вы можете посмотреть документацию, предоставленную Hugging Face, где указываются доступные модули для каждой семьи моделей.
  • lora_dropout: Если вы ранее обучали модели глубокого обучения, вы, вероятно, знакомы с методом исключения (dropout). Он используется для предотвращения переобучения. В данном случае, учитывая короткую продолжительность обучения и ограниченные данные, вы можете экспериментировать со значением dropout равным 0.
  • bias: Имеется три варианта — none, all и lora_only. Для классификации текста обычно используется значение none. Для более сложных задач вы можете выбирать между all и lora_only.

Теперь создадим каталог, который будет содержать вновь донастроенную модель, которую нужно указать в качестве аргумента класса TrainingArguments.

#Создание каталога для моделиimport osworking_dir = './'output_directory = os.path.join(working_dir, "peft_lab_outputs")

#Создание TrainingArgsimport transformersfrom transformers import TrainingArguments # , Trainertraining_args = TrainingArguments(    output_dir=output_directory,    auto_find_batch_size=True, # Найти правильный размер партии, который подходит для размера данных.    learning_rate= 2e-4, # Больший коэффициент обучения, чем при полной донастройке.    num_train_epochs=5)

Класс TrainingArguments получает параметры, с которыми мы все знакомы, такие как количество эпох обучения и коэффициент обучения.

Теперь у нас есть все, что нужно для обучения модели.

  • Модель.
  • TrainingArgs.
  • Набор данных.
  • Настройка LoRA.
tokenizer.pad_token = tokenizer.eos_tokentrainer = SFTTrainer(    model=foundation_model,    args=training_args,    train_dataset=train_sample,    peft_config = lora_config,    dataset_text_field="prompt",    tokenizer=tokenizer,    data_collator=transformers.DataCollatorForLanguageModeling(tokenizer, mlm=False))trainer.train()

TrainOutput(global_step=65, training_loss=2.7377777099609375, metrics={‘train_runtime’: 404.0462, ‘train_samples_per_second’: 0.619, ‘train_steps_per_second’: 0.161, ‘total_flos’: 966262938697728.0, ‘train_loss’: 2.7377777099609375, ‘epoch’: 5.0})

Теперь мы можем сохранить эту модель, которая должна работать правильно.

#Сохранение моделиpeft_model_path = os.path.join(output_directory, f"lora_model")trainer.model.save_pretrained(peft_model_path)

Проверка донастроенной модели.

#Импорт peftfrom peft import AutoPeftModelForCausalLM, PeftConfig#import osdevice_map = {"": 0}working_dir = './'output_directory = os.path.join(working_dir, "peft_lab_outputs")peft_model_path = os.path.join(output_directory, f"lora_model")#Загрузка модели.loaded_model = AutoPeftModelForCausalLM.from_pretrained(                                        peft_model_path,                                        torch_dtype=torch.bfloat16,                                        is_trainable=False,                                        load_in_4bit=True,                                        device_map = 'auto')

#Запрос к донастроенной модели.input_sentences = tokenizer("I want you to act as a motivational coach. ", return_tensors="pt").to('cuda')foundational_outputs_sentence = get_outputs(loaded_model, input_sentences, max_new_tokens=50)print(tokenizer.batch_decode(foundational_outputs_sentence, skip_special_tokens=True))

[«Я хочу, чтобы вы выступали в качестве мотивационного тренера. Вы будете работать с человеком, который испытывает сложности в своей карьере и не может добиться успеха. У этого человека может быть некоторый опыт ранее, но сейчас он ищет новые возможности, которые могут помочь ему достичь большего.\nТекущая ситуация клиента»]

Мне нравится эта реакция!

Заключение.

Давайте сравним ответы:

  • Предварительно обученная модель: Я хочу, чтобы вы выступали в качестве мотивационного тренера. Я не имею в виду это в смысле указывания людям, что они должны делать, а скорее воодушевления и помощи им мотивировать свои собственные действия.\nВы можете начать, задавая такие вопросы:\n\nКаковы ваши цели?\nКак это поможет достичь их?\n\nДалее.
  • Модель с тонкой настройкой: Я хочу, чтобы вы выступали в качестве мотивационного тренера. Я предоставлю некоторые детали об индивидуале, который нуждается в помощи в повышении уверенности, и ваша цель – «Идеи, как помочь человеку повысить самоуверенность». Ваше первое предложение должно быть «Предоставление поддержки, когда они в ней больше всего нуждаются»; мой ответ

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

Я уверен, что с более продолжительным обучением можно достичь лучших результатов.

Вы можете проводить тесты, изменяя обучающие переменные и делая собственные выводы. Если вы хотите бросить себе вызов, попробуйте повторить упражнение, настраивая модель Mistral 7B!

Ресурсы.

Полный курс о Больших языковых моделях доступен на Github. Чтобы быть в курсе новых статей, рекомендуется следить за репозиторием или отмечать его звездочкой. Таким образом, вы будете получать уведомления при добавлении нового контента.

GitHub – peremartra/Large-Language-Model-Notebooks-Course: Практический курс по большим языковым моделям…

Практический курс по большим языковым моделям. . Вклад в peremartra/Large-Language-Model-Notebooks-Course…

github.com

Если вам нужна дополнительная информация о том, как работает QLoRA: https://arxiv.org/abs/2305.14314

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

Пере Мартра

Пере Мартра

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

Просмотреть список10 историй

Я регулярно пишу о глубоком обучении и искусственном интеллекте. Рассмотрите возможность подписаться на меня в VoAGI , чтобы получать обновления о новых статьях. И, конечно же, добро пожаловать соединиться со мной в LinkedIn и Twitter.