A B C D E F G H I J K L M N O P Q R S T V W Y Z А Б В Г Е И К М О П С Т Ц
Fi Fo

Fine tuning

Fine tuning

Fine tuning (дообучение модели) это продолжение обучения уже готовой нейросети на своей небольшой выборке, при котором меняются её веса. Промптинг и RAG (Retrieval-Augmented Generation) работают с контекстом, то есть с тем, что модель видит на входе прямо сейчас, а fine-tuning переписывает саму модель. Разница принципиальная. После дообучения нужное поведение держится без единой подсказки в промпте, но и поправить его так же дёшево, как строчку инструкции, уже не выйдет.

 

Что такое дообучение модели и какую задачу оно закрывает

Базовая языковая модель обучена на общем корпусе и умеет всё понемногу. Она не знает вашего формата ответа, вашей терминологии и вашего тона. Дообучение берёт такую модель и прогоняет по ней ещё несколько эпох на маленькой размеченной выборке, обновляя веса тем же градиентным спуском, что и на большом корпусе, только несравнимо дешевле и короче.

Главную мысль про fine-tuning стоит зафиксировать сразу, потому что дальше всё крутится вокруг неё. Дообучение ставит модели форму и поведение, а не добавляет ей знаний. Если модель начала отвечать одной строкой строгого формата, это заслуга дообучения. Если она при этом путает Apache HBase с Apache Hadoop, дообучение такую беду не лечит, потому что фактическая точность живёт в другом месте, а именно в подложенном на вход контексте.

 

Что именно меняется в модели

Обучаемое внутри трансформера это матрицы весов линейных слоёв, прежде всего проекций внимания q_proj, k_proj, v_proj и o_proj, плюс слои прямого прохода. Методы дообучения различаются тем, какую часть этих матриц они трогают и в каком виде держат всё остальное.

 

Полное дообучение

Полное дообучение (full fine-tuning) обновляет все веса без исключения. Для модели на 7 млрд параметров это означает держать в памяти сами веса, их градиенты и состояние оптимизатора Adam, то есть на порядок величины больше, чем нужно на инференс той же модели. Результат самый гибкий из возможных, цена самая высокая, и на каждую задачу получается отдельная полная копия модели весом в десятки гигабайт.

 

LoRA и адаптеры

LoRA (Low-Rank Adaptation) исходит из наблюдения, что поправка к весам при дообучении имеет низкий ранг, поэтому её можно приблизить произведением двух узких матриц. Исходную матрицу замораживают, а рядом ставят пару обучаемых матриц A и B внутренней размерности r, обычно от 4 до 64. На выходе слоя результат базового умножения складывают с низкоранговой поправкой, домноженной на масштаб alpha, делённый на r. Учатся только A и B.

Схема встраивания LoRA-адаптера в линейный слой трансформера

 

Практический эффект виден по одной цифре. В демо ниже на модели Qwen2.5-0.5B-Instruct обучаемыми оказались 1 081 344 параметра из 495 114 112, то есть 0.218 процента. Веса адаптера заняли 4,35 МБ против 953 МБ у базовой модели, в 219 раз меньше. Отсюда и вся экономика подхода, ведь на одной базе можно держать десяток адаптеров под разные задачи и переключать их прямо в рантайме. Кроме LoRA семейство PEFT (Parameter-Efficient Fine-Tuning) включает IA3, AdaLoRA и prefix tuning, их перечень и требования к версиям собраны в документации Hugging Face PEFT.

 

QLoRA и квантованная база

QLoRA идёт дальше и квантует замороженную базу в 4 бита, оставляя адаптеры в обычной точности. Память под базовые веса падает примерно вчетверо, поэтому дообучение семимиллиардной модели становится посильным для одной потребительской видеокарты. Расплата двойная. Квантование стоит части качества, а библиотека bitsandbytes собрана под CUDA, из-за чего на Apple Silicon этот вариант недоступен, и в демо ниже он поэтому не показан.

 

Принцип работы

Доводка модели под задачу почти всегда разбивается на два непохожих этапа. Сначала модель учат давать правильный ответ, потом учат выбирать лучший из двух правильных.

 

SFT и функция потерь только по ответу

