Fine-tuning LLM для нового языка: пошаговый гайд с кодом и LoRA | AiManual
AiManual Logo Ai / Manual.
11 Янв 2026 Гайд

Как fine-tune модель под новый язык программирования: полное руководство с кодом

Полное руководство по тонкой настройке LLM для поддержки нового языка программирования. Сбор датасета, выбор модели, обучение с LoRA, код на Python и разбор оши

Ваша модель путает синтаксис Elixir с Erlang? На запрос "напиши на Zig" выдает случайные символы? Это не потому, что она глупая. Ее просто не учили. Базовые LLM вроде CodeLlama или DeepSeek-Coder отлично знают Python и JavaScript, но спотыкаются о нишевые или новые языки. Решение - fine-tuning. Но не тот, о котором пишут в блогах с общими фразами, а конкретный, хирургический, под новый синтаксис.

Забудьте про дообучение на случайных сниппетах с GitHub. Модель запомнит мусор и будет генерировать баги. Нужен структурированный подход.

Проблема: почему модели не знают новые языки?

Большие языковые модели учатся на данных. Если в предобучении не было Vlang, Gleam или Mojo, модель будет импровизировать, основываясь на похожих токенах. Результат - синтаксический кошмар. Полное переобучение модели с нуля - дорого и бессмысленно. Мы берем сильную базовую модель (например, выбранную по этому гайду) и адаптируем ее, меняя лишь малую часть весов.

1 Собираем датасет: не просто код, а контекст

Это самый критичный этап. Плохие данные = бесполезная модель. Нельзя просто скачать 1000 файлов .zig и скормить их модели.

  • Формат инструкция-ответ: Каждый пример - это пара. Инструкция на естественном языке ("напиши функцию, которая читает конфиг в формате TOML") и код на целевом языке.
  • Разнообразие: Функции, классы, импорты, работа с API, обработка ошибок. Берите примеры из официальной документации и реальных проектов.
  • Качество: Код должен компилироваться. Используйте линтеры. Один баг в датасете - и модель его выучит.
// Пример записи в датасете (формат ChatML)
{
  "messages": [
    { "role": "user", "content": "Implement a quicksort function in Zig." },
    { "role": "assistant", "content": "pub fn quicksort(comptime T: type, arr: []T) void {\n    if (arr.len <= 1) return;\n    // ... rest of the code\n}" }
  ]
}
💡
Используйте синтетические данные. Попросите GPT-4 или Claude 3.5 сгенерировать примеры кода на новом языке по описанию. Затем вручную проверьте и исправьте. Это быстрее, чем искать готовое.

2 Выбираем метод тонкой настройки: Full fine-tuning или LoRA?

Full fine-tuning (обновление всех весов модели) требует огромных ресурсов и легко приводит к катастрофическому забыванию (модель забудет Python, выучив Zig). Для адаптации под новый язык достаточно LoRA (Low-Rank Adaptation).

  • Принцип: Встраиваем в модель небольшие адаптивные слои, а основные миллиарды параметров замораживаем. Обучается только 1-5% весов.
  • Плюсы: Быстро, дешево, несколько адаптеров можно переключать для разных языков. Идеально для экспериментов.
  • Минусы: Максимальная точность может быть чуть ниже, чем у full fine-tuning. Но для нашей задачи - более чем достаточно.

Если вы работаете на Mac, посмотрите гайд по Unsloth-MLX для эффективного прототипирования.

3 Пишем код обучения: от сырых данных до адаптера

Вот полный скрипт на Python с использованием библиотек Transformers, PEFT (для LoRA) и TRL. Предполагаем, что базовая модель - CodeLlama-7b.

from datasets import load_dataset
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    TrainingArguments,
    pipeline
)
from peft import LoraConfig, get_peft_model, TaskType
from trl import SFTTrainer
import torch

# 1. Загружаем и токенизируем датасет
dataset = load_dataset("json", data_files="zig_dataset.jsonl", split="train")
tokenizer = AutoTokenizer.from_pretrained("codellama/CodeLlama-7b-hf")
tokenizer.pad_token = tokenizer.eos_token  # Важно для кодирования!

def tokenize_function(examples):
    # Объединяем все сообщения в один текст для авторегрессии
    text = tokenizer.apply_chat_template(examples["messages"], tokenize=False)
    return tokenizer(text, truncation=True, padding="max_length", max_length=1024)

tokenized_dataset = dataset.map(tokenize_function, batched=True)

# 2. Конфигурация LoRA
lora_config = LoraConfig(
    r=16,           # Ранг матрицы адаптации (больше -> мощнее, но больше весов)
    lora_alpha=32,
    target_modules=["q_proj", "v_proj"],  # Какие слои адаптировать (для Llama)
    lora_dropout=0.05,
    bias="none",
    task_type=TaskType.CAUSAL_LM
)

