Как я сделал свой локальный Jev на базе Qwen

Аннотация

Мы привыкли, что языковая модель отвечает текстом и даже ради одного решения начинает писать слово, объяснение или JSON. 20 сентября 2026 года компания TypeSafe представила Jev — модель, которая получает состояние системы и сразу возвращает готовые решения с вероятностями.

Сам Jev сразу показался мне очень полезной вещью. Я постоянно работаю с определением языка, выбором инструментов, классификацией документов и передачей сложных запросов оператору. Обычно ради одного такого выбора мы запускаем обычную LLM и затем вытаскиваем решение из сгенерированного текста. Jev создан именно для этого класса задач и может заметно улучшить скорость и надёжность таких частей моих приложений.

А сделать собственную локальную версию я решил по двум причинам. Первая — практическая: многие мои системы работают с 1С, финансовыми документами и другими данными в закрытом контуре. Я не могу просто подключить API Jev, поэтому мне нужен способ использовать новую технологию локально и не зависеть от облачного провайдера. Вторая — исследовательская: мне было интересно провести reverse engineering понятной части механизма, разобраться, как он работает, и проверить, насколько близко к нему можно подойти с обычной локальной Qwen.

В итоге я собрал собственный Jev-style контур. Когда допустимые ответы известны заранее, он сразу показывает предпочтительный вариант и силу этого выбора.

Часть I. Как работает Jev

Сначала соберём идею Jev в одну понятную цепочку. Начнём с обычного сообщения клиента, посмотрим, как языковая модель строит ответ, и прочитаем готовое решение перед генерацией текста.

1. Почему Jev меня заинтересовал

Я постоянно работаю с языковыми моделями внутри реальных бизнес-систем. Часто модель выполняет одну маленькую, но важную работу:

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

Это всё задачи выбора и классификации. Они встречаются почти в каждом агенте и сервисе, который я строю. Причём одно решение часто запускает следующее: сначала определить язык, затем выбрать сценарий, потом инструмент, а после его результата решить, нужен ли человек.

Обычная LLM умеет всё это делать. Каждый такой вызов запускает полноценную генерацию текста ради одного итогового варианта. Поэтому Jev сразу показался мне очень подходящим инструментом для хорошо знакомого класса рабочих задач. Более быстрые и типизированные решения с явной неопределённостью улучшают всю цепочку внутри приложения.

После этого возник уже другой вопрос: как применить эту идею у себя?

Самые интересные новые модели обычно сначала появляются как внешний API. Закрытые клиентские контуры не могут использовать их напрямую: финансовые документы, данные из 1С и внутренняя переписка должны оставаться внутри инфраструктуры клиента.

Это первая причина, почему я решил сделать локальный вариант Jev: стоимость API здесь ничего не решает, потому что закрытый контур исключает внешний сервис, а мне хотелось получить пользу новой технологии внутри собственной инфраструктуры.

Вторая мотивация была исследовательской: мне просто стало интересно, как Jev устроен.

После Tiny Manas я уже знал, как языковая модель учится предсказывать следующий токен, а Jev использует эту знакомую механику для прямого выбора между вариантами. Мне захотелось разобрать публично понятную часть системы, провести небольшой reverse engineering и проверить две вещи: что именно можно повторить без закрытых весов TypeSafe и насколько далеко получится зайти на обычной локальной модели, если нужный ответ уже содержится в её первом предсказании.

И вот это я решил проверить руками.

2. Что такое Jev

TypeSafe представила Jev 20 сентября 2026 года. Компания называет его первой System One Model — моделью для быстрых решений внутри программ.

Обычная LLM получает текст и отвечает текстом, а Jev работает с состоянием системы и набором заранее известных вопросов, возвращая готовые значения с вероятностями. Если совсем просто:

обычная LLM:  «напиши ответ»
Jev:          «выбери один из этих ответов»

Название System One отсылает к быстрой, почти интуитивной части мышления из книги Даниэля Канемана: модели дают узкий вопрос и просят быстро оценить варианты, не ожидая рассуждения на несколько страниц.