SFT (Supervised Fine-Tuning) это обучение на парах вопрос-ответ. Тонкость в том, что в чат-разметку попадает и системный промпт, и реплика пользователя, а воспроизводить их модель учить незачем. Поэтому функция потерь считается только по токенам ответа ассистента, а всё остальное маскируется. В нашем прогоне на примере про HDFS из 87 токенов последовательности под loss попали 48, ровно текст ответа вместе со служебным маркером конца реплики.

 

DPO и обучение на парах предпочтений

DPO (Direct Preference Optimization) работает уже поверх SFT и учит модель не правильному ответу, а предпочтению. На вход идёт тройка из запроса, удачного ответа и неудачного, а модель сдвигается в сторону первого относительно опорной копии самой себя. Это упрощённая замена обучению с подкреплением на человеческой обратной связи, без отдельной модели вознаграждения. В библиотеке TRL версии 1.0 оба тренера, SFTTrainer и DPOTrainer, лежат в одном пакете вместе с GRPO и reward-моделями.

Этапы дообучения языковой модели от базовых весов до адаптера в продакшене

 

 

Нейронные сети на Python

Код курса
PYNN
Ближайшая дата курса
12 октября, 2026
Продолжительность
24 ак.часов
Стоимость обучения
66 000

 

Полное дообучение, LoRA и QLoRA

Три метода отличаются не качеством как таковым, а тем, сколько ресурсов вы готовы отдать за последние проценты. Сводка ниже собрана по документации PEFT и фреймворка запуска дообучения Axolotl, а строки про доступное железо дополнены фактом нашего стенда. Порядки величин здесь качественные, потому что конкретные цифры сильно зависят от длины контекста и размера батча.

Критерий Полное дообучение LoRA QLoRA
Что обучается все веса модели низкоранговые матрицы поверх замороженной базы то же, база ещё и квантована в 4 бита
Память относительно инференса кратно больше чуть больше инференса примерно четверть от LoRA по базе
Размер артефакта полная копия модели, десятки ГБ единицы или десятки МБ единицы или десятки МБ
Железо кластер или профессиональные ускорители одна видеокарта, MPS, в пределе CPU только CUDA из-за bitsandbytes
Потеря качества нет небольшая на сложных сдвигах домена плюс потеря от квантования
Когда брать меняется сама предметная область меняются формат, стиль, поведение то же, но железа не хватает

Практика последних лет проста. Начинают с LoRA, к QLoRA переходят, когда модель не влезает в память, а полное дообучение остаётся уделом тех, кто строит свою базовую модель, а не приспосабливает чужую.

 

Сценарии применения

Дообучение оправдано там, где промптом задачу решить можно, но каждый раз и дорого. Типичные поводы взяться за fine-tuning выглядят так.

  • Жёсткий формат вывода. Модель обязана отдавать строку, JSON или разметку определённого вида, а инструкция в промпте соблюдается через раз.
  • Свой стиль и тон. Поддержка, юридические тексты, внутренняя документация, где важна манера изложения компании.
  • Узкая доменная терминология. Модель должна понимать сокращения предприятия и не переспрашивать.
  • Экономия на длине промпта. Инструкция на две тысячи токенов, которая едет с каждым запросом, переносится в веса и перестаёт оплачиваться.
  • Дистилляция. Маленькая модель учится повторять поведение большой, чтобы работать на своём железе.

Во всех пяти случаях меняется поведение модели, а не набор фактов, которыми она оперирует, и это не совпадение.

 

Когда дообучение не нужно

Если задача в том, чтобы модель знала свежие или закрытые факты, дообучение это неверный инструмент. Факты меняются, а перезапуск обучения на каждое изменение никто не выдержит. Здесь работает RAG, где нужный фрагмент находится в базе и подкладывается в промпт. Обновить факт в такой базе стоит одной записи. Когда речь заходит про эксплуатацию всей этой конструкции, про версии адаптеров, откаты и мониторинг, начинается отдельная дисциплина, разобранная в статье Что такое LLMOps или MLOps для больших языковых моделей.

 

Практика LoRA-дообучения на локальной машине

По традиции весь код используемый в статье выкладываем на наш GitHub репозиторий
ДООБУЧЕНИЕ МОДЕЛИ (FINE-TUNING) from airflow import DAG from airflow.operators.bash import BashOperator from datetime import datetime with DAG( dag_id="spark_submit_demo", start_date=datetime(2025, 1, 1), schedule="@daily", catchup=False ) as dag: run = BashOperator( task_id="run_job", bash_command="spark-submit app.py" ) GitHub code example ДООБУЧЕНИЕ МОДЕЛИ (FINE-TUNING)

