惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
U
Unit 42
H
Help Net Security
博客园_首页
雷峰网
雷峰网
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
博客园 - 【当耐特】
Recent Announcements
Recent Announcements
Recorded Future
Recorded Future
aimingoo的专栏
aimingoo的专栏
爱范儿
爱范儿
C
CXSECURITY Database RSS Feed - CXSecurity.com
The Cloudflare Blog
S
Security @ Cisco Blogs
M
MIT News - Artificial intelligence
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
Spread Privacy
Spread Privacy
The GitHub Blog
The GitHub Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
K
Kaspersky official blog
MyScale Blog
MyScale Blog
阮一峰的网络日志
阮一峰的网络日志
C
CERT Recently Published Vulnerability Notes
V
Vulnerabilities – Threatpost
WordPress大学
WordPress大学
C
Cisco Blogs
G
Google Developers Blog
N
News and Events Feed by Topic
P
Palo Alto Networks Blog
Apple Machine Learning Research
Apple Machine Learning Research
Schneier on Security
Schneier on Security
博客园 - 聂微东
Security Latest
Security Latest
Security Archives - TechRepublic
Security Archives - TechRepublic
O
OpenAI News
云风的 BLOG
云风的 BLOG
IT之家
IT之家
PCI Perspectives
PCI Perspectives
Microsoft Security Blog
Microsoft Security Blog
NISL@THU
NISL@THU
小众软件
小众软件
Scott Helme
Scott Helme
大猫的无限游戏
大猫的无限游戏
L
LINUX DO - 最新话题
Microsoft Azure Blog
Microsoft Azure Blog
Simon Willison's Weblog
Simon Willison's Weblog
N
News and Events Feed by Topic
F
Fortinet All Blogs
The Last Watchdog
The Last Watchdog

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Как дообучать локальные LLM в 2026 году: практическое руководство
kmoseenk (OT · 2026-04-23 · via Все публикации подряд на Хабре

Уровень сложностиСредний

Время на прочтение18 мин

Охват и читатели1.9K

Туториал

Перевод

В 2026 году возможность дообучения локальных LLM стала реальной опцией для отдельных разработчиков и небольших команд. Это стало возможным благодаря снижению требований к видеопамяти (VRAM), развитию инструментов и расширению набора базовых моделей с открытыми лицензиями.

Если раньше адаптация LLM под узкоспециализированные задачи была доступна только хорошо финансируемым лабораториям, то теперь улучшения QLoRA и унифицированные фреймворки вроде Unsloth позволяют дообучать модели с 8 миллиардами параметров на одной потребительской видеокарте с 12 ГБ памяти. В этом руководстве разобран полный процесс: от выбора, нужен ли вообще этап дообучения (или fine-tuning), до подготовки данных, настройки и запуска обучения, оценки результатов и экспорта модели для локального вывода.

Как дообучать локальные LLM

  • Оцените, действительно ли требуется дообучение, или задачу можно решить с помощью промпт-инжиниринга или генерации с дополнением извлечения (RAG), используя подход принятия решений с учётом стоимости, задержек и приватности данных.

  • Подготовьте аппаратную и программную среду: Python, PyTorch, Unsloth и экосистему Hugging Face.

  • Соберите качественный набор данных объёмом от 500 до 10 000 примеров в форматах ChatML, ShareGPT или Alpaca, с удалением дубликатов и фильтрацией по длине.

  • Выберите базовую модель с открытой лицензией в диапазоне 7–8 млрд параметров (Llama 3.1 8B, Mistral 7B или Qwen 2.5 7B).

  • Настройте обучение с использованием QLoRA: задайте ранг, скорость обучения и целевые модули через Unsloth и SFTTrainer.

  • Отслеживайте значения функции потерь на обучении и валидации, чтобы вовремя выявить переобучение или расхождение; при росте валидационной ошибки останавливайте обучение раньше.

  • Объедините адаптеры LoRA с базовой моделью и экспортируйте результат в формат GGUF для локального вывода через llama.cpp или Ollama.

  • Оцените дообученную модель с помощью количественных метрик и качественного сравнения с базовой моделью «бок о бок».

Когда выбирать дообучение, а когда — промпт-инжиниринг или RAG

Подход к выбору: как принять правильное решение

Прежде чем переходить к процессу дообучения, имеет смысл системно сравнить три ключевых стратегии настройки поведения LLM: промпт-инжиниринг, RAG и дообучение. У каждой из них свои компромиссы по стоимости, задержкам, приватности данных, точности и сложности поддержки.

Параметр

Промпт-инжиниринг

RAG

Дообучение

Начальные затраты

Почти нулевые

Средние (пайплайн эмбеддингов, векторное хранилище)

Высокие (вычисления, подготовка датасета)

Задержка при выводе

Низкая

Выше (извлечение + генерация)

Низкая (без этапа извлечения)

Приватность данных

Зависит от поставщика API

Данные остаются локальными при self-hosted-развертывании

Данные полностью остаются локальными

Потолок точности

Ограничен размером контекста и знаниями базовой модели

Высокий для фактических данных; зависит от качества извлечения

Максимальный для поведенческой и стилистической адаптации

Сложность поддержки

Низкая (обновление промптов)

Средняя (актуализация индекса)

Выше (переобучение на новых данных)

Лучшие сценарии использования

Прототипирование, общие задачи, few-shot-сценарии

Вопросы и ответы с опорой на знания, поиск по документам

Доменные термины, строгие форматы вывода, офлайн-развертывание

Используйте эту таблицу как ориентир при выборе подхода. Важно понимать, что эти стратегии не исключают друг друга. RAG и дообучение хорошо сочетаются: дообученная модель, которая дополнительно извлекает информацию из базы знаний, часто показывает результаты лучше, чем любой из подходов по отдельности.

Как понять, нужно ли дообучение на самом деле

Дообучение — оправданный выбор, если:

  • Требуется строгое и стабильное форматирование вывода, которого невозможно добиться только с помощью промптов.

  • Модель должна усвоить специализированную терминологию предметной области (юриспруденция, медицина, проприетарные кодовые базы).

  • Необходимо поведенческое выравнивание — модель должна последовательно придерживаться определённой роли и тона во всех ответах.

  • Критична низкая задержка, и этап извлечения, добавляемый RAG, недопустим.

  • Развёртывание происходит в изолированной среде без доступа к внешним API и векторным хранилищам.

Однако дообучение — избыточное решение в тех случаях, когда грамотно составленный системный промпт уже даёт нужное поведение, когда нехватку знаний можно компенсировать добавлением контекста на этапе вывода или когда датасет содержит меньше 200–300 качественных примеров и не способен заметно повлиять на поведение модели. В таких ситуациях вычислительные затраты и длительность итераций при дообучении дают всё меньшую отдачу.

Предварительные условия и требования к оборудованию

Минимальное оборудование для разных методов дообучения

Требования к оборудованию сильно различаются в зависимости от метода дообучения и размера модели.

Полное дообучение модели с 7 млрд параметров требует 48 ГБ VRAM или больше. Это означает, что понадобится NVIDIA A6000 или конфигурация с несколькими GPU. Для моделей свыше 13 млрд параметров уже необходимы многомашинное обучение или карты A100/H100 с 80 ГБ памяти.

LoRA (низкоранговая адаптация, англ. Low-Rank Adaptation) существенно сокращает число обучаемых параметров, снижая требования к VRAM до 16–24 ГБ. RTX 4090 (24 ГБ) или RTX 5090 без проблем справляются с 7B-моделями при использовании LoRA.

QLoRA ещё сильнее снижает требования — до 8–12 ГБ, за счёт квантизации базовой модели до 4-битной точности и обучения только низкоранговых адаптеров в более высокой точности.

RTX 4070 Ti (12 ГБ) или аналогичная потребительская карта подходит для моделей класса 7B–8B.

Для тех, у кого нет доступа к локальному GPU, облачные инстансы на RunPod, Lambda или Vast.ai с GPU A100 или H100 позволяют обойтись без покупки выделенного оборудования. Стоимость зависит от провайдера и типа GPU, поэтому перед запуском стоит проверить актуальные тарифы.

Настройка программного стека

Стек для дообучения в 2026 году в основном строится вокруг Python 3.11+, PyTorch 2.5+, CUDA 12.x и экосистемы Hugging Face (transformers, datasets, peft, trl). Unsloth предоставляет оптимизированные ядра обучения, которые уменьшают потребление памяти и повышают пропускную способность. Для воспроизводимости критически важно жёстко фиксировать версии зависимостей.

# Пример кода 1: Настройка окружения с зафиксированными версиями
conda create -n finetune python=3.11 -y
conda activate finetune

# Проверьте версию драйвера CUDA с помощью nvidia-smi и выберите
# подходящий URL индекса на странице https://pytorch.org/get-started/locally
pip install torch==2.5.1 --index-url https://download.pytorch.org/whl/cu124
pip install "unsloth>=2025.3,<2026.0"  # Check https://pypi.org/project/unsloth for latest stable version
pip install transformers==4.48.0 datasets==3.2.0 peft==0.14.0 trl==0.14.0
pip install bitsandbytes==0.45.0
pip install wandb tensorboard
pip install sentencepiece protobuf

# Проверьте доступность CUDA
python -c "import torch; print(f'CUDA available: {torch.cuda.is_available()}, Device: {torch.cuda.get_device_name(0)}')"

Это окружение обеспечивает стабильную основу. Пакет unsloth модифицирует внутренние компоненты Hugging Face, ускоряя обучение и снижая потребление памяти, тогда как bitsandbytes добавляет поддержку 4-битной квантизации для сценариев QLoRA и необходим для оптимизатора paged_adamw_8bit, используемого в процессе обучения.

Подготовка датасета для дообучения

Форматы и стандарты датасетов

В экосистеме дообучения 2026 года доминируют три формата данных.

  • ChatML использует структурированные токены <|im_start|> и <|im_end|> с явными ролями (system, user, assistant). Это нативный формат для большинства диалоговых моделей и предпочтительный выбор для задач следования инструкциям и разговорного дообучения.

  • ShareGPT хранит диалоги в виде списка ходов с полями from и value. Этот формат хорошо подходит для многоходовых диалогов и широко используется в общественных датасетах на Hugging Face.

  • Для одношаговых задач «инструкция — ответ» и задач классификации самым простым вариантом остаётся формат Alpaca, содержащий поля instruction, input и output.

Размер датасета критически зависит от задачи. Для простых задач классификации или форматирования заметное улучшение может быть достигнуто уже на 500–1000 примерах. Для сложных задач следования инструкциям или адаптации модели к новой предметной области требуется от 3000 до 10 000 качественных примеров. При объёме свыше 10 000 примеров отдача начинает снижаться, если только предметная область не является исключительно широкой.

Очистка и валидация данных

Редко бывает так, что исходные данные сразу приходят в нужном формате. Следующий скрипт преобразует записи из CSV или JSON в формат ChatML, удаляет дубликаты, фильтрует данные по длине токенов и формирует разбиение на обучающую и валидационную выборки.

# Пример кода 2: Подготовка датасета — преобразование CSV/JSON в ChatML
import json
import hashlib
import random
import logging
from pathlib import Path

logger = logging.getLogger(__name__)


def load_raw_data(filepath):
    """Load from CSV or JSON."""
    path = Path(filepath)
    if path.suffix == ".json":
        with open(path, encoding="utf-8") as f:
            return json.load(f)
    elif path.suffix == ".csv":
        import csv
        with open(path, encoding="utf-8-sig") as f:  # utf-8-sig handles BOM
            reader = csv.DictReader(f)
            return list(reader)
    raise ValueError(f"Unsupported format: {path.suffix}")


def to_chatml(record, system_prompt="You are a helpful domain expert."):
    """Convert a record with 'instruction' and 'response' fields to ChatML."""
    return {
        "messages": [
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": record["instruction"].strip()},
            {"role": "assistant", "content": record["response"].strip()},
        ]
    }


def deduplicate(records):
    """Remove exact duplicates based on instruction + response SHA-256 hash."""
    seen = set()
    unique = []
    for r in records:
        if "instruction" not in r or "response" not in r:
            logger.warning("Record missing required fields; skipping: %s", r)
            continue
        key = hashlib.sha256(
            (r["instruction"] + r["response"]).encode()
        ).hexdigest()
        if key not in seen:
            seen.add(key)
            unique.append(r)
    return unique


def filter_by_length(records, min_chars=50, max_chars=4000):
    """Remove records that are too short or too long."""
    valid = []
    for r in records:
        resp = r.get("response", "")
        if not isinstance(resp, str):
            logger.warning("Non-string response field; skipping: %s", r)
            continue
        if min_chars <= len(resp) <= max_chars:
            valid.append(r)
    return valid


def prepare_dataset(input_path, output_dir, val_ratio=0.1, system_prompt="You are a helpful domain expert."):
    random.seed(42)  # Set seed first for full reproducibility
    raw = load_raw_data(input_path)
    logger.info("Loaded %d raw records", len(raw))

    raw = deduplicate(raw)
    logger.info("After dedup: %d", len(raw))

    raw = filter_by_length(raw)
    logger.info("After length filter: %d", len(raw))

    random.shuffle(raw)
    split_idx = int(len(raw) * (1 - val_ratio))
    train_data = [to_chatml(r, system_prompt) for r in raw[:split_idx]]
    val_data = [to_chatml(r, system_prompt) for r in raw[split_idx:]]

    out = Path(output_dir)
    out.mkdir(parents=True, exist_ok=True)
    with open(out / "train.json", "w", encoding="utf-8") as f:
        json.dump(train_data, f)
    with open(out / "val.json", "w", encoding="utf-8") as f:
        json.dump(val_data, f)
    logger.info("Train: %d, Val: %d", len(train_data), len(val_data))


# Использование
prepare_dataset("raw_data.json", "./dataset", val_ratio=0.1)

Типичные ошибки при работе с датасетом

Переобучение на небольших датасетах — самый частый сценарий неудачи. Если примеров меньше 500, модель начинает запоминать обучающие образцы вместо того, чтобы обобщать закономерности. Ещё одна распространённая проблема — утечка меток, когда информация из ожидаемого ответа просачивается во входные данные. Это искусственно завышает метрики качества, но на выходе даёт бесполезную модель. Непоследовательное форматирование примеров — например, смешение Markdown и обычного текста или различающаяся структура ответов — заставляет модель обучаться на шуме оформления, а не на целевом поведении.

Что нужно понимать про LoRA, QLoRA и полное дообучение

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

При полном дообучении обновляются все параметры модели. Такой подход даёт более глубокую адаптацию, чем остальные методы, но требует пропорционально большего объёма VRAM. Одно только хранение весов требует 4 байта на параметр для fp32 или 2 байта на параметр для bf16; если добавить состояния оптимизатора (8 байт на параметр для AdamW) и буферы градиентов, получится полная оценка потребления VRAM.

Для модели на 7 млрд параметров в bf16 это означает 14 ГБ только на сами веса, а с учётом состояний оптимизатора суммарные требования вырастают до 48 ГБ и выше. Полное дообучение также несёт максимальный риск катастрофического забывания, когда модель, специализируясь, теряет свои общие способности. Такой подход оправдан только в тех случаях, когда сдвиг предметной области действительно велик, а датасет достаточно объёмен — речь обычно идёт о десятках тысяч примеров, чтобы полное обновление всех параметров имело смысл.

LoRA (низкоранговая адаптация, Low-Rank Adaptation)

LoRA замораживает веса базовой модели и добавляет небольшие обучаемые матрицы в определённые слои, как правило в матрицы проекций механизма внимания (q_proj, k_proj, v_proj, o_proj). Эти матрицы используют ранговое разложение: матрица обновления весов W аппроксимируется как произведение B·A, где A имеет форму (r × d_in), а B — (d_out × r), причём ранг r значительно меньше min(d_in, d_out).

За счёт этого число обучаемых параметров уменьшается на порядки. Ключевые гиперпараметры здесь — ранг (обычно от 8 до 64; более высокий ранг позволяет уловить более сложные изменения, но увеличивает расход памяти), alpha (масштабирующий коэффициент, который обычно делают равным рангу или 2× рангу; отношение alpha/rank работает как эффективный множитель для скорости обучения) и выбор целевых модулей.

QLoRA и улучшения 2026 года

QLoRA сочетает LoRA с 4-битной квантизацией базовой модели в формате NormalFloat (NF4), страничными оптимизаторами, которые заранее резервируют страницы оперативной памяти CPU, чтобы система могла переносить состояния оптимизатора на CPU при заполнении памяти GPU, а также с двойной квантизацией, когда квантуются и сами константы квантизации. Базовая модель загружается в 4-битной точности, а адаптеры LoRA и вычисления продолжают работать в bf16 или fp16. Такая архитектура позволяет дообучать 8B-модель, укладываясь менее чем в 10 ГБ VRAM, если длина последовательности не превышает 512 токенов, размер пакета равен 1 и включён контрольный пересчёт градиентов. При увеличении длины последовательности или размера пакета потребление VRAM растёт пропорционально.

С 2024 года экосистема QLoRA заметно повзрослела. Более широкая поддержка архитектур моделей означает, что QLoRA из коробки работает с семействами Llama 3, Mistral, Qwen 2.5, Phi-3 и Gemma 2. Оптимизации ядер в Unsloth дополнительно уменьшают расход памяти за счёт слияния операций и сокращения объёма хранимых промежуточных активаций.

Дообучение с Unsloth и Hugging Face: пошагово

Выбор базовой модели

При выборе модели в 2026 году нужно находить баланс между возможностями модели, соответствием доступному объёму VRAM и условиями лицензии.

Модели класса 7B–8B — оптимальная точка для потребительского оборудования: Llama 3.1 8B, Mistral 7B v0.3 и Qwen 2.5 7B уверенно помещаются в 12 ГБ GPU при использовании QLoRA. Llama 3.1 8B распространяется по лицензии сообщества Llama (она допускает большинство коммерческих сценариев, но перед развёртыванием стоит проверить актуальные условия на ai.meta.com/llama/license). Mistral 7B и Qwen 2.5 используют лицензию Apache 2.0.

Более крупные модели — варианты на 13B, которым требуется от 16 ГБ памяти даже с QLoRA, и модели на 70B, которым нужны несколько GPU или карты с 80 ГБ памяти даже при использовании QLoRA — тоже могут быть вариантом, если у вас есть соответствующее оборудование. Но для большинства сценариев локального дообучения именно диапазон 7B–8B с QLoRA даёт наилучший баланс между качеством и доступностью.

Примечание: Llama 3.1 8B — это модель с ограниченным доступом на Hugging Face. Перед загрузкой нужно принять лицензию Meta на huggingface.co/meta-llama/Meta-Llama-3.1-8B и пройти аутентификацию через huggingface-cli login.

Настройка запуска обучения

Следующий скрипт показывает полный запуск обучения QLoRA с использованием Unsloth и SFTTrainer из библиотеки trl:

# Пример кода 3: Полный скрипт обучения QLoRA с Unsloth
from unsloth import FastLanguageModel
from trl import SFTTrainer
from transformers import TrainingArguments
from datasets import load_dataset
import torch

# Конфигурация модели
max_seq_length = 2048
model_name = "unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit"

# Загружаем модель с 4-битной квантизацией через Unsloth
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name=model_name,
    max_seq_length=max_seq_length,
    dtype=None,  # Автоопределение: bf16 на Ampere+, иначе fp16
    load_in_4bit=True,
)

