Конспект 8: Fine-tuning и адаптация моделей
Оглавление
- Введение в Fine-tuning
- Когда нужен fine-tuning
- Типы fine-tuning
- PEFT - Parameter-Efficient Fine-Tuning
- LoRA и QLoRA
- Подготовка данных
- Процесс fine-tuning
- Валидация и оценка
- Развертывание fine-tuned моделей
- Практические примеры
- Лучшие практики
Введение в Fine-tuning
Что такое Fine-tuning?
Определение: Fine-tuning — это процесс адаптации предварительно обученной модели путем дополнительного обучения на специфических данных.
Аналогия:
Base Model (Foundation Model)
↓ (обучена на 2 триллионах текстов)
Fine-tuning (на 10,000 ваших примеров)
↓
Specialized Model
↓
Работает лучше всего на вашей задаче
Жизненный цикл модели
Pre-training (Общее обучение)
├─ Обучена на 2-3 триллионах токенов
├─ Знает основные паттерны языка
└─ Может решать много задач
↓
Fine-tuning (Специализация)
├─ Дополнительное обучение на 1K-100K примеров
├─ Адаптация к вашему домену
└─ Улучшение качества на 20-50%
↓
Deployment (Развертывание)
├─ Использование в production
├─ Мониторинг качества
└─ Периодическое переобучение
Когда нужен fine-tuning
Матрица решений
Качество
↑
|
Fine-tuning ● |
(дорого) |
|
Prompting ●────────────┼─────────→ Стоимость
(дешево) |
|
Сценарии для fine-tuning
| Сценарий | Fine-tuning? | Причина |
|---|---|---|
| Стандартная классификация | ❌ Нет | Zero-shot работает хорошо |
| Специфичный домен (медицина) | ✅ Да | Нужны специальные знания |
| Очень большой объем запросов | ✅ Да | Дешевле обучить, чем платить API |
| Требуется специфичный стиль | ✅ Да | Модель должна писать в вашем стиле |
| Требуется приватность | ✅ Да | Нельзя отправлять данные в облако |
| Редкий язык | ✅ Да | Base model плохо его знает |
| Уникальный формат вывода | ⚠️ Иногда | Prompting часто достаточно |
Анализ ROI
Затраты на fine-tuning:
- Data preparation: $5K-50K
- Training GPU time: $1K-10K
- Инженеры (2-4 недели): $10K-40K
- Total: $16K-100K
Экономия:
- Сравним fine-tuned vs API
Пример:
10 миллионов запросов/год
API (GPT-4): 10M × 500 tokens × $0.00003 = $150K/год
Fine-tuned: $50K (one-time) + $10K (maintenance) = $60K/year
ROI: (150K - 60K) / 50K = 1.8x в первый год
Типы fine-tuning
1. Full Fine-tuning (Complete Retraining)
Что обновляется: Все параметры модели
Исходная модель: 7 миллиардов параметров
Fine-tuning: Обновляются все 7B параметров
Требования:
- GPU память: 80GB+ (A100)
- Время обучения: 2-7 дней
- Стоимость: $5K-20K
- Требуемые данные: 10K+ примеров
Плюсы: - ✅ Максимальное улучшение качества - ✅ Может переучить поведение модели - ✅ Лучше всего подходит для специфичных доменов
Минусы: - ❌ Очень дорого ($5K+) - ❌ Требует много данных (10K+) - ❌ Долгое обучение (дни) - ❌ Нужна мощная инфраструктура
Когда использовать: - Специфичная медицинская диагностика - Очень большой домен-специфичный датасет - Требуется полная переработка поведения модели
2. PEFT Fine-tuning (Parameter-Efficient)
Что обновляется: Только малая часть параметров
Исходная модель: 7 миллиардов параметров
PEFT update: 0.1-1% параметров (~7-70 миллионов)
Требования:
- GPU память: 8-16GB (RTX4090)
- Время обучения: 2-8 часов
- Стоимость: $100-500
- Требуемые данные: 1K-5K примеров
Плюсы: - ✅ Дешево ($100-500) - ✅ Быстро (часы, не дни) - ✅ Можно обучить на одном GPU - ✅ Нужно меньше данных
Минусы: - ❌ Небольшое улучшение качества (+10-20%) - ❌ Не может переучить основное поведение
Когда использовать: - Большинство реальных задач - Когда бюджет ограничен - Когда нужна быстрая итерация
3. Instruction Tuning
Что обновляется: Способность следовать инструкциям
Dataset:
[
{"instruction": "Классифицируй эмейл", "input": "...", "output": "SPAM"},
{"instruction": "Резюмируй текст", "input": "...", "output": "..."},
...
]
Результат:
- Модель лучше следует инструкциям
- Улучшение на 15-30%
Примеры моделей: - Alpaca (fine-tuned Llama с instruction tuning) - Vicuna - OpenAssistant
4. RLHF (Reinforcement Learning from Human Feedback)
Процесс:
1. Fine-tune на instruction tuning данных
2. Обучить reward model (предсказывает предпочтение человека)
3. Обучить LLM максимизировать reward (RL)
4. Повторить с новыми примерами
Результат: Модель, которая производит "лучшие" ответы по мнению людей
Примеры: - ChatGPT (построен с RLHF) - Claude (обучен с Constitutional AI, вариант RLHF)
Требования: - Очень сложно реализовать - Нужны аннотаторы - Требует много вычислений - Стоимость: $50K-1M+
PEFT - Parameter-Efficient Fine-Tuning
Главные техники PEFT
1. LoRA (Low-Rank Adaptation)
Идея: Вместо обновления всей матрицы весов, добавляем две маленькие матрицы
Original weight matrix W: (d × d)
↓
LoRA adaptation:
W' = W + ΔW
где ΔW = A × B
A: (d × r)
B: (r × d)
r (rank): обычно 8-64 (маленький)
Экономия параметров:
Полный fine-tuning: d² параметров
LoRA: 2 × d × r параметров
Пример (d=4096, r=16):
Полный: 16M параметров
LoRA: 131K параметров
Экономия: 99.2%
Как это работает:
Input x
↓
┌─────────────────┐
│ Original W │ ← Не обновляется
└─────────────────┘
↓ y_original
├─────────────────┐
│ + │
│ ┌─────────────┐│
│ │ LoRA (AB) ││ ← Обновляется (0.1% параметров)
│ └─────────────┘│
└─────────────────┘
↓ y_final
Output
Где добавляется LoRA:
Обычно в:
- Query и Value проекции в attention
- Feed-forward слоях
- Может быть везде
Параметры LoRA:
rank (r): 8, 16, 32, 64 (меньше = дешевле, но худше)
alpha: масштабирующий коэффициент (обычно = r)
target_modules: где применять (attention, mlp, all)
lora_dropout: dropout для регуляризации
2. QLoRA (Quantized LoRA)
Улучшение LoRA с quantization:
Исходная модель 7B (float32): 28GB VRAM
↓
Quantized to 4-bit: 2GB VRAM
↓
LoRA adapters: 0.1GB VRAM
↓
Total: 2.1GB VRAM (вместо 28GB!)
Процесс:
1. Загрузить базовую модель в 4-bit
2. Добавить LoRA слои
3. Обучать только LoRA (базовая модель frozen)
4. Во время inference: 4-bit модель + LoRA адаптеры
Требования: - GPU: RTX 3090 (24GB) или даже RTX 4090 - VRAM: 8-16GB (очень экономно) - Скорость обучения: -30-40% медленнее
Когда использовать: - У вас ограниченный GPU - Нужна экономия памяти - Готовы потерять 10-20% скорости обучения
3. Prefix Tuning
Идея: Добавить обучаемый префикс в начало последовательности
Обычный prompt:
[User: "Классифицируй эмейл"]
С prefix tuning:
[Learnable prefix tokens (100 tokens)] + [User: "..."]
Обучаемые параметры: только префикс (очень мало)
Плюсы: - Очень мало параметров - Разные задачи = разные префиксы
Минусы: - Качество может быть хуже чем LoRA - Занимает место в контексте
4. Adapter Tuning
Идея: Добавить маленькие адаптер слои между слоями
Original Layer
↓
[Adapter Down (compress)]
↓
[ReLU activation]
↓
[Adapter Up (expand)]
↓
Output
Размер адаптера: ~3-5% параметров модели
LoRA и QLoRA
LoRA архитектура в деталях
Query matrix W_q (4096 × 4096)
↓
W_q' = W_q + A @ B
где A (4096 × 8), B (8 × 4096)
↓
Forward pass:
y = x @ W_q' = x @ W_q + x @ A @ B
Практическая настройка LoRA параметров
Parameter | Мало | Среднее | Много |
|----------|------|---------|-------|
| rank (r) | 8 | 32 | 64 |
| alpha | 16 | 64 | 128 |
| dropout | 0.1 | 0.05 | 0.01 |
| training time | 1h | 4h | 8h |
| memory | 8GB | 12GB | 16GB |
| quality gain | +10% | +20% | +25% |
QLoRA пример конфигурации
{
"model": "meta-llama/Llama-2-7b-hf",
"quantization_config": {
"load_in_4bit": true,
"bnb_4bit_compute_dtype": "float16",
"bnb_4bit_quant_type": "nf4",
"bnb_4bit_use_double_quant": true
},
"lora_config": {
"r": 16,
"lora_alpha": 32,
"lora_dropout": 0.05,
"bias": "none",
"task_type": "CAUSAL_LM",
"target_modules": ["q_proj", "v_proj"]
},
"training_args": {
"num_train_epochs": 3,
"per_device_train_batch_size": 4,
"gradient_accumulation_steps": 4,
"learning_rate": 2e-4,
"warmup_steps": 100,
"logging_steps": 10,
"save_steps": 100
}
}
Подготовка данных
Структура датасета
Для instruction tuning:
[
{
"instruction": "Классифицируй эмейл как SPAM или HAM",
"input": "You have won $1,000,000! Click here to claim.",
"output": "SPAM"
},
{
"instruction": "Резюмируй текст в одно предложение",
"input": "N8N это платформа для автоматизации...",
"output": "N8N автоматизирует рабочие процессы."
}
]
Для chat fine-tuning:
[
{
"messages": [
{"role": "system", "content": "Ты Python разработчик"},
{"role": "user", "content": "Как работает @property?"},
{"role": "assistant", "content": "@property позволяет..."}
]
}
]
Размер датасета
Правило: 100 примеров на 1B параметров модели
7B модель: 700 примеров (минимум)
13B модель: 1,300 примеров
70B модель: 7,000 примеров
Но лучше:
- 1K примеров для базового fine-tuning
- 5K+ для хорошего качества
- 10K+ для очень хорошего качества
Качество данных > Количество
Сценарий 1: 1000 хороших примеров
→ Fine-tuning качество: 85%
Сценарий 2: 5000 плохих примеров
→ Fine-tuning качество: 60%
Вывод: 1000 clean examples > 5000 noisy examples
Подготовка данных - чек-лист
☑️ Данные репрезентативны (представляют задачу)
☑️ Баланс классов (если классификация)
☑️ Нет дублей или близких дублей
☑️ Оформатированы правильно (JSON, CSV)
☑️ Разделены на train/val/test (80/10/10)
☑️ Примеры проверены вручную (sampling)
☑️ Размер достаточен (100+ минимум)
☑️ Нет PII данных (приватность)
☑️ Лицензия позволяет использовать
☑️ Версионировано (git, S3)
Процесс fine-tuning
Шаг 1: Подготовка окружения
Локально (с GPU):
# Установка зависимостей
pip install transformers datasets peft bitsandbytes
# Для QLoRA потребуется:
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
В облаке:
Google Colab Pro (бесплатно, 40GB GPU)
Lambda Labs (дешево, $0.6-1.5/час GPU)
Replicate (управляемый сервис)
Together AI (специально для fine-tuning)
Шаг 2: Загрузка базовой модели
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import get_peft_model, LoraConfig, TaskType
model_id = "meta-llama/Llama-2-7b-hf"
# Для обычного fine-tuning
model = AutoModelForCausalLM.from_pretrained(
model_id,
device_map="auto",
torch_dtype=torch.float16
)
# ДЛЯ QLoRA (4-bit quantization)
from transformers import BitsAndBytesConfig
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_use_double_quant=True
)
model = AutoModelForCausalLM.from_pretrained(
model_id,
quantization_config=bnb_config,
device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained(model_id)
Шаг 3: Конфигурация LoRA
peft_config = LoraConfig(
r=8,
lora_alpha=16,
lora_dropout=0.1,
bias="none",
task_type=TaskType.CAUSAL_LM,
target_modules=["q_proj", "v_proj"], # Attention layers
modules_to_save=["lm_head"] # Сохранить также финальный слой
)
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()
# Output: trainable params: 4194304 || all params: 6738415616 || trainable: 0.06%
Шаг 4: Подготовка данных
from datasets import load_dataset
dataset = load_dataset("json", data_files="data.json")
def formatting_func(examples):
outputs = []
for i in range(len(examples["instruction"])):
text = f"""Instruction: {examples["instruction"][i]}
Input: {examples["input"][i]}
Output: {examples["output"][i]}"""
outputs.append(text)
return {"text": outputs}
dataset = dataset.map(
formatting_func,
batched=True,
remove_columns=dataset.column_names
)
# Токенизация
def tokenize_function(examples):
return tokenizer(
examples["text"],
truncation=True,
max_length=512,
padding="max_length"
)
tokenized_datasets = dataset.map(tokenize_function, batched=True)
train_dataset = tokenized_datasets["train"]
eval_dataset = tokenized_datasets["test"]
Шаг 5: Обучение
from transformers import Trainer, TrainingArguments
training_args = TrainingArguments(
output_dir="./lora-finetuned-model",
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
learning_rate=2e-4,
warmup_steps=100,
weight_decay=0.001,
logging_steps=10,
save_steps=100,
evaluation_strategy="steps",
eval_steps=50,
save_total_limit=3,
load_best_model_at_end=True,
metric_for_best_model="eval_loss",
greater_is_better=False,
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=eval_dataset,
data_collator=DataCollatorForLanguageModeling(tokenizer, mlm=False)
)
trainer.train()
Шаг 6: Сохранение
# Сохранить LoRA адаптеры
model.save_pretrained("./lora-model")
# Сохранить полную модель (если хочешь)
merged_model = model.merge_and_unload()
merged_model.save_pretrained("./merged-model")
# Загрузить позже
from peft import AutoPeftModelForCausalLM
loaded_model = AutoPeftModelForCausalLM.from_pretrained(
"./lora-model",
device_map="auto"
)
Валидация и оценка
Метрики оценки
Перплексия (Perplexity):
- Как "удивлена" модель предсказаниями
- Ниже = лучше
- Обычно: 10-50 для хорошо обученной модели
BLEU Score:
- Для генерации текста
- 0-100 (выше = лучше)
- >30 считается хорошим
ROUGE Score:
- Для summarization
- >0.3 считается хорошим
Exact Match:
- Для QA и классификации
- % ответов которые совпадают на 100%
F1 Score:
- Для классификации
- >0.8 считается хорошим
Валидация на тестовом сете
eval_results = trainer.evaluate()
print(f"Perplexity: {eval_results['eval_loss']:.4f}")
print(f"Eval loss: {eval_results['eval_loss']:.4f}")
# Manual evaluation
model.eval()
test_input = "Classify: This is great!"
inputs = tokenizer(test_input, return_tensors="pt")
outputs = model.generate(**inputs, max_length=100)
print(tokenizer.decode(outputs[0]))
A/B тестирование fine-tuned vs Base
# Тестовый датасет
test_examples = [
"Classify: You won $1000!",
"Classify: How are you doing?",
...
]
# Оценка base модели
base_accuracy = evaluate_model(base_model, test_examples)
# Оценка fine-tuned
finetuned_accuracy = evaluate_model(finetuned_model, test_examples)
improvement = (finetuned_accuracy - base_accuracy) / base_accuracy * 100
print(f"Improvement: {improvement:.1f}%")
# Если < 10% - может быть не стоит fine-tuning
# Если > 20% - отличный результат
Развертывание fine-tuned моделей
Локальное развертывание
from peft import AutoPeftModelForCausalLM
from transformers import AutoTokenizer
import torch
# Загрузить fine-tuned модель
model = AutoPeftModelForCausalLM.from_pretrained(
"path/to/lora-model",
device_map="auto",
torch_dtype=torch.float16
)
tokenizer = AutoTokenizer.from_pretrained("base_model_id")
# Инферен
input_text = "Classify: This is spam"
inputs = tokenizer(input_text, return_tensors="pt")
outputs = model.generate(**inputs, max_length=50)
result = tokenizer.decode(outputs[0])
print(result)
Облачное развертывание
HuggingFace Hub:
# Загрузить в HuggingFace
model.push_to_hub("username/my-finetuned-llama")
# Использовать через API
from transformers import pipeline
pipe = pipeline(
"text-generation",
model="username/my-finetuned-llama"
)
result = pipe("Classify: This is great!")
Replicate или Together AI:
import replicate
output = replicate.run(
"username/my-finetuned-llama:version-id",
input={"prompt": "Classify: ..."}
)
Merge и оптимизация
# Merge base модель и LoRA
merged = model.merge_and_unload()
# Quantize для инферена
from transformers import AutoModelForCausalLM
from bitsandbytes.nn import Linear4bit
# Можно quantize до 8-bit или 4-bit для deployment
Практические примеры
Пример 1: Fine-tune для classification
Задача: Классифицировать Support tickets
Dataset:
[
{
"instruction": "Classify support ticket",
"input": "Cannot login to my account",
"output": "TECHNICAL"
},
{
"instruction": "Classify support ticket",
"input": "How much does pro plan cost?",
"output": "BILLING"
}
]
Процесс:
1. Подготовить 500+ примеров
2. LoRA config: r=8, target_modules=["q_proj", "v_proj"]
3. Обучить 2-3 эпохи
4. Оценить на test сете
5. Развернуть локально или в облаке
Результат: - Base модель: 65% accuracy - Fine-tuned: 92% accuracy - Improvement: +27% ✅
Пример 2: Fine-tune для generation
Задача: Генерация SQL запросов
Dataset:
[
{
"instruction": "Generate SQL query",
"input": "Get users older than 30",
"output": "SELECT * FROM users WHERE age > 30;"
}
]
Процесс: 1. 1000+ примеров SQL запросов 2. LoRA config: r=16 (SQL сложнее) 3. Обучить 3-5 эпох 4. Оценить синтаксис + semantic correctness
Лучшие практики
Do's ✅
-
Начните с малого
Сначала: 100 примеров + LoRA r=8 Потом: 1000 примеров + r=16 Полный fine-tuning: если нужно -
Используйте QLoRA для больших моделей
70B модель + QLoRA = 16GB GPU Вместо: 280GB GPU -
Валидируйте на реальных примерах
Не только метрики, но и manual testing -
Версионируйте данные и модели
git для кода W&B для экспериментов S3 для весов -
Мониторьте degradation в production
Иногда fine-tuned модель худше на edge cases Установите алерты на качество -
Переиспользуйте LoRA адаптеры
Можно обучить несколько адаптеров для разных задач
Don'ts ❌
-
Не переучивайте на маленьком датасете
<50 примеров = переобучение (overfitting) -
Не игнорируйте базовое качество
Если base модель плохая, fine-tuning не спасет -
Не используйте очень большой learning rate
Может полностью испортить модель LR: 1e-4 до 5e-4 обычно хорошо -
Не забывайте eval во время обучения
Без eval нельзя знать когда остановиться -
Не деплойте без тестирования
Протестируйте на real data перед production
Чек-лист fine-tuning
Подготовка:
☑️ Данные очищены и отформатированы
☑️ Размер датасета достаточен (100+)
☑️ Train/val/test split сделан
☑️ GPU/вычисления готовы
Обучение:
☑️ LoRA конфигурация выбрана
☑️ Learning rate оптимален
☑️ Обучение мониторится (логи)
☑️ Early stopping настроено
Валидация:
☑️ Метрики считаются
☑️ Тестирование на новых примерах
☑️ Сравнение с base моделью
☑️ Edge cases проверены
Развертывание:
☑️ Модель сохранена
☑️ Инферен код готов
☑️ API/интерфейс работает
☑️ Мониторинг включен
Дата создания: December 2025
Версия: 1.0
Автор: Pavel
Применение: Model Adaptation, Domain-Specific Fine-tuning, Production ML