Jev в 25 строках на Python
О модели Jev от компании Typesafe (System One Models) не говорит разве что ленивый: её называют новым рубежом больших языковых моделей и «новой парадигмой ИИ». Команда NobodyWho, разработчики open-source-движка для локального озвучивания игр, отнеслась к этому хайпу скептически и решила показать суть технологии наглядно. Их аргумент — рабочая реализация Jev в 25 строках Python на модели Qwen3-0.6B через llama-cpp-python.
Это, конечно, пародийный пост, но с серьёзным подтекстом: ядро «прорывной» технологии — обычная классификация через логиты следующего токена, то есть приём, знакомый любому, кто работал с LLM на низком уровне. Разберём этот код по шагам.
Что делает Jev на самом деле
Если отрезать маркетинг, Jev — это модель, которая получает промпт с набором вариантов и выдаёт вероятности для каждого из них. Не генерирует текст, а классифицирует: вход — письмо, выход — «легитимное / спам / фишинг» с распределением вероятностей.
Именно это и делает код ниже. Никакого вызова API, никакой синтетики для дообучения, никакого reinforcement learning для калибровки решений (RLCD) — только открытая модель с Hugging Face, локальный инференс и немного numpy.
Шаг 1: загружаем модель
# /// script
# requires-python = S0
# dependencies = [ S1 , S2 , S3 ]
# ///
import numpy
from llama_cpp import Llama
# Really, you can use any GGUF model from https://huggingface.co/models?library=gguf
model = Llama.from_pretrained(
repo_id="Qwen/Qwen3-0.6B-GGUF",
filename="Qwen3-0.6B-Q8_0.gguf",
n_ctx=512,
logits_all=True,
verbose=False,
)
Здесь три важных детали:
- Скрипт оформлен по стандарту PEP 723 — метаданные зависимостей прямо в комментарии. Запускается одной командой через
uv run. - Модель — крошечный Qwen3-0.6B в квантовании Q8_0. Подойдёт любая GGUF-модель, кстати.
logits_all=True— ключевой флаг. Он заставляет llama.cpp возвращать логиты для всех позиций, а не только сэмплировать следующий токен. Без этого трюк не сработает.
Шаг 2: промпт и варианты выбора
labels = ["A", "B", "C"]
choices = ["Legitimate", "Spam", "Phishing"]
email = "Payroll asks for your password on a non-company sign-in page."
options = "\n".join(
f"{label}. {choice}" for label, choice in zip(labels, choices, strict=True)
)
prompt = f"""<|im_start|>system
Choose one option.<|im_end|>
<|im_start|>user
Email: {email}\n\n{options}<|im_end|>
<|im_start|>assistant
<think>\n\n</think>\n\n"""
model.eval(tokens=model.tokenize(text=prompt.encode(), add_bos=False, special=True))
Задача: классифицировать письмо как легитимное, спам или фишинг. Промпт собран в формате чата Qwen (im_start/im_end), а в конце добавлена пустая reasoning-обёртка <think>, чтобы модель сразу перешла к ответу.
Классический приём constrained classification: перечисляем варианты под метками A/B/C и дальше будем смотреть не на свободную генерацию, а только на вероятности токенов этих самых меток.
Шаг 3: от логитов к вероятностям
logits = model.scores[model.n_tokens - 1]
token_ids = [model.tokenize(text=label.encode(), add_bos=False)[0] for label in labels]
choice_logits = numpy.asarray([logits[token_id] for token_id in token_ids])
logprobs = choice_logits - numpy.logaddexp.reduce(choice_logits)
probabilities = numpy.exp(logprobs)
for name, scores in (
("Logits", choice_logits),
("Log probabilities", logprobs),
("Probabilities", probabilities),
):
values = numpy.round(scores.astype(float), 3).tolist()
print(f"{name}:", dict(zip(choices, values, strict=True)))
# Logits: { S4 : 26.254, S5 : 27.262, S6 : 29.614}
# Log probabilities: { S7 : -3.482, S8 : -2.474, S9 : -0.122}
# Probabilities: { S10 : 0.031, S11 : 0.084, S12 : 0.885}
Вот здесь и живёт вся «магия» Jev:
- Берём логиты последней позиции последовательности — это распределение следующего токена до нормировки.
- Находим токен, соответствующий каждой метке (первый токен строк «A», «B», «C»).
- Выбираем логиты только этих трёх токенов и делаем log-softmax уже по ним:
logits - logsumexp(logits). - Возводим в экспоненту и получаем вероятности.
Для тестового письма модель с вероятностью 88,5% говорит «Phishing» — и она права: письмо просит пароль на не-корпоративной странице входа. Один прогон вперёд, ноль генерации текста, готовое калиброванное решение.
Почему это «тот самый» Jev
Авторы честно перечисляют, чего в их коде нет: названия «System One decision model», вызова API, синтетических данных и RLCD-калибровки. И так же честно перечисляют, что совпадает:
- Это классификация: промпт с вариантами на входе, вероятности на выходе.
- Это быстро — один forward-проход вместо авторегрессионной генерации.
- Это локально — данные никуда не отправляются, что для классификации корпоративной переписки принципиально.
Стоит оговориться: в исходном анализе от Arcturus Labs отмечалось, что вероятности оригинального Jev тоже не всегда корректны — так что отсутствие RLCD-калибровки не делает пародийную версию сильно хуже оригинала.
Практический смысл
Приём, показанный в посте, сам по себе очень полезен — независимо от отношения к Jev:
- Дешёвая классификация. Модель на 0,6B параметров в Q8 занимает меньше гигабайта. Для маршрутизации запросов, модерации или триажа тикетов этого часто достаточно.
- Structured output без парсинга. Не нужно заставлять модель выдать JSON и надеяться, что она не сломает формат. Вы просто читаете вероятности из логитов.
- Каскады. Дешёвая локальная модель фильтрует поток, а дорогая API-модель подключается только для спорных случаев с низкой уверенностью.
- Приватность по умолчанию. Всё выполняется на своей машине: письма, тикеты и логи не покидают инфраструктуру. Для задач, где данные нельзя отдавать третьим сторонам, это часто решающий аргумент — именно его NobodyWho и подчёркивают.
Такой же механизм лежит в основе constraint-декодирования и методов вроде grammar-based sampling — просто там вероятности маскируются по правилам грамматики, а здесь берутся только токены-метки.
Подводные камни приёма
Если соберётесь использовать этот подход в продакшене, учтите несколько ограничений:
- Метка должна быть одним токеном. Код берёт первый токен строки метки, поэтому работают короткие метки вроде «A», «B», «C» или «yes»/«no». Многословные варианты («Legitimate») в перечислении логитов не попадают целиком — выбирается только их первый токен, поэтому имена классов живут в списке
choices, а в логитах участвуют лишь однобуквенные метки. - Нормировка по вариантам, а не по словарю. Log-softmax считается только по выбранным меткам, так что «вероятность» означает «доля уверенности среди этих трёх опций». Модель может считать все три варианта маловероятными на фоне остального словаря — вы этого не увидите.
- Качество зависит от модели. 0,6B параметров хватает для тривиальной фильтрации, но на тонких задачах вероятности будут заметно шумнее, чем у старших моделей.
Все три ограничения лечатся: метки можно расширять до нескольких токенов (суммируя логиты), в нормировку можно включать «безопасный» вариант-выход, а модель — просто взять побольше. Архитектура приёма от этого не меняется.
Открытые реализации
Если тема зацепила, у пародии есть вполне серьёзные продолжения — авторы сами на них ссылаются:
- OpenJev — обзор более полных открытых реализаций.
- openjev-sglang — реализация на SGLang.
- OpenJev на DiffusionGemma — вариант на диффузионной модели.
Весь код из поста — это, по сути, готовый стартовый шаблон: зависимости через PEP 723, GGUF-модель с Hugging Face и двадцать пять строк на получение калиброванных вероятностей классов. Оригинал от NobodyWho — здесь, их open-source-проект NobodyWho — на GitHub.