Зачем учить модель звонить? (Проблема)
Представь: у тебя есть крутой текстовый агент на базе Gemma 3. Он умный, быстрый, работает локально. Но когда ему нужно записать событие в календарь, отправить email или получить данные из базы - он только говорит об этом. Не делает. Просто генерирует текст типа "Я бы сейчас вызвал функцию send_email()". Бесполезно.
Вот в чем проблема. Современные LLM - это болтуны. Они отлично рассуждают, но не умеют взаимодействовать с внешним миром. А ты хочешь, чтобы твой агент реально работал: бронировал столики, управлял умным домом, писал в API твоего внутреннего сервиса. Без отправки данных в облако. Конфиденциально.
Почему именно Gemma 3 4B и QLoRA? (Неочевидный выбор)
Все сейчас бегут за большими моделями. Llama 3.3 70B, Qwen2.5 72B. Но для вызова процедур размер - не главное. Важна точность следования формату и скорость ответа. Gemma-3-4b-it - это 4 миллиарда параметров, оптимизированных Google именно для инструкций. Она легкая, быстрая, и самое главное - отлично адаптируется под конкретную задачу через тонкую настройку.
QLoRA - это волшебная палочка для бедных. Точнее, для тех, у кого нет стойки с H100. Метод позволяет обучать модель, замораживая основные веса и добавляя крошечные адаптеры. Потребление памяти падает в разы. На RTX 4090 с 24 ГБ VRAM ты можешь обучать модель, которая в полном виде весит в 4 раза больше доступной памяти. Магия? Практически.
Не повторяй классическую ошибку: не пытайся обучать полную модель на 24 ГБ. Даже с градиентным чекпоинтингом это будет мучительно медленно. QLoRA дает ускорение в 3-5 раз при сравнимом качестве.
Что тебе понадобится (Железо и софт)
| Компонент | Минимальные требования | Рекомендация |
|---|---|---|
| GPU VRAM | 16 ГБ | 24 ГБ (RTX 4090/3090) |
| CUDA | 11.8 | 12.6-13.0 |
| Память ОЗУ | 32 ГБ | 64 ГБ |
| Python | 3.10 | 3.11 |
| Библиотеки | PyTorch 2.0+, Transformers, PEFT, TRL | Последние версии с поддержкой Flash Attention 2 |
Если у тебя Mac - ситуация сложнее. QLoRA через PyTorch на Metal работает, но стабильность хромает. Лучше арендовать удаленный GPU, если свой парк не тянет. Кстати, о ценах на аренду мы уже писали в материале про альтернативы DeepInfra.
1Готовим датасет: не накосячь с форматом
Здесь ломается 80% проектов. Ты не можешь взять случайные диалоги и надеяться, что модель научится вызывать функции. Нужен специфический датасет в формате "пользовательский запрос - вызов функции".
Структура каждого примера должна содержать:
- Системный промпт с описанием доступных функций (их названия, параметры, описание)
- Запрос пользователя (естественный язык)
- Ожидаемый ответ модели (строго структурированный вызов функции в JSON-подобном формате)
Пример плохого датасета: просто диалоги ассистента. Пример хорошего: "Забронируй столик на завтра в 19:00" -> {"function": "book_restaurant", "arguments": {"date": "2024-12-01", "time": "19:00"}}.
2Настраиваем окружение: хрупкий зоопарк зависимостей
Создай новый виртуальный окружение. Не пытайся использовать системный Python - сломаешь что-нибудь. Установи PyTorch с поддержкой CUDA 12.x. Если у тебя старая карта с CUDA 11.8 - готовься к танцам с бубном.
Основные библиотеки: transformers, peft, accelerate, trl, datasets, bitsandbytes. Последняя - самая капризная. bitsandbytes отвечает за 4-битную квантование в QLoRA. Если установка падает с ошибками компиляции - попробуй предварительно скомпилированную версию или собери из исходников с правильными флагами.
Не используй pip install bitsandbytes без указания версии. Совместимость с твоей CUDA и cuDNN критична. Лучший способ: найти wheel файл для твоей конфигурации или собрать через `CUDA_VERSION=118 python setup.py install` (подставив свою версию CUDA).
3Конфигурация обучения: где экономить, а где нет
Открой скрипт train_qlora.py (возьми из репозитория Unsloth или самостоятельно адаптируй стандартный из TRL). Ключевые параметры:
- LoRA rank (r): 64 - хороший баланс. Можно уменьшить до 32 для экономии памяти, но качество упадет.
- Alpha: 16 или 32. Отношение alpha к rank влияет на масштабирование адаптеров.
- Dropout: 0.1. Не увеличивай - модель и так обучается на маленьком датасете.
- Целевые модули: q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj. Все внимание и FFN слои.
Настройки оптимизатора: AdamW 8-bit через bitsandbytes. Learning rate: 2e-4 для начала. Градиентный аккумуляция: 4 шага. Размер батча: 1 (из-за ограничений памяти).
Эпохи: 3-5 достаточно. Gemma 3 быстро сходится. Больше - переобучение.
4Запуск обучения: следи за метриками, а не за анимацией прогресс-бара
Запусти обучение. На RTX 4090 с 24 ГБ VRAM одна эпоха на датасете в 1000 примеров займет около 30 минут. Используй wandb или tensorboard для логирования. Следи не только за loss, но и за точностью вызова функций.
Создай валидационный набор из 100 примеров, которые модель не видела во время обучения. После каждой эпохи запускай скрипт evaluate_tool_calling.py, который проверяет:
- Выбрана ли правильная функция
- Корректно ли заполнены аргументы
- Полный ли JSON (нет обрезанных строк)
Если точность на валидации падает после третьей эпохи - остановись. Это переобучение.
5Слияние адаптеров и сохранение: момент истины
После обучения у тебя есть два файла: базовая модель Gemma-3-4b-it и адаптеры LoRA (adapter_model.bin). Теперь их нужно объединить для инференса.
Используй скрипт merge_peft.py. Важно: сохрани как в формате safetensors, так и в GGUF для совместимости с llama.cpp. GGUF формат позволит запускать модель на более широком спектре железа, включая Mac с Apple Silicon.
Проверь размер итоговой модели: оригинальная Gemma 3 4B весит около 8 ГБ в FP16. После слияния с адаптерами - примерно столько же. Но если конвертируешь в GGUF с квантованием Q4_K_M - получишь около 2.5 ГБ. Идеально для развертывания.
Ошибки, которые все совершают (И как их избежать)
| Ошибка | Симптомы | Решение |
|---|---|---|
| Неправильный формат датасета | Модель генерирует текст вместо JSON | Используй четкие разделители и промпт-инжиниринг в системном сообщении |
| Слишком высокий learning rate | Loss скачет, модель не сходится | Уменьши LR до 1e-5, используй LR scheduler |
| Нехватка разнообразия в данных | Модель работает только на примерах из трейна | Добавь аугментацию: синонимы, перефразирование, разные форматы дат |
| Проблемы с квантованием | NaN в loss, крах обучения | Используй `bnb_4bit_quant_type="nf4"` и `bnb_4bit_compute_dtype=torch.float16` |
Инференс: как заставить модель реально работать
Обученная модель - это только половина дела. Теперь нужно интегрировать ее в систему, которая будет:
- Принимать запрос пользователя
- Вызывать модель с промптом, включающим описание функций
- Парсить выход модели (извлекать JSON)
- Валидировать аргументы (проверять типы, диапазоны)
- Выполнять реальный вызов функции или API
- Возвращать результат пользователю (или передавать его обратно модели для генерации ответа)
Используй паттерн агента с ReAct (Reasoning + Acting). Модель сначала рассуждает, нужна ли функция, затем генерирует вызов. После получения результата от функции - генерирует финальный ответ пользователю.
Для максимальной производительности на инференсе конвертируй модель в GGUF с динамическим квантованием и используй llama.cpp или Ollama.
Что дальше? (Вместо заключения)
Теперь у тесть есть агент, который понимает естественный язык и умеет выполнять действия. Но это начало. Следующие шаги:
- Добавь цепочку вызовов: одна функция может вызывать другую
- Реализуй долгую память контекста, чтобы агент помнил предыдущие взаимодействия (пригодится наш гайд по оптимизации контекста)
- Настрой RAG для доступа к документации функций и внутренним базам знаний
- Экспериментируй с цепочкой мыслей для сложных многошаговых задач
Самое интересное: когда ты настроишь этот пайплайн, ты сможете создавать полностью автономных агентов, работающих на твоем железе, с твоими данными, без единого запроса в облако. Это уровень свободы, ради которого стоит потратить выходные на настройку QLoRA.
P.S. Если что-то пойдет не так - проверь, точно ли у тебя хватает VRAM. Иногда проблема не в коде, а в том, что фоновый Chrome съел 4 гигабайта видеопамяти.