Fine-tuning Gemma 3 для вызова процедур: QLoRA гайд с кодом | AiManual
AiManual Logo Ai / Manual.
11 Янв 2026 Гайд

Fine-tuning Gemma 3 для вызова процедур: пошаговый гайд с кодом и датасетом

Полный гайд по тонкой настройке Gemma 3 для вызова процедур. QLoRA, датасет, код для RTX 4090. Создаем текстового агента за 2 часа.

Почему тонкая настройка Gemma 3 для вызова процедур — это не просто модно, а необходимо

Представьте: у вас есть идеальный текстовый ассистент. Он понимает контекст, отвечает на вопросы, пишет код. Но когда вы просите его "забронировать столик в ресторане" или "отправить email клиенту", он просто описывает, как это сделать. Не делает. Просто болтает.

Это основная проблема современных LLM — они отлично генерируют текст, но не умеют взаимодействовать с внешним миром. И вот здесь появляется fine-tuning для вызова процедур (tool calling или function calling). Мы берем Gemma 3 — одну из самых эффективных open-source моделей — и учим ее не просто отвечать, а действовать.

Важно: Эта статья не про теорию. Здесь будет рабочий код, реальный датасет и конкретные команды. Если у вас есть RTX 4090 (или любой GPU с 24 ГБ VRAM), через 2 часа у вас будет собственная модель, которая умеет вызывать процедуры.

Что такое вызов процедур и зачем это нужно

Вызов процедур (tool calling) — это способность модели понимать, когда пользователь хочет выполнить какое-то действие, и генерировать структурированный запрос для этого действия. Вместо "Я могу рассказать, как отправить email" модель должна выдать:

{
  "function": "send_email",
  "arguments": {
    "to": "client@example.com",
    "subject": "Договор",
    "body": "Прикрепляю договор..."
  }
}

Зачем это нужно? Три основные причины:

  1. Конфиденциальность — ваши данные никогда не покидают ваш сервер. Если вы юрист или врач, это критически важно. Помните статью про юристов, которые не хотят сливать клиентские тайны? Здесь тот же принцип.
  2. Интеграция — модель может работать с вашей внутренней CRM, базой данных, API.
  3. Контроль — вы точно знаете, какие процедуры доступны, и можете их ограничивать.

Что вам понадобится: железо и софт

Давайте без иллюзий. Для fine-tuning Gemma 3 4B вам нужно:

  • GPU с 24 ГБ VRAM — RTX 4090 идеально подходит. Можно использовать несколько карт с меньшей памятью, но это сложнее. Если у вас только 12 ГБ — посмотрите статью про работу на 12 ГБ VRAM.
  • Python 3.10+ — более старые версии могут вызывать проблемы с библиотеками.
  • CUDA 12.1+ — если у вас NVIDIA карта.
  • Около 30 ГБ свободного места — для модели, датасета и чекпоинтов.

Совет: Не пытайтесь запускать это на CPU. Обучение займет дни, если не недели. Даже на Mac M-серии это будет мучительно медленно (хотя для инференса они подходят — об этом в гайде по Mac).

Подготовка: устанавливаем всё необходимое

1Создаем виртуальное окружение

Первое правило fine-tuning'а — изолировать зависимости. Иначе вы сломаете системный Python.

python -m venv gemma_finetune
source gemma_finetune/bin/activate  # На Windows: gemma_finetune\Scripts\activate

2Устанавливаем PyTorch с CUDA

Здесь многое зависит от вашей версии CUDA. Для RTX 4090 с CUDA 12.1:

pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121

3Устанавливаем библиотеки для fine-tuning

pip install transformers datasets accelerate peft bitsandbytes trl
pip install sentencepiece protobuf

Что здесь важно:

  • PEFT — библиотека для parameter-efficient fine-tuning (QLoRA)
  • bitsandbytes — для 4-битной квантизации
  • trl — для reinforcement learning (хотя в этом гайде не используем, но полезно иметь)

