Для чего используется LoRA (Low-Rank Adaptation) в глубоком обучении?

Ответ

LoRA — это метод параметр-эффективного дообучения (fine-tuning) больших моделей, особенно трансформеров. Вместо обновления всех миллиардов параметров исходной модели, LoRA обучает небольшие низкоранговые матрицы, которые добавляются к весам определенных слоев (обычно attention-механизмов). Эти адаптеры затем можно "включить" для конкретной задачи.

Зачем это нужно:

  • Резкое снижение вычислительных затрат и потребления памяти. Можно дообучать большие LLM (например, LLaMA 7B) на одном GPU с 24 ГБ памяти.
  • Возможность быстрого переключения задач. Обученные адаптеры для разных доменов (медицина, код, юридические тексты) весят несколько мегабайт и подгружаются поверх замороженной базовой модели.
  • Снижение риска катастрофического забывания. Так как основные веса заморожены, модель не забывает свои исходные знания.

Техническая суть: Для весового слоя W (размерности d x k) LoRA представляет его обновление как произведение двух низкоранговых матриц: ΔW = B * A, где B (d x r), A (r x k), и ранг r << min(d, k). В прямом проходе используется h = Wx + ΔWx = Wx + BAx.

Пример дообучения модели для генерации кода с использованием библиотеки PEFT:

from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import LoraConfig, get_peft_model, TaskType
import torch

# Загрузка базовой модели (например, CodeGen)
model_name = "Salesforce/codegen-350M-mono"
tokenizer = AutoTokenizer.from_pretrained(model_name)
base_model = AutoModelForCausalLM.from_pretrained(model_name, load_in_8bit=True, device_map='auto')  # Используем 8-битную квантизацию

# Конфигурация LoRA
lora_config = LoraConfig(
    task_type=TaskType.CAUSAL_LM,
    r=8,                 # Ранг адаптеров
    lora_alpha=32,       # Коэффициент масштабирования
    lora_dropout=0.1,
    target_modules=["q_proj", "v_proj"],  # К каким слоям attention добавлять адаптеры
    bias="none",
)

# Обертывание модели
model = get_peft_model(base_model, lora_config)
model.print_trainable_parameters()  # Выведет: trainable params: 8,388,608 || all params: 350,000,000 || 0.24%

# Далее следует стандартный цикл обучения.
# Будет обновляться менее 1% параметров!

Итог: LoRA — это стандартный де-факто метод для адаптации LLM под конкретные задачи или домены с ограниченными ресурсами, широко используемый в индустрии и research.

Ответ 18+ 🔞

Слушай, а вот есть же такая штука — LoRA. Ну, в общем, если по-простому, то это такой способ доучить большую, ебаную модель, чтобы она не была такой уж общей, а знала что-то конкретное. Представь себе: у тебя есть модель, которая весит, блядь, как слон, и параметров в ней — овердохуища. И чтобы её под себя подогнать, раньше надо было перелопатить все эти миллиарды весов. Это ж пиздец, ёпта! Денег, времени, видеокарт — всё в трубу.

А тут придумали гениальную, хитрожопую идею. Зачем трогать всю эту махину? Да похуй на неё! Мы её заморозим, как мамонта в вечной мерзлоте. А вместо этого прилепим к ней маленькие, лёгкие модульчики — адаптеры. Они, эти адаптеры, и будут учиться под конкретную задачу. Хочешь, чтобы модель код писала — вот тебе один адаптер. Хочешь, чтобы медицинские статьи разбирала — вот тебе другой. А базовая модель остаётся нетронутой, как истукан.

Ну и зачем это всё, спросишь?

  • Экономия, мать её, дикая. Раньше для дообучения модели на 7 миллиардов параметров нужен был серверный шкаф. А теперь — одна видюха с 24 гигами памяти, и ты уже король. Ебать мои старые костыли, прогресс!
  • Переключаться — раз плюнуть. Обученный адаптер весит пару мегабайт, как картинка с котиком. Захотел перейти с генерации кода на анализ законов — выгрузил один модуль, загрузил другой. Всё, блядь, готово. Удобно же, ёпта?
  • Модель не превращается в овощ. Поскольку основные веса не трогаем, модель не забывает всё то, чему её учили изначально. То есть, катастрофического забывания, этой вечной беды, — нет. Доверия к такому подходу, конечно, не ноль, но уже побольше.

А как это, блядь, работает внутри? Ну, смотри. Берём какой-нибудь слой с весами W. Вместо того чтобы его пинать, мы добавляем к его выходу результат работы двух маленьких, низкоранговых матриц: B и A. Их произведение ΔW = B * A и есть наше обновление. Главный фокус в том, что ранг r у них — хуй с горы, очень маленький. И в итоге прямой проход выглядит так: h = Wx + ΔWx = Wx + BAx. Всё гениальное — просто, как три копейки.

Вот, смотри, как это в коде выглядит (используем библиотеку PEFT):

from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import LoraConfig, get_peft_model, TaskType
import torch

# Берём какую-нибудь модель, которая код генерит
model_name = "Salesforce/codegen-350M-mono"
tokenizer = AutoTokenizer.from_pretrained(model_name)
# Грузим модель, можно даже в 8-битном формате, чтобы ещё легче было
base_model = AutoModelForCausalLM.from_pretrained(model_name, load_in_8bit=True, device_map='auto')

# Настраиваем нашу LoRA
lora_config = LoraConfig(
    task_type=TaskType.CAUSAL_LM,
    r=8,                 # Ранжик маленький
    lora_alpha=32,       # Коэффициент масштабирования
    lora_dropout=0.1,
    target_modules=["q_proj", "v_proj"],  # Куда именно прилепляем наши адаптеры (обычно к слоям attention)
    bias="none",
)

# Обматываем базовую модель нашими приблудами
model = get_peft_model(base_model, lora_config)
model.print_trainable_parameters()  # Посмотрим, что по параметрам
# Выведет что-то вроде: trainable params: 8,388,608 || all params: 350,000,000 || 0.24%
# Видишь? Обучаемых параметров меньше 1%! Остальные — намертво заморожены.

# Ну а дальше — обычный цикл обучения. Крутишь датасет, считаешь loss.
# Греется и меняется только наша маленькая добавка, а не вся гигантская модель.

Итог, блядь: LoRA — это сейчас, можно сказать, золотой стандарт. Все, кто в теме, используют этот метод, чтобы приспосабливать огромные языковые модели под свои нужды, не разоряясь на аренду дата-центров. Штука реально полезная и, я бы сказал, элегантная. Чистая магия, только на матрицах.