Меня зовут Иван Исаев, я занимаюсь МЛ с 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
pythontrain.py--batch_size 12 --max_epochs 500
--lora --lora_modality_names vision text
--self_contrast --datasets dreambooth
--device cuda:0
Выборочное применение LoRA к слоям:
bash
pythontrain.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)
pythontrain.py--batch_size 12 --max_epochs 500
--linear_probing --lora_modality_names vision text
--self_contrast --datasets dreambooth# Этап 2: LoRA Fine-tuning (загрузка heads из этапа 1)
pythontrain.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. Это даёт гибкость, но требует больше инженерных усилий.
Автор: ivan_isaev
