FRIDA Decisions — открытая модель быстрого мышления для русского языка. Она выбирает вариант из списка, ставит оценку по шкале, отвечает «да» или «нет» и ранжирует кандидатов за один проход энкодера, без генерации текста и за десятки миллисекунд на RTX 5060 Ti. Её можно поставить везде, где сейчас LLM работает классификатором или судьёй: маршрутизация обращений, модерация, разметка данных, ранжирование для RAG. На нашем бенчмарке razvilka модель набирает 0,893 — наравне с коммерческим API TypeSafe Jev (0,897) и с LLM Qwen3.5-35B-A3B, которая в 40 раз больше.

Коротко, что внутри:

  • Скорость. 28-34 мс на запрос на RTX 5060 Ti. Коммерческий API по независимым замерам отвечает примерно за 430 мс вместе с сетью.

  • Цена. Почти в 20 раз дешевле коммерческого API в пересчёте на запрос на арендованной карте при полной загрузке.

  • Сотни вариантов в одном запросе. Все варианты ответа идут в одну последовательность, текст кодируется один раз. При типичном запросе с тремя вопросами это в 6 раз меньше токенов, чем при наивной раскладке, а каталог из 243 интентов разбирается за 0,44 с вместо 4,65 с.

  • Открытость. Веса и код инференса под MIT, бенчмарк razvilka (у каждой задачи своя лицензия), ONNX-версия для CPU, запуск на Маке через MLX, vLLM-сервер.

Объясним, как модель устроена под капотом, и покажем, как запустить её у себя.

Зачем модель, которая ничего не генерирует?

В любом LLM-продукте есть десятки мелких решений, которые не требуют текста на выходе:

  • LLM-роутинг: какой модели, индексу или агенту отдать запрос.

  • Guardrails: токсично, похоже ли на мошенничество, нарушает ли политику.

  • Выбор инструмента (tool calling): какую из сотни функций вызвать.

  • Разбор тикета: тема, срочность, тон — всё сразу.

  • RAG: есть ли ответ в контексте, какие пассажи релевантны (реранкинг).

Обычно такие решения отдают LLM: промпт, генерирование, разбор JSON. Это дорого и медленно, и под такие задачи уже появился отдельный класс моделей быстрого рассуждения (decision models): на входе текст и закрытый список из N вариантов, на выходе — лучший вариант и вероятности всех N. Например, коммерческий API TypeSafe Jev: по оценкам самой TypeSafe, в таких задачах он в 40-200 раз быстрее и примерно в 400 раз дешевле фронтирных LLM. TypeSafe называет такие модели «Системой 1» по Канеману: быстрый ответ без рассуждений, в отличие от медленной «Системы 2» у LLM, которая рассуждает и генерирует.

Но подобный сервис — это внешний API: сетевая задержка, оплата за токены, лимиты запросов и данные, уходящие наружу. Мы сделали такую модель для русского языка на основе собственного энкодера и выкладываем в открытый доступ, её можно запустить у себя. Модели хватает около 2 ГБ видеопамяти, а без видеокарты она работает и на CPU: int8-версия в ONNX отвечает примерно за 0,9 с на шести потоках.

Как это устроено?

На вход модель получает текст (state) и один или несколько вопросов к нему. Каждый вопрос относится к одному из четырёх типов:

Тип

Что спрашивает

Что возвращает

choice

Какой из K вариантов верен?

Ключ варианта и распределение по всем.

score

Где текст на порядковой шкале?

Число от 0 до K−1.

noul

Верно ли утверждение?

Вероятность «да» (поле noul).

ranking

Как упорядочить кандидатов по запросу?

Порядок и маржа каждого.

Названия типов, включая noul для вопросов «да» и «нет», взяты из протокола Jev, поэтому запросы в его формате подходят и нам.

Список меток и их описания передаются текстом прямо в запросе. Новая таксономия — это новый JSON, а не новое обучение:

{"state": "Здравствуйте, у меня не приходит код подтверждения уже час.",
 "questions": {
   "urgency5": {"type": "score",
                "instructions": "Оцени срочность обращения.",
                "criteria": ["Очень низкая: вопрос можно решить в любое время",
                             "Низкая: неудобство, но всё работает",
                             "Средняя: что-то не работает, но есть обходной путь",
                             "Высокая: клиент не может пользоваться сервисом",
                             "Критическая: угроза деньгам или безопасности аккаунта"]},
   "spam": {"type": "noul",
            "instructions": "Является ли это сообщение спамом?",
            "criteria": {"true": "сообщение является спамом: реклама, чужие ссылки, просьба перевести деньги или сообщить код",
                         "false": "вопрос или жалоба по нашему сервису"}}}}

Ответ модели (поле answers; часть полей опущена, числа округлены до трёх знаков):

{"urgency5": {"type": "score", "score": 2.73,
              "probabilities": {"0": 0.007, "1": 0.007, "2": 0.271, "3": 0.685, "4": 0.03}},
 "spam": {"type": "noul", "noul": 0.06}}

Самый вероятный уровень срочности — «Высокая» (0,69), score — ожидаемое значение по шкале. Вероятность спама 0,06.

В основе нашего решения находится FRIDA, эмбеддинг-модель для русского языка, о которой мы уже рассказывали на Хабре. Архитектурно это энкодер от FRED-T5 (24 слоя, 823 млн параметров). Для FRIDA Decisions мы дообучили её из эмбеддера в кросс-энкодер: модель читает каждого кандидата в контексте текста и вопроса и выдаёт на него одно число. Это как у реранкеров, только метки задаются в запросе, поэтому набор классов можно менять без дообучения. Декодера нет, токенов на выходе ноль, поэтому ответ всегда один из объявленных вариантов, а вероятность каждого варианта видна сразу.

Качество: бенчмарк razvilka

Для русского языка мы собрали свой бенчмарк razvilka и публикуем его вместе с моделью: 735 примеров в 15 задачах, все четыре типа вопросов, тексты и человеческая разметка из опубликованных русских корпусов. Эту разметку дополнительно проверяли две фронтирные модели, спорные примеры удалены целиком. Лексический бейзлайн, который выбирает вариант по общим с текстом словам, набирает на нём 0,333, случайный ответ — 0,257. Скрипт подсчёта и тетрадка, которая прогоняет на бенчмарке любую модель, лежат в репозитории.

На уровне коммерческого API. Для ориентира мы прогнали тот же бенчмарк через TypeSafe Jev: 659 верных ответов из 735 против наших 656, то есть 0,897 и 0,893 (у нас state до 512 токенов). Разница в три примера статистически незначима (парный тест Макнемара, p = 0,84), на вопросах «да/нет» счёт ровный, 122 против 122.

Открытые модели того же класса. Ближе всех decider-2b, открытая репродукция Jev размером 1,9B, вдвое больше нашей: 0,833, мы впереди на 44 примера (p < 0,001). Энкодеры и реранкеры до 1 млрд на графике отстают на 32-40 процентных пунктов. Против GLiNER и laya мы чаще выигрываем, чем проигрываем, в каждой из 15 задач бенчмарка, против KaLM-Reranker — во всех, кроме ранжирования, где идём вровень.

Малые LLM как классификаторы. Мы спросили две MoE-модели с 3-4 млрд активных параметров без рассуждений (у GPT-OSS они не отключаются, поэтому минимальные): те же текст, вопрос и варианты в промпте, на выходе ключ варианта в JSON. Qwen3.5-35B-A3B набирает 0,897, и разница с нами незначима (p = 0,84): они сильнее в ранжировании, мы — в выборе из списка. Но это 35 млрд параметров, около 70 ГБ весов в bf16, через API выходит около $0,06 за 1000 запросов, на уровне TypeSafe, а вместо распределения по вариантам Qwen отдаёт один ответ строкой. GPT-OSS-20b набирает 0,856, мы впереди на 27 примеров (p = 0,02).

Razvilka, 735 примеров, один прогон на модель, каждая в своём формате входа, подсчёт одинаковый
Razvilka, 735 примеров, один прогон на модель, каждая в своём формате входа, подсчёт одинаковый

По типам вопросов сильнее всего choice: выбор из списка меток, то есть роутинг, тематика и интенты: 457 верных из 500 (0,914).