# Применяем адаптеры LoRA
model = FastLanguageModel.get_peft_model(
    model,
    r=16,                          # Ранг: 8-64, выше = больше ёмкость
    lora_alpha=32,                 # Коэффициент масштабирования, обычно 2x ранга
    target_modules=[
        "q_proj", "k_proj", "v_proj", "o_proj",
        "gate_proj", "up_proj", "down_proj",
    ],
    lora_dropout=0.05,
    bias="none",
    use_gradient_checkpointing="unsloth",  # Оптимизированный gradient checkpointing от Unsloth
)

# Загружаем датасет (формат ChatML)
dataset = load_dataset("json", data_files={
    "train": "./dataset/train.json",
    "validation": "./dataset/val.json",
})

# Форматируем диалоги для токенизатора и удаляем исходный столбец
def format_chat(example):
    return {"text": tokenizer.apply_chat_template(
        example["messages"], tokenize=False, add_generation_prompt=False
    )}

dataset = dataset.map(format_chat, remove_columns=["messages"])

# Аргументы обучения
training_args = TrainingArguments(
    output_dir="./output",
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,       # Эффективный размер пакета = 16
    warmup_steps=50,
    num_train_epochs=3,
    learning_rate=2e-4,
    bf16=True,
    logging_steps=10,
    save_strategy="steps",
    save_steps=100,
    save_total_limit=3,                  # Предотвращает переполнение диска; сохраняет 3 последних чекпойнта
    eval_strategy="steps",
    eval_steps=100,
    load_best_model_at_end=True,         # Сохраняем лучший чекпойнт, а не последний
    metric_for_best_model="eval_loss",
    optim="paged_adamw_8bit",
    lr_scheduler_type="cosine",
    seed=42,
    report_to=[],                        # Явно пустой список; без внешней отчётности. Меняйте на ["wandb"] только для нечувствительных данных при заданном WANDB_API_KEY
)

