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

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

Теперь представим ситуацию, в которой необходимо адаптировать LLM под конкретную прикладную задачу. Распространённый подход — дообучение или тонкая настройка (fine-tuning), то есть корректировка существующих весов модели на новом датасете. Однако этот процесс крайне медленный и требует значительных вычислительных ресурсов, особенно при запуске на локальной машине с ограниченным «железом».

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

Во время дообучения можно «заморозить» (то есть оставить без изменений) часть слоёв нейросети, чтобы снизить сложность обучения, но и этот подход оказывается недостаточным при масштабировании из-за высоких вычислительных затрат.

Чтобы справиться с этой проблемой, в данной статье мы рассмотрим ключевые принципы LoRA (Low-Rank Adaptation, адаптация с пониженной степенью ранга) — популярной техники, позволяющей снизить нагрузку на вычислительные ресурсы при дообучении крупных моделей. В качестве бонуса мы также взглянем на QLoRA — метод, основанный на LoRA, но дополненный квантованием для ещё большей эффективности.

Рассмотрим нейросеть подробнее

Рассмотрим полносвязную нейронную сеть. Каждый её слой состоит из n нейронов, полностью соединённых с m нейронами следующего слоя. Всего получается n ⋅ m связей, которые можно представить в виде матрицы соответствующих размеров.

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

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

Трюк с умножением

Матрица весов в нейронной сети может иметь чрезвычайно большие размеры. Вместо того чтобы хранить и обновлять полную матрицу, можно разложить её в произведение двух меньших матриц. В частности, если матрица весов имеет размерность n × m, её можно аппроксимировать с помощью двух матриц размером n × k и k × m, где k — это значительно меньшая внутренняя размерность (k ≪ n, m).

Например, предположим, что исходная матрица весов имеет размер 8192 × 8192, что соответствует примерно 67 миллионам параметров. Если выбрать k = 8, факторизованная версия будет состоять из двух матриц: одна размером 8192 × 8, а вторая — 8 × 8192. Вместе они содержат около 131 тысячи параметров — более чем в 500 раз меньше по сравнению с оригиналом, что радикально снижает требования к памяти и вычислениям.

Большую матрицу можно аппроксимировать в виде произведения двух меньших.
Большую матрицу можно аппроксимировать в виде произведения двух меньших.

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

Даже при малом значении k, например, k = 8, удаётся эффективно аппроксимировать исходную матрицу с минимальной потерей точности. На практике иногда эффективно работают даже значения k = 2 или k = 4.

LoRA

То, что мы рассмотрели ранее, идеально иллюстрирует основной принцип LoRA. LoRA расшифровывается как Low-Rank Adaptation — адаптация с пониженным рангом. Термин low-rank означает технику аппроксимации большой матрицы весов путём её разложения на произведение двух меньших матриц с гораздо меньшим рангом k. Такой подход существенно снижает количество обучаемых параметров, при этом сохраняя основную вычислительную мощность модели.

Обучение

Предположим, у нас есть входной вектор x, подаваемый в полносвязный слой нейронной сети, который до дообучения представлен матрицей весов W. Чтобы получить выходной вектор y, достаточно перемножить матрицу и вектор: y = Wx.

Во время дообучения цель — адаптировать модель под прикладную задачу, изменяя веса. Это можно выразить как обучение дополнительной матрицы ΔW, так что:
y = (W + ΔW)x = Wx + ΔWx.

Как мы видели в предыдущем разделе, можно заменить ΔW на произведение двух матриц BA, тогда в итоге получаем:
y = Wx + BAx.

Таким образом, мы «замораживаем» матрицу W и решаем задачу оптимизации только для матриц A и B, которые содержат в разы меньше параметров, чем ΔW.

Но вычисление произведения (BA)x при каждом проходе модели довольно медленное, поскольку операция матричного умножения BA остаётся ресурсоёмкой. Чтобы этого избежать, можно воспользоваться ассоциативным свойством матричного умножения и переписать выражение как B(Ax).

Сначала вектор x умножается на матрицу A, результатом чего становится вектор меньшей размерности. Затем этот вектор умножается на матрицу B, что снова даёт вектор на выходе. Такая последовательность операций работает значительно быстрее.

Обучение с LoRA: как это работает
Обучение с LoRA: как это работает

С точки зрения обратного распространения ошибки (backpropagation), LoRA также даёт несколько существенных преимуществ. Несмотря на то, что вычисление градиента для одного нейрона требует примерно того же количества операций, теперь в сети гораздо меньше обучаемых параметров, а именно:

  • требуется рассчитывать существенно меньше градиентов для матриц A и B, чем потребовалось бы для W;

  • больше не нужно хранить огромную матрицу градиентов для W.

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

В качестве технической детали: перед дообучением матрица A инициализируется случайными значениями из гауссовского распределения, а матрица B — нулями. На старте используем нулевую матрицу B, чтобы поведение модели не изменилось, поскольку BAx = 0 ⋅ Ax = 0, и, соответственно, y по-прежнему будет равно Wx.

