Всем привет! Меня зовут Анастасия Рысьмятова, я руковожу юнитом LLM в Авито.
В этой статье я расскажу, как мы с командой создали и адаптировали нашу большую языковую модель A-vibe: зачем решили развивать собственную LLM, как построили токенизатор, собрали датасеты, провели SFT и RL и что получили в итоге. Поделюсь основными экспериментами и покажу наши результаты.

Сегодня мы выпустили в опенсорс свое семейство генеративных моделей – A-Vibe и A-Vision, статья приурочена к этому событию.

Оглавление

  1. Кто мы и чем занимаемся

  2. Как мы обучали модель

  3. Метрики качества Avibe

  4. Бенчмарки скорости Avibe

Кто мы и чем занимаемся

Сейчас юнит LLM — команда из 25 человек. Мы в юните занимаемся тем, что делаем core-модель для авито, а потом адаптируем ее под разные бизнес-кейсы.

 За 2 года мы:

  • написали более 60 продуктовых сервисов;

  • запустили более 50 АБ-тестов;

  • внедрили и масштабировали свыше 12 продуктов в разных категориях.

Мы создаём решения, которые помогают 72+ млн уникальных пользователей Авито: упрощают работу и автоматизируют рутинные действия по размещению контента, коммуникации, поиску нужного на платформе, где размещается более 220 млн активных объявлений. А также помогают 12 тысячам сотрудникам компании: снижают нагрузку и ускоряют внутренние процессы.

Как мы обучали модель Avibe

Создавая юнит LLM, мы видели много возможностей для улучшения опыта пользователей Авито и упрощения работы наших сотрудников с помощью больших языковых моделей. Мы рассматривали несколько сценариев использования LLM внутри компании:

  1. Проприетарные модели по API — отказались из-за вопросов безопасности, ограниченной гибкости, а ещё мы хотели развивать экспертизу внутри компании.

  2. Опенсорсные модели — они показывают хорошие метрики, но на русском языке результаты хуже, чем на английском, и токенизаторы часто не оптимизированы под русскоязычные данные.

  3. Обучение своего LLM-претрейна — это дорого и долго, поэтому хотелось начать с чего-то более простого и менее рискованного.

Мы хотели развивать собственную инфраструктуру и компетенции внутри компании, поэтому выбрали четвертый вариант, который позволил нам получить модель с лучшими метриками по качестве и скорости на задачах Авито: 

Адаптация опенсорс моделей – взять лучшую по нашим бенчмаркам опенсорсную модель, сделать для неё собственный токенизатор, подменить токенизатор у опенсорсной модели, а дальше провести SFT и сделать RL.

Адаптация токенизатора

Токенизаторы открытых моделей (далее – OS-модели) не очень хорошо адаптированы под русский язык. В этом можно убедиться, если взять большой корпус на русском языке, протокенизировать его с помощью токенизатора OS-модели и посчитать среднюю длину токенов в символах. Затем обучить собственный токенизатор преимущественно на русском языке – и проделать тоже самое. Ниже приведена таблица, в которой мы сравниваем плотность токенизации для токенизатора Qwen-2.5 и токенизаторов, которые обучили сами: 

Токенизатор

Токенов с кириллицей

Токенов с латиницей

Других токенов

chars/tokens

qwen 2.5

3 996

187 769

111 566

3.24

tokenizer_ver5_32k

18 515

12 207

1 278

3.65

tokenizer_ver6_60k

36 493

21 940

1 567

3.95

ver5, ver6 – версии датасетов, на которых был обучен токенизатор;

32k, 60k – размер словаря токенизатора.

Видно, что обучая модель с собственным токенизатором, мы можем сократить число параметров модели. Это достигается за счёт уменьшения размера словаря токенизатора. Кроме того, повышается плотность токенизации: один и тот же текст будет занимать меньшее число токенов как во входном контексте, так и при генерации.

Существует два основных подхода к обучению модели с собственным токенизатором:

  1. Обучение модели с нуля с этапа pretrain’а. Это требует работы по подготовке обучающего датасета большого размера, соответствующих размеров вычислительного кластера. Веса модели при этом инициализируются случайным образом.

  2. Адаптация токенизатора у предобученной модели. Как это, например, предлагается сделать в статье.

