Меня зовут Иван Исаев, я занимаюсь МЛ с 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 в едином пространстве. Для специфических задач (например, оценки качества видеоописаний) стандартной предобученной модели часто недостаточно — требуется файнтюнинг.

Почему здесь используется кастомная 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. Это даёт гибкость, но требует больше инженерных усилий.

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