
oQ в oMLX: что я нашёл про кванты Qwen3.8 и где мой замер разошёлся с ожиданиями
Месяц назад я мерил движки для локальной Qwen3.8-27B. С тех пор oMLX улучшил свой квантизатор oQ, на Hugging Face появились десятки oQ-сборок Qwen3.8, а в 0.7.1.dev1 oMLX научился отдавать модели-решения. Собрал, что известно, и перемерил на своём M4 Max 48 GB: oQ4e против обычного 4-bit, с MTP и без, и модель-решение clef на 600 размеченных адресах.
С чего началось
В сентябре я мерил восемь движков для локальной 27B и в итоге померил свой ноутбук. Выигрывал тогда не движок, а квант и то, сколько памяти остаётся системе. С тех пор вокруг Qwen3.8 на Mac поменялось достаточно, чтобы собрать второй заход.
Собирал не руками. Тему qwen-3-8 ведёт мой исследовательский CLI pro2you: он гоняет поиск по одобренным запросам, качает страницы, вытаскивает из них факты с цитатами и раз в раунд пересобирает сводку. В этот раунд я добавил запросы про oMLX 0.7.1 и квантизацию oQ, а ещё научил инструмент ходить в API Hugging Face: страницу модели он теперь читает не как HTML, а как карточку с лицензией, параметрами, файлами по форматам и списком квантов.
Итог раунда: 59 запросов, 161 новый адрес, 261 скачанная страница, плюс 493 факта. Всего по теме 5160 фактов. Ниже только то, у чего есть первоисточник. Где цифра чужая и никем не перепроверена, я так и пишу.
Что такое oQ
oQ - квантизатор, встроенный в oMLX. На выходе обычная MLX-модель в safetensors, её читают и mlx-lm, и mlx-vlm, свой загрузчик не нужен (документация oQ).
Главное отличие от обычного mlx-lm 4-bit - смешанная точность. Перед записью oQ прогоняет калибровочные промпты, меряет, насколько каждый слой страдает от квантования, и докидывает биты туда, где это помогает сильнее всего. Общий размер при этом держится около выбранного уровня.