Выбирая первый – дорогой по трудозатратам и затратам вычислительных ресурсов – путь, мы бы тем самым отказались от использования достижений LLM-сообщества, пытаясь повторить и улучшить их работу. Соответственно, мы выбрали для себя путь адаптации доступных OS-моделей. 

В области адаптации у нас уже был отлаженный пайплайн, про результаты которого я уже рассказывала:

Пайплайн состоит из 4 основных шагов:

  1. Обучаем свой токенизатор с требуемыми характеристиками по размеру словаря и плотности токенизации на разных доменах.

  2. Подменяем токенизатор у лучших OS-моделей, используя двухэтапный алгоритм: сначала замораживаем все слои сети, кроме слоя эмбеддингов (такой этап мы называем freeze), а затем размораживаем все параметры сети и учим на тех же данных (этап unfreeze).

  3. Делаем этап инструктивного обучения SFT.

  4. Делаем DPO.

Но у данного способа подмены был недостаток. Пересечение токенов в токенизаторах OS  моделей и наших моделей небольшое (обычно около 2 тыс. токенов), а это значит, что инициализированные эмбединги токенов плохо подходили к модели. Возникла идея: а что если попробовать объединить два токенизатора – OS модели и наш – таким образом, чтобы в новом токенизаторе остались англоязычные токены от токенизатора открытой модели, а кириллические токены от нашего токенизатора. В момент когда решили провести такой эксперимент мы работали с моделью Qwen3-8B-Base.

Поэтому в дальнейших экспериментах участвовала эта модель и её токенизатор.

Тут еще больше контента

Как мы объединили два токенизатора

Первым этапом адаптации необходимо было отфильтровать английские токены из токенизатора qwen3. Делается это в 2 этапа – наивно оставляем кандидатов, состоящих из латинских символов (этого недостаточно, так как многие алфавиты используют латиницу в качестве системы знаков). После чего выделяем токены, специфичные только для английского языка через подсчет частотности на чистом английском корпусе данных, в качестве которого был выбран dclm. После прогона токенизатора на них частотные английские токены «всплывут» наверх и останется лишь взять 99 перцентиль. Нам также  важна высокая эффективность токенизатора на кодовых задачах – дополнительно мы оставили кодовые токены, обсчет которых был проведен на внутренней кодовой базе Авито. По итогу 80к токенов были классифицированы как английские и кодовые. 

Аналогичный подход был применен к нашему ver6_60k токенизатору, однако в качестве корпуса для подсчета статистик был использован fineweb_ru. Используя их и покрытие 99 перцентилем мы получили токенизатор с размером словаря 36к, показывающий высокую плотность на русском языке.

Наконец, необходимо взять лучшее из двух миров и объединить эти токенизаторы: поскольку ver6 был специально обучен в qwen-совместимом сетапе (такой же pre-tokenizer и normalizer) и описанный выше подход обеспечивает непересекающиеся словари у токенизаторов, мердж тривиален. Просто подкладываем новые токены в vocab и добавляем merges в результирующий токенизатор. 

Таким образом, мы получаем токенизатор прекрасно показывающий себя в двух целевых доменах – английском и русском языках.

Ниже на графиках приведено сравнение токенизаторов:

  • qwen3_original – оригинальный токенизатор модели;

  • Ruadapt – токенизатор модели ruadapt (ссылка на модель);

  • ver6_original – наш обученный токенизатор ver6_60k;

  • qwen3_tokenizer_en_pruned – обрезанный qwen3 с размером словаря 80k;

  • ver6_tokenizer_ru_pruned – обрезанный ver6_60k с размером словаря 36k.

При адаптации объединенного токенизатора инициализировать эмбеддинги для токенов из оригинального токенизатора qwen3 мы можем эмбеддингами из qwen3. На freeze этапе обучения мы замораживаем эти эмбеддинги, так как считаем, что они уже адаптированы под веса модели, и учим только оставшиеся кириллические эмбеддинги из токенизатора ver6.

В итоге чтобы сделать адаптацию токенизатора нужно выполнить три шага:

  1. Объединить два токенизатора (добавить в токенизатор OS модели новые русские токены).

  2. Инициализировать эмбеддинги для новых токенов (добавить новые векторы в эмбеддинговый слой модели).

  3. Запустить обучение на большом корпусе текста, и новые эмбеддинги проучить.