Сам Jev назван в честь экономиста Уильяма Стэнли Джевонса. Идея простая: когда единица интеллекта становится значительно дешевле, мы не просто экономим на старых задачах, мы начинаем использовать интеллект там, где раньше это вообще не имело смысла.

3. Почему обычная LLM делает лишнюю работу

Представим, что у вас есть система для обработки сообщений службы поддержки, которая принимает новые обращения и передаёт каждое из них в один из трёх отделов. billing разбирается с оплатой, возвратами и повторными списаниями, technical support решает технические проблемы, а account access помогает со входом и доступом к аккаунту.

В систему приходит новое сообщение:

С карты дважды списали деньги за одну подписку.

Система должна определить, какой отдел получит запрос:

billing
technical support
account access

Задача сводится к одному маршруту, однако обычная языковая модель всё равно начинает писать ответ. Если попросить JSON, она по очереди создаст открывающую скобку, имя поля, двоеточие, слово billing, кавычки и закрывающую скобку, после чего программа дождётся конца ответа, прочитает JSON и достанет из него название отдела.

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

Одинаковый вход, но разный способ прочитать ответ модели.

Jev убирает эту лишнюю часть. Он получает обычный текст и оценивает варианты, которые уже дала программа.

4. Где модель прячет свой первый выбор

Чтобы понять механику, вспомним, как LLM вообще пишет текст. Она выбирает первый токен, добавляет его к строке, затем выбирает второй, потом третий и так далее.

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

A:       8.2
B:       5.5
C:       4.8
слово:   1.1
точка:  -0.3
... ещё тысячи вариантов

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

Теперь дадим трём нужным ответам короткие метки:

A = billing
B = technical support
C = account access

Модель читает смысл всех трёх вариантов и должна вернуть одну букву. В первом конкурсе нас интересуют три участника: A, B и C.

Restricted softmax: все остальные токены временно не участвуют в выборе.

Мы берём их оценки и превращаем в понятные доли:

A = 91%  → billing
B =  6%  → technical support
C =  3%  → account access

Операция называется restricted softmax. Название звучит очень страшно, но вы не переживайте: сама идея простая. Обычный softmax распределяет 100% между всеми кандидатами, а restricted softmax делает это только между вариантами, которые разрешила программа.

Решение появляется до генерации первого токена.

5. Зачем нужны буквы A, B и C

Возникает логичный вопрос. Почему не взять оценку самого слова billing?

Токенайзер может представить billing одним токеном или разбить его на несколько. technical support почти наверняка состоит из нескольких токенов. Варианты разной длины пришлось бы оценивать по частям, снова запуская последовательную генерацию.

Буквы A, B и C работают как номера камер хранения: промпт подробно описывает содержимое каждой камеры, а модель возвращает один короткий номер.

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

6. Что именно получает программа

Обычный классификатор мог бы вернуть только billing. Jev-style scoring возвращает больше:

billing            0.91
technical support  0.06
account access     0.03

Теперь программа видит не только победителя, но и характер победы: 0.91 / 0.06 / 0.03 выглядит как уверенный выбор, а 0.40 / 0.37 / 0.23 — как почти ничья. В обоих случаях первым будет billing, однако первый запрос можно маршрутизировать автоматически, а второй лучше показать оператору.

Можно записать обычное правило:

если первый вариант выше 0.80
и опережает второй хотя бы на 0.20:
    выполнить действие автоматически
иначе:
    отправить человеку

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

Здесь нас поджидает ещё одна проблема. Представьте, что сообщение вообще не относится ни к billing, ни к technical support, ни к account access. Restricted softmax всё равно обязан распределить между ними 100%, поэтому модель выберет самый подходящий из трёх неправильных ответов.

Для такого случая в список добавляют OTHER или ESCALATE. OTHER означает, что ни один из известных вариантов не подходит, а ESCALATE сразу передаёт запрос человеку или в отдельный процесс разбора. После этого у модели появляется честный выход из ситуации, где правильной двери среди предложенных просто нет.

И только теперь вернёмся к числу 0.91. Оно показывает силу предпочтения внутри заданного списка, а реальную долю правильных ответов нужно отдельно измерять на размеченных примерах. Если модель получает 0.91 на сотне похожих запросов, мы должны проверить, сколько из них она действительно классифицировала правильно.

