LoRA для function calling
ТЕХНИЧЕСКОЕ ЗАДАНИЕ: LoRA для function calling
1. Цель задачи
Разработать и обучить LoRA-адаптер на основе небольшой LLM (например, Phi-2, LLaMA-2-7B или Qwen2-1.5B) для задачи function calling – преобразования инструкции пользователя на естественном языке в форматированный вызов API (имя функции + параметры). Создать синтетический датасет из 1000 примеров «инструкция → API call». Цель – достичь точности (accuracy) не менее 90% на тестовой выборке (200 примеров) при условии, что модель правильно определяет имя функции и корректно извлекает все обязательные параметры.
Ключевой результат Файл обученной LoRA-адаптации (≈5–20 MB) и скрипт инференса, который по новой инструкции возвращает валидный JSON с вызовом функции.
2. Исходные данные
Перед началом необходимо иметь:
| Что нужно | Откуда взять |
|---|---|
| Базовая LLM (трансформер) | Hugging Face Hub: microsoft/phi-2, NousResearch/Llama-2-7b-hf или Qwen/Qwen2-1.5B-Instruct |
| Среда для дообучения | Google Colab (T4/A100) / локальный GPU (минимум 10 ГБ VRAM) |
| Датасет (1000 примеров) | Сгенерировать синтетически (см. Этап 1) |
| Библиотеки | transformers>=4.37, peft, bitsandbytes, accelerate, datasets, scikit-learn, json, fire |
| Git-репозиторий | Создать новый в GitHub/GitLab (код + адаптер) |
Если нет доступа к GPU — используем Google Colab (бесплатный T4) или Kaggle Notebooks (GPU P100). Для моделей 7B потребуется quantization (QLoRA).
Если нет реального датасета — симулируем:
- Определить 5-10 функций (например,
get_weather(city, date), send_email(to, subject, body), search_web(query),calculate(expression), get_news(topic, limit)) - Написать Python-скрипт, который генерирует 1000 различных инструкций (варьируя параметры, формулировки, опечатки) и соответствующие JSON-вызовы.
- Разделить на train/val/test (700/100/200) и сохранить в формате JSONL.
3. Технологический стек
| Компонент | Инструменты | Назначение |
|---|---|---|
| Базовая модель | Phi-2 / LLaMA-2-7B / Qwen2-1.5B | Генерация function call |
| LoRA (PEFT) | peft, bitsandbytes (4-bit QLoRA) | Параметро-эффективное дообучение |
| Датасет | datasets, JSONL | Загрузка и препроцессинг |
| Токенизация | transformers.AutoTokenizer | Подготовка входных последовательностей |
| Обучение | Trainer или SFTTrainer от TRL | Тренировка с early stopping |
| Метрики | scikit-learn, json | Accuracy, F1 по параметрам |
| Логирование | WandB / TensorBoard (опционально) | Мониторинг лосса и метрик |
| Среда | Google Colab / локальный Docker | Вычисления |
4. Этапы выполнения
Этап 1: Генерация и подготовка датасета (2-3 часа)
Действия
-
Определить схему функций Например, 5 функций с обязательными и опциональными параметрами:
functions = { "get_weather": ["city", "date"], "send_email": ["to", "subject", "body"], "search_web": ["query"], "calculate": ["expression"], "get_news": ["topic", "limit"] } -
Написать генератор примеров Использовать шаблоны инструкций (не менее 10 вариаций на функцию):
- Прямой запрос: «Какая погода в Москве завтра?» →
{"name": "get_weather", "args": {"city": "Москва", "date": "2025-02-01"}} - Косвенный запрос: «Узнай, не будет ли дождя в Лондоне сегодня» → аналогично
- С синонимами: «отправь письмо …», «отправь e-mail …», «напиши письмо …»
- С лишними деталями, опечатками.
- Прямой запрос: «Какая погода в Москве завтра?» →
-
Создать скрипт
generate_dataset.py, который:- Выбирает случайную функцию, генерирует случайные аргументы (из заранее заданных списков городов, дат, тем).
- Составляет инструкцию (вставляя параметры в шаблон).
- Формирует JSON-вызов.
- Записывает в JSONL: {"instruction": "...", "output": "..."}.
-
Разделить на train/val/test (700/100/200).
-
Проверить качество – нет ли дубликатов, все ли JSON валидны, все ли параметры присутствуют.
Ожидаемый результат этапа Файлы train.jsonl, val.jsonl, test.jsonl с 1000 примеров, скрипт генерации.
Этап 2: Настройка токенизации и формат для function calling (1 час)
Действия
-
Выбрать формат промпта Для Phi-2 / Qwen2 подойдёт простой chat template:
User: {instruction} Assistant: {function_call}Для LLaMA – использовать стандартный шаблон
[INST] ... [/INST]. -
Написать функцию токенизации, которая:
- Добавляет токены
<|user|>и<|assistant|>(или EOS). - Форматирует
outputкак валидный JSON (можно минифицировать). - Обрезает последовательность до max_length=256 (инструкция короткая).
- Важно Убедиться, что в labels маскируются токены инструкции (loss только на output).
- Добавляет токены
-
Проверить на 5 примерах – запустить токенизацию и проверить, что
input_idsи labels корректны.
def tokenize_fn(examples, tokenizer, max_length=256):
texts = [f"User: {inst}\nAssistant: {out}" for inst, out in zip(examples["instruction"], examples["output"])]
tokenized = tokenizer(texts, truncation=True, max_length=max_length, padding="max_length", return_tensors="pt")
labels = tokenized["input_ids"].clone()
# маскируем User-часть
user_len = tokenizer(f"User: {examples['instruction']}\nAssistant:", return_tensors="pt")["input_ids"].shape[1]
labels[:, :user_len] = -100
tokenized["labels"] = labels
return tokenized
Ожидаемый результат этапа Готовый DataLoader или Dataset с корректными input_ids, attention_mask, labels.
Этап 3: Настройка LoRA и обучение (2-3 часа)
Действия
-
Загрузить базовую модель с 4-bit QLoRA (если память ограничена):
from transformers import AutoModelForCausalLM, BitsAndBytesConfig bnb_config = BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_compute_dtype=torch.float16) model = AutoModelForCausalLM.from_pretrained("microsoft/phi-2", quantization_config=bnb_config, device_map="auto") -
Настроить LoRA через peft.LoraConfig:
r=8, lora_alpha=16, target_modules – для Phi-2: ["q_proj", "v_proj"] (уточнить по архитектуре).lora_dropout=0.05, bias="none",task_type="CAUSAL_LM".
-
Использовать SFTTrainer из библиотеки TRL:
per_device_train_batch_size=4,gradient_accumulation_steps=4(эффективный batch 16).- learning rate = 2e-4, LR scheduler cosine, warmup steps=50.
- evaluation_strategy="steps", eval_steps=50, save_steps=100, metric_for_best_model="eval_loss".
- max_seq_length=256.
-
Запустить обучение на 5 эпох Логировать loss и accuracy (кастомная функция compute_metrics – см. Этап 4).
-
После обучения сохранить LoRA-адаптер
model.save_pretrained("./lora-function-calling").
Ожидаемый результат этапа Папка lora-function-calling/ с adapter_model.bin, adapter_config.json, а также best_model/ с лучшим чекпоинтом.
Этап 4: Оценка модели (1-2 часа)
Действия
-
Загрузить адаптер на базовую модель (без QLoRA, в полной точности или снова 4-bit):
from peft import PeftModel base = AutoModelForCausalLM.from_pretrained("microsoft/phi-2", device_map="auto") model = PeftModel.from_pretrained(base, "./lora-function-calling") -
Написать функцию инференса, которая:
- Принимает инструкцию.
- Форматирует как
User: ...\nAssistant:. - Генерирует ответ (temperature=0.1, max_new_tokens=128).
- Парсит JSON из вывода (игнорируя текст до первого
{и после последнего}).
-
Прогнать на тестовой выборке (200 примеров). Для каждого:
- Сравнить предсказанное
nameиargsс эталоном (перебор параметров с учётом порядка). - Точность (accuracy) считается так
- Если
nameсовпадает И все обязательные параметры совпадают (тип и значение) – пример верный. - Показатель: доля верных примеров.
- Если
- Сравнить предсказанное
-
Если accuracy < 90%
- Проанализировать ошибки (неправильный парсинг, неверное имя, лишние параметры).
- Увеличить количество эпох, изменить LoRA rank, добавить больше шаблонов в генератор.
- Повторить обучение.
Ожидаемый результат этапа Отчёт с accuracy (цель ≥90%), разбивка по каждой функции, примеры ошибок.
Этап 5: Упаковка и документирование (1 час)
Действия
-
Создать финальный скрипт
inference.py:import fire, json from transformers import AutoModelForCausalLM, AutoTokenizer from peft import PeftModel class FunctionCaller: def __init__(self, base_model, lora_adapter): self.tokenizer = AutoTokenizer.from_pretrained(base_model) self.tokenizer.pad_token = self.tokenizer.eos_token base = AutoModelForCausalLM.from_pretrained(base_model, device_map="auto") self.model = PeftModel.from_pretrained(base, lora_adapter) def call(self, instruction, temperature=0.1, max_tokens=128): prompt = f"User: {instruction}\nAssistant:" inputs = self.tokenizer(prompt, return_tensors="pt").to(self.model.device) outputs = self.model.generate(**inputs, temperature=temperature, max_new_tokens=max_tokens, do_sample=True) response = self.tokenizer.decode(outputs[0], skip_special_tokens=True) # извлечь JSON из ответа start = response.find('{') end = response.rfind('}')+1 return json.loads(response[start:end]) if __name__ == "__main__": fire.Fire(FunctionCaller) -
Добавить
requirements.txtиREADME.mdс инструкцией по запуску. -
Зафиксировать вес адаптера в Git LFS или загрузить на Hugging Face Hub.
Ожидаемый результат этапа Рабочий репозиторий с кодом, обученным адаптером, инструкцией и результатами оценки.
5. Критерии приемки (Definition of Done)
- Сгенерирован датасет из 1000 примеров (train/val/test) с валидными JSON.
- LoRA-адаптер обучен до достижения accuracy ≥90% на тестовой выборке (минимум 90/100 верных ответов).
- Средняя длина генерации ≤128 токенов.
- Адаптер и инференс-скрипт работают на CPU (с той же моделью) без ошибок.
- Код загружен в git-репозиторий, все зависимости фиксированы в
requirements.txt. -
README.mdсодержит: цель, структуру репозитория, инструкцию по установке и запуску, описание датасета и метрики. - Проведён анализ ошибок – в отчёте указаны основные типы ошибок (неверное имя функции, неправильные аргументы, невалидный JSON).
6. Ожидаемый результат
Основной артефакт
- Папка
lora-function-calling/с файлами:adapter_model.binadapter_config.json- (опционально)
tokenizer_config.json, если использован кастомный токенизатор.
Сопутствующие файлы
data/–train.jsonl,val.jsonl,test.jsonlscripts/–generate_dataset.py,train.py,inference.py,evaluate.pyresults/– файлevaluation_report.mdс точностью, матрицей ошибок, примерами.requirements.txt,README.md
Дополнительно (по желанию):
- Загрузка адаптера на Hugging Face Hub (репозиторий
yourusername/phi2-lora-function-calling). - Простая демонстрация в виде Streamlit-приложения.
7. Возможные сложности и их решение
| Сложность | Решение |
|---|---|
| Не хватает VRAM для 7B модели | Использовать QLoRA (4-bit) или выбрать меньшую модель (Phi-2 2.7B, Qwen2-1.5B). |
| Генерация датасета даёт однообразные инструкции | Добавить больше шаблонов, использовать LLM (ChatGPT) для парафраза 50-100 примеров вручную. |
| Модель не может распарсить JSON из вывода | Увеличить вес парсинга: обучить модель генерировать строго после ключевого слова Assistant:, убрать лишние токены. |
| Accuracy низкая (70-80%) | Увеличить rank LoRA до 16, добавить эпохи, проверить маскировку loss (не учить User-часть). |
| Генерация слишком длинная или содержит лишний текст | Уменьшить max_new_tokens, добавить stop_token для завершения JSON, использовать temperature=0.1. |
| Не удаётся воспроизвести обучение из-за случайных seed | Зафиксировать seeds в train.py (random.seed, np.random.seed, torch.manual_seed). |
8. Бюджет времени (оценка)
| Этап | Время |
|---|---|
| 1. Генерация датасета | 2-3 часа |
| 2. Настройка токенизации | 1 час |
| 3. Настройка LoRA и обучение | 2-3 часа |
| 4. Оценка и дообучение | 1-2 часа |
| 5. Упаковка и документирование | 1 час |
| Итого | 7-10 часов |
Примечание Для первого раза может потребоваться 10-12 часов, если возникнут сложности с выбором модели, отладкой парсинга или настройкой QLoRA.
9. Связанные вопросы из базы знаний
| Вопрос | Тема |
|---|---|
| 42 | Как работает LoRA? |
| 67 | Сравнение PEFT методов (LoRA, AdaLoRA, IA3) |
| 128 | Форматы данных для instruction fine-tuning |
| 219 | Использование SFTTrainer из TRL |
| 304 | Оценка качества function calling (метрики) |
| 411 | QLoRA: квантизация и обучение в 4-bit |
| 538 | Как сгенерировать синтетический датасет с помощью LLM? |
| 672 | Masking loss для обучения чат-моделей |
| 745 | Инференс адаптеров на CPU |
| 819 | Деплой LoRA-модели через Hugging Face Inference API |
10. Чек-лист самопроверки
- Я проверил, что датасет содержит ровно 1000 уникальных примеров, никакой не повторяется.
- Я проверил, что валидационный и тестовый наборы не пересекаются с тренировочным.
- Я убедился, что скрипт обучения воспроизводится при фиксированном seed.
- Я запустил
inference.py --instruction="Как погода в Нью-Йорке на послезавтра?"и получил корректный JSON. - Я сохранил логи обучения и выгрузил их вместе с репозиторием (опционально).
- Я проверил, что accuracy на тесте ≥90% минимум в двух запусках с разными seed.