Раньше на третьем шаге мы проливали градиенты не только на новые эмбеддинги, но и на старые, однако тем самым мы портили уже выученные представления. Чтобы этого избежать, мы оставляем старые эмбеддинги в текущем состоянии, замораживаем их.

Оказалось, что сделать частичную заморозку слоя эмбеддингов не так тривиально, поэтому оставлю здесь наш способ.

Скрытый текст

Почему requires_grad=False не решает проблему?

requires_grad включается/выключается на весь параметр. У эмбеддингов это одна большая матрица, и torch не позволяет заморозить веса эмбеддингов лишь частично. А нам нужно «заморозить» несколько строк, а не весь эмбеддинговый слой.  Значит, ищем другой способ.

hook на градиент

В pytorch есть такая штука как hook — это коллбэк, который PyTorch автоматически вызывает в определённый момент вычислений (обычно при backward или forward). С его помощью можно перехватить и изменить данные (например, градиент) прямо «на лету».

Мы решили не менять сам параметр, а обнулять градиент у нужных строк эмбеддинга на каждом backward(). Для этого мы написали функцию, которая перехватывает градиент и может его занулить, прежде чем оптимизатор сделает шаг.

# Псевдо-код: делаем hook, который зануляет строки градиента у заданных token_ids
def make_row_zero_hook(token_ids):
    def hook(grad):
        idx = to_device(token_ids, grad.device)
        return zero_rows(grad, idx)  # зануляем строки по индексам
    return hook

  # Теперь чтобы он вызывался при backward-е на весах эмбеддингов, вызываем register_hook
def install_freeze_hooks(model, token_ids, include_lm_head=True):
    hook = make_row_zero_hook(unique_sorted(token_ids))
    get_input_embeddings(model).weight.register_hook(hook)

Это решает нашу задачу, но есть нюанс

AdamW делает «усадку» веса отдельно от градиента:

w ← w − lr * step − lr * weight_decay * w .

Даже если градиент ноль, член − lr * weight_decay * w всё равно двигает веса. Значит, для наших зафиксированных строк нужно отключить weight decay на соответствующих параметрах.

# Псевдо-код: говорим Trainer’у не применять decay к эмбеддингу
class NoDecayEmbeddingsTrainer(Trainer):
    def get_decay_parameter_names(self, model):
        decay = base_decay_list(model)
        decay -= all_embedding_weight_names(model)
        return sorted(decay)

Как мы это используем на практике

  1. Собираем список «старых» токенов (их id — те строки, которые хотим не трогать).

  2. Ставим hook на входные эмбеддинги и на lm_head.

  3. Обучаем как обычно, но в Trainer выключаем weight_decay для эмбеддингов.

# Псевдо-код
old_token_ids = collect_old_token_ids(tokenizer, old_tokenizer)
install_freeze_hooks(model, old_token_ids, include_lm_head=True)
trainer = NoDecayEmbeddingsTrainer(model=model, args=training_args, ...)
trainer.train()

Итого:

  • «старые» токены не размываются, качество адаптации растёт;

  • обучение сосредоточено на новых токенах;

  • никаких форков оптимизатора: только hook для градиента и отключение weight decay на нужных параметрах. Простое решение, которое стабильно работает.

Данные для адаптации токенизатора

Для адаптации токенизатора мы собрали около 700 млрд токенов, но в итоговом запуске и экспериментах использовали семпл из 150 млрд. токенов. Это сильно сокращало время адаптации, при этом значимо не просаживало метрики. 

Распределение данных по языкам: 

  • 31% — русский язык;

  • 31% — английский язык;

  • код на разных языках программирования;

  • тексты на других языках для сохранения многоязычности.

Мы следили за балансом доменов (код, гуманитарные, естественные и точные науки) и подмешивали SFT-датасет прямо в обучение токенизатора.

Все собранные нами данные для подмены токенизатора проходят через наш пайплайн предобработки в котором реализованы методы фильтрации и дедупликации данных. 

Жми сюда!

SFT этап

На этом этапе мы учили модель отвечать на вопросы, вести диалог и понимать system prompt. Датасет (>800 тыс. примеров) включал:

  • открытые наборы данных;

  • синтетические примеры;

  • логи общения сотрудников Авито с LLM;

  • школьные и олимпиадные задачи;

  • данные из реальных бизнес-кейсов.