# Инициализируем trainer
trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    train_dataset=dataset["train"],
    eval_dataset=dataset["validation"],
    args=training_args,
    dataset_text_field="text",
    max_seq_length=max_seq_length,
    packing=True,                        # Упаковываем короткие примеры для повышения эффективности
)

# Запускаем обучение; необходимо соблюдать всю конфигурацию выше
trainer.train()

Примечание о приватности: если вы измените значение report_to на ["wandb"], метаданные обучения и кривые функции потерь будут отправляться на внешние серверы Weights & Biases. Для изолированных или проприетарных сценариев используйте ["tensorboard"].

Шаблоны конфигурации с комментариями для типовых сценариев — дообучение на инструкциях, многоходовый чат, классификация текста — обычно поставляются как сопутствующие файлы конфигурации. Основные параметры, которые нужно подбирать под конкретную задачу: rank (8 для простых задач форматирования, 32–64 для сложных сдвигов предметной области), learning rate (от 1e-4 до 3e-4 для QLoRA) и число эпох (1–3 для больших датасетов, 3–5 для небольших).

Запуск обучения и мониторинг

# Пример кода 4: Необязательная настройка логирования в WandB и сохранения чекпойнта

# Требуется переменная окружения WANDB_API_KEY.
# Для офлайн- или приватного использования: wandb.init(..., mode="offline")
import os
import wandb