Razvilka, 735 примеров, 15 задач сведены в пять групп, попарное сравнение по каждому примеру
Razvilka, 735 примеров, 15 задач сведены в пять групп, попарное сравнение по каждому примеру

Справа — доля примеров группы, где мы ответили верно, а другая модель ошиблась, слева — наоборот. В ранжировании для RAG мы правы, а GLiNER и laya ошибаются в 58-68% примеров. У KaLM-Reranker сам реранкер и здесь идёт с нами вровень, зато в тональности и модерации отстаёт сильнее всех.

Скорость и цена

Типичный запрос (текст около 400 токенов, три вопроса) на RTX 5060 Ti занимает 34 мс, с одним вопросом — 28 мс. Это медиана 30 запросов после прогрева в своём процессе, вместе с токенизацией и упаковкой, но без сети. Пиковая память, выделенная PyTorch за весь прогон бенчмарка, — 1,8 ГБ плюс CUDA-контекст, так что модель помещается на любую современную игровую карту. У TypeSafe Jev независимые замеры PriorBench и jev-benchmark дают около 430 мс через OpenRouter вместе с сетью (у jev-benchmark p50 422 мс, p95 542 мс). Если поднять у себя vLLM-сервер и ходить в него по HTTP, то на той же машине ответ занимает 40-44 мс.

Как запускаем

Железо

Запрос: текст ~400 токенов, три вопроса

razvilka

PyTorch, bf16

RTX 5060 Ti

34 мс

0,893

vLLM-сервер, bf16, по HTTP

RTX 5060 Ti

44 мс; около 60 запросов/с при 8 одновременных

0,890

MLX, bf16

Apple M4 Pro

139 мс

0,891

ONNX int8

CPU Ryzen 5 7500F, 6 потоков

0,88 с

0,891

PyTorch, fp32

CPU Ryzen 5 7500F, 6 потоков

2,3 с

—

Пропускная способность vLLM измерена на запросах размера razvilka (около 260 токенов). CPU измеряли на тексте в 384 токена на машине с фоновой нагрузкой. Бесплатной T4 в Colab тоже хватает: каталог из 243 интентов считается на ней примерно за секунду.

Отдельно о длине входа. Модель обучалась на текстах до 512 токенов, и по умолчанию пакет обрезает текст до 384. Благодаря относительным позициям T5 технически можно подать и больше, но на длинных текстах качество может падать. Длинные документы надёжнее резать на фрагменты и спрашивать по каждому. В следующем релизе контекстное окно, обещаем увеличить!

Цена за 1000 таких запросов:

  • TypeSafe Jev берёт $0,042 за миллион входных токенов. Запрос с текстом в 384 токена и тремя вопросами он выставил в счёт как 1277 токенов, то есть около $0,054.

  • Та же карта в аренду у российских облачных провайдеров стоит от 26 ₽ в час (пример тарифа, на 1 октября 2026), то есть около 0,25 ₽, или $0,003 по курсу 85 ₽ за доллар.

Получается почти в 20 раз дешевле на арендованной в РФ карте, без лимитов на запросы, и данные не уходят с машины. Это цена при полной загрузке: аренда стоит около 620 ₽ (~$7) в сутки независимо от нагрузки, поэтому своя карта выгоднее API начиная примерно со 135 тысяч запросов в сутки. vLLM-сервер обрабатывает на той же карте около 60 запросов в секунду, так что с ним разница в цене ещё больше.

Под капотом

Упаковка: текст кодируется один раз на все варианты

Наивный способ спросить кросс-энкодер про K вариантов — собрать K пар «текст + вариант» и прогнать каждую. Тогда длинный текст оплачивается K раз, и цена запроса растёт как длина текста, умноженная на число вариантов.

Мы кладём всё в одну последовательность: [текст] [вопрос 1] [вариант 1] [вариант 2] … [вопрос 2] …. Трёхуровневая блочная маска делает так, чтобы каждый вариант видел только текст, свой вопрос и себя, а позиции каждого варианта начинались сразу после его вопроса, как если бы он был там один.

