Меня зовут Иван Исаев, я занимаюсь МЛ с 2014 года, руководил направлением МЛ в крупном телекоме, отделом МЛ в крупной RTB компании, последние годы работаю ведущим инженером в блокчейн-компании внутри экосистемы Bittensor. Люблю порешать Kaggle соревы, и потратив некоторое время смог прокачаться до мастера. Последние пару лет много упражнялся с NLP и LLM для классификации и других задач, в том числе с мультимодальными моделями (ImageBind, VITA и другими). В Bittensor у меня было несколько задач, в которых я смог поработать с файнтюнингом LLM для продовых задач. В этой публикации я хотел бы рассказать про некоторые детали файнтюнинга, которые мне кажутся интересными (мой первый опыт с файнтюнингом был на Kaggle с Mistral, когда я прочел этот пост).
Я начну с базовых вещей, которые могут быть интересны начинающим, а ближе к концу статьи расскажу про тюнинг нескольких Vision-to-Text LLMs, с которыми довелось поработать на проде, и про файнтюнинг ImageBind с помощью кастомной реализации LoRA.
1. Введение
Что такое дообучение LLM и зачем оно нужно?
Дообучение больших языковых моделей (LLM) — это способ адаптировать их под свои задачи, сделать их умнее на своих данных и сэкономить ресурсы.
Когда стоит дообучать, а когда хватит prompt engineering или RAG? Если задача уникальная или данных много — дообучай. Если задача простая — попробуй сначала промпты.
2. PEFT и его разновидности
PEFT (Parameter-Efficient Fine-Tuning) — что это и зачем?
PEFT — это целый класс методов, которые позволяют дообучать только часть параметров модели, экономя память и ускоряя обучение. LoRA — лишь один из них!
Основные методы PEFT:
LoRA (Low-Rank Adaptation):
Добавляет low-rank матрицы к замороженным весам. Это позволяет эффективно дообучать большие модели без необходимости обновлять все параметры.
Пример:
python from peft import LoraConfig, get_peft_model lora_config = LoraConfig( r=8, lora_alpha=32, target_modules=["q_proj", "v_proj"], lora_dropout=0.05, bias="none" ) lora_model = get_peft_model(base_model, lora_config)
Когда использовать? Если нужно дообучить LLM на своей задаче с минимальными затратами памяти и вычислений.
QLoRA (Quantized LoRA):
Сочетает LoRA и 4-bit квантование (через bitsandbytes). Это позволяет запускать дообучение даже на одной видеокарте с 24ГБ памяти для моделей 33B+.
Пример:
python from transformers import BitsAndBytesConfig bnb_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_compute_dtype=torch.float16, bnb_4bit_quant_type="nf4" ) # Далее применяем LoRA как обычно
Когда использовать? Если очень мало памяти, а модель большая.
AdaLoRA:
Адаптивная версия LoRA, которая динамически изменяет ранк (размерность low-rank матриц) в процессе обучения.
Зачем это нужно? Если задача сложная и на разных этапах обучения требуется разная "гибкость" адаптации, AdaLoRA сама подстраивает количество дообучаемых параметров, чтобы не тратить лишние ресурсы.
Пример:
python from peft import AdaLoraConfig adalora_config = AdaLoraConfig( init_r=12, # начальный ранк target_r=8, # целевой ранк beta1=0.85, beta2=0.85, target_modules=["q_proj", "v_proj"] )
Когда использовать? Если у тебя ограничены ресурсы, но хочется максимальной отдачи от дообучения, и задача не тривиальная (например, сложная генерация или специфичная классификация).
BitFit:
Дообучаются только bias-термы (смещения) в слоях модели, все остальные веса остаются замороженными.
Зачем это нужно? Это самый "лёгкий" способ дообучения, почти не требует памяти и вычислений, но может дать прирост качества на простых задачах.
Пример:
python from peft import BitFitConfig bitfit_config = BitFitConfig( bias_pattern=["bias", "LayerNorm.bias"], modules_to_save=None )
Когда использовать? Если задача простая (например, бинарная классификация), а ресурсов совсем мало, или если нужно быстро проверить, есть ли смысл в дообучении вообще.
Prefix Tuning:
Добавляет обучаемые виртуальные токены к входу модели, которые "подсказывают" ей, как себя вести.
Пример:
python from peft import PrefixTuningConfig prefix_config = PrefixTuningConfig( num_virtual_tokens=20, task_type="CAUSAL_LM" )
Когда использовать? Для задач, где важно задать контекст или стиль генерации.
P-Tuning:
Похож на Prefix Tuning, но реализован иначе. Использует soft prompts, которые обучаются вместе с моделью.
Пример:
python from peft import PromptTuningConfig ptuning_config = PromptTuningConfig( num_virtual_tokens=10, task_type="CAUSAL_LM" )
Когда использовать? Для задач, где нужно дообучить модель на небольшом количестве примеров (few-shot).
Сравнение методов:
Метод |
Память |
Скорость |
Для чего лучше всего |
Особенности |
LoRA |
низкая |
высокая |
большинство LLM |
просто внедрять |
QLoRA |
очень низкая |
высокая |
большие модели, мало RAM |
требует квантования |
AdaLoRA |
низкая |
высокая |
сложные задачи, динамика |
ранк меняется в процессе |
P-Tuning |
средняя |
высокая |
prompt-based задачи |
хорош для few-shot |
BitFit |
минимальная |
высокая |
простые задачи |
только bias |
3. LoRA — Low-Rank Adaptation
Что такое LoRA и как она работает?
LoRA — это метод, который добавляет низкоранговые адаптации к весам модели. Вместо изменения всех параметров, мы добавляем небольшие матрицы, которые обучаются для конкретной задачи.
4. Какие модули дообучать?
Основные параметры
В LoraConfig есть параметр target_modules : q_proj, k_proj, v_proj, o_proj, gate_proj и др.
Для разных моделей (Mistral, Qwen, Llama) — обычно дообучают q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj.
Как выбрать? Если не знаешь — начни с q_proj и v_proj, потом экспериментируй. Для некоторых моделей (например, Qwen) список может отличаться — смотри документацию или исходники.
Пример для Mistral:
python lora_config = LoraConfig( target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"] )
Описание модулей
Подробнее про каждый модуль:
q_proj (Query projection): Преобразует входные токены в запросы. Влияет на то, какую информацию модель считает важной для обработки. Дообучение помогает модели лучше фокусироваться на релевантной информации для вашей задачи.
k_proj (Key projection): Создает ключи для механизма внимания. Определяет, как модель индексирует информацию. Дообучение улучшает способность модели находить связи между различными частями входных данных.
v_proj (Value projection): Преобразует входные данные в значения. Отвечает за фактическое содержание, которое будет использовано для генерации выхода. Дообучение помогает модели лучше представлять специфичную для задачи информацию.
o_proj (Output projection): Объединяет результаты механизма внимания. Влияет на финальное представление информации. Дообучение улучшает способность модели формировать итоговый ответ.
gate_proj (Gate projection): Контролирует поток информации в модели. Используется в FFN (feed-forward network) для управления тем, какая информация пропускается дальше. Дообучение помогает модели лучше фильтровать релевантную информацию.
up_proj (Up projection): Увеличивает размерность в FFN. Позволяет модели работать с более богатыми представлениями данных. Дообучение расширяет способность модели улавливать сложные паттерны.
down_proj (Down projection): Уменьшает размерность обратно в FFN. Сжимает информацию до исходной размерности. Дообучение помогает модели лучше сохранять важную информацию при сжатии.
Частота использования различных модулей
Дополнительные модули
Когда добавляют lm_head:
Для улучшения качества генерации текста
Когда нужно адаптировать выходной слой
В сложных задачах, требующих максимальной адаптации
Когда добавляют embed_tokens:
При работе с новым словарем
Для специализации на определенной области
При адаптации к специфичным токенам
5. Фреймворки и инструменты
HuggingFace Trainer, SFTTrainer, TRL — что выбрать?
HuggingFace Trainer — универсальный инструмент для обучения и дообучения моделей. Подходит для большинства задач, поддерживает LoRA через интеграцию с PEFT.
SFTTrainer (из TRL) — специализирован для supervised fine-tuning (SFT, дообучение с учителем), часто используется с LoRA и для instruction tuning. Позволяет легко запускать дообучение на диалоговых и генеративных задачах. SFT — это когда мы учим модель на парах "вопрос-ответ" или "инструкция-результат", где правильные ответы заранее известны. TRL (transformers reinforcement learning) предоставляет удобные инструменты для работы с SFT, включая форматирование данных, оценку результатов, а также поддерживает RLHF, reward modeling, PPO обучение и другие продвинутые техники. Библиотека полностью совместима с LoRA, QLoRA и другими методами PEFT.
Пример запуска SFTTrainer:
python from trl import SFTTrainer trainer = SFTTrainer( model=model, args=training_args, train_dataset=dataset ) trainer.train()
Когда что использовать? Если задача простая — хватит Trainer. Для диалоговых и instruction задач — SFTTrainer.
6. Квантование
fp16 vs 8bit quantization
Квантование — это способ уменьшить память за счёт уменьшения точности чисел.
fp16 — быстрее и экономнее, но иногда теряется точность. Поддерживается большинством современных GPU.
8bit/4bit — ещё экономнее, позволяет запускать огромные модели на одной видеокарте, но не все модели и операции поддерживаются. Используется в QLoRA.
Пример запуска с 8bit:
python from transformers import BitsAndBytesConfig bnb_config = BitsAndBytesConfig(load_in_8bit=True) model = AutoModelForCausalLM.from_pretrained( "model_name", quantization_config=bnb_config )
7. Instruction finetuning
Что это такое и для чего?
Instruction finetuning — дообучение модели на задачах, где в промпте явно указана инструкция ("Переведи текст...", "Ответь на вопрос...").
Можно ли использовать для классификации? Да, но иногда проще использовать обычный supervised fine-tuning (например, для DeBERTa).
Для классификации LoRA не обязателен, но может ускорить обучение и снизить требования к памяти.
Пример для классификации с DeBERTa:
python from transformers import Trainer trainer = Trainer( model=model, args=training_args, train_dataset=dataset ) trainer.train()
8. Практические детали
Когда стоит делать полное дообучение, а когда хватит PEFT?
Если задача очень специфичная или требуется максимальное качество — можно попробовать full finetuning, но это дорого.
Task types в LoraConfig:
CAUSAL_LM: генерация текста, чат-боты, completion задачи
FEATURE_EXTRACTION: создание эмбеддингов, поиск похожих текстов, кластеризация
SEQ_CLS: классификация текстов, анализ тональности, детекция AI
Пример:
python lora_config = LoraConfig(task_type="SEQ_2_SEQ_LM")
Какой learning rate выбрать?
Обычно начинают с 1e-4 для LoRA, 5e-5 для QLoRA. Используй learning rate finder или grid search.
Какой batch size оптимален?
Максимальный, который помещается в память. Для LoRA обычно 4-16, для QLoRA 1-4.
Сколько эпох нужно?
1-3 эпохи для LoRA, 3-5 для QLoRA. Следи за validation loss.
Как ускорить обучение:
Используй gradient accumulation для увеличения эффективного batch size
Применяй mixed precision (fp16/bf16)
-
Используй DeepSpeed ZeRO для больших моделей:
ZeRO (Zero Redundancy Optimizer) оптимизирует использование GPU памяти
Stage 1 оптимизирует оптимизатор, Stage 2 добавляет градиенты, Stage 3 - веса модели
Пример конфига:
python # deepspeed_config.json { "zero_optimization": { "stage": 2, "offload_optimizer": { "device": "cpu" } } }
-
Кэшируй эмбеддинги для статических данных:
Если данные не меняются между эпохами, можно сохранить их эмбеддинги
Это особенно полезно для больших датасетов с длинными текстами
Пример:
python # Сохранение эмбеддингов embeddings = model.get_input_embeddings()(input_ids)
torch.save
(embeddings, 'cached_
embeddings.pt
') # Использование кэша embeddings = torch.load('cached_
embeddings.pt
')
Метрики для оценки качества для генерации
BLEU: сравнивает n-граммы сгенерированного и эталонного текста
ROUGE: оценивает полноту и точность для суммаризации
METEOR: учитывает синонимы и морфологические варианты
BERTScore: использует контекстные эмбеддинги для сравнения (Все доступны на https://huggingface.co/metrics)
Мультимодальная оценка:
- Например, ImageBind Similarity: оценка через косинусную близость эмбеддингов
- Используется и эффективен для оценки качества описаний к видео, так как ImageBind хорошо улавливает временные зависимости и действия в видеопотоке
- Также подходит для image-to-text генерации, где важна семантическая близость между модальностями
python from imagebind import ImageBind # Загружаем модель model = ImageBind() # Получаем эмбеддинги для текста и изображения/видео text_emb = model.encode_text(generated_text) media_emb = model.encode_video(source_video) # или encode_image() # Считаем косинусную близость similarity = F.cosine_similarity(text_emb, media_emb)
9. Тюнинг Vision-to-Text LLMs: Moondream2, VITA, InternVL, MiniCPM
Vision-to-Text LLMs — это модели, которые принимают на вход изображение (или видео) и генерируют текстовое описание, ответ на вопрос или другую текстовую задачу. В последние годы появилось много открытых и коммерческих моделей, которые можно дообучать под свои задачи.
Общие советы по тюнингу Vision-to-Text LLMs
Для дообучения таких моделей обычно требуется пара (image, text) или (video, text).
Важно правильно подготовить датасет: картинки должны быть в нужном формате, а тексты — чистыми и релевантными.
Часто используется cross-entropy loss для генерации, но для задач классификации можно использовать другие loss (например, BCE).
Аугментации изображений (crop, resize, color jitter) могут помочь, но нужно быть аккуратным — модель должна видеть "реальные" данные.
Для ускорения обучения можно использовать LoRA/QLoRA, если модель поддерживает PEFT.
Moondream2
Открытая мультимодальная модель (image-to-text, VQA).
Архитектура: CLIP-энкодер + LLM (основана на Microsoft Phi, также поддерживает Llama и Mistral).
Для дообучения нужен датасет с парами (image, text/question/answer).
Пример запуска дообучения (на HuggingFace):
python from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer model = AutoModelForCausalLM.from_pretrained("vikhyatk/moondream2") tokenizer = AutoTokenizer.from_pretrained("vikhyatk/moondream2") # Подготовь свой датасет с image_path и text # ... training_args = TrainingArguments( output_dir="./results", per_device_train_batch_size=4, num_train_epochs=3, fp16=True ) trainer = Trainer( model=model, args=training_args, train_dataset=your_dataset ) trainer.train()
Советы: модель генерирует быстро, но в ней нет system prompt (в отличие от DeepSeekVL2, который генерирует медленнее, но тоже достаточно быстро и имеет system prompt). Можно использовать ансамбль из нескольких моделей с разными промптами и гиперпараметрами для улучшения качества и разнообразия генерации. Также подходит для генерации чанков текста для RAG с сохранением в векторную БД тех, что превышают порог уникальности.
VITA (Vision-and-Text Aligned Multimodal LLM)
Мультимодальная LLM, которая принимает на вход изображение и текст (например, вопрос) и генерирует текстовый ответ или описание.
Используется для задач VQA (визуальный вопрос-ответ), image captioning, reasoning по картинке и других мультимодальных задач.
Архитектура: визуальный энкодер (обычно ViT или CLIP) + языковая модель (LLM) + специальные мультимодальные адаптеры.
Для дообучения нужен датасет с парами (image, text/question, answer/target).
Пример LoRA-тюнинга:
python from peft import LoraConfig, get_peft_model lora_config = LoraConfig( r=8, lora_alpha=32, target_modules=["vision_adapter", "text_adapter"], lora_dropout=0.05 ) model = get_peft_model(base_model, lora_config) # Далее стандартный Trainer
Советы: для VQA и captioning можно использовать разные loss (cross-entropy для генерации, BCE для multi-label). Важно правильно форматировать промпты (например, "Q: ... A: ...").
InternVL
Мощная китайская мультимодальная модель (аналог LLaVA, поддерживает VQA, captioning, reasoning).
Для дообучения требуется датасет с изображениями и текстами (или вопросами/ответами).
Пример запуска (на PyTorch):
python # Обычно используется кастомный training loop for batch in dataloader: images, texts = batch["image"], batch["text"] outputs = model(images, texts) loss = loss_fn(outputs, texts) loss.backward() optimizer.step()
Советы: внимательно следи за форматами входа (размеры, нормализация), InternVL чувствителен к preprocessing.
MiniCPM
Лёгкая и быстрая open-source Vision-to-Text модель, хорошо подходит для edge-инференса и быстрых прототипов.
Поддерживает LoRA/QLoRA для дообучения.
Пример LoRA-тюнинга:
python from peft import LoraConfig, get_peft_model lora_config = LoraConfig( r=4, lora_alpha=16, target_modules=["visual_adapter", "text_adapter"], lora_dropout=0.1 ) model = get_peft_model(base_model, lora_config) # Trainer или кастомный цикл
Советы: MiniCPM хорошо масштабируется на небольших датасетах, но требует аккуратного подбора lr и batch size.
Best practices и лайфхаки
Используй mix precision (fp16/bf16), если позволяет железо.
Для генерации подписей (captioning) — cross-entropy loss, для VQA — можно комбинировать с классификационным loss.
Не забывай валидировать модель на своих данных, а не только на публичных бенчмарках.
Для inference можно использовать quantization (8bit/4bit), если важна скорость. Также хорошо ускоряет инференс vllm, sglang должен ускорять еще лучше, но его я не пробовал с этими моделями.
10. Файнтюнинг ImageBind с помощью кастомной реализации LoRA
ImageBind — это мультимодальная модель от Meta AI, способная сопоставлять эмбеддинги изображений, текста, аудио, глубины, тепловых данных и IMU в едином пространстве. Для специфических задач (например, оценки качества видеоописаний) стандартной предобученной модели часто недостаточно — требуется файнтюнинг.
HuggingFace: https://huggingface.co/facebook/imagebind-1.4b
ImageBind-LoRA: https://github.com/fabawi/ImageBind-LoRA
Почему здесь используется кастомная LoRA, а не стандартный PEFT?
В отличие от большинства LLM и vision-to-text моделей, где LoRA реализуется через библиотеку PEFT (HuggingFace), для ImageBind применяется кастомная реализация LoRA.
Причины:
В стандартном PEFT LoRA есть task_type (CAUSAL_LM, SEQ_2_SEQ_LM и т.д.), а для ImageBind задача — контрастивное обучение эмбеддингов, а не генерация или классификация.
Необходима гибкая интеграция LoRA-слоёв в attention-модули разных модальностей (vision, text, audio и др.).
Требуется точный контроль над тем, какие слои и модальности дообучаются.
Как устроена кастомная LoRA для ImageBind
LoRA применяется к проекциям Query, Key, Value (QKV) в multi-head attention.
Оригинальные attention-слои заменяются на обёртку с LoRA-адаптацией:
python class LoRALayer(nn.Module): def _init__(self, w: nn.Module, w_a: nn.Module, w_b: nn.Module): super().__init__() self.w = w # Оригинальный attention слой self.w_a = w_a # LoRA матрица A (dim → rank) self.w_b = w_b # LoRA матрица B (rank → dim) def forward(self, x: torch.Tensor, attn_mask: torch.Tensor, **kwargs): # Комбинируем оригинальный и LoRA-адаптированный выходы return self.w(x, attn_mask=attn_mask) + self.w_b(self.w_a(x))
Все параметры базовой модели замораживаются (requires_grad=False
), обучаются только LoRA-адаптеры.
LoRA применяется только к выбранным слоям и модальностям (например, vision и text).
Отличия от классического LoRA/PEFT
Классический LoRA (PEFT) |
Кастомная LoRA для ImageBind |
Использует task_type |
Нет task_type, только контрастивный loss |
Интеграция через HuggingFace |
Прямое внедрение в PyTorch-код модели |
Поддержка генерации/классификации |
Только контрастивное обучение эмбеддингов |
Автоматический выбор слоёв |
Ручной выбор модальностей и attention-слоёв |
Совместимость с Trainer/SFTTrainer |
Кастомный train loop или скрипты |
Пример запуска кастомного LoRA-файнтюнинга
Базовый запуск:
bash python
train.py
--batch_size 12 --max_epochs 500 \ --lora --lora_modality_names vision text \ --self_contrast --datasets dreambooth \ --device cuda:0
Выборочное применение LoRA к слоям:
bash python
train.py
--batch_size 12 --max_epochs 500 \ --lora --lora_modality_names vision text \ --lora_layer_idxs_vision 1 2 3 4 5 6 \ --self_contrast --datasets dreambooth
Двухэтапный подход: Linear Probing + LoRA
bash # Этап 1: Linear Probing (только heads) python
train.py
--batch_size 12 --max_epochs 500 \ --linear_probing --lora_modality_names vision text \ --self_contrast --datasets dreambooth # Этап 2: LoRA Fine-tuning (загрузка heads из этапа 1) python
train.py
--batch_size 12 --max_epochs 500 \ --lora --lora_modality_names vision text \ --self_contrast --datasets dreambooth
Ключевые гиперпараметры:
- LoRA rank: 4–16 (обычно 4)
- Learning rate: 5e-6
- Batch size: 12–32
- Temperature (для InfoNCE): 0.07
- Weight decay: 1e-4
- Gradient clipping: 1.0
Инференс с кастомной LoRA
python import torch from models import imagebind_model from models import lora as LoRA from models.imagebind_model import ModalityType, load_module # Загружаем базовую модель ImageBind model = imagebind_model.imagebind_huge(pretrained=True) # Применяем LoRA адаптеры model.modality_trunks.update( LoRA.apply_lora_modality_trunks( model.modality_trunks, rank=4, modality_names=[ModalityType.TEXT,
ModalityType.VISION
] ) ) # Загружаем обученные LoRA параметры LoRA.load_lora_modality_trunks( model.modality_trunks, checkpoint_dir=".checkpoints/lora/550_epochs_lora", postfix="_dreambooth_last" ) model.eval()
Практические советы
Начинай с Linear Probing, чтобы не испортить базовые эмбеддинги.
Применяй LoRA только к нужным модальностям и слоям.
Используй InfoNCE loss для контрастивного обучения.
Сохраняй только LoRA-адаптеры (экономия памяти).
Для логирования удобно использовать comet-ml или wandb.
Важное отличие
В отличие от классических LLM/vision-to-text моделей, здесь LoRA внедряется вручную в PyTorch-код, а не через PEFT/Trainer. Это даёт гибкость, но требует больше инженерных усилий.