# Инициализируем WandB только если это явно запрошено и ключ доступен
def init_wandb_if_configured(project: str, run_name: str) -> bool:
    """Returns True if WandB was initialized, False otherwise."""
    api_key = os.environ.get("WANDB_API_KEY")
    if not api_key:
        print("WANDB_API_KEY not set; skipping WandB logging.")
        return False
    wandb.init(project=project, name=run_name)
    return True


# Вызывается перед trainer.train() в сессии из примера 3, не как отдельный скрипт
init_wandb_if_configured("llm-finetune", "llama3.1-8b-qlora-domain")

# trainer должен быть определён в той же сессии (из примера 3)
# Сохраняем финальный чекпойнт
trainer.save_model("./output/final_checkpoint")
tokenizer.save_pretrained("./output/final_checkpoint")

print("Training complete. Final checkpoint saved.")

Ожидаемая длительность обучения зависит от оборудования. На RTX 4090 (24 ГБ) при датасете из 5000 примеров и конфигурации выше обучение в течение 3 эпох при длине последовательности 2048 обычно занимает 1–2 часа. RTX 4070 Ti (12 ГБ) будет работать дольше из-за меньшего размера пакета и большего числа шагов накопления градиента — как правило, 2–4 часа для того же датасета.

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

Если обучающая ошибка продолжает снижаться, а валидационная растёт, это сигнал к тому, что обучение нужно остановить или уменьшить число эпох.