Второй шаг — кеш состояния. Текст никого, кроме себя, не видит, поэтому его ключи и значения на каждом слое не зависят от вопросов. Мы прогоняем текст один раз, сохраняем K/V, и дальше для любого числа вариантов и вопросов кодируются только их собственные токены. Если первый запрос по документу большой и упаковался в несколько строк, то K/V текста остаются в кеше, и следующий запрос про тот же документ текст уже не кодирует: в документе на 326 токенов повторный запрос с 13 вариантами занимает 25 мс против 240 мс при наивной раскладке. У декодеров такой кеш есть из коробки благодаря каузальной маске, а в энкодере все токены видят друг друга, и так просто не выйдет. Для энкодера мы сделали свою схему и получили кеш как у декодера, не отказываясь от двунаправленного внимания энкодера; подробнее про неё — в нашей статье про ранжирование ответов Салюта.

Наивная раскладка, упаковка, кеш состояния
Наивная раскладка, упаковка, кеш состояния

Чем длиннее текст и чем больше вариантов, тем больше серых полос в блоке «Наивно» и тем сильнее выигрыш.

Где упаковка выигрывает сильнее всего?

Замер на RTX 5060 Ti, bf16. Наивная раскладка: та же модель, по одной последовательности на кандидата, пачками по 8; решения обеих раскладок совпали. По времени выигрыш меньше, чем по токенам: на каждую строку есть фиксированные накладные расходы.

Сценарий

Текст, токенов

Кандидатов

Токенов наивно/с кешем

Время наивно

Время с кешем

Выигрыш

Выбор интента из каталога бота поддержки

331

243 интента

97 685/4 871

4,65 с

0,44 с

×11

Полный разбор письма в поддержку

302

16 вопросов, 65 кандидатов

23 487/1 853

1,10 с

0,18 с

×6

Повторный запрос к тому же документу

326

13

5 081/277

0,24 с

25 мс

×9

В итоге стоимость запроса растёт не как «текст × варианты», а как «текст + варианты». Именно это делает практичным каталог из сотен инструментов или десятки вопросов к одному документу.

Данные и обучение

Модель обучена на 1,42 млн примеров (1,5 млн вопросов) по 151 виду вопросов. Около 71% примеров на русском, 29% на английском. В основном это данные с человеческой или естественной разметкой; по нашей оценке, около четверти — с LLM-генерацией или модельной разметкой. Формулировки вопросов аугментированы перефразами, чтобы модель понимала смысл инструкции, а не заученную формулировку.

Обучение финальной версии: доли обучающих примеров, точное количество видов вопросов
Обучение финальной версии: доли обучающих примеров, точное количество видов вопросов

Обучали на современных серверных картах для DL, но по объёму вычислений это примерно 50 часов на одной потребительской карте RTX 5060 Ti. LoRA ранга 16 на проекциях внимания q, k, v, o во всех 24 слоях плюс скалярная голова: 4,7 млн обучаемых параметров из 823 млн; в релизе LoRA влита в веса.

Как модель учится?

Каждый вопрос разворачивается в кандидатов, и на каждого кандидата модель выдаёт одно число. Так выглядит вопрос о теме обращения в упакованной строке:

Числа z и p иллюстративные, не выход модели
Числа z и p иллюстративные, не выход модели

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

  • choice — «ключ: описание метки»;

  • score — описание уровня шкалы;

  • noul — два кандидата, «да» и «нет»: свои формулировки из запроса или стандартные;

  • ranking — сам пассаж.

Число z для кандидата даёт линейная голова поверх выходов энкодера на его участке строки: в упакованной строке десятки кандидатов, и у каждого свой участок.

На каждый вопрос приходится один член функции потерь, и он повторяет то, как ответ читается на выходе:

  • choice и score: softmax по кандидатам вопроса, p_i = exp(z_i) / Σ exp(z_j), и кросс-энтропия к правильному, L = −ln (p_{\text{верный}}). На выходе choice — самый вероятный вариант, score — ожидаемый уровень Σ i·p_i.

  • ranking: тот же softmax, но если правильных кандидатов несколько, то цель делится между ними поровну: L = −(1/|G|) Σ ln (p_g). По форме это listwise softmax; с одним правильным кандидатом он совпадает с InfoNCE.

  • noul: два кандидата сравниваются друг с другом, p_{\text{да}} = σ(z_{\text{да}} − z_{\text{нет}}), и L = −ln (p_{\text{да}}), если правильный ответ «да», и L = −ln(1 − p_{\text{да}}), если «нет».

