CodeFinetuner - открытый пайплайн для LoRA-дообучения небольшой модели автодополнения кода под конкретную кодовую базу. Схема полная: исходники проекта разбираются через tree-sitter и превращаются в примеры FIM, модель учится на них через LoRA, проходит оценку по нескольким метрикам, конвертируется в GGUF и подключается к редактору через llama.vim или llama.vscode. Всё работает локально, без облака и без отправки кода наружу.
Типовая база для такого сценария - Qwen2.5-Coder-3B: три миллиарда параметров, что позволяет уместить и обучение, и инференс на потребительском железе. Обучение поддерживается на Mac через MPS и на NVIDIA GPU через CUDA, опционально подключается Unsloth для ускорения и экономии VRAM.
Ключевая оговорка, которую автор проекта проговаривает отдельно: рост метрик на тестовом наборе не означает, что модель стала полезнее в редакторе. Инструменты автодополнения сэмплируют токены иначе, чем жадное декодирование, применяемое при оценке. Проверять ценность дообученной модели нужно прямо в процессе написания кода, а не по таблице с цифрами.
Что такое CodeFinetuner и какую задачу он решает
CodeFinetuner собирает в один сценарий четыре вещи, которые обычно разбросаны по разным инструментам: подготовку данных из реального репозитория, LoRA-дообучение, оценку качества и конвертацию в формат для локального запуска. Отдельный скрипт для нарезки датасета, отдельный тренер, отдельная тетрадка с метриками и отдельная возня с квантованием - здесь это этапы одного пайплайна.
Задача формулируется так: научить модель предсказывать код именно вашего проекта. Универсальная модель автодополнения уверенно пишет циклы, обработку исключений и типовые вызовы популярных библиотек. Про ваш внутренний HTTP-клиент, имена модулей, порядок аргументов и собственные соглашения об именах она не знает ничего.
Чем дообученная модель отличается от готовой
Готовая модель обучена на огромном корпусе публичного кода, поэтому её сила - общие паттерны языка. Там, где начинается специфика проекта, качество падает: подсказки уходят в обобщённые конструкции, модель предлагает методы, которых у вашего класса нет, и путает похожие по смыслу, но разные по устройству модули.
Дообучение на своей кодовой базе подтягивает именно этот слой: названия внутренних сущностей, типичные цепочки вызовов, стиль обработки ошибок, формат логирования. Работает это, пока в репозитории достаточно однотипного кода, из которого выводится устойчивый паттерн. Десяток файлов с уникальной логикой в каждом ничего не даст.
Честная рамка: дообученная модель не заменяет универсальную, а дополняет её там, где проект живёт по своим правилам. И метрики тут плохой судья - об этом в разделе про оценку.
Почему LoRA, а не полный fine-tuning
LoRA обучает небольшие адаптерные матрицы поверх замороженных весов базовой модели. Число обучаемых параметров падает с миллиардов до миллионов, и вместе с ним падает потребление VRAM: градиенты и состояния оптимизатора приходится держать в памяти только для маленькой надстройки, а не для всей сети.
Практический результат: обучение помещается на одну потребительскую карту и даже на Apple Silicon. Полный fine-tuning той же трёхмиллиардной модели потребовал бы кратно больше памяти и времени, а выигрыш в качестве для узкой задачи автодополнения неочевиден.
Как параметрически эффективные методы сокращают обучаемые параметры до десятых долей процента от общего объёма и уменьшают чекпоинты с десятков гигабайт до нескольких мегабайт, разобрано в отдельном материале про PEFT-методы для дообучения LLM.
Как устроен пайплайн: от исходников до GGUF
Порядок этапов жёсткий: разбор исходников, генерация FIM-примеров, LoRA-дообучение, оценка, конвертация в GGUF. Каждый следующий шаг опирается на артефакты предыдущего, и перепрыгнуть этап подготовки данных не получится: качество примеров определяет, чему вообще научится модель.
Парсинг кода через tree-sitter в примеры FIM
tree-sitter разбирает файлы в синтаксическое дерево. Пайплайну это даёт структурные границы: функции, методы, классы, блоки. Нарезка по дереву отсекает мусор вроде служебных комментариев и не превращает обучающие примеры в случайные обрывки кода, разрезанные посреди выражения.
FIM (Fill-in-the-Middle) - это формат примера, где модель видит префикс и суффикс, а между ними пропуск, который нужно восстановить. Схематично это выглядит так:
fim_prefix: def parse_config(path):\n raw = read_file(path)\n
fim_suffix: return config
fim_middle: <пропущенный фрагмент, который предсказывает модель>Точные служебные токены зависят от модели, но смысл один: контекст приходит с двух сторон от места вставки. Для автодополнения это точное совпадение с реальностью. Курсор стоит в середине файла, выше и ниже уже написан код, и модель должна вставить фрагмент в позицию курсора.
Классическое предсказание следующего токена для этой задачи слабее. Оно тренирует модель смотреть только назад, тогда как редактор даёт и контекст после курсора: закрывающие скобки, объявления из нижней части файла, уже написанные сигнатуры. FIM эту информацию задействует, поэтому и стал стандартом для моделей автодополнения кода.
LoRA-дообучение модели
На подготовленных FIM-примерах модель дообучается с LoRA. Базой выступает небольшая кодовая модель, в описании проекта приведён Qwen2.5-Coder-3B. Размер базы напрямую определяет требования к железу, и 3B здесь компромисс: заметно легче моделей на 7B и выше, но всё ещё способна писать осмысленный код.
Обучение идёт на Mac через MPS или на NVIDIA GPU через CUDA. Опционально подключается Unsloth для ускорения. Конкретные гиперпараметры и настройки в описании проекта не приводятся, их подбирают под датасет и железо: слишком большой learning rate на маленьком наборе примеров быстро уводит адаптер в переобучение.
Результат этапа - LoRA-адаптер. Его можно держать отдельно от базовых весов или слить с ними перед конвертацией.
Конвертация в GGUF для локального инференса
GGUF - формат моделей для llama.cpp-совместимых движков. Он хранит веса в квантованном виде, что сокращает размер файла, и позволяет запускать инференс на CPU, на GPU или с разделением слоёв между ними. Именно в GGUF модель и попадает в редактор.
Логика связки простая: веса и адаптер сливаются в одну модель, модель конвертируется в GGUF, файл подхватывает локальный сервер, к серверу обращается плагин редактора. Пользователь видит обычные подсказки по мере ввода, а весь обмен данными остаётся внутри машины. Как квантование и llama.cpp позволили запускать большие языковые модели на обычном процессоре, подробно разобрано в отдельной статье. Формат общий для всей экосистемы: в GGUF выходят и готовые модели, включая свежие релизы с sparse attention и контекстом в миллион токенов.
Метрики оценки: CodeBLEU, edit similarity, exact match, perplexity
Пайплайн считает четыре метрики. Каждая смотрит на качество под своим углом, и по отдельности ни одна не даёт полной картины.
Что показывает каждая метрика
| Метрика | Что измеряет | Особенность |
|---|---|---|
| CodeBLEU | Совпадение с эталоном по n-граммам с учётом синтаксиса и семантики кода | Ближе к человеческой оценке, чем обычный BLEU |
| Edit similarity | Близость сгенерированного кода к эталону на уровне правок | Показывает, сколько правок нужно до рабочего варианта |
| Exact match | Полное совпадение с эталоном | Жёсткая: одно отличие обнуляет результат |
| Perplexity | Уверенность модели в предсказаниях | Чем ниже, тем лучше |
CodeBLEU расширяет обычный BLEU: к совпадениям n-грамм добавляются синтаксическое дерево и граф потока данных. Метрика учитывает структуру кода, а не только текст, поэтому одинаково записанные, но по-разному устроенные фрагменты она различает.
Edit similarity измеряет, сколько правок отделяет сгенерированный фрагмент от эталонного. Для автодополнения это ближе к реальности: разработчику важно, сколько он доработает предложенную подсказку, а не совпала ли она посимвольно.
Exact match сравнивает строки буквально. Полезна как нижняя граница, но в коде один и тот же результат достигается десятком способов, поэтому метрика почти всегда занижает качество и годится скорее для отслеживания динамики.
Perplexity показывает, насколько уверенно модель предсказывает следующий токен. Падение perplexity на валидации говорит, что модель освоила статистику вашего кода. Оно не говорит, что её подсказки удобны, уместны и не раздражают.
Почему метрики не равны реальной пользе
Автор CodeFinetuner прямо предупреждает: улучшения на тестовых метриках не гарантируют реальной пользы в редакторе. Причина в расхождении процедур генерации. Оценка идёт при жадном декодировании, то есть модель на каждом шаге выбирает самый вероятный токен. Инструменты автодополнения так не работают: они сэмплируют с температурой, обрезают распределение по top-k или top-p, иногда предлагают несколько вариантов сразу.
Отсюда практический разрыв. Модель, которая лучше проходит тесты при жадном декодировании, при сэмплировании может вести себя иначе: выдавать более разнообразные, но менее точные подсказки. Плюс в редакторе есть факторы, которых нет в тестовом наборе: задержка ответа, длина контекста, поведение при незавершённой строке.
Проверка одна: открыть редактор, поработать час на реальной задаче и посмотреть, сколько подсказок принимается без правок, сколько переписывается и сколько мешает. Это единственный тест, который отражает ценность модели для вас.
Требования к железу и ускорение через Unsloth
Пайплайн не привязан к одной платформе, но и не универсален: список поддерживаемых бэкендов ограничен.
Mac (MPS) vs NVIDIA GPU (CUDA)
Поддерживаются два бэкенда: MPS для Apple Silicon и CUDA для карт NVIDIA. Оба проходят один и тот же сценарий - подготовка данных, LoRA-дообучение, оценка, конвертация. Утверждать, какой из них быстрее, без замеров нельзя, и в описании проекта такого сравнения нет.
Выбор диктует доступное железо. Единственный надёжный ориентир - размер базовой модели: чем больше параметров, тем выше требования к памяти. Qwen2.5-Coder-3B для потребительской машины разумная отправная точка, модели на 7B и выше потребуют заметно больше ресурсов.
Зачем нужен Unsloth и когда его подключать
Unsloth - опциональная библиотека для ускорения обучения и снижения потребления VRAM. В CodeFinetuner она подключается как ускоритель, если стандартного пути не хватает. Конкретный прирост зависит от модели, конфигурации и железа, гарантированных цифр здесь нет.
Смысл включать её есть в двух случаях: обучение не влезает в память или занимает неприемлемо много времени. Если задача - разобраться в пайплайне на маленькой модели, можно начать без неё и добавить позже. Как Unsloth снижает требования к памяти, работает с квантизациями и поддерживает новые архитектуры, разобрано в этом материале про Unsloth.
Подключение модели к редактору: llama.vim и llama.vscode
Готовая GGUF-модель превращается в локальный сервер автодополнения, к которому подключается плагин. В проекте названы два варианта, по одному на каждый популярный редактор.
llama.vim для Vim
llama.vim отдаёт подсказки по мере ввода прямо в Vim, работая с локально запущенной GGUF-моделью. Код не покидает машину, внешние API не задействованы. Подсказка приходит асинхронно, пока вы продолжаете печатать.
llama.vscode для VS Code
llama.vscode делает то же самое для VS Code. Источник подсказок тот же: локальный инференс на вашей машине. Оба плагина предполагают, что сервер уже поднят, а модель сконвертирована в GGUF.
После подключения начинается этап, который нельзя пропустить. Метрики измерили модель на статичном наборе примеров, а редактор показывает поведение на живом коде: где модель молчит, где предлагает лишнее, где попадает в стиль проекта. Практическую пользу проверяют именно так, и это ровно та оговорка, с которой автор проекта начал описание.
Кому подходит CodeFinetuner и какие у него ограничения
Когда дообучение имеет смысл
Дообучение окупается, когда в проекте много устоявшейся специфики. Признаки подходящей кодовой базы: собственный фреймворк или DSL, крупный внутренний API, единый стиль обработки ошибок и логирования, сотни модулей, повторяющих одну структуру. Модель, обученная на таких данных, получает шанс подхватить шаблоны, которых нет в публичном коде.
Обратный случай: небольшой проект на популярных библиотеках со стандартными паттернами. Универсальная модель уже видела большую часть нужных конструкций, и выигрыш от дообучения может не оправдать затраченного времени. Граница между этими случаями размыта, поэтому решать приходится по своему контексту.
Основные ограничения и риски
- Метрики не равны реальной пользе. Оценка идёт при жадном декодировании, редактор работает с сэмплированием, поэтому результат надо проверять в живом написании кода.
- Пайплайн требует настройки. Подготовить данные, обучить адаптер, оценить, сконвертировать, поднять инференс: это не одна кнопка.
- Поддерживаются только два бэкенда: MPS и CUDA. На других ускорителях обучение может не запуститься вовсе.
- Unsloth опционален, но без него на более крупной модели может не хватить VRAM.
- Дообучение сужает модель. Адаптация под один проект делает подсказки менее полезными на чужом коде, поэтому для сторонних репозиториев лучше держать отдельную модель.
CodeFinetuner как референс для собственного пайплайна
Даже без намерения пользоваться проектом напрямую он даёт готовую схему end-to-end LoRA-файнтюнинга: разбор исходников через tree-sitter, генерацию FIM-примеров, обучение адаптера, набор метрик для оценки, конвертацию в GGUF и подключение к редактору. Каждый этап можно взять отдельно и заменить своим инструментом, сохранив общую логику.
Ценность чаще всего именно в порядке шагов. Многие собирают дообучение стихийно: скачали датасет, обучили, посмотрели на loss, забыли про оценку и про инференс. Здесь схема полная, включая пункт, до которого обычно не доходят: проверку модели в реальной работе.
Если у вас лежит репозиторий с собственной спецификой, начните с малого: возьмите узкий набор файлов, разберите их в FIM-примеры, обучите адаптер на небольшой модели и посмотрите, как подсказки ведут себя прямо в редакторе. Это быстрее, чем спорить о метриках.