Качество контролировалось через LLM as a judge.

В SFT датасете использовалось много данных c FC. Хочется отдельно рассказать про интересный подход генерации синтетических данных. Данный подход был предложен в статье APIGen-MT: Agentic Pipeline for Multi-Turn Data Generation via Simulated Agent-Human Interplay. Вот что сделали мы:

  1. Спарсили описания ручек сервисов Авито.

  2. При помощи LLM перевели описания ручек в openai формат описания тулов.

  3. Чтобы провалидировать выход прошлого шага на соответствие openai формату, для каждого описания тула делаем запрос в openai api с описанием тула. Если запрос не вернулся с ошибкой – описание валидное.

    Дальше хотим на основе описания тулов генерировать синтетические диалоги. Чтобы разнообразить и улучшить качество данных, мы используем подход из статьи Scaling Synthetic Data Creation with 1,000,000,000 Personas. Использовали их датасет с описаниями персон. На этом этапе наши вводные: датасет с персонами и датасет с описания тулов.

  4. Семплируем несколько тулов и персону. LLM создает описание среды и политики агента действующего в этой среде:

    • домен – краткое описание среды;

    • роли – какие роли участвуют в диалоге;

    • описания возможностей тулов;

    • ограничения использования тулов;

    • описание данных;

    • правила поведения агента с ноткой персонализации (тут как раз и нужна засемпленая персона).

  5. По выходу предыдущего шага + полного описания тулов + персоны LLM создает конфигурация будущего диалога:

    a) системный промпт для агента который будет использовать тулы (опять же с персонализацией);

    b) цель юзера, который будет общаться с агентом;

    c) план того, как должен идти диалог.

  6. LLM судья оценивает получившиеся конфигурации и дает фидбек по улучшению. Шаг 5 принимает фидбек и улучшает конфигурацию диалога. Судья принимает уже новую конфигурацию и опять ее оценивает. Такой цикл обратной связи и итеративного улучшения конфигурации диалога повторяется до 5 раз. Если после 5 раз LLM судья все еще оценивает конфигурацию как некачественную, она выбрасывается.

    Наконец, можно перейти к самой генерации диалогов. Чтобы данные были более качественные, модель не галлюцинировала, а полнота и сложность диалогов были максимально приближены к реальным, каждая реплика будет генерироваться отдельным запросом к LLM (вместо подхода, когда одним разом модель бы сгенерировала весь диалог). 

  7. В генерации диалога у нас будут участвовать:

    • модель, которая генерирует реплики юзера, ей мы в промт подаем персону юзера и цель юзера (шаг 5b);

    • модель, которая генерирует реплики ассистента;

    • модель, которая эмулирует вызовы API. Она принимает описание тула, запрос и возвращает результат запроса;

    • модель, которая решает, когда завершить генерацию диалога.

  8. В конце уже сгенерированные диалоги оцениваются LLM судьей. Он оценивает корректность использования тулов, а так же смотрит, соответствует ли диалог плану из пункта 5с.

GRPO этап

На GRPO этапе мы хотели сделать модель умнее. Мы сконцентрировались на улучшении результатов модели в function calling, математических и кодовых задачах.

Было выявлено, что обучение GRPO на одном из доменов не роняет значимо метрики в других доменах, поэтому запуски GRPO на задачах из разного домена проводили последовательно. Далее приведено краткое описание экспериментов, которые мы провели.

Function Calling

На данном этапе использовали собственный синтетический датасет для FC, который был описан выше. Далее делали подготовку этих данных к GRPO: 

  1. Из multi-turn диалоги получали пары prefix -> ответ ассистента

  2. Также добавляли irrelevant примеры для того, чтобы модель лучше понимала, когда использовать вызов функции, а когда – нет.

В качестве reward использовали rule-based, который давал строго 1, если вызов функций полностью совпадал с golden, 0 при наличии любых несоответствий. В таком виде модель не переобучается на отдельные компоненты (по типу формата, выбор отдельных функций), а обучается полностью корректно выполнять задачу. 

Запускалось на 4x8xh100 GPU, 2.5к примеров, 1.6к итераций, gradient accumulation – 8, n rollouts – 32, temperature=0.7, lr=1e-6.