| Уровень | Базовые биты | Бит на вес | Когда брать |
|---|---|---|---|
| oQ2 | 2 | 2.8-3.0 | огромные MoE, которые иначе не влезают |
| oQ3 | 3 | 3.5-3.7 | мало памяти |
| oQ4 | 4 | 4.6-4.7 | вариант по умолчанию |
| oQ5 | 5 | 5.5-5.7 | качество выше |
| oQ6 | 6 | 6.5-6.7 | очень близко к оригиналу |
| oQ8 | 8 | 8.5 | всё квантованное в 8 бит |
Есть ещё промежуточные oQ2.5, oQ2.7 и oQ3.5 для MoE. Для плотных моделей документация советует начинать с oQ3e: на 2 битах плотная модель теряет заметно больше, чем MoE.
Буква e в названии - это oQe. Раскладка битов та же, размер и скорость те же, отличается округление. oQe сначала собирает матрицу важности (imatrix, как в llama.cpp) и при округлении бережёт каналы, по которым идёт основной сигнал.
Что oQ не трогает или держит в повышенной точности:
- выходную голову и эмбеддинги - 8 бит, если позволяет бюджет;
- роутеры MoE - полная точность;
- энкодеры зрения и звука - не квантуются;
- нормы и параметры рекуррентного состояния (SSM, линейное внимание) - не квантуются;
- MTP-головы при
Preserve MTP weights- сохраняются, отсюда суффикс-mtpв названиях.
Суффикс -fp16 значит, что неквантованные веса и масштабы лежат в float16, а не в bfloat16.
Что поменялось в квантизации
Повод для захода - новость, что в 0.7.1.dev1 поменялся способ квантизации. В документации oQ есть раздел про это: в oQe добавили повторную подгонку масштаба и смещения взвешенными наименьшими квадратами, PR #4385. Подгонка повторяется несколько раз, и результат оставляют, только если ошибка падает.
| Модель | Оригинал | oQ3e до | oQ3e после | oQ4e до | oQ4e после |
|---|---|---|---|---|---|
| Qwen3.5-9B | 76.53 (18 GB) | 72.53 | 73.79 (4.6 GB) | 75.11 | 77.05 (5.6 GB) |
| Qwen3.6-35B-A3B | 82.68 (67 GB) | 81.58 | 82.68 (16 GB) | 82.79 | 83.05 (20 GB) |
Это замер автора oMLX: MMLU 1000, Winogrande 300, MBPP 300 и GSM8K 300, 1900 вопросов, greedy, без размышлений. По его же словам, KL-дивергенция к оригиналу падает на 12-15%.
Два замечания. Первое: oQ4e “после” выше оригинала у обеих моделей. Это значит, что разница в полбалла на 1900 вопросах лежит в шуме, и читать таблицу надо как “oQ4e почти не теряет”, а не как “квант лучше оригинала”. Второе: подгонка #4385 вошла в 0.7.1.dev1, это прямо сказано в release notes. Там же оговорка: меняются только новые конвертации. oQe-сборки, собранные раньше, подгонки не получают.
Ранняя версия того же сравнения, ещё без подгонки, есть для Qwen3.6-27B, ближайшего родственника нашей модели: оригинал 85.50 (55.6 GB), mlx-lm 4-bit 84.25 (16.1 GB), oQ4 84.50, oQ4e 84.81 (16.7 GB). То есть oQ4e забирает больше половины того, что теряет обычный 4-bit, ценой 0.6 GB.
Какие oQ-сборки Qwen3.8 есть на Hub
Цифры загрузок - из API Hugging Face, это число за последние 30 дней на 2026-10-11.
| Сборка | Загрузок за 30 дней |
|---|---|
Jundot/Qwen3.8-Flash-Next-oQ4e-mtp | 19 931 |
Jundot/Qwen3.8-27B-oQ4e-mtp | 9 886 |
Jundot/Qwen3.8-27B-oQ8e-mtp | 1 844 |
Jundot/Qwen3.8-27B-oQ6e-mtp | 1 608 |
Jundot/Qwen3.8-27B-oQ4e-fp16-mtp | 1 267 |
Jundot/Qwen3.8-27B-oQ8e-fp16-mtp | 693 |
Jundot - автор oMLX, это официальные сборки. Вокруг них уже выросло сообщество: thederby выложил полную линейку oQ4e-oQ8e для 27B, notnaurw - три варианта oQ5e с разными стратегиями раскладки битов (SAGE-PATHS, SAGE-ARCS, SAGE-CREST), scottlowry - oQ-сборки дообученных Swift-1.5 и ThinkingCap. У SAGE-CREST автор приводит KL на 128K контекста на 25% лучше равномерного 6-bit (карточка). Это его замер, я не перепроверял.
Почти все сборки сохраняют MTP-голову и визуальный энкодер. На SAGE-PATHS посчитали, что энкодер весит около 0.92 GB в bf16 и в чисто текстовой работе лежит мёртвым грузом (карточка). У oQ для этого есть опция Text Only.
Отдельно про Flash-Next. Это MoE на 125B, из них 6B активных, плюс n-gram таблица на 51B и MTP на 4B (карточка umixer). Самая скачиваемая oQ-сборка вообще - именно она. Цифры 45 tok/s на M4 Max и 25 tok/s на M2 Ultra для неё гуляют по статьям, но первоисточника с замером я не нашёл.
Мой замер: M4 Max 48 GB
Чужие таблицы ниже в основном про M5 и машины на 128 GB. У меня M4 Max с 40-ядерным GPU и 48 GB, из которых Metal отдаёт около 40 GB. Поэтому главное я перемерил сам, встроенным бенчмарком oMLX 0.7.1.dev1, тем же, что пишет в базу omlx.ai.
Сравнивал три варианта одной модели на одной версии движка:
mlx-communityQwen3.8-27B 4bit, 14.8 GB - обычный равномерный 4-bit;thederby/Qwen3.8-27B-oQ4e-mtp, 17.0 GB - oQ4e, собранный уже на 0.7.1.dev1, с новой подгонкой. Автор сборки пишет, что подгонка снизила KL к оригиналу с 0.0428 до 0.0358 против прошлых oQ4e этой модели;- он же с включённым MTP.
Контекст 1k, 4k, 16k и 32k, генерация 128 токенов, два набора текстов (проза и Python), каждый прогон дважды. Docker и лишние модели перед замером выгрузил: в первой попытке защита памяти oMLX обрывала длинные контексты.
Проза (novel_en), среднее двух прогонов, в скобках разброс:
| Вариант | Контекст | PP, tok/s | TG, tok/s | Пик памяти |
|---|---|---|---|---|
| MLX 4bit | 1k | 254 | 28.3 | 17.0 GB |
| 4k | 262 | 28.0 | 18.3 GB | |
| 16k | 248 | 26.0 | 19.2 GB | |
| 32k | 230 | 24.5 | 20.4 GB | |
| oQ4e без MTP | 1k | 250 | 27.8 | 17.6 GB |
| 4k | 257 | 26.4 | 18.9 GB | |
| 16k | 250 | 25.1 | 19.8 GB | |
| 32k | 233 | 23.8 | 21.0 GB | |
| oQ4e с MTP | 1k | 249 | 33.8 (31.3-36.2) | 19.7 GB |
| 4k | 255 | 34.9 (34.2-35.6) | 21.0 GB | |
| 16k | 241 | 40.3 (38.8-41.7) | 22.0 GB | |
| 32k | 229 | 29.3, один прогон | 23.3 GB |
Время до первого токена на 32k у всех трёх одинаковое: 140-146 секунд.
Что из этого следует.
Сам квант скорости не даёт. Без MTP oQ4e на 2-6% медленнее равномерного 4-bit по генерации, префилл тот же. Файл на 2.2 GB больше, а генерация на Mac упирается в пропускную способность памяти. Часть разницы может быть и от нагрева: 4-bit шёл первым в каждой серии, а к oQ4e чип уже уходил в тепловое состояние 2. oQ4e берут ради качества, а не ради скорости.
Скорость даёт MTP. На прозе плюс 20-60%: 34-40 tok/s вместо 25-28. На 32k выигрыш падает до 23%. Цена - около 2.3 GB памяти сверху. На 48 GB это заметно: первый прогон MTP на 32k защита памяти oMLX остановила, не хватило 0.27 GB.