Прогон сделан на macOS 26.5.2 с Apple Silicon, на ускорителе MPS (Metal Performance Shaders), без всякой CUDA. Модель взята маленькая, Qwen2.5-0.5B-Instruct, а задача выбрана так, чтобы результат читался глазами. Модель учат отвечать одной строкой строгого формата с определением и категорией вместо нескольких абзацев свободного текста. Обучающая выборка это сорок пар по терминам инженерии данных, они лежат прямо в файле dataset.py, там же переписанный шаблон чата с разметкой реплик ассистента. Штатный шаблон Qwen2.5 такой разметки не содержит, поэтому без правки маска ответа получается пустой.

# torch 2.13.0, transformers 5.15.1, peft 0.20.0, trl 1.10.0, Qwen2.5-0.5B-Instruct,
# прогнано на macOS 26.5.2 (Apple Silicon, MPS) 2026-08-24
import json
import os
import subprocess
import time

import torch
from datasets import Dataset
from peft import LoraConfig
from transformers import AutoModelForCausalLM, AutoTokenizer
from trl import SFTConfig, SFTTrainer

from dataset import CHAT_TEMPLATE, train_records

BASE_MODEL = "Qwen/Qwen2.5-0.5B-Instruct"
ADAPTER_DIR = "lora_adapter"
SEED = 42


def main():
    device = "mps" if torch.backends.mps.is_available() else "cpu"
    print(f"устройство: {device}, torch {torch.__version__}")

    tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL)
    # Шаблон с разметкой ответа: без него assistant_only_loss работать не может
    tokenizer.chat_template = CHAT_TEMPLATE
    # float32: на MPS половинная точность в обучении даёт нестабильный loss
    model = AutoModelForCausalLM.from_pretrained(BASE_MODEL, dtype=torch.float32)
    model.config.use_cache = False  # несовместимо с расчётом градиентов

    records = train_records()
    dataset = Dataset.from_list(records)
    print(f"примеров в обучающей выборке: {len(dataset)}")

    # Ранг r задаёт размер низкоранговых матриц, alpha масштабирует их вклад.
    # target_modules: проекции внутри блока внимания, классический минимум для LoRA.
    lora_config = LoraConfig(
        r=8,
        lora_alpha=16,
        lora_dropout=0.05,
        target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
        task_type="CAUSAL_LM",
    )

    training_args = SFTConfig(
        output_dir="checkpoints",
        num_train_epochs=8,
        per_device_train_batch_size=4,
        learning_rate=2e-4,
        lr_scheduler_type="constant",
        logging_steps=5,
        max_length=256,
        packing=False,
        # loss только по токенам ответа: вопрос модель воспроизводить не учим
        assistant_only_loss=True,
        save_strategy="no",
        report_to=[],
        seed=SEED,
        disable_tqdm=True,
    )

    trainer = SFTTrainer(
        model=model,
        args=training_args,
        train_dataset=dataset,
        processing_class=tokenizer,
        peft_config=lora_config,
    )

    # Доля обучаемых параметров: главная цифра, ради которой LoRA и берут
    trainable = sum(p.numel() for p in trainer.model.parameters() if p.requires_grad)
    total = sum(p.numel() for p in trainer.model.parameters())
    print(f"всего параметров: {total:,}")
    print(f"обучаемых параметров: {trainable:,} ({100 * trainable / total:.3f}%)")

    started = time.time()
    result = trainer.train()
    elapsed = time.time() - started

    history = [row for row in trainer.state.log_history if "loss" in row]
    if history:
        print(f"loss на старте: {history[0]['loss']:.4f}")
        print(f"loss в конце:   {history[-1]['loss']:.4f}")
    print(f"шагов обучения: {result.global_step}")
    print(f"время обучения: {elapsed:.1f} с")

    trainer.save_model(ADAPTER_DIR)
    size = subprocess.run(["du", "-sh", ADAPTER_DIR], capture_output=True, text=True)
    print(f"адаптер сохранён: {size.stdout.strip()}")
    for name in sorted(os.listdir(ADAPTER_DIR)):
        print(f"  {name}")

    with open("train_metrics.json", "w", encoding="utf-8") as fh:
        json.dump(
            {
                "trainable_params": trainable,
                "total_params": total,
                "trainable_share_percent": round(100 * trainable / total, 4),
                "steps": result.global_step,
                "seconds": round(elapsed, 1),
                "loss_first": history[0]["loss"] if history else None,
                "loss_last": history[-1]["loss"] if history else None,
            },
            fh,
            ensure_ascii=False,
            indent=2,
        )