Это делает начальную фазу дообучения более стабильной. Затем, в процессе обратного распространения ошибки, модель постепенно адаптирует веса матриц A и B, осваивая новую информацию.

После обучения

По завершении обучения мы получаем оптимальные матрицы A и B. Всё, что остаётся сделать — перемножить их, чтобы получить ΔW, после чего просто прибавить ΔW к предобученной матрице W и таким образом получить финальные веса.

Хотя операция перемножения BA может показаться ресурсоёмкой, её нужно выполнить всего один раз — так что беспокоиться об этом не стоит. Более того, после сложения больше нет необходимости хранить A, B или ΔW.

Важный момент

Несмотря на вдохновляющую идею LoRA, может возникнуть вопрос: а почему бы при обычном обучении нейросети просто не представить y в виде BAx, минуя тяжёлую матрицу W и её умножение y = Wx?

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

В оптимизации с LoRA мы рассматриваем Wx как «предшествующее знание» большой модели, а ΔWx = BAx — как знание, специфичное для конкретной задачи, полученное в ходе дообучения. Таким образом, значение W в общем качестве модели по-прежнему остаётся крайне важным.

Адаптер

Изучая теорию LLM, важно упомянуть термин «адаптер», который часто встречается в научных публикациях о языковых моделях.

В контексте LoRA адаптер — это комбинация матриц A и B, предназначенная для решения конкретной прикладной задачи (downstream task) при фиксированной матрице W.

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

(A₁, B₁) — адаптер для задач «вопрос–ответ»;
(A₂, B₂) — адаптер для задач автоматического реферирования текста;
(A₃, B₃) — адаптер, обученный для разработки чат-бота.

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

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

Динамическое переключение адаптеров в реальном времени

Главное преимущество адаптеров в том, что их можно динамически переключать. Представьте себе систему чат-бота, в которой пользователь может выбирать, в каком стиле бот будет отвечать — например, в роли Гарри Поттера, злой птички из Angry Birds или Криштиану Роналду.

Однако из-за ограничений по ресурсам может оказаться невозможным хранить или дообучать три отдельные большие модели — они слишком объёмны. Как поступить в такой ситуации?

На помощь приходят адаптеры! Всё, что нужно, — это одна большая модель W и три отдельных адаптера, по одному на каждого персонажа.

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

В памяти остаются только матрица W и три пары матриц-адаптеров: (A₁, B₁), (A₂, B₂), (A₃, B₃). Каждый раз, когда пользователь выбирает нового персонажа, достаточно динамически заменить адаптер, выполнив сложение матриц W и BA, соответствующего выбранному персонажу. В результате мы получаем систему, которая превосходно масштабируется — если в будущем нужно добавить новых персонажей, это делается без усилий.

QLoRA

QLoRA — ещё один популярный термин, который отличается от LoRA только первой буквой Q, означающей quantized («квантованный»). Термин «квантование» (quantization) обозначает уменьшение количества бит, используемых для хранения весов нейронной сети.

Например, веса нейросети могут быть представлены в виде чисел с плавающей точкой (float), для которых требуется 32 бита на каждое значение. Суть квантования — в сжатии этих весов до меньшей точности без существенной потери качества или ухудшения работы модели. Вместо 32 бит можно, например, использовать только 16 бит.

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

QLoRA использует квантование для предобученной матрицы W, чтобы уменьшить её объём за счёт сжатия весов.

Бонус: prefix-tuning

Prefix-tuning — любопытная альтернатива LoRA. Здесь также используются адаптеры для разных прикладных задач, но в данном случае адаптеры встраиваются непосредственно в слой внимания (attention) трансформера.

Если конкретнее, во время обучения все слои модели остаются замороженными, за исключением тех, которые добавляются в виде префиксов к отдельным embedding-представлениям внутри слоя внимания. В отличие от LoRA, prefix-tuning не изменяет структуру самой модели и, как правило, имеет ещё меньше обучаемых параметров.

Как и в случае с LoRA, для учёта префиксного адаптера требуется операция сложения, но теперь с меньшим количеством элементов.

Однако, если нет жёстких ограничений по вычислительным ресурсам и ресурсам памяти, адаптеры LoRA всё ещё предпочтительнее в большинстве случаев по сравнению с prefix-tuning.

Заключение

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

Изящность подхода LoRA заключается в сжатии матрицы весов через её декомпозицию, что не только ускоряет обучение моделей, но и снижает требования к объёму памяти. Кроме того, LoRA отлично иллюстрирует идею адаптеров, которые можно гибко использовать и подменять для разных прикладных задач.

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

И наконец, мы рассмотрели альтернативный подход — prefix-tuning, который выполняет ту же функцию, что и адаптеры, но без изменения структуры модели.


В заключение приглашаем всех желающих на открытые уроки:

Кроме того, пройдите вступительное тестирование, чтобы оценить свой уровень и узнать, подойдет ли вам программа курса NLP / Natural Language Processing.

Комментарии (0)