Главный бенчмарк, которым мы пользовались это BFCL, по итогам обучения получилось ощутимо увеличить скорость BFCL V3 увеличили с 49,68% до 59,55% BFCL V3 переведенный на русский увеличили с 40.22% до 49.25%, подробные результаты приведены в разделе Метрики качества.

Код

Мы собрали датасет из 120k алгоритмических задач на Python, каждая из которых содержит от 3 до 450 тестов. Для расчёта реворда мы прогоняли решения, сгенерированные моделью, через тесты: за каждый успешно пройденный тест модель получала положительную награду, а за проваленные тесты, таймауты и прочие ошибки назначался штраф.

Обучение проводилось с использованием GRPO: 5k итераций,  gradient accumulation – 8, распределённый запуск на 4x8xh100 GPU.

По итогам эксперимента значимого улучшения на бенчмарках зафиксировано не было (смотрели humaneval, mbpp, mera_code_ruhumaneval, mera_code_codecorrectness, mera_code_strucom).

Математика

Для обучения использовали данные математических задач 10k примеров  на английском и 10k переведенных на русский язык. В качестве reward взяли rool-based ревард который возвращает 1, если финальный ответ совпадал с golden answer и 0 иначе.

Генерацию кандидатов совершали через vllm в colocated режиме – это ускорило процесс обучения в несколько раз.

Запускалось на 8xh100 GPU, 4k итераций, gradient accumulation – 8, lr=5e-7, beta=1e-3, temperature=0.7 (при больших значениях обучение сильно нестабильно, при меньших значениях 0.3-0.7  особо нет разницы).

num_generations=64 (увеличение num_generations заметно улучшило качество)

Кроме подбора гиперпараметров на качество удалось повлиять с помощью балансировки сложности задач. GRPO обучение может поломаться, если в обучении будет много простых задач, которые LLM успешно решает, ведь иначе на таких семплах получается около нулевой advantage, а значит нечем будет обновлять политику. Чтобы исключить из обучения простые примеры, для каждой задачи задачи из обучающей выборки вычисляли pass rate из 8 генераций ответов. Далее исключили задачи с pass rate 8/8. 

В итоге получилось добиться буста с 0,6  до 0.69 на math500_ru.

DPO этап

На DPO этапе мы хотели сделать модель безопаснее, а также улучшить ее ответы, сделать их более структурированными и понятными для людей.

Данные

Данные для обучения мы использовали двух типов

  1. SAFE DATA – общие вопросы.

  2. UNSAFE DATA – небезопасные вопросы.

SAFE DATA

Собрали датасет из 120k примеров общих вопросов для повышения диалоговых способностей модели. Датасет представляет из себя набор простых задач, касающихся различных областей науки и вопросов, содержащих в себе несложные задания, например, «Что ты посоветуешь мне взять в поездку в Норвегию?».

UNSAFE DATA

Взяли открытый датасет с вопросам разного уровня опасности (minor, major, critical). Датасет был англоязычным, поэтому перевели его с помощью большой открытой модели. В итоге получилось около 40k небезопасных вопросов на русском языке. 

ПОДГОТОВКА ДАННЫХ

Для DPO-этапа требуется preference dataset, что значит, что необходимо для каждого вопроса разметить chosen и rejected ответы.

Подход к сбору датасета состоял из следующих шагов:

  • для каждого вопроса из датасета с помощью целевой модели (модели, которую планируем обучать с помощью DPO) собираем 8 генерацией с подобранным генерационным конфигом. Конфиг подбирался так, чтобы ответ модели были полными, разнообразными, но еще не выглядел, как галлюцинация;

  • затем с помощью большой открытой модели и подобранного промпта размечались из 8-ми генераций лучшая и худшая. Для каждого датасета промпт и критерии оценки были свои;

  • уравновешивали число данных с длинными и короткими ответами. Уравновешивание происходило по коэффициенту разницы длин между ответами.

Помимо такого формата сборки датасета, были эксперименты, где chosen являлся ответ большой открытой модели, а rejected – ответ целевой модели, но эти эксперименты не дали качественных результатов.

ОЦЕНКА МОДЕЛИ

Помимо оценки качества обучаемых моделей с помощью классических бенчмарков, мы завели специфичные доменные бенчмарки: safe и unsafe бенчмарк, работающий на подходе LLM as a judge.