На коде цифры MTP не годятся для выводов. На Python я получил 50-55 tok/s на 1k, 40 и 82 tok/s в двух прогонах на 16k и 101-106 tok/s на 32k. Генерация идёт всего 128 токенов, и на длинном куске Python-кода модель, похоже, продолжает то, что уже было в промпте. Черновик MTP тогда угадывается почти всегда. Без MTP на тех же текстах обе сборки дают 23-28 tok/s, как на прозе.
Qwen3.8-Flash-Next на 48 GB не влезает ни в каком oQ. oQ3 весит около 84 GB, oQ4e ещё больше. Все красивые цифры Flash-Next ниже - для машин от 128 GB.
Для сравнения: база прогонов oMLX
У oMLX есть открытая база замеров: omlx.ai/benchmarks. Пользователи прогоняют встроенный бенчмарк и отправляют результат, на 2026-10-11 там 524 164 записи. В таблице сайта есть чип, память, модель, квант, длина контекста, скорость префилла (PP) и генерации (TG). Версии oMLX и времени до первого токена в общем списке нет, они есть только на странице отдельного прогона.
Ниже - все прогоны Qwen3.8, которые попали в первую страницу фильтра по модели. Каждая строка - один чужой прогон, повторов и разброса нет. Чип и память указаны в первой строке группы, ниже идут прогоны той же машины на другом контексте.
Qwen3.8-27B
| Чип | Память | Сборка | Контекст | PP, tok/s | TG, tok/s |
|---|---|---|---|---|---|
| M4 Pro 20c | 48 GB | MLX 4bit | 1k | 127 | 15.4 |
| 4k | 132 | 15.0 | |||
| M4 Pro | 48 GB | MLX 4bit | 16k | 94 | 17.4 |
| M5 Pro 16c | 48 GB | OptiQ 4bit, uncensored | 1k | 190 | 8.4 |
| 4k | 215 | 8.1 | |||
| M5 Pro 20c | 64 GB | MTPLX 4bit | 32k | 413 | 10.6 |
| M4 Max 40c | 128 GB | MLX 4bit | 4k | 249 | 27.4 |
| 16k | 227 | 24.7 | |||
| M5 Max 40c | 128 GB | MLX 4bit | 4k | 1007 | 32.0 |
| 16k | 886 | 30.6 | |||
| M5 Max 40c | 128 GB | oQ4e-mtp, heretic-ara | 1k | 728 | 48.1 |
| 4k | 879 | 54.1 | |||
| 8k | 803 | 62.6 | |||
| 16k | 755 | 73.0 | |||
| M5 Max 40c | 64 GB | oQ4e-mtp | 16k | 966 | 83.6 |
| 128k | 619 | 47.6 | |||
| M5 Ultra 80c | 96 GB | oQ4e-mtp | 1k | 1392 | 52.2 |
| 4k | 1559 | 50.8 |
Строки M4 Pro на 16k и M4 Max взяты со страниц отдельных прогонов (M4 Pro, M4 Max), там же видно версию: oMLX 0.5.7 для M4 Max. На M4 Pro 16k первый токен пришёл через 2.9 минуты.
Qwen3.8-Flash-Next
| Чип | Память | Сборка | Контекст | PP, tok/s | TG, tok/s |
|---|---|---|---|---|---|
| M3 Max 40c | 128 GB | oQ4e-mtp | 1k | 454 | 47.4 |
| 4k | 568 | 32.2 | |||
| M5 Max 40c | 128 GB | oQ3-mtp | 1k | 502 | 52.4 |
| 4k | 871 | 61.2 | |||
| 8k | 1401 | 61.6 | |||
| 16k | 1715 | 73.8 | |||
| M5 Max 40c | 128 GB | oQ4e-mtp | 1k | 1130 | 76.7 |
| 4k | 2022 | 82.0 | |||
| 16k | 2633 | 84.5 | |||
| 64k | 2406 | 70.1 | |||
| 128k | 2311 | 62.0 | |||
| 195k | 2313 | 73.5 | |||
| M5 Max 40c | 128 GB | oQ8e-mtp | 64k | 398 | 11.3 |
| 64k | 404 | 7.5 | |||
| M3 Ultra 60c | 256 GB | oQ6e-mtp | 4k | 1019 | 87.9 |
| 128k | 993 | 57.5 | |||
| M3 Ultra 80c | 512 GB | oQ6e-mtp, uncensored | 1k | 1022 | 84.7 |
| 4k | 1320 | 79.6 | |||
| 195k | 1275 | 64.6 |
Что я из этого забираю.
Префилл на M5 вырос в разы, генерация - нет. MLX 4bit на одном и том же 4k: M4 Max даёт 249 tok/s префилла, M5 Max - 1007, вчетверо больше. Генерация при этом 27.4 против 32.0, плюс 17%. Префилл упирается в вычисления, генерация - в пропускную способность памяти, и M5 прибавил в первом. Для агента, который каждый раз читает длинный контекст, это важнее, чем tok/s в чате. Оговорка: это разные люди и, скорее всего, разные версии oMLX.
oQ4e-mtp против обычного 4bit - в 2-2.7 раза по генерации. На M5 Max на 16k: MLX 4bit 30.6 tok/s, oQ4e-mtp 73.0 и 83.6 в двух разных прогонах. Это не чистый эффект кванта: в -mtp сборках работает спекулятивное декодирование, и именно оно даёт основной прирост. Сколько из разницы приходится на oQ, а сколько на MTP, по этой базе не разделить.
Flash-Next почти не замечает длину контекста. На M5 Max с oQ4e-mtp генерация держится в коридоре 62-85 tok/s от 1k до 195k токенов, префилл - выше 2000 tok/s. У плотной 27B на том же чипе oQ4e-mtp на 128k падает до 47.6. Это сходится с архитектурой: у Flash-Next 6B активных параметров и три четверти слоёв на линейном внимании (Gated DeltaNet), KV-кэш растёт только у 12 слоёв из 48.
oQ8e Flash-Next на 128 GB - ловушка. 7.5-11.3 tok/s против 70 у oQ4e на том же M5 Max. По моей оценке, 8-битная сборка модели на 180B параметров вместе с n-gram таблицей не помещается в 128 GB, и oMLX подкачивает веса. В базе это не подписано, вывод мой.
Внизу линейки 27B на грани. M4 Pro и M5 Pro с 48 GB дают 8-15 tok/s, а OptiQ на M5 Pro 16c медленнее, чем MLX 4bit на M4 Pro. Почему так, по строке в базе не понять.
Про MTP есть и обратный пример. У halfpennymac на M4 Pro MTP в llama.cpp замедлила генерацию на 26%, с 10.7 до 7.9 tok/s, хотя черновик принимался в 65% случаев. Так что “-mtp всегда быстрее” - это про oMLX на чипах с запасом пропускной способности, а не правило.
Мой замер: модель-решение через oMLX
Тут я проверил сам. В pro2you релевантность найденной страницы решает не чат-модель, а модель-решение: она читает текст один раз и возвращает вероятность для каждого варианта ответа, ничего не генерируя. Я использую clef-flash от Cloudflare (Qwen3.5-9B плюс голова решения) через /v1/systemone в llama.cpp, квант GGUF Q4_K_M.