Создаем датасет для обучения

Вот где большинство гайдов спотыкаются. Они говорят "используйте датасет", но не говорят, какой именно. Я покажу вам реальный датасет, который работает.

Нам нужно научить модель двум вещам:

  1. Распознавать, когда пользователь хочет вызвать процедуру
  2. Генерировать корректный JSON для вызова

Создадим файл dataset.jsonl:

{
  "messages": [
    {"role": "user", "content": "Отправь email Ивану с темой 'Встреча завтра' и текстом 'Жду тебя в 10:00'"},
    {
      "role": "assistant",
      "content": "",
      "tool_calls": [
        {
          "function": {
            "name": "send_email",
            "arguments": "{\"to\": \"ivan@example.com\", \"subject\": \"Встреча завтра\", \"body\": \"Жду тебя в 10:00\"}"
          }
        }
      ]
    }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "send_email",
        "description": "Отправляет электронное письмо",
        "parameters": {
          "type": "object",
          "properties": {
            "to": {"type": "string", "description": "Email получателя"},
            "subject": {"type": "string", "description": "Тема письма"},
            "body": {"type": "string", "description": "Текст письма"}
          },
          "required": ["to", "subject", "body"]
        }
      }
    }
  ]
}

И еще несколько примеров для разнообразия:

{
  "messages": [
    {"role": "user", "content": "Найди рестораны итальянской кухни рядом со мной"},
    {
      "role": "assistant",
      "content": "",
      "tool_calls": [
        {
          "function": {
            "name": "search_businesses",
            "arguments": "{\"query\": \"итальянские рестораны\", \"location\": \"current_location\", \"radius\": 5000}"
          }
        }
      ]
    }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "search_businesses",
        "description": "Ищет бизнесы по запросу",
        "parameters": {
          "type": "object",
          "properties": {
            "query": {"type": "string", "description": "Поисковый запрос"},
            "location": {"type": "string", "description": "Локация для поиска"},
            "radius": {"type": "number", "description": "Радиус поиска в метрах"}
          },
          "required": ["query", "location"]
        }
      }
    }
  ]
}
💡
Совет по датасету: Вам нужно 500-1000 таких примеров для качественного обучения. Можно сгенерировать их с помощью GPT-4 или Claude, но обязательно проверьте вручную хотя бы 10%. Модель научится плохо, если датасет содержит ошибки.

Основной код обучения

Теперь самое интересное — код для fine-tuning с использованием QLoRA. QLoRA (Quantized Low-Rank Adaptation) позволяет обучать огромные модели на скромном железе, замораживая основные веса и обучая только небольшие адаптеры.

Создаем файл train.py:

import torch
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    TrainingArguments,
    BitsAndBytesConfig
)
from peft import LoraConfig, prepare_model_for_kbit_training, get_peft_model
from trl import SFTTrainer
from datasets import load_dataset
import os

# Конфигурация 4-битной квантизации
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_id = "google/gemma-3-4b-it"
model = AutoModelForCausalLM.from_pretrained(
    model_id,
    quantization_config=bnb_config,
    device_map="auto",
    trust_remote_code=True
)
tokenizer = AutoTokenizer.from_pretrained(model_id)
tokenizer.pad_token = tokenizer.eos_token

# Подготовка модели для k-bit обучения
model = prepare_model_for_kbit_training(model)

# Конфигурация LoRA
lora_config = LoraConfig(
    r=16,  # Rank
    lora_alpha=32,
    target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)

# Загружаем датасет
dataset = load_dataset("json", data_files="dataset.jsonl", split="train")

# Функция форматирования данных
def format_dataset(example):
    # Здесь мы форматируем данные для обучения
    # В реальном коде нужно добавить обработку tool_calls
    messages = example["messages"]
    text = tokenizer.apply_chat_template(messages, tokenize=False)
    return {"text": text}

dataset = dataset.map(format_dataset)