Для каждого домена – диалогового и небезопасного – были заданы промпты с подробным описанием критериев оценки качества ответов моделей на выделенных тестовых вопросах. 

Бенчмарк хорошо отражал изменения в качестве ответов моделей и позволял отслеживать состояние их обучения, подбирая наилучший конфиг обучения. 

В результате DPO этапа нам удалось вырастить наши внутренние бенчмарки по безопасности и увеличить скор RU_ARENA с  77,7  до 79,2 (arena-hard-v0.1 с судьей gpt-4-1106-preview).

Метрики качества Avibe

Замеры метрик проводились с помощью библиотеки с жадными параметрами генерации. Модели Qwen3-8B и RuadaptQwen3-8B-Hybrid замерялись в режиме no_think. Методология расчетов конкретного бенчмарка может отличаться от замеров других разработчиков из-за особенностей данных и различных формулировок системного промпта/ количества few shot. Для наибольшей воспроизводимости вместе с моделью прикладываем часть данных для бенчмарков (gpqa diamond_ru, math500_ru, rudrop, ru_bfcl), а также PullRequest-ы в lm-evaluation-harness.

Общие знания о мире

Model

mmlu_ru

mmlu_en

gpqa

diamond_ru

gpqa

diamond_en

shlepa

baby mmlu

avibe_sft

0,721

0,751

0,364

0,374

0,484

0,766

avibe_grpo

0,718

0,750

0,343

0,354

0,489

0,765

avibe_dpo_final

0,718

0,752

0,343

0,318

0,486

0,766

Qwen3-8B

0,701

0,730

0,318

0,369

0,454

0,682

RuadaptQwen3-8B-Hybrid

0,698

0,729

0,399

0,268

0,444

0,456

baby mmlu объединяем question и choices и выбираем ответ с минимальной перплексией, используем вот такие данные

Внутренние бенчмарки по задачам Авито

Model

okk

relevance

i2p

avibe_sft

0,724

0,962

0,720

avibe_grpo

0,712

0,962

0,763

avibe_dpo_final

0,719

0,959

0,766

Qwen3-8B

0,717

0,908

0,668

RuadaptQwen3-8B-Hybrid

0,672

0,575

0,677

okk нужно ответить цифрой от 0 до 6 которая соответствует ответу на вопрос: Есть ли нарушения из перечисленных в тексте? Данные описания объявлений

relevance определить релевантны ли между парой запрос - выдача поиска

i2p передается текст описания item-a (товара) и нужно выбрать нужный параметр, релевантный к товару  

Математические и кодовые способности моделей (STEM)

Model

gsm8k_en

math_500

ru

math_500 en

ruhumaneval (pass@10)

avito_stash* (ppl)

avibe_sft

0,901

0,602

0,72

0,445

2,211

avibe_grpo

0,920

0,692

0,714

0,390

2,335

avibe_dpo_final

0,910

0,686

0,714

0,378

2,385

Qwen3-8B

0,927

0,546

0,736

0,420

3,811

RuadaptQwen3-8B-Hybrid

0,902

0,690

0,744

0,418

2,879

*avito_stash - бенчмарк по внутреннему stash, маскировали часть кода и замеряли ppl

DOoM

Бенчмарк, оценивающий способности моделей в математике и физике

Оценка проведена без ризонинга 

Model

total

russian math

russian physics

avibe_dpo_final

0,280

0,367

0,194

RuadaptQwen3-8B-Hybrid

0,253

0,322

0,184

Qwen3-8B

0,240

0,286

0,194

Работа с языком

 

Model

ru 

facts

rublimp

ru

drop 

avibe_sft

0,727

0,932

0,407

avibe_grpo

0,709

0,930

0,426

avibe_dpo_final

0,718

0,930

0,394

Qwen3-8B

0,724

0,916

0,318

RuadaptQwen3-8B-Hybrid

0,737

0,853

0,232

Работа с функциями

BFCL V3 

 

Model

BFCL V3

en

avibe_sft

49,68%

avibe_grpo

59,55%

avibe_dpo_final

58,63%

Qwen3-8B

60,2%

RuadaptQwen3-8B-Hybrid (reasoning)

54,91%

