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

Оглавление
Кто мы и чем занимаемся
Сейчас юнит LLM — команда из 25 человек. Мы в юните занимаемся тем, что делаем core-модель для авито, а потом адаптируем ее под разные бизнес-кейсы.
За 2 года мы:
написали более 60 продуктовых сервисов;
запустили более 50 АБ-тестов;
внедрили и масштабировали свыше 12 продуктов в разных категориях.
Мы создаём решения, которые помогают 72+ млн уникальных пользователей Авито: упрощают работу и автоматизируют рутинные действия по размещению контента, коммуникации, поиску нужного на платформе, где размещается более 220 млн активных объявлений. А также помогают 12 тысячам сотрудникам компании: снижают нагрузку и ускоряют внутренние процессы.
Как мы обучали модель Avibe
Создавая юнит LLM, мы видели много возможностей для улучшения опыта пользователей Авито и упрощения работы наших сотрудников с помощью больших языковых моделей. Мы рассматривали несколько сценариев использования LLM внутри компании:
Проприетарные модели по API — отказались из-за вопросов безопасности, ограниченной гибкости, а ещё мы хотели развивать экспертизу внутри компании.
Опенсорсные модели — они показывают хорошие метрики, но на русском языке результаты хуже, чем на английском, и токенизаторы часто не оптимизированы под русскоязычные данные.
Обучение своего 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 – размер словаря токенизатора.
Видно, что обучая модель с собственным токенизатором, мы можем сократить число параметров модели. Это достигается за счёт уменьшения размера словаря токенизатора. Кроме того, повышается плотность токенизации: один и тот же текст будет занимать меньшее число токенов как во входном контексте, так и при генерации.
Существует два основных подхода к обучению модели с собственным токенизатором:
Обучение модели с нуля с этапа pretrain’а. Это требует работы по подготовке обучающего датасета большого размера, соответствующих размеров вычислительного кластера. Веса модели при этом инициализируются случайным образом.
Адаптация токенизатора у предобученной модели. Как это, например, предлагается сделать в статье.
Выбирая первый – дорогой по трудозатратам и затратам вычислительных ресурсов – путь, мы бы тем самым отказались от использования достижений LLM-сообщества, пытаясь повторить и улучшить их работу. Соответственно, мы выбрали для себя путь адаптации доступных OS-моделей.
В области адаптации у нас уже был отлаженный пайплайн, про результаты которого я уже рассказывала:
Пайплайн состоит из 4 основных шагов:
Обучаем свой токенизатор с требуемыми характеристиками по размеру словаря и плотности токенизации на разных доменах.
Подменяем токенизатор у лучших OS-моделей, используя двухэтапный алгоритм: сначала замораживаем все слои сети, кроме слоя эмбеддингов (такой этап мы называем freeze), а затем размораживаем все параметры сети и учим на тех же данных (этап unfreeze).
Делаем этап инструктивного обучения SFT.
Делаем 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.
В итоге чтобы сделать адаптацию токенизатора нужно выполнить три шага:
Объединить два токенизатора (добавить в токенизатор OS модели новые русские токены).
Инициализировать эмбеддинги для новых токенов (добавить новые векторы в эмбеддинговый слой модели).
Запустить обучение на большом корпусе текста, и новые эмбеддинги проучить.
Раньше на третьем шаге мы проливали градиенты не только на новые эмбеддинги, но и на старые, однако тем самым мы портили уже выученные представления. Чтобы этого избежать, мы оставляем старые эмбеддинги в текущем состоянии, замораживаем их.
Оказалось, что сделать частичную заморозку слоя эмбеддингов не так тривиально, поэтому оставлю здесь наш способ.
Скрытый текст
Почему 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)
Как мы это используем на практике
Собираем список «старых» токенов (их id — те строки, которые хотим не трогать).
Ставим hook на входные эмбеддинги и на
lm_head.Обучаем как обычно, но в 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. Вот что сделали мы:
Спарсили описания ручек сервисов Авито.
При помощи LLM перевели описания ручек в openai формат описания тулов.
-
Чтобы провалидировать выход прошлого шага на соответствие openai формату, для каждого описания тула делаем запрос в openai api с описанием тула. Если запрос не вернулся с ошибкой – описание валидное.
Дальше хотим на основе описания тулов генерировать синтетические диалоги. Чтобы разнообразить и улучшить качество данных, мы используем подход из статьи Scaling Synthetic Data Creation with 1,000,000,000 Personas. Использовали их датасет с описаниями персон. На этом этапе наши вводные: датасет с персонами и датасет с описания тулов.
-
Семплируем несколько тулов и персону. LLM создает описание среды и политики агента действующего в этой среде:
домен – краткое описание среды;
роли – какие роли участвуют в диалоге;
описания возможностей тулов;
ограничения использования тулов;
описание данных;
правила поведения агента с ноткой персонализации (тут как раз и нужна засемпленая персона).
-
По выходу предыдущего шага + полного описания тулов + персоны LLM создает конфигурация будущего диалога:
a) системный промпт для агента который будет использовать тулы (опять же с персонализацией);
b) цель юзера, который будет общаться с агентом;
c) план того, как должен идти диалог.
-
LLM судья оценивает получившиеся конфигурации и дает фидбек по улучшению. Шаг 5 принимает фидбек и улучшает конфигурацию диалога. Судья принимает уже новую конфигурацию и опять ее оценивает. Такой цикл обратной связи и итеративного улучшения конфигурации диалога повторяется до 5 раз. Если после 5 раз LLM судья все еще оценивает конфигурацию как некачественную, она выбрасывается.
Наконец, можно перейти к самой генерации диалогов. Чтобы данные были более качественные, модель не галлюцинировала, а полнота и сложность диалогов были максимально приближены к реальным, каждая реплика будет генерироваться отдельным запросом к LLM (вместо подхода, когда одним разом модель бы сгенерировала весь диалог).
-
В генерации диалога у нас будут участвовать:
модель, которая генерирует реплики юзера, ей мы в промт подаем персону юзера и цель юзера (шаг 5b);
модель, которая генерирует реплики ассистента;
модель, которая эмулирует вызовы API. Она принимает описание тула, запрос и возвращает результат запроса;
модель, которая решает, когда завершить генерацию диалога.
В конце уже сгенерированные диалоги оцениваются LLM судьей. Он оценивает корректность использования тулов, а так же смотрит, соответствует ли диалог плану из пункта 5с.
GRPO этап
На GRPO этапе мы хотели сделать модель умнее. Мы сконцентрировались на улучшении результатов модели в function calling, математических и кодовых задачах.
Было выявлено, что обучение GRPO на одном из доменов не роняет значимо метрики в других доменах, поэтому запуски GRPO на задачах из разного домена проводили последовательно. Далее приведено краткое описание экспериментов, которые мы провели.
Function Calling
На данном этапе использовали собственный синтетический датасет для FC, который был описан выше. Далее делали подготовку этих данных к GRPO:
Из multi-turn диалоги получали пары
prefix -> ответ ассистента.Также добавляли 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 этапе мы хотели сделать модель безопаснее, а также улучшить ее ответы, сделать их более структурированными и понятными для людей.
Данные
Данные для обучения мы использовали двух типов
SAFE DATA – общие вопросы.
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
Бенчмарк, оценивающий способности моделей в математике и физике
Оценка проведена без ризонинга
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 этапе;
Марку Каширскому за эксперименты с токенизатором;
Ярославу Хрипкову за организацию пайплайна сбора данных для подмены токенизатора;
Вике Берестовой за подсчет всех метрик и валидацию наших чекпоинтов;
Андрею Токареву за оценку скорости наших моделей;
Юлии Мазепе за эксперименты с подменой токенизатора;
Камилю Измайлову и Никите Ятченко за эксперименты по ускорению нашего инференса.
А если хотите вместе с нами помогать людям и бизнесу через технологии — присоединяйтесь к командам. Свежие вакансии есть на нашем карьерном сайте.
Комментарии (4)

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

PaveLuchkov
27.10.2025 14:31Собственно за всем текстом так и не понятно где модель используется? В какие бизнес процессы ее адаптируют? Или продукт ради продукта?

lampa
27.10.2025 14:31Я так понимаю модерирует отзывы и сразу их отправляет в бан =) А чтобы восстановить отзыв, например "Всё отлично", нужно написать полотно текста в поддержку (читай в пустоту) и скинуть скрины
Keeper22
Микросервисы вам эта же модель и пишет?