# Аргументы обучения
training_args = TrainingArguments(
    output_dir="./gemma-tool-calling",
    num_train_epochs=3,
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,
    warmup_steps=100,
    logging_steps=10,
    save_steps=100,
    eval_steps=100,
    learning_rate=2e-4,
    fp16=True,
    optim="paged_adamw_8bit",
    save_total_limit=3,
    report_to="none"
)

# Тренер
trainer = SFTTrainer(
    model=model,
    args=training_args,
    train_dataset=dataset,
    dataset_text_field="text",
    max_seq_length=2048,
    tokenizer=tokenizer
)

# Запускаем обучение
trainer.train()

# Сохраняем модель
trainer.save_model("./gemma-tool-calling-final")
tokenizer.save_pretrained("./gemma-tool-calling-final")

Запускаем обучение

Теперь просто запускаем:

python train.py

И ждем. На RTX 4090 с датасетом в 1000 примеров это займет около 2-3 часов.

Внимание: Если у вас заканчивается память, уменьшайте per_device_train_batch_size до 2 или даже 1. Также можно уменьшить max_seq_length до 1024, но это ухудшит качество.

Тестируем обученную модель

После обучения создаем файл test.py:

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel

# Загружаем базовую модель
base_model = "google/gemma-3-4b-it"
model = AutoModelForCausalLM.from_pretrained(
    base_model,
    torch_dtype=torch.float16,
    device_map="auto"
)

# Загружаем адаптеры LoRA
model = PeftModel.from_pretrained(model, "./gemma-tool-calling-final")
model = model.merge_and_unload()

tokenizer = AutoTokenizer.from_pretrained(base_model)

# Тестовый промпт
prompt = "Отправь email Марии с темой 'Документы' и текстом 'Отправляю договор на подписание'"

inputs = tokenizer(prompt, return_tensors="pt").to("cuda")

with torch.no_grad():
    outputs = model.generate(
        **inputs,
        max_new_tokens=200,
        temperature=0.7,
        do_sample=True
    )

response = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(response)

Идеальный вывод должен быть похож на:

{
  "function": "send_email",
  "arguments": {
    "to": "maria@example.com",
    "subject": "Документы",
    "body": "Отправляю договор на подписание"
  }
}

Проблемы, с которыми вы столкнетесь (и как их решить)

1. Out of memory ошибки

Самая частая проблема. Решения:

  • Уменьшите per_device_train_batch_size
  • Увеличьте gradient_accumulation_steps (это компенсирует уменьшение batch size)
  • Используйте gradient checkpointing: model.gradient_checkpointing_enable()

2. Модель не учится вызывать процедуры

Если после обучения модель просто продолжает генерировать текст вместо JSON:

  • Проверьте датасет — возможно, там ошибки форматирования
  • Увеличьте количество эпох (но не более 5, иначе переобучится)
  • Попробуйте другой learning rate (1e-4 или 3e-4)

3. Плохое качество JSON

Если модель генерирует невалидный JSON:

  • Добавьте в промпт явное указание на формат: "Ответь в формате JSON: {...}"
  • Используйте constrained decoding (сложнее, но эффективнее)
  • Добавьте в loss функцию штраф за невалидный JSON

Как интегрировать это в реальное приложение

Допустим, вы создаете ассистента для бронирования столиков. После того как модель сгенерировала JSON, вам нужно:

  1. Парсить JSON
  2. Валидировать параметры
  3. Вызывать реальную функцию
  4. Возвращать результат пользователю

Пример простой интеграции:

import json
import re

def parse_tool_call(response):
    # Ищем JSON в ответе модели
    json_match = re.search(r'\{.*\}', response, re.DOTALL)
    if not json_match:
        return None
    
    try:
        tool_call = json.loads(json_match.group())
        return tool_call
    except json.JSONDecodeError:
        return None