BFCL V3, переведенный на русский

group

Qwen_Qwen3-8B

ruadapt

reasoning

avibe

sft

avibe

grpo

avibe

dpo

Overall Acc

50.72%

46.35%

40.22%

49.25%

49.00%

Non-Live AST Acc

74.58%

74.04%

67.62%

73.17%

73.19%

Non-Live Simple AST

69.83%

70.17%

70.50%

72.67%

73.75%

Non-Live Multiple AST

81.00%

81.50%

74.00%

82.50%

82.00%

Non-Live Parallel AST

79.50%

81.00%

68.50%

75.00%

75.00%

Non-Live Parallel Multiple AST

68.00%

63.50%

57.50%

62.50%

62.00%

Live Acc

65.62%

62.99%

53.84%

68.46%

68.15%

Live Simple AST

76.36%

73.26%

64.73%

68.22%

67.83%

Live Multiple AST

61.16%

58.40%

60.97%

60.68%

58.78%

Live Parallel AST

75.00%

56.25%

87.50%

87.50%

87.50%

Live Parallel Multiple AST

70.83%

54.17%

66.67%

66.67%

66.67%

Multi Turn Acc

10.62%

1.50%

4.88%

3.50%

3.12%

Multi Turn Base

14.00%

1.50%

5.50%

4.50%

4.00%

Multi Turn Miss Func

10.50%

2.00%

8.00%

2.50%

2.50%

Multi Turn Miss Param

7.50%

1.50%

2.50%

5.00%

3.50%

Multi Turn Long Context

10.50%

1.00%

3.50%

2.00%

2.50%

Relevance Detection

94.44%

94.44%

94.44%

94.44%

88.89%

Irrelevance Detection

74.07%

70.93%

39.76%

81.62%

82.26%

MERA

Модель

MERA 

text

МЕRА CODE

общий тотал

MERA CODE приватный тотал

MERA Industrial

медицина

MERA Industrial

с/х

avibe_sft

0,616

0,304

0,367

0,609

0,513

avibe_grpo

0,624

0,314

0,351

0,612

0,521

avibe_dpo_final

0,618

0,312

0,367

0,565

0,483

Qwen3-8B

0,510

0,280

0,336

0,517

0,3

RuadaptQwen3-8B-Hybrid

0,498

0,322

0,380

0,26

0,437

RU_ARENA

arena-hard-v0.1 с судьей gpt-4-1106-preview (консистентно с результатами на гитхабе )

model

score

score with length control

avg #Tokens

gpt-4-1106-preview

90,9

81,4

541

gpt-4o-mini

83,9

75,4

448

T-Tech-T-pro-it-1.0

83,8

73,8

502

gigachat_max_26.20_uncen

82,7

72,5

514

gigachat_max_with_censor

80

68,9

515

avibe_dpo_final

79,9

65,5

831

vikhr-nemo-12b-instruct-r-21-09-24

79,8

65,5

627

avibe_sft

77,7

63,0

755

avibe_grpo

77,6

63,5

792

Qwen/Qwen3-8B

76,7

63,0

894

gemma-2-9b-it-sppo-iter3

73,6

59,0

509

RefalMachine/RuadaptQwen3-8B-Hybrid 

72,0

56,2

821

T-Tech-T-lite-it-1.0

71

59,3

544

RU_ARENA

arena-hard-v0.1 с судьей gpt-4o-mini (консистентно с результатами на hf)

model

score

score with length control

avg #Tokens

avibe_grpo

93,3

86,5

792

avibe_dpo_final

89,7

81,5

831

Qwen/Qwen3-8B

89,2

81,7

894

RefalMachine/RuadaptQwen3-8B-Hybrid 

88,8

80,8

821

Бенчмарки скорости Avibe

Замеры по скорости проводились на примерах из нашего SFT датасета, который состоит преимущественно из русскоязычных текстов. В новом токенизаторе плотность токенизации выше, поэтому число токенов в контексте и при генерации стало меньше для одинаковых примеров. Кроме того, размер самой модели сократился до 7.9B при 8.2B у Qwen3-8B. За счет этого одинаковые русскоязычные примеры адаптированной моделью обрабатываются быстрее в среднем на 15-25% в сравнении с исходной Qwen3-8B (результат зависит от задачи и данных, на некоторых задачах мы видим ускорение до двух раз).