Объединение адаптеров и экспорт модели

После завершения обучения адаптеры LoRA существуют как отдельные файлы весов. Для развёртывания их нужно объединить с базовой моделью и при необходимости преобразовать в формат GGUF для движков вывода, таких как llama.cpp или Ollama. Для экспорта в GGUF нужны либо локально установленные инструменты сборки llama.cpp, либо соответствующие дополнительные компоненты Unsloth; ориентируйтесь на документацию Unsloth для установленной у вас версии.

# Пример кода 5: Объединение весов LoRA и экспорт в GGUF
import torch
from pathlib import Path
from unsloth import FastLanguageModel

checkpoint_path = Path("./output/final_checkpoint")
assert checkpoint_path.exists(), f"Checkpoint not found: {checkpoint_path}"

# Проверяем, что это объединённый чекпойнт, а не только адаптеры
adapter_config = checkpoint_path / "adapter_config.json"
assert not adapter_config.exists(), (
    "final_checkpoint appears to contain LoRA adapters only. "
    "Run save_pretrained_merged first, then reload from merged_model path."
)

# Повторно загружаем модель — для объединения без потерь она должна быть загружена в 16 бит, а не в 4
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name=str(checkpoint_path),
    max_seq_length=2048,
    dtype=torch.bfloat16,
    load_in_4bit=False,  # Для объединения без потерь в 16 бит должно быть False
)