Функция потерь батча — среднее по всем вопросам в нём. Учим в той же упаковке, что и на инференсе, так что кандидаты и при обучении не видят друг друга.

Числа z и p иллюстративные, правильные ответы заданы нами
Числа z и p иллюстративные, правильные ответы заданы нами

В этом примере модель права во всех четырёх вопросах, но уверена по-разному, и функция потерь это отражает: от 0,290 за тему до 0,592 за выбор статьи, где неверный кандидат набрал 0,335. Уверенная ошибка стоит дороже: если бы правильным ответом на вопрос о возврате было «нет», тот же выход обошёлся бы в −ln(1 − 0,690) = 1,17, больше чем втрое.

Как запустить?

Пакет ставится из GitHub, веса скачиваются с Hugging Face при первом запуске:

# PyTorch, GPU или CPU
pip install "frida-decisions[torch] @ git+https://github.com/ai-forever/FRIDA-Decisions@v0.4.0"
# int8 ONNX для CPU, без torch
pip install "frida-decisions[onnx] @ git+https://github.com/ai-forever/FRIDA-Decisions@v0.4.0"
# MLX для Apple Silicon, без torch
pip install "frida-decisions[mlx] @ git+https://github.com/ai-forever/FRIDA-Decisions@v0.4.0"
# vLLM-сервер (Linux, GPU)
pip install "frida-decisions[vllm] @ git+https://github.com/ai-forever/FRIDA-Decisions@v0.4.0"

from frida_decisions import Judge


judge = Judge.from_pretrained("ai-forever/FRIDA-Decisions")  # GPU, если есть, иначе CPU
request = {
    "state": "Здравствуйте, у меня не приходит код подтверждения уже час.",
    "questions": {
        "topic": {"type": "choice", "instructions": "К какой теме относится обращение?",
                  "criteria": {"login": "вход в аккаунт, коды, пароли",
                               "payment": "оплата, списания, возвраты"}},
        "spam": {"type": "noul", "instructions": "Является ли это сообщение спамом?"},
    },
}
answers = judge(request)["answers"]
print(answers["topic"]["choice"], answers["spam"]["noul"])

Варианты запуска:

  • PyTorch: основной путь, с упаковкой и кешем состояния.

  • ONNX Runtime: для CPU, без torch. Упаковка работает и здесь, потому что маска и позиции подаются на вход графа, а int8-версия квантует активации по каждому токену и примерно в 2,5 раза быстрее PyTorch в fp32.

  • vLLM: сервер для многих пользователей сразу (Linux, GPU). Он собирает запросы в батчи и держит прочитанные тексты в кеше префиксов, так что повторный вопрос к тексту стоит только своих токенов. Есть и вариант без сервера: VllmJudge запускает движок vLLM внутри вашей программы. Команда запуска — в карточке модели.

  • MLX на Apple Silicon: MlxJudge запускает модель на GPU Мака без torch, с той же упаковкой и кешем состояния; в fp32 он принимает те же решения, что и PyTorch, на всех примерах razvilka. Этот бэкенд прислал запросом на слияние @akolotov, спасибо!

  • Colab: тетрадка с установкой, первым запросом, всеми четырьмя типами вопросов, каталогом из 243 интентов в одном запросе и int8 ONNX на CPU. Хватает бесплатной T4: каталог на ней считается примерно за секунду.

  • Демо на Hugging Face Spaces: готовые сценарии — выбор инструмента, guardrails, маршрутизация, ранжирование, разбор тикета, упаковка — и свой запрос. Демо работает на CPU с int8-версией.

Итог

Решения «выбери из списка», «поставь оценку», «да или нет» и «упорядочи» не требуют ни генерирования, ни внешнего API: для LLM-роутинга, guardrails, распознавания интентов и реранкинга хватает быстрой decision model. Наш опыт показывает, что такую модель можно собрать на открытом энкодере без большого вычислительного бюджета: на русском она получается рядом с коммерческим сервисом по качеству, локально отвечает на порядок быстрее, чем API через сеть, а при полной загрузке карты обходится почти в 20 раз дешевле. Попробуйте её на своих задачах в демо или в Colab.

Ссылки:

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