В oMLX 0.7.1.dev1 появилась поддержка тех же моделей через тот же /v1/systemone (#4315). В заметке к релизу отдельно сказано, что oQ оставляет голову clef нетронутой, и есть готовые Jundot/clef-oQ4e и Jundot/clef-flash-oQ4e.
Набор: 600 адресов, размеченных тремя моделями-судьями (qwen3.8-27b, gemma-4-31b, gpt-oss-120b), правильный ответ - решение большинства. Вопрос один: “по теме ли страница”. Запросы идут последовательно, на M4 Max 48 GB.
| clef-flash Q4_K_M, llama.cpp | clef-flash-oQ4e, oMLX | clef-oQ4e (27B), oMLX | |
|---|---|---|---|
| AUC по большинству судей | 0.951 | 0.982 | 0.986 |
| AUC там, где судьи единогласны | 0.985 | 0.997 | 1.000 |
| Точность при пороге 0.5 | 0.910 | 0.908 | 0.907 |
| Лучший порог и точность при нём | 0.50 / 0.910 | 0.29 / 0.928 | 0.17 / 0.933 |
| Вероятность “да”: 5%, медиана, 95% | 0.29 / 0.43 / 0.75 | 0.01 / 0.05 / 0.96 | 0.01 / 0.07 / 0.98 |
| Время на запрос: медиана / p95 | 0.60 / 0.88 с | 0.55 / 0.89 с | 2.35 / 4.73 с |
Самое интересное не в AUC, а в предпоследней строке. У llama.cpp вероятности сжаты в коридор от 0.29 до 0.75: модель “почти никогда не уверена”. У oQ4e они разъезжаются от 0.01 до 0.96. Похоже, это и есть сохранённая голова: в GGUF Q4_K_M она квантуется вместе со всем остальным, и шкала размазывается. Проверить это напрямую я не могу - сравнивать надо ту же голову в fp16 внутри GGUF, такой сборки у меня нет.
Практическое следствие: порог из старого конфига переносить нельзя. При 0.5 clef-flash-oQ4e почти не ошибается в “да” (аккуратность 0.991), но пропускает каждую пятую страницу по теме (полнота 0.800). Порог приходится опускать:
| Порог | Полнота | Аккуратность | Точность |
|---|---|---|---|
| 0.05 | 0.962 | 0.847 | 0.907 |
| 0.10 | 0.921 | 0.900 | 0.920 |
| 0.20 | 0.864 | 0.950 | 0.920 |
| 0.29 | 0.849 | 0.987 | 0.928 |
| 0.50 | 0.800 | 0.991 | 0.908 |
Для фильтра находок мне важнее не потерять страницу по теме, чем пропустить лишнюю, поэтому беру 0.1: полнота выше, чем у llama.cpp при 0.5 (0.921 против 0.868), при сопоставимой аккуратности (0.900 против 0.924).
27B-версия clef почти не добавляет: AUC 0.986 против 0.982, а на единогласных адресах она не ошиблась ни разу. Но каждый запрос в четыре раза дольше. Для фильтра, который гоняет сотни страниц за прогон, это плохой обмен.
У clef-flash скорость одинаковая на обоих движках, ошибок 500 за 600 запросов ни одной ни у одной из oMLX-моделей. У llama.cpp они случались, поэтому в конфиге стояли повторы.
Чего я не нашёл
- Сравнения качества oQ4e и
mlx-lm 4-bitдля Qwen3.8-27B на задачах нет ни у кого. Есть KL от автора сборки и задачи для Qwen3.6-27B от автора oMLX. Свой замер качества я ещё не делал, только скорость. - Сравнение 4-bit и oQ4e в базе omlx.ai смешивает железо, версии и MTP, поэтому таблица “для сравнения” - справка, а не вывод.
Что я забрал себе
- Модель-решение в pro2you переедет на
clef-flash-oQ4eв oMLX с порогом 0.1, llama.cpp останется запасным. - bge-m3 в oMLX отдаёт те же векторы, что llama.cpp, косинус 1.0000 на проверочных текстах, так что кэш эмбеддингов переживёт переезд.
- На M4 Max 48 GB для Qwen3.8-27B беру oQ4e с MTP: на прозе 34-40 tok/s вместо 26-28, а запас памяти до 32k контекста ещё есть.
- Для следующего захода: качество oQ4e против
mlx-lm 4-bitна моих задачах встроенным бенчмарком точности oMLX, с официальным шаблоном чата. И EmbeddingGemma 2, которую oMLX 0.7.1.dev1 уже умеет.