Проблемы в генерации стоп-сигналов в Llama 2

Проблемы в генерации стоп-сигналов в Llama 2' (Problems in stop signal generation in Llama 2)

Исследование с потенциальными решениями

Лама: фото Людмилы Шуваловой

Запуск Llama 2 от Meta вызвал волну восторга в сообществе, отмечая наступление эры хорошо работающих крупных языковых моделей, которые ранее были доступны только через специфические API компаний.

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

В этом блоге я рассмотрю проблему сбоев генерации остановки в самой маленькой модели Llama 2, модели Llama 2–7b, и обсудим несколько потенциальных способов решения. Реализацию в следующих разделах можно найти в этой записной книжке GoogleColab с типом выполнения T4.

Сбой генерации остановки

В этом разделе мы воспользуемся мощностью модели Llama 2–7b, используя графический процессор T4 с достаточным объемом оперативной памяти в Google Colab (2,21 кредитов/час). Необходимо помнить, что графический процессор T4 имеет объем VRAM 16 ГБ, что достаточно для хранения весов Llama 2–7b (7b × 2 байта = 14 ГБ в FP16).

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

Давайте теперь рассмотрим следующий фрагмент кода. Здесь мы продемонстрируем, как загрузить модель “meta-llama/Llama-2–7b-chat-hf” с конфигурацией Bite and Byte и создать конвейер генерации текста на основе этой загруженной модели.

# Битовая и байтовая конфигурацияbnb_config = transformers.BitsAndBytesConfig(    load_in_4bit=True,  # квантование 4 бита    bnb_4bit_quant_type='nf4',  # нормализованный float 4    bnb_4bit_use_double_quant=True,  # вторичное квантование после первого    bnb_4bit_compute_dtype=bfloat16  # тип вычисления)model_id= "meta-llama/Llama-2-7b-chat-hf"device = f'cuda:{cuda.current_device()}' if cuda.is_available() else 'cpu'# Токенизатор Llama 2tokenizer =transformers.AutoTokenizer.from_pretrained(model_id)# Модель Llama 2model = transformers.AutoModelForCausalLM.from_pretrained(    model_id,    trust_remote_code=True,    quantization_config=bnb_config,    device_map='auto',)model.eval()# Определение конвейераpipeline = transformers.pipeline(model=model,    tokenizer=tokenizer,    return_full_text=True,  # langchain ожидает полный текст    task='text-generation',    # передаем параметры модели здесь    max_new_tokens=500,  # максимальное количество токенов для генерации в выводе    temperature=0.1,    )

Эта конфигурация позволяет нам генерировать текст для заданного признака менее чем за одну минуту. Давайте проверим это с помощью простого вопроса: “Что я могу сделать в Париже?” Ниже вы найдете ответ (Пожалуйста, имейте в виду, что ваши результаты могут отличаться из-за настроек температуры).

Париж, столица Франции, известен своей великолепной архитектурой, художественными музеями, историческими памятниками и романтической атмосферой. мечта каждого. Вот некоторые из лучших вещей, которые можно сделать в Париже:1. Посетите символические достопримечательности: Эйфелева башня, музей Лувр, собор Парижской Богоматери, Триумфальная арка и Елисейские поля - обязательные достопримечательности.2. Исследуйте музеи и художественные галереи: Лувр, Музей д'Орсе и Центр Помпиду - некоторые из самых известных музеев мира.3. Совершите речной круиз: Речной круиз по Сене - отличный способ увидеть достопримечательности и мосты города, узнать его историю и культуру.4. Откройте для себя районы города: От модного Елисейского поля до очаровательного Монмартра каждый парижский район имеет свой уникальный характер и очарование.# Я пропустил некоторый текст здесь из-за ограничения пространством# Конечно, существует бесчисленное

Ясно, что модели трудно произвести удовлетворительный ответ; она имеет трудности в определении момента завершения своего вывода. При токенизации сгенерированного текста становится очевидно, что последний токен не является 2, что представляет собой токен eos (конец последовательности) в токенизаторе модели.

При более тщательном рассмотрении оценок токенов (вероятности), предоставленных моделью, я заметил, что token_id 2 (eso_token_id) имеет оценку “-inf.” Это означает, что он не может быть сгенерирован.

Попытки решения проблемы

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

Logits Processor