Для ускорения генерации можно использовать спекулятивный декодинг. Спекулятивный декодинг наиболее эффективен на небольших батчах – на единичных запросах ускорение достигает 2.7x. В реальности модель обрабатывает множество запросов параллельно. Для оценки прироста по скорости в условиях, приближенных к реальным, провели бенчмарк с нагрузкой 12 rps (максимальная нагрузка, которую держит Qwen3-8B без накопления очереди на данных SFT). В таких условиях спекулятивный декодинг снижает время ответа до 1.9x. 

Спекулятивный декодинг

В качестве алгоритма спекулятивного декодинга мы использовали недавно вышедший EAGLE-3. Основными отличиями EAGLE-3 от EAGLE-2 являются:

  •  Отказ от предсказания признаков — прямая генерация токенов

Вместо того чтобы draft-модель предсказывала будущие hidden states (которые используется в последнем слое таргет-модели для генерации токенов), она прямо генерирует токены. Это упрощает задачу и устраняет лишние предсказания  признаков.

  • Использование признаков с разных слоев

Вместо использования только признаков из одного (предпоследнего) слоя, EAGLE-3 объединяет признаки из разных слоёв (начальных, средних, последних), чтобы дать более богатую информацию draft-модели. 

  • Ещё большая оптимизация инференса в Vllm и Sglang

В экспериментах, описанных в оригинальной статье, EAGLE-3 достигал ускорения до ~6.5х, при этом показывая прирост ~1.4х по сравнению с EAGLE-2

Для обучения драфт-модели мы использовали подмножество наших SFT-данных — около 100 млн токенов, преимущественно на русском языке, с сохранением доменной и стилевой близости к основной модели.

В итоге мы смогли получить драфт-модель, у которой acceptance length (среднее количество верно сгенерированных токенов драфт-моделью) составляло 2.4 токена, в то время, как Eagle-2 давал в среднем 2.1 токена. 

Для инференса спекулятивного декодинга мы использовали имплементации, реализованные в популярных движках vllm и sglang. Оба фреймворка активно внедряют поддержку новых инструментов, в том числе eagle. На момент написания этой статьи, eagle-3 быстрее работал на sglang. 

Результаты наших внутренних замеров показали, что использование eagle-3 через sglang в различных сетапах (разное количество предсказываемых токенов, разные top-k и т.д) на разовых запросах дают ускорение до 2.7х, которое постепенно снижается и становится нулевым при batch_size = 64). Это расходится с оригинальной статьей, заявляющей ускорение до 6.5х раз. Скорее всего такие ускорения достигаются на очень узких доменах с сильно переобученными драфт-моделями, у которых очень высокие acceptance length.

Кликни здесь и узнаешь

Эта масштабная работа была сделана благодаря команде, поэтому хочется упомянуть и поблагодарить каждого:

  • спасибо Айдару Хусаинову и Серёже Кляхандлеру за помощь в организации работы над проектами;

  • Коле Хохлову за оптимизацию нашего обучения на кластере;

  • Олесе Моисеенко, Альберту Акопяну, Даниилу Карпову и Артему Городецкому за проведение экспериментов на RL этапе;

  • Косте Веснину, Роме Прилепскому, Тимофею Мазуренко и Диме Сиракову за проведение экспериментов на  SFT этапе;

  • Марку Каширскому за эксперименты с токенизатором;

  • Ярославу Хрипкову за организацию пайплайна сбора данных для подмены токенизатора;

  • Вике Берестовой за подсчет всех метрик и валидацию наших чекпоинтов;

  • Андрею Токареву за оценку скорости наших моделей;

  • Юлии Мазепе за эксперименты с подменой токенизатора;

  • Камилю Измайлову и Никите Ятченко за эксперименты по ускорению нашего инференса.

А если хотите вместе с нами помогать людям и бизнесу через технологии — присоединяйтесь к командам. Свежие вакансии есть на нашем карьерном сайте.

Комментарии (2)


  1. Keeper22
    27.10.2025 14:31

    Микросервисы вам эта же модель и пишет?


  1. Spaceoddity
    27.10.2025 14:31

    А зачем вам всё это? Вы бы лучше интерфейсы свои допилили, а не карго-культ под OpenAi устраивали...