7. Какие решения умеет описывать Jev

В публичном API TypeSafe есть три основных вида вопросов.

Noul — обычное «да или нет». Например: совпадает ли счёт с заказом? Нужна ли ручная проверка?

Choice — один вариант из списка. Например: в какой отдел отправить обращение? К какому типу относится документ?

Score — место на упорядоченной шкале. Например: риск низкий, средний, высокий или критический?

Из таких маленьких ответов можно собрать большой рабочий процесс: обычный код проверяет суммы, даты, права и обязательные поля, а модель разбирает решения, которые трудно выразить простым if. Workflow-примеры TypeSafe показывают такую схему на счетах, инцидентах безопасности и клиентских обращениях.

Десятки таких решений внутри одного процесса превращают скорость в важную характеристику системы.

8. Какую часть Jev можно повторить

Обычная языковая модель позволяет заранее определить варианты и прочитать оценки первого токена перед генерацией текста. Именно эту часть я воспроизвёл локально.

TypeSafe дополняет этот механизм новой архитектурой, параллельной выдачей многих решений и отдельным методом обучения RLCD. Компания не публикует веса, устройство модели и полный процесс калибровки.

Я называю свою систему Jev-style моделью на базе Qwen. Она повторяет понятный принцип выдачи решения на другой модели и runtime.

Теория на этом закончилась. Теперь можно открыть капот и посмотреть, как всё это было собрано на локальной машине.

Часть II. Как я сделал локальный Jev на базе Qwen

1. Отправная точка: Qwen на Mac Studio

Для эксперимента мне был доступен Mac Studio с Apple M3 Ultra, причём под модель я мог использовать не больше 60 ГБ объединённой памяти. В этот предел помещалась Qwen3.8-27B-8bit, поэтому она и стала отправной точкой исследования.

Qwen3.8-27B — плотная модель на 27 миллиардов параметров: 64 слоя, hidden dimension 5120, словарь 248 320 токенов и нативный контекст 262 144 токена. Я использую закреплённую MLX-квантизацию с конкретной revision, чтобы повторный запуск не получил незаметно другие веса. MLX хорошо подходит для этой машины: MLX-LM предназначен именно для инференса и дообучения LLM на Apple silicon.

Вторую копию 27B-модели держать в памяти было бы слишком дорого, поэтому scoring я встроил в тот же локальный процесс. Так эксперимент использовал уже загруженные веса и не расходовал память на дублирование модели.

Исследовательский контур целиком работает на одной локальной машине.

2. Как появилась идея реализации

Отправной точкой стал разбор Avi Chawla. В нём тот же принцип воспроизводится через готовый /v1/score в SGLang. Такой endpoint действительно предназначен для оценки выбранных token ID — это есть и в официальной документации SGLang.

Мой локальный runtime был устроен иначе, поэтому сначала я запросил top_logprobs через OpenAI-compatible chat API. Параметр принимался, однако ответ возвращал logprobs: null, и нужные числа оставались внутри процесса модели.

Затем я прошёл глубже по runtime. Планировщик после prefill уже получал полные next-token log probabilities и использовал их для декодирования. Самая дорогая часть вычисления существовала; оставалось провести несколько выбранных значений от тензора до API.

Я добавил к тому же процессу два исследовательских маршрута:

  • /v1/jev/tokenize рендерит реальный chat template и проверяет, какие метки занимают ровно один токен;
  • /v1/jev/score выполняет prefill, извлекает только заявленные позиции, нормализует их и возвращает компактный ответ.

Полный вектор из 248 320 значений не сериализуется: наружу выходят только несколько разрешённых оценок, что уменьшает трафик и не превращает внутренности модели в новый открытый API.

3. Один запрос шаг за шагом

Рассмотрим реальный путь решения.

Шаг 1. Прикладной код отправляет состояние и семантические варианты. Он знает duplicate_card_payment и forgot_password, но ничего не знает о token ID.

Шаг 2. Локальный код строит полный prompt. Описание рядом с короткой меткой даёт Qwen смысл каждого варианта.