if __name__ == "__main__":
    main()

Восемь эпох по сорока примерам дали 80 шагов и уложились в три минуты на ноутбуке. Вот что напечатал скрипт.

всего параметров: 495,114,112
обучаемых параметров: 1,081,344 (0.218%)
loss на старте: 2.5928
loss в конце:   0.6465
шагов обучения: 80
время обучения: 171.2 с
адаптер сохранён: 15M	lora_adapter

Loss падал ровно, от 2.593 к 0.647, а доля верно предсказанных токенов ответа выросла с 0.505 до 0.884. Каталог адаптера весит 15 МБ, потому что рядом с весами сохраняется токенизатор, сами веса это те самые 4,35 МБ. Дальше скрипт compare_before_after.py задаёт одни и те же вопросы базовой модели и ей же с навешенным адаптером, генерация жадная, поэтому разница объясняется адаптером, а не случайностью выбора токенов.

ДО,    HDFS: Hadoop Distributed File System (HDFS) - это распределенная файловая система,
             используемый для хранения больших объемов данных в компьютерных системах.
ПОСЛЕ, HDFS: HDFS - распределённая файловая система Hadoop, которая хранит копии файлов на
             нескольких серверах для обработки пакетов. Категория: хранение.

Формат встал. Тот же эффект виден и на отложенных терминах, которых в обучении не было, значит модель не заучила сорок строк, а подхватила манеру отвечать.

 

Ограничения и грабли

Самое интересное в этом прогоне не то, что получилось, а то, что не получилось. Формат перенёсся на все четыре отложенных термина, а фактическая точность нет. Про Apache HBase модель после дообучения начинает ответ словами «Apache Hadoop», Data Vault относит к категории MLOps, а в русское предложение про Apache Hive у неё вклиниваются китайские иероглифы. До дообучения ответы были ровно такими же неверными, просто длиннее. Лучшей иллюстрации главного тезиса не придумаешь, ведь дообучение поставило форму и не тронуло знания.

Не всегда все получается хорошо но тем и замечателен эксперимент с дообучением

Остальные грабли этого класса задач стоит знать заранее.

  • Переобучение на маленькой выборке. Сорок примеров и восемь эпох это край, дальше модель начинает выдавать заученные определения на любой вопрос.
  • Половинная точность на MPS. В float16 обучение на Apple Silicon даёт нестабильные градиенты и NaN в loss, поэтому в коде стоит float32.
  • Разметка ответа в шаблоне чата. Штатный шаблон Qwen2.5 не содержит блока generation, поэтому режим подсчёта loss только по ответу с ним не работает и шаблон приходится переписывать руками.
  • Катастрофическое забывание. Долгое дообучение под узкую задачу портит общие способности модели, а LoRA страдает от этого меньше полного дообучения ровно потому, что база заморожена.
  • Смена версий библиотек. В TRL 1.10.0 параметр называется max_length, а не max_seq_length, и подобные переименования между минорными версиями встречаются регулярно.

Ни одна из этих ловушек не видна на этапе планирования, все они всплывают на первом же реальном прогоне, что и есть аргумент в пользу маленькой модели на старте.

Разработка и внедрение ML-решений

Код курса
MLOPS
Ближайшая дата курса
9 ноября, 2026
Продолжительность
24 ак.часов
Стоимость обучения
66 000

 

Заключение

Дообучение модели меняет её веса, а не контекст, и потому закрывает вопросы формы, стиля и поведения, оставляя вопросы знания за RAG и промптингом. LoRA сделала эту процедуру дешёвой настолько, что рабочий адаптер снимается на ноутбуке за три минуты и весит меньше пяти мегабайт, а QLoRA растягивает тот же приём на модели, которые иначе не влезли бы в память. Дальше начинается инженерия, то есть версионирование адаптеров, регресс-тесты качества и выкатка. Как это устроено в промышленной эксплуатации, разбираем на курсе Архитектура ML систем в нашем лицензированном учебном центре в Москве.

 

Референсные ссылки