# Объединяем веса LoRA с базовой моделью
model.save_pretrained_merged(
    "./output/merged_model",
    tokenizer,
    save_method="merged_16bit",  # Объединённые веса в полной точности
)

# Экспортируем в GGUF для llama.cpp / Ollama
model.save_pretrained_gguf(
    "./output/gguf_model",
    tokenizer,
    quantization_method="q4_k_m",  # Варианты: q4_k_m, q5_k_m, q8_0
)

print("Model merged and exported to GGUF format.")

Выбор метода квантизации влияет и на размер модели, и на её качество. Q4_K_M даёт наилучшее соотношение размера и качества для большинства сценариев развёртывания — около 4,5 бита на один вес. Q5_K_M даёт немного более высокую точность — примерно 5,5 бита на вес. Q8_0 сохраняет почти полное качество при 8 битах на вес, но по сравнению с Q4_K_M увеличивает размер файла почти вдвое. Для модели класса 8B ожидаемый размер файлов GGUF составляет примерно 4,9 ГБ для Q4_K_M, 5,7 ГБ для Q5_K_M и 8,5 ГБ для Q8_0. Размеры масштабируются пропорционально числу параметров.

Оценка дообученной модели

Количественная оценка

Оценка должна сочетать общие метрики языкового моделирования со специализированными метриками под конкретную задачу. Перплексия (perplexity) на отложенной валидационной выборке даёт базовый ориентир: чем ниже перплексия, тем лучше модель предсказывает данные валидации, хотя напрямую качество решения задачи она не измеряет. Гораздо важнее метрики, связанные с самой задачей. Для задач классификации стоит измерять F1-меру и точность точного совпадения. Для задач генерации BLEU и ROUGE оценивают пересечение n-грамм, но их связь с человеческими предпочтениями для открытой генерации довольно слабая; поэтому по возможности лучше использовать автоматические метрики, привязанные к задаче, например LLM в роли судьи (LLM-as-judge), или ручную оценку.

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