# 3. Загружаем модель и применяем LoRA
model = AutoModelForCausalLM.from_pretrained(
    "codellama/CodeLlama-7b-hf",
    load_in_4bit=True,  # Квантование для экономии памяти
    device_map="auto",
    torch_dtype=torch.float16
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()  # Должно показать ~1% параметров

# 4. Настройка аргументов обучения
training_args = TrainingArguments(
    output_dir="./zig-coder-lora",
    num_train_epochs=3,
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,
    warmup_steps=100,
    logging_steps=10,
    save_steps=500,
    learning_rate=2e-4,          # Для LoRA LR может быть выше
    fp16=True,
    optim="adamw_torch",
    report_to="none"
)

# 5. Создаем тренер и запускаем обучение
trainer = SFTTrainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_dataset,
    tokenizer=tokenizer,
    dataset_text_field="id",  # Заглушка, т.к. мы уже токенизировали
    max_seq_length=1024
)

trainer.train()

# 6. Сохраняем адаптер
model.save_pretrained("./zig-coder-lora-final")
tokenizer.save_pretrained("./zig-coder-lora-final")

Не ставьте per_device_train_batch_size=1 с gradient_accumulation_steps=32, чтобы сэкономить память. Градиенты будут шумными, обучение нестабильным. Лучше уменьшить max_length или использовать gradient checkpointing.

4 Тестируем: как понять, что модель выучила язык, а не запомнила примеры?

Самый простой способ - дать ей задачу, которой не было в датасете. Например, попросить написать код с использованием библиотеки, которую вы не включали в обучение.

# Загрузка обученного адаптера для инференса
from peft import PeftModel

base_model = AutoModelForCausalLM.from_pretrained(
    "codellama/CodeLlama-7b-hf",
    device_map="auto",
    torch_dtype=torch.float16
)
model = PeftModel.from_pretrained(base_model, "./zig-coder-lora-final")

pipe = pipeline("text-generation", model=model, tokenizer=tokenizer)

prompt = "Write a Zig function that parses a JSON string using the std.json module."
# Используйте правильный шаблон чата для вашей модели
messages = [{"role": "user", "content": prompt}]
text = tokenizer.apply_chat_template(messages, tokenize=False)

result = pipe(text, max_new_tokens=256, temperature=0.2)
print(result[0]['generated_text'])

Что смотреть в выводе?

  • Синтаксис: Соответствует ли код правилам языка (импорты, объявление функций, типы)?
  • Логика: Решает ли функция поставленную задачу? Или это просто набор токенов, похожих на Zig?
  • Креативность: Может ли модель обобщать? Если в датасете была работа с файлами, а вы просите сетевой запрос - справится ли?

Где все ломается: частые ошибки и как их избежать

Ошибка Причина Решение
Модель генерирует бессмысленный синтаксис Слишком маленький датасет (менее 1000 примеров) или плохое качество данных. Увеличьте датасет до 5-10к качественных примеров. Используйте синтетическую генерацию с последующей проверкой.
Модель забыла старые языки (катастрофическое забывание) Слишком высокий learning rate или много эпох на узком датасете. Используйте LoRA с низким LR (1e-4 до 5e-4). Добавьте в датасет 20% примеров на других языках (Python, JS) для стабилизации.
Обучение падает с ошибкой CUDA out of memory Слишком большой batch size или длина последовательности. Включите gradient checkpointing (model.gradient_checkpointing_enable()), уменьшите max_length до 512, используйте квантование (load_in_4bit=True).
Модель повторяет одни и те же конструкции из датасета Переобучение. Модель запомнила примеры, а не выучила синтаксис. Уменьшите число эпох (достаточно 2-3). Добавьте dropout в LoRA конфиг. Используйте более разнообразный датасет.

Что дальше? Интеграция в рабочий процесс

Обученный адаптер - это файлы на 50-200 МБ. Их можно загрузить в любой инстанс базовой модели. Как использовать?

💡
Неочевидный совет: fine-tuning под новый язык - это не только код. Обучите модель на документации и сообщениях об ошибках. Тогда она сможет не только генерировать, но и объяснять, почему код не компилируется. Это превращает ее из кодогенератора в настоящего инженера.

Главное - не бояться экспериментов. Начните с маленького датасета на 500 примеров и одной эпохи обучения. Посмотрите, как меняются поколения. Затем масштабируйте. Через неделю у вас будет собственная модель, которая говорит на языке, которого не знает даже GPT-5. И это того стоит.