Сегодня год назад мы публично представили нашу первую модель Fast Apply. С тех пор мы многое узнали о том, как настраивать небольшие специализированные модели для задач, связанных с кодом.
Сегодня мы публикуем в открытом доступе то, что мы узнали в процессе обучения этой серии моделей — отборку датасета, методы обучения и методы инференса, которые привели к созданию Relace Apply 3, нашей лучшей на данный момент модели, способной работать со скоростью более 10 000 токенов в секунду при сохранении передовой точности.
Проблема
При внесении изменений в код не имеет смысла использовать дорогую LLM для повторного создания всего неизменного, существующего кода.
Редактирование файла из тысячи строк (~10 000 токенов) путем его переписывания с нуля с помощью Claude 4.5 Sonnet занимает более 100 секунд и стоит не менее 0,18 доллара. Для кодовых агентов это неприемлемо с точки зрения продукта.
Решение заключается в том, чтобы заставить фронтальную модель выводить дифф, который минимально выражает изменения, которые нужно внести (т. е. жесткие токены), и использовать легкий алгоритм для эффективного применения диффа обратно в существующий код. Это не только экономит средства, но и, если алгоритм слияния быстрый, значительно ускоряет время генерации от начала до конца.
В течение долгого времени LLM были неспособны надежно генерировать дифф-форматы, которые можно было бы объединять с помощью фиксированного алгоритма, такого как замена строк или UDiff.
Компания Cursor разработала гибкое решение этой проблемы — позволить фронтальной модели генерировать «ленивые» диффы и использовать небольшую, быструю модель применения в качестве алгоритма слияния. Однако Cursor никогда не делал свою модель доступной для других компаний для использования за пределами IDE.
Мы решили обучить собственную модель применения, которую мог бы использовать любой желающий.
Зачем использовать LLM в качестве алгоритма слияния?
На практике фронтальные модели могут генерировать широкий спектр патологических диффов. Любой замкнутый алгоритм, который вы напишете для выполнения слияния, будет уязвим к крайним случаям. В агентских настройках, где часто нет человеческого надзора, эти ошибки накапливаются и приводят к неправильному коду.
В приведенном выше коде пользователь начинает с функции, называемой handler. Цель диффа справа — переименовать функцию handler в messageHandler и добавить параметр для столбца name в запросе к базе данных.
Будет трудно написать алгоритм, который предвидит эти два шага. Стандартный алгоритм слияния, вероятно, просто добавит новую функцию, называемую messageHandler, дублируя исходную функцию.
Преимущество использования LLM в качестве алгоритма слияния заключается в том, что она может гибко выводить намерение диффа. Предобучение на триллионах кодовых токенов обеспечивает распознавание шаблонов, устойчивое к многим крайним случаям, которые встречаются в производстве.
Кроме того, разделив задачу на два этапа — генерацию диффа и слияние — можно использовать гораздо меньшую, быструю модель, которая сосредоточится исключительно на слиянии. Это разделение работы на основе сложности — это шаблон, который мы часто используем в Relace для улучшения общей производительности кодовых агентов.
Чтобы достичь хороших результатов без необходимости предобучения модели, мы настраиваем готовые небольшие модели на высококачественном датасете, который соответствует распределению в производстве.
Создание датасета
Тренировочный набор для быстрого применения содержит три компонента: initial_code, diff и merged_code. Исходный код и дифф передаются в качестве входных данных модели, а объединенный код является выходным результатом, который мы обучаем модель генерировать.
Для тонкой настройки вам нужен небольшой набор высококачественных примеров. Мы обнаружили, что размер датасета гораздо менее важен, чем разнообразие и качество данных слияния. Наша первая модель была обучена только на 30 000 точек данных, и мы наблюдали незначительные улучшения после 100 000 точек данных.
Входные данные
Рано компания Kortix AI выпустила открытый датасет, созданный путем постобработки данных, собранных из публичных репозиториев GitHub. Учитывая некоторый исходный код, они заставляли фронтальную модель, такую как Claude, (1) придумать изменение, которое нужно внести, и (2) сгенерировать «ленивый» дифф для него.
Оказалось, что это неправильный подход, поскольку он не отражает фактическое распределение данных в производственных средах. Богатое разнообразие крайних случаев в диффах возникает из-за перегрузки контекста. LLM должна следовать инструкциям в длинных системных промптах, одновременно выводя намерение из шумных разговоров с нетехническими пользователями.
Чтобы получить высококачественные, сложные слияния с множеством крайних случаев в тренировочный набор, мы сотрудничали с компаниями, занимающимися преобразованием промптов в приложения. Мы делали снимки реального контекста для задач кодирования LLM и повторно запускали их с дополнительными инструкциями для генерации «ленивых» изменений. Это позволило нам выбирать напрямую из истинного распределения initial_code и diff, которое будет наблюдаться в производстве.
Выходные данные
Для генерации правильного merged_code мы используем дистилляцию с отбором по отклонению. Идея заключается в том, чтобы подать исходный код и дифф в хорошо настроенную фронтальную «учительскую» модель, руководствуясь набором правил, как следует обрабатывать слияния.
Этот подход позволяет быстро генерировать большое количество кандидатных данных, но крайне важно фильтровать ошибки учителя. Когда это делается правильно, обученная модель-ученик может даже превзойти учителя.
Однако правильное фильтрация сложна. Мы разработали многоэтапный процесс создания высококачественного LLM-as-a-judge, который мог бы масштабироваться до тысяч точек данных.
Оценка слияний
Мы начали с ручного просмотра 500 случайно выбранных примеров, чтобы создать набор железобетонных истинных данных. Для нашей первой модели Apply эти точки данных были полностью синтетическими, но позже мы повторили процесс, используя реальные производственные данные из предыдущих итераций хостинговой модели.
Чтобы упростить процесс при сохранении качества, мы создали собственный внутренний инструмент оценки: Git-стиль просмотрщика диффов с инструментами аннотации для категоризации результатов слияния.
Даже с этим инструментом нам потребовалось более 40 часов, чтобы тщательно обеспечить 100% правильность. В процессе начали появляться шаблоны, и мы разделили слияния на 6 категорий:
- Правильное слияние: Модель правильно реализовала намерение диффа.
Правильное слияние: Модель правильно реализовала намерение диффа.
- Нефункциональная ошибка: Модель реализовала намерение диффа, за исключением несущественных деталей, таких как вариации в комментариях и форматировании (например, определения функций в разном порядке).
Нефункциональная ошибка: Модель реализовала намерение диффа, за исключением несущественных деталей, таких как вариации в комментариях и форматировании (например, определения функций в разном порядке).
- Сглаживание: Модель исправила ошибку, включенную диффом, которая привела бы к невозможности компиляции или повреждению кода.
Сглаживание: Модель исправила ошибку, включенную диффом, которая привела бы к невозможности компиляции или повреждению кода.
- Функциональная/ошибка слияния: Модель не выполнила намерение диффа (например, пропустила части кода и вставила код в неправильных местах).
Функциональная/ошибка слияния: Модель не выполнила намерение диффа (например, пропустила части кода и вставила код в неправильных местах).
- Ошибка галлюцинации: Модель добавила или изменила код, не указанный диффом (может быть неудачной попыткой сглаживания).
Ошибка галлюцинации: модель добавила или изменила код, не указанный в diff (возможно, это неудачные попытки сглаживания).
- Ошибки усечения: модель выполнила неполное слияние (с вырезанными частями конечного кода), что привело к некомпилируемому коду.
Ошибки усечения: модель выполнила неполное слияние (с вырезанными частями конечного кода), что привело к некомпилируемому коду.
Примечание: категории 1–3 считаются правильными слияниями, а 4–6 — неправильными. Мы сохраняем систему из шести классов для более детальной оценки, но при создании судьи LLM переходим к бинарной классификации.
Отступление о сглаживании
Мы часто обнаруживали, что передовые LLM генерировали diff, приводящие к немного неправильному коду. Самый распространенный пример — когда diff использует новую библиотеку в коде, не импортируя ее на самом деле.
Это подняло вопрос: должен ли модель быстрого применения строго следовать diff или исправлять ошибку? На практике мы обнаружили, что клиенты, создающие кодовые агенты, предпочитают, чтобы модель автоматически исправляла небольшие ошибки, чтобы уменьшить трение для конечных пользователей.
Категория галлюцинации включает случаи, когда модель применения превысила свои возможности и ввела больше неправильного кода, пытаясь исправить ошибки.
LLM в роли судьи
После того, как у нас был этот набор из 500 проверенных категоризированных примеров слияния, следующим шагом было согласовать LLM в качестве судьи с нашей аннотированной вручную базовой датой. Было бы совершенно нереально вручную оценить достаточное количество слияний кода, чтобы создать полную обучающую выборку из более чем 100 000 примеров.
Техника LLM-as-a-judge использует разрыв между генерацией и проверкой. Т.е. LLM легче оценить, является ли ответ правильным, чем фактически сгенерировать ответ. Тем не менее LLM часто совершает ошибки, и важно сначала правильно согласовать ее с оценкой человека.
Как и в любой задаче бинарной классификации, существуют два режима ошибок:
- Ложный положительный результат — плохое слияние неправильно классифицируется как хорошее.
Ложный положительный результат — плохое слияние неправильно классифицируется как хорошее.
- Ложный отрицательный результат — хорошее слияние неправильно классифицируется как плохое.
Ложный отрицательный результат — хорошее слияние неправильно классифицируется как плохое.
Поскольку у нас есть большая синтетическая выборка, ложные положительные результаты являются более проблемным режимом ошибки. Гораздо хуже, чтобы плохие слияния оставались в выборке, чем уменьшить производительность выборки, выбросив несколько ложных отрицательных результатов.
Мы использовали Claude 4 Sonnet в качестве судьи и итеративно настраивали промпт, пока не достигли уровня ложных положительных результатов около 1%. Для сравнения, у первоначального наивного судьи без настройки уровень ложных положительных результатов составлял около 16%!
Масштабирование
С согласованным судьей LLM можно масштабно фильтровать оставшиеся данные.
Для Relace Apply 3 мы начали с 200 000 наборов initial_code, diff и синтетически сгенерированного merged_code. Мы выбрали репрезентативное распределение примеров на десятки языков, но сосредоточились в основном на TypeScript/Javascript, Markdown, Python, Ruby и HTML.
Чтобы еще больше сократить количество ошибок, мы добавили дополнительный этап постобработки с использованием комбинации инструментов статического анализа — проверка синтаксиса с помощью парсера кода, дедупликация и фильтрация на основе регулярных выражений для распространенных нежелательных поведений, выявленных на основе отзывов клиентов.
После всей фильтрации у нас остался высоконадежный обучающий набор из примерно 145 000 точек данных.
Обучение с использованием LoRA
Для небольших моделей мы постоянно обнаруживали, что качество данных является самым важным ингредиентом. С чистой, ин-дистрибутивной выборкой обучение сводится к специализации высококачественной базовой модели для задачи слияния без катастрофического забывания общего кодирования.
Мы обучали наши модели применения с использованием Supervised Fine-Tuning (SFT) поверх открытых кодовых моделей в диапазоне от 3 до 8 миллиардов параметров. Это дало нам правильный баланс между выразительностью, скоростью инференса и эффективностью затрат.
Мы построили собственную обучающую трубку на основе библиотеки HuggingFace transformers, но вы также можете просто использовать готовые библиотеки, такие как axolotl или unsloth.
Поскольку постобучение обычно использует гораздо меньшие наборы данных, чем предобучение, обновление каждого параметра модели является неэффективным. Вместо того, чтобы переобучать все миллиарды весов, мы используем Low-rank adaptation (LoRA) — легкий метод тонкой настройки, который добавляет небольшое количество обучаемых «адаптерных» матриц поверх замороженной базовой модели.
Это позволяет нам специализировать модель для задач слияния, не стирая ее кодовую интуицию. Базовая модель остается нетронутой, а адаптеры учатся алгоритму слияния, который нас интересует.
Мы провели серию сеточных поисков для настройки размера адаптера (ранга), коэффициента масштабирования (альфа) и скорости обучения и остановились на следующей конфигурации, которая дала лучший результат по потере оценки и скорости сходимости.
Ранг LoRA
Альфа LoRA
Скорость обучения
Оптимизатор
128
5e-5
AdamW
Интересно, что мы независимо подтвердили оптимальные гиперпараметры для LoRA, опубликованные в недавнем блоге Thinking Machines blog post.
Использование LoRA позволило нам обучить Relace Apply 3 на всех ~145 000 точках данных с использованием одной видеокарты Nvidia H200 на Modal с длиной контекста до 64 000 токенов.
Использование Modal позволило нам запускать параллельные обучающие процессы на видеокартах H200, не сталкиваясь с трудностями работы со сложными облачными провайдерами. Хотя обучение небольших моделей позволяет вам обойтись пакетом размером 1 на одной видеокарте, запуск большого гиперпараметрического поиска обычно требует много ручной настройки. С Modal мы запускали наши Python-скрипты и позволяли ему обрабатывать масштабирование за нас — не нужно каждый раз ssh-ать к новым экземплярам видеокарт.
После обучения в BF16 мы конвертируем веса модели в FP8 с использованием библиотеки llm-compressor от vLLM. Этот этап конвертации имеет решающее значение — используя FP8-ядра на новых видеокартах Nvidia, мы достигаем значительного скачка производительности без потери точности.
Чтобы подтвердить, что процесс квантизации был эффективно безубыточным, мы оценили полученную модель на основе наших 500 удержанных примеров истинного значения, чтобы подтвердить, что результаты были одинаковыми.
10 000 ток/с с спекулятивным декодированием
После обучения модели наша цель заключалась в том, чтобы она казалась меньше медленной языковой моделью и больше закрытой формой алгоритма для слияния кода. Чтобы достичь такого пользовательского опыта, нам нужно было максимально увеличить скорость инференса — что означало перейти к спекулятивному декодированию.
LLM обычно генерируют токены последовательно, где каждый новый токен зависит от тех, которые пришли раньше. Эта зависимость делает инференс по своей природе медленным, поскольку каждый шаг требует полного прямого прохода через модель.
Спекулятивное декодирование использует тот факт, что прямые проходы сильно ограничены памятью. Т.е. перемещение весов LLM в sRAM видеокарты занимает гораздо больше времени, чем фактическое выполнение умножения матриц с помощью тензорных ядер. Предсказывая последовательность из k токенов, мы можем обрабатывать много токенов параллельно (как при префилле) и переходить к вычислительно-ограниченному режиму.
После прямого прохода модель проверяет, какие из предсказанных токенов совпадают с «истинными» следующими токенами, и сохраняет те, которые являются правильными. Чем лучше «предсказание», тем больше токенов вы принимаете и тем больше ускоряете модель.
Для объединения кода большие разделы initial_code и diff почти идентичны тому, что появляется в merged_code. Мы можем использовать это сильное предварительное условие, чтобы получить длинные, высококачественные предположения о том, какие токены модель должна вывести, и добиться огромного ускорения.
Однако скорость и точность тесно связаны при спекулятивном декодировании. Каждый галлюцинированный токен прерывает предполагаемую последовательность, сбрасывая цепочку проверки и теряя вычислительные ресурсы.
Обучая модель на тщательно очищенном наборе данных, мы минимизировали эти сбои и смогли довести производительность Relace Apply 3 до 10k ток/с:
Результаты
Мы протестировали Relace Apply на двух наборах данных: (1) наш вручную проверенный эталонный набор из 500 примеров и (2) второй набор патологических слияний, собранных из отзывов клиентов.
В обоих случаях Relace Apply 3 демонстрирует лучшую точность слияния. Были достигнуты существенные улучшения по сравнению с предыдущим поколением моделей Apply благодаря целевой настройке набора данных на основе отзывов клиентов.
Предыдущие поколения быстрых моделей Apply испытывали трудности, когда фрагменты редактирования содержали несколько форматов diff в одном (например, сочетание формата \\ ... существующий код ... с редакциями UDiff). Мы включили подмножества редакций UDiff и String Replace в обучающие данные для Relace Apply 3, что позволило ему действовать как универсальный слиянец.
Relace Apply 3 также вводит нативную поддержку контекста длиной 256k, что позволяет ему обрабатывать очень большие файлы без снижения производительности. В сочетании с пропускной способностью 10k ток/с это делает Relace Apply 3 самой быстрой, точной и длинноконтекстной моделью на рынке.
Fast Apply год спустя
Когда год назад мы выпустили нашу первую модель Fast Apply, LLM были печально известны тем, что плохо выводили валидные форматы diff. Детерминистические подходы, такие как Search-and-Replace или UDiff, были хрупкими, специфичными для модели и требовали обширной инженерии промптов.
Точность форматирования diff стала таким узким местом, что полиглотный рейтинг Aider отслеживал ее как отдельный метрический показатель — одна колонка для точности, другая для производительности.
Fast Apply изменил это. Это была первая модель, которая сделала структурированные редактирования кода надежными, и наши клиенты сразу почувствовали разницу.
Сегодня передовые модели стали гораздо лучше форматировать diff благодаря интенсивному усиленному обучению на инструментах редактирования строк, но они все еще не идеальны. Компании, которые используют исключительно детерминистические стратегии, такие как Cline, нуждаются в дополнительном алгоритме слияния, чтобы еще больше повысить точность редактирования diff.
Лучшие модели теперь достигают примерно 96% успеха редактирования, но модели без такого рода настройки все еще терпят неудачу около 10% времени.
Таким образом, хотя мы ожидаем, что со временем модели apply могут быть вытеснены по мере улучшения точности diff, лежащая в их основе философия остается центральной для новых проектов.
Fast Apply доказал, что небольшие, специализированные модели могут давать результаты SoTA, когда обучаются на высококачественных, специфических для задачи наборах данных. Методы, которые мы разработали, теперь направляют нашу более широкую работу по ускорению codegen с помощью небольших агентных моделей для утилитарных задач, таких как поиск, разрешение конфликтов слияния и рефакторинг.
Следите за новыми релизами в ближайшее время!
Мы нанимаем
Если вы дошли до этого момента, скорее всего, вы нашли это интересным!
Мы нанимаем прагматичных исследователей (физика/математики/компьютерные науки/ML) и исключительных инженеров, чтобы создавать такие модели, на которые полагаются реальные продуктовые команды. Посетите нашу страницу вакансий и присоединяйтесь к нам!