Языковая модель, такая как Llama 2, обрабатывает последовательность текстовых токенов в качестве входных данных и создает последовательность условных вероятностей для следующего токена на основе контекста от начального токена до текущего. В связи с этим, стоит рассмотреть возможность ручной корректировки этих вероятностей по мере приближения к максимальному лимиту токенов с целью увеличения вероятности встречи токена eos. Мы делаем это, определив наш собственный LogitsProcessor под названием “EosTokenRewardLogitsProcessor” с двумя начальными входами eos_token_id и max_length, где последний представляет собой максимальную длину, при которой модель должна сгенерировать токен eos:

class EosTokenRewardLogitsProcessor(LogitsProcessor):  def __init__(self,  eos_token_id: int, max_length: int):            if not isinstance(eos_token_id, int) or eos_token_id < 0:            raise ValueError(f"`eos_token_id` должен быть положительным целым числом, но он равен {eos_token_id}")        if not isinstance(max_length, int) or max_length < 1:          raise ValueError(f"`max_length` должен быть целым числом, большим 1, но он равен {max_length}")        self.eos_token_id = eos_token_id        self.max_length=max_length  def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor) -> torch.FloatTensor:    cur_len = input_ids.shape[-1]    # начинаем увеличивать вознаграждение для токена eos_token от 80% максимальной длины на длине    for cur_len in (max(0,int(self.max_length*0.8)), self.max_length ):      ratio = cur_len/self.max_length      num_tokens = scores.shape[1] # размер словаря      scores[:, [i for i in range(num_tokens) if i != self.eos_token_id]] =\      scores[:, [i for i in range(num_tokens) if i != self.eos_token_id]]*ratio*10*torch.exp(-torch.sign(scores[:, [i for i in range(num_tokens) if i != self.eos_token_id]]))      scores[:, self.eos_token_id] = 1e2*ratio    return scores

В методе “__call__” класса мы увеличиваем вероятность (оценку) токена eos на основе длины последовательности. Когда длина приближается к 80% от указанной максимальной длины, мы устанавливаем оценку для eos_token_id равной 1e2, умноженной на соотношение длин и соответствующим образом снижаем оценки других токенов.

Теперь объявим обработчик logits в определении конвейера:

pipe = transformers.pipeline(model=model,    tokenizer=tokenizer,    return_full_text=True,  # langchain ожидает полного текста    task='text-generation',    # мы передаем параметры модели сюда    #stopping_criteria=stopping_criteria,  # без этого модель болтает во время чата    logits_processor=logits_process_list,    max_new_tokens=500,  # максимальное количество генерируемых токенов в выводе    temperature=0.1,    )

Запустите конвейер снова с тем же промптом «Что я могу делать в Париже», и мы получаем:

Париж, столица Франции, известен своей потрясающей архитектурой, художественными музеями, историческими достопримечательностями и романтической атмосферой.

Все работает хорошо! У нас есть полный ответ, даже если он может выглядеть коротким.

Настройка модели

Если модель не может сгенерировать токен EOS, почему бы не научить ее делать это? Концепция улучшения производительности модели путем настройки ее с помощью набора данных, включающего ответы, завершающиеся токеном EOS, безусловно, является многообещающим подходом для исследования.

В этом разделе я бесстыдно использовал основу, заложенную в этой статье блога, которая использовала метод эффективной настройки параметров (PEFT), такой как QLoRA, для настройки модели Llama 2–7b. Подобно своему предшественнику, LoRA, QLoRA использует небольшой набор обучаемых параметров (адаптеры), при этом сохраняя неизменными основные параметры модели. Он представляет два примечательных нововведения: 4-битное нормальное число с плавающей запятой (NF4), метод оптимальной квантизации данных для нормальных данных с информационной точки зрения, и двойная квантизация. Для более глубокого понимания обратитесь к оригинальной статье, если вас действительно интересует этот вопрос.

Давайте обучим модель на наборе данных, называемом «timdettmers/openassistant-guanaco», который вы можете найти в базе данных Hugging Face. Этот набор данных имеет следующий формат, где разговоры между человеком и помощником разделены символом «###».

Автор изображения: «timdettmers/openassistant-guanaco’/ dataset

Перед обучением нам нужно преобразовать данные в шаблон запроса Llama 2:

<s>[INST] <<SYS>>{your_system_message}<</SYS>> {user_message_1} [/INST]

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

# Загрузка конфигурации LoRApeft_config = LoraConfig(    lora_alpha=lora_alpha,    lora_dropout=lora_dropout,    r=lora_r,    bias="none",    task_type="CAUSAL_LM",)# Задание параметров обучения с учителемtrainer = SFTTrainer(    model=model,    train_dataset=dataset,    peft_config=peft_config,    dataset_text_field="text",    max_seq_length=max_seq_length,    tokenizer=tokenizer,    args=training_arguments,    packing=packing,)# Обучение моделиtrainer.train()

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

Один из значимых параметров в этой конфигурации – “lora r”, представляющий относительно небольшое значение для обоих измерений пар рангового разложения матриц весов. Обучение происходило исключительно на этих двух матрицах, дополняя существующие веса.

Мы обучаем модель в течение 250 шагов, и в графике ниже показана потеря обучения:

Изображение автора: потеря обучения Llama 2 за 250 шагов

Теперь давайте запустим конвейер с настроенной моделью. В этот раз получим следующий результат:

 Париж, столица Франции, известен своей потрясающей архитектурой, богатой историей и культурными достопримечательностями. Вот несколько популярных вещей, которые можно сделать в Париже:1. Посетите символические достопримечательности: Эйфелева башня, Триумфальная арка, собор Парижской Богоматери и музей Лувр - это некоторые из самых известных достопримечательностей Парижа.2. Исследуйте музеи и художественные галереи: Париж славится множеством мировых музеев и художественных галерей, включая Лувр, Музей д'Орсе и Центр Помпиду.3. Наслаждайтесь парками и садами города: Париж имеет несколько красивых садов и парков, включая сады Люксембург, Тюильри и Шамп де Марс.4. Прокатитесь на речном круизе: Речной круиз по Сене - отличный способ увидеть достопримечательности города и узнать его историю.5. Покупайте на Елисейских полях: Знаменитая авеню усеяна бутиками премиум-класса, универмагами и кафе.6. Посетите кабаре: Мулен Руж - известное кабаре, где представляют живое и захватывающее шоу.7. Посетите Монмартр: Очаровательный район Монмартр славится своей художественной атмосферой, уличными артистами и потрясающими видами на город.8. Пройдите пищевую экскурсию: Париж славится своими кулинарными изысками, и пищевая экскурсия - отличный способ попробовать известную кухню города.9. Посетите Версальский дворец: Версальский дворец - красивый королевский дворец, расположенный неподалеку от Парижа.10. Посетите показ мод: Париж - модная столица мира, и посещение показа мод - отличный способ увидеть последние тренды и стили.Это только несколько из множества вещей, которые можно сделать в Париже. В городе есть что-то для каждого, от истории и культуры до еды и развлечений.

Это довольно красивый ответ!

Бонус: дайте другую подсказку

В конечном итоге, у нас есть простой и эффективный трюк в нашем распоряжении, особенно когда чрезмерная многословность модели не является проблемой. Мы можем явно указать в подсказке, что нам требуется краткий ответ. Например, когда я спрашиваю модель: “Что я могу сделать в Париже? Ответьте в пяти предложениях или меньше”, она предоставляет следующее:

Конечно! Вот пять вещей, которые вы можете сделать в Париже: 1. Посетите Эйфелеву башню, иконический памятник, предлагающий потрясающий вид на город. 2. Исследуйте Лувр и увидьте известные произведения искусства, такие как Мона Лиза. 3. Прогуляйтесь по реке Сена и насладитесь живописными мостами и очаровательными улицами города. 4. Посетите собор Парижской Богоматери, красивую и историческую церковь. 5. Насладитесь вкусным французским обедом в традиционном бистро или ресторане.

Это короткий, но четкий и полный ответ.

Критерии остановки: неудачная попытка

Для тех, кто заинтересован, Hugging Face представила еще один API под названием StoppingCriteria, предназначенный для установления определенных условий, принуждающих последовательность прекратить работу. Однако, когда речь идет о определении пользовательского критерия, прекращающего работу модели при обнаружении определенных токенов (например, ‘\n’), он может не предоставить всеобъемлющего решения проблемы. В качестве примера я попытался создать класс StopOnTokens:

# определение пользовательского объекта критериев остановкиclass StopOnTokens(StoppingCriteria): def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor, **kwargs) -> bool: for stop_ids in stop_token_ids: if torch.eq(input_ids[0][-len(stop_ids):], stop_ids).all(): return True return Falsestopping_criteria = StoppingCriteriaList([StopOnTokens()])

Однако модель все равно не дает полного ответа.

Заключение

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

Изображение от Jose Aragones

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