def execute_tool_call(tool_call):
    if tool_call["function"] == "send_email":
        # Здесь реальная логика отправки email
        send_email(**tool_call["arguments"])
        return "Email отправлен"
    elif tool_call["function"] == "search_businesses":
        # Поиск в базе данных
        results = search_database(**tool_call["arguments"])
        return f"Найдено {len(results)} мест"
    else:
        return "Неизвестная процедура"

Что дальше? Улучшаем модель

После того как базовый вариант работает, можно улучшать:

  1. Добавить больше процедур — чем больше разнообразных примеров в датасете, тем лучше модель обобщает
  2. Использовать Reinforcement Learning — награждать модель за корректные вызовы и наказывать за ошибки
  3. Добавить цепочку мыслей — заставить модель сначала рассуждать, потом действовать. Об этом есть отличная статья про темную цепочку мыслей
  4. Кэшировать результаты — если вы часто вызываете одни и те же процедуры
💡
Совет от практика: Не пытайтесь сделать идеальную модель с первого раза. Сначала добейтесь рабочего прототипа с 3-5 процедурами, протестируйте его в реальных условиях, а потом уже улучшайте. Часто оказывается, что пользователям нужно не то, что вы предполагали.

FAQ: частые вопросы

Можно ли использовать этот подход для других моделей?

Да, абсолютно. Тот же код с минимальными изменениями работает для Llama, Mistral, Qwen. Главное — поменять model_id и возможно target_modules в конфигурации LoRA.

Сколько нужно данных для качественного обучения?

Для простых процедур (отправка email, поиск) достаточно 500-1000 примеров. Для сложных (анализ данных, генерация кода) нужно 2000-5000. Качество данных важнее количества.

Почему именно Gemma 3, а не другие модели?

Gemma 3 4B — идеальный баланс между качеством и требованиями к памяти. Она достаточно умна для понимания контекста, но достаточно мала, чтобы обучаться на одной карте. Для сравнения, Qwen3-30B требует больше ресурсов.

Можно ли запускать обучение на нескольких GPU?

Да, добавьте в TrainingArguments:

ddp_find_unused_parameters=False
deepspeed="configs/deepspeed_config.json"

И используйте DeepSpeed. Но это тема для отдельной статьи.

Ошибки, которые совершают все (и как их избежать)

ОшибкаПочему происходитКак исправить
Модель генерирует бесконечный текстНет стоп-токенов или max_new_tokens слишком большойДобавить stop sequences или уменьшить max_new_tokens
Обучение слишком медленноеСлишком маленький batch size или нет fp16Использовать gradient accumulation и включить fp16
Модель забывает базовые знанияСлишком высокий learning rate или много эпохУменьшить LR до 1e-5 и использовать меньше эпох
Невалидный JSON в ответахМодель не обучена формату JSONДобавить больше примеров с правильным JSON в датасет

Что делать, если нет RTX 4090

Есть несколько вариантов:

  1. Использовать облако — Google Colab Pro или AWS с GPU инстансами
  2. Квантовать модель сильнее — использовать 3-битную или даже 2-битную квантизацию
  3. Взять меньшую модель — например, Gemma 2B вместо 4B
  4. Использовать CPU обучение — очень медленно, но работает

Или посмотрите как Tencent ускоряет генерацию на слабом железе.

Заключительные мысли

Fine-tuning Gemma 3 для вызова процедур — это не магия, а вполне доступная технология. За один вечер можно создать прототип, который превращает пассивного чат-бота в активного помощника.

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

И помните: ваша натренированная модель — это только половина системы. Вторая половина — это надежная инфраструктура для выполнения этих самых процедур. Модель может идеально генерировать JSON для отправки email, но если ваша функция отправки падает каждые 10 минут — пользователь этого не оценит.

Начните с малого. Обучите модель на 2-3 простых процедурах. Протестируйте. Убедитесь, что это работает. И только потом масштабируйтесь.

И последнее: не зацикливайтесь на точности в 99.9%. Для большинства практических задач 85-90% вполне достаточно. Оставшиеся 10-15% случаев можно обработать через fallback-механизм или ручную проверку.