Качественное тестирование и редтиминг

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

# Пример кода 6: Сравнение вывода двух моделей бок о бок
import torch
from unsloth import FastLanguageModel


def run_inference(model, tokenizer, messages, device="cuda"):
    """Tokenize, generate, and decode only new tokens."""
    inputs = tokenizer.apply_chat_template(
        messages,
        tokenize=True,
        return_tensors="pt",
        add_generation_prompt=True,
    ).to(device)
    attention_mask = (inputs != tokenizer.pad_token_id).long().to(device)
    with torch.no_grad():
        output = model.generate(
            input_ids=inputs,
            attention_mask=attention_mask,
            max_new_tokens=256,
            pad_token_id=tokenizer.eos_token_id,
        )
    # Декодируем только вновь сгенерированные токены, без промпта
    new_tokens = output[0][inputs.shape[-1]:]
    return tokenizer.decode(new_tokens, skip_special_tokens=True)


# Тестовые промпты
test_prompts = [
    "Explain the key differences between LoRA and full fine-tuning.",
    "Generate a compliance report summary for Q3 2025.",
    # Медицинские промпты включены только для проверки деградации в предметной области;
# не развёртывайте дообученные модели для медицинских рекомендаций без клинической проверки.
    "What are the side effects of metformin?",
]