Шаг 3. Endpoint токенизации проверяет метки в точном контексте после chat template. Небезопасные кандидаты отбрасываются. Валидное сопоставление кэшируется для одинакового вопроса и набора вариантов: выполнять эту проверку перед каждым бизнес-решением бессмысленно.

Шаг 4. Qwen обрабатывает весь запрос ровно так же, как перед обычным ответом. Веса, архитектура и знания модели не меняются.

Шаг 5. Runtime получает logits следующей позиции и оставляет только token ID выбранных меток.

Шаг 6. Restricted softmax превращает эти логиты в значения, сумма которых равна единице.

Шаг 7. Технические метки снова превращаются в семантические имена. Прикладная программа получает duplicate_card_payment, а не букву B.

Шаг 8. Обычный код применяет политику. Например: автоматически продолжать процесс, если вероятность выше 0,80 и отрыв от второго варианта больше 0,20; иначе отправлять человеку. Порог принадлежит бизнес-логике, а не скрыт в тексте промпта.

4. Что именно я измерял

Первый bounded run использовал 20 реальных сообщений из Banking77 и десять банковских intent-классов. Каждый пример проходил три пути на одной и той же Qwen:

  1. Jev-style scoring;
  2. генерация одной метки;
  3. генерация компактного JSON.

Все три пути дали 100% на этой маленькой выборке. Scoring занял по медиане 1,543 с, одна метка — 1,590 с, JSON — 2,082 с. Однотокенный baseline почти невозможно обогнать в разы: оба пути платят за один и тот же prefill. Поэтому основным сравнением стал компактный JSON, который реально встречается в приложениях.

После этого я расширил эксперимент до трёх форм System One-вопросов:

  • Noul: 40 банковских сообщений, бинарный вопрос об ATM withdrawal;
  • Choice: 30 сообщений и одновременно 30 банковских intent-вариантов;
  • Score: 48 законченных примеров из реального test split Stanford Sentiment Treebank с пятью уровнями тональности.

Последний блок остановился на 48 из 50 примеров: к этому моменту данных уже хватало для решения. Пропущенные строки ничем не заменялись.

5. Главный результат: решения сохранились, задержка уменьшилась

Медианная задержка на реальном Mac Studio. Меньше — лучше.
ПримитивПримеровScoring p501 token p50JSON p50vs JSON
Noul · 2 варианта401,617 с1,784 с2,314 с1,43×
Choice · 30 вариантов302,729 с2,826 с3,624 с1,33×
Score · 5 уровней481,579 с1,799 с2,309 с1,46×

Самая важная строка не помещается в таблицу задержек: scoring и однотокенная генерация выбрали одинаковый вариант в 118 случаях из 118. Новый inference path сохранил решения Qwen и прочитал их до оформления в строку.

Против одного токена выигрыш составил 1,04–1,14 раза, а против компактного JSON — 1,33–1,46 раза. В абсолютных числах scoring экономил примерно 0,7–0,9 секунды на медианном запросе, и эта разница накапливается в любой последовательной цепочке из десятков решений или при большом потоке документов.

Compact JSON возвращал только победителя, а scoring за меньшее время давал всё распределение. Функционально эквивалентный JSON с вероятностью для каждого из 30 вариантов потребовал бы намного больше токенов, но после достаточного объёма данных я остановил измерения и не стал тратить дополнительное вычислительное время.

6. Откуда берётся разница с 40–200 раз

На этой машине основное время короткого запроса уходит на чтение prompt большой 27B-моделью. Этот этап называется prefill. Девять JSON-токенов занимают меньшую часть задержки, поэтому удаление autoregressive tail экономит только время генерации после prefill.

Однотокенная генерация делает тот же prefill и один decode step. Результат 1,04× на 30-классовом Choice соответствует этой структуре вычислений.

TypeSafe получает 40–200× с новой архитектурой, parallel sampler, одновременными ответами на несколько вопросов и обучением RLCD. Их workflow также сравнивает полный набор вероятностных решений с генеративными frontier-моделями, которые создают значительно более длинную структуру. Сама TypeSafe отмечает, что 193,6× по скорости и 444,6× по стоимости находятся на верхней границе реальных выигрышей.

Local Jev-style Qwen сохраняет решения базовой модели, убирает парсинг и даёт полный набор вероятностей на 25–32% быстрее компактного JSON. Ускорение на порядок потребует специализированной модели или параллельного runtime.

7. Качество здесь — контроль, а не соревнование моделей

На расширенной выборке accuracy scoring составила 85,0% для Noul, 86,7% для 30-way Choice и 43,8% для сложной пятиуровневой тональности. Однотокенный baseline получил те же значения и сделал те же ошибки. Полное совпадение связывает качество с Qwen и постановкой задачи; scoring сохранил её поведение.

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

Проверка перестановки вариантов нашла ещё одну границу: победитель сохранился в 8 из 10 бинарных кейсов, 8 из 10 Choice-кейсов и 9 из 10 Score-кейсов. Модель чувствительна к порядку списка, поэтому сырые вероятности пока нельзя объявлять калиброванным риском и сразу подключать к финансовым действиям без отдельной проверки.

Следующий инженерный уровень здесь понятен: permutation aggregation, стабильные описания критериев, отдельные calibration и test splits, temperature scaling и бизнес-пороги, выбранные до production-пилота. Это ближе к настоящей decision model, чем ещё один красивый prompt.

8. Где это полезно в бизнес-системах

Генерация остаётся подходящим инструментом для писем, объяснений проводок и аналитических отчётов. Local Jev-style inference работает с маленькими развилками внутри бизнес-процесса:

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

Именно здесь появляется бизнес-ценность: модель становится несколькими «мягкими if», встроенными между точными правилами системы, где суммы, даты, права доступа и обязательные поля остаются в обычном коде, а модель решает то, что трудно выразить жёсткой формулой.

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

В самом эксперименте вся цепочка работала локально на Mac Studio: запрос попадал в уже загруженную Qwen, scorer возвращал распределение, и никакой внешний модельный API в вычислениях не участвовал.

9. Что я буду делать дальше

Первый этап ответил на главный вопрос: сильную локальную Qwen действительно можно использовать как typed decision scorer без переобучения. Теперь есть несколько независимых направлений.

Калибровка. Собрать размеченный набор из реальных задач выбранной предметной области, отделить calibration от test и проверить, соответствует ли уверенность реальной частоте ошибок.

Устойчивость. Уменьшить зависимость от порядка вариантов и добавить обязательный ESCALATE там, где пространство ответов неполно.

Параллелизм. Сейчас локальный endpoint отвечает на один вопрос за проход. Несколько независимых heads или батч вопросов над общим состоянием могут дать ускорение в несколько раз.

Специализация. Если реальные данные покажут повторяемый класс решений, можно сравнить prompt-only Qwen с дообучением, distillation или отдельной меньшей decision model. Проверенный inference path даст для этого точку отсчёта.

10. Что я вынес из этого эксперимента

Jev предлагает другой интерфейс между AI и программой: свободный текст подходит для общения с человеком, а код удобнее работает с ограниченными типами, явными вероятностями и предсказуемыми ветками.

Я начал с очень знакомого класса задач: мои приложения постоянно определяют язык, выбирают инструменты, классифицируют документы и решают, когда пора подключать оператора. Для этих решений Jev предложил подходящий интерфейс, а исследовательский интерес привёл меня внутрь механизма и к локальной реализации. В результате получился работающий scorer поверх Qwen3.8-27B, который сохраняет решения базовой модели и быстрее возвращает полный набор вероятностей.

Измерения показали и границу текущего подхода: на коротком ответе доминирует prefill, поэтому выигрыш против компактного JSON составил 1,33–1,46×. Для ускорения в десятки раз уже понадобятся специализированная архитектура и параллельная обработка, зато теперь понятно, какая часть идеи работает, где лежит предел и что именно нужно построить дальше.

Под словом «модель» часто скрывают один огромный чёрный ящик. В этом эксперименте улучшение появилось из простого вопроса: зачем заставлять модель писать целую страницу, если нужное решение уже появилось в первом векторе?