# # Загружаем и тестируем модели последовательно, чтобы избежать OOM на потребительских GPU
for model_name, label in [
    ("unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit", "BASE MODEL"),
    ("./output/final_checkpoint", "FINE-TUNED"),
]:
    model, tokenizer = FastLanguageModel.from_pretrained(
        model_name=model_name,
        max_seq_length=2048,
        load_in_4bit=True,
    )
    FastLanguageModel.for_inference(model)

    for prompt in test_prompts:
        messages = [{"role": "user", "content": prompt}]
        result = run_inference(model, tokenizer, messages)
        print(f"
{'='*60}")
        print(f"PROMPT: {prompt}")
        print(f"
{label}:
{result}")

    # Освобождаем VRAM перед загрузкой следующей модели
    del model
    torch.cuda.empty_cache()

Помимо позитивных тестовых сценариев, критически важен редтиминг. Проверяйте, не возникла ли деградация на вопросах по общим знаниям, с которыми базовая модель раньше справлялась корректно. Ищите галлюцинации, появившиеся из-за переобучения. Проверяйте пограничные случаи в предметной области, чтобы убедиться, что модель действительно усвоила целевое поведение, а не просто воспроизводит поверхностные шаблоны.

Устранение проблем и лучшие практики

Типичные сценарии сбоев

Если функция потерь не хочет сходиться, самая частая причина — слишком высокая скорость обучения. Для QLoRA разумно начинать с 2e-4 и снижать до 1e-4, если loss ведёт себя нестабильно. Также стоит проверить форматирование датасета: некорректные шаблоны ChatML приводят к тому, что модель обучается на мусорных токенах.

Модель хорошо показывает себя в предметной области, но хуже справляется с общими задачами? Это катастрофическое забывание. Уменьшите число эпох, снизьте скорость обучения или уменьшите ранг LoRA. Сохранить общие способности помогает добавление в обучающий набор небольшой доли общих инструктивных данных — порядка 5–10%.

Если обучающая ошибка стремится к нулю, а валидационная растёт, это классический признак переобучения. Уменьшите число эпох, увеличьте dropout или расширьте датасет. Если примеров меньше 1000, обычно достаточно держать число эпох на низком уровне — от 1 до 3.

При ошибках CUDA OOM сначала уменьшайте размер пакета, а затем увеличивайте число шагов накопления градиента, чтобы сохранить эффективный размер пакета. Включите контрольный пересчёт градиентов — в конфигурации Unsloth выше он уже задан. Если памяти всё равно не хватает, уменьшайте длину последовательности или ранг LoRA.

Советы по подбору гиперпараметров

Для скорости обучения полезно быстро прогнать небольшой набор значений — 1e-4, 2e-4 и 3e-4 — на малой подвыборке данных (10% обучающего набора, 1 эпоха). Это позволяет быстро понять нужный порядок величины.

Выбор ранга зависит от сложности задачи: ранг 8 подходит для изменений формата и стиля, 16–32 — для умеренного сдвига предметной области, 64 — для существенного внедрения новых знаний.

Число эпох лучше держать небольшим: 1–3 для датасетов объёмом более 5000 примеров и 3–5 для меньших наборов.

Если результаты плохие, опубликованные абляционные исследования показывают, что увеличение объёма и качества датасета почти всегда даёт больший эффект, чем тонкая настройка гиперпараметров.

Если результаты плохие, опубликованные абляционные исследования показывают, что увеличение объёма и качества датасета почти всегда даёт больший эффект, чем тонкая настройка гиперпараметров.

Если после статьи осталось ощущение, что «вроде понятно, но руками не пробовал», это нормальная стадия. Мы как раз проводим пару открытых занятий, где можно спокойно разобрать базовые вещи, посмотреть на формат обучения и задать любые вопросы — без обязательств. Заодно это удобный способ понять, насколько вам вообще откликается тема и уровень.

  • 29 апреля в 20:00. «Деревья решений для задач классификации и регрессии».
    Разберёте, как устроены решающие деревья и где они реально применяются. Будет практика: попробуете обучить модель и посмотреть, как она ведёт себя на данных. Записаться

  • 20 мая в 18:00. «Препарируем рекомендательные системы методами ML». Пройдёте по классическим подходам к рекомендациям и соберёте простой вариант своими руками. Заодно станет понятнее, как такие системы устроены под капотом и где начинаются реальные сложности. Записаться

Если хочется выстроить системное обучение и дойти до уровня специалиста по Data Science — с понятной базой, практикой и последовательным усложнением — это как раз про специализацию «Машинное обучение» со стартом 28 мая.