Как мы запустили Ornith‑35B на ноутбуке с 8 ГБ VRAM и разогнали её до 99 ток/с на AMD Strix Halo

от автора

Согласно опубликованным бенчмаркам, Ornith‑1.0‑35B показала себя лучше Qwen3.5‑397B‑A17B в тех тестах, где модель должна не просто написать код, а самостоятельно работать в терминале и пользоваться инструментами. На Terminal‑Bench 2.1 с Terminus‑2 разница составила 64,2% против 53,5%.

Benchmark

Ornith‑1.0‑35B

Qwen3.5‑397B

Ornith − Qwen

Terminal‑Bench 2.1, Terminus‑2

64,2

53,5

+10,7

Terminal‑Bench 2.1, Claude Code

62,8

48,6

+14,2

SWE‑bench Verified

75,6

76,4

−0,8

SWE‑bench Pro

50,4

51,6

−1,2

SWE‑bench Multilingual

69,3

69,3

0,0

NL2Repo

34,6

36,8

−2,2

ClawEval Avg

69,8

70,7

−0,9

SWE Atlas — QnA

37,1

20,4

+16,7

SWE Atlas — RF

29,7

18,4

+11,3

SWE Atlas — TW

27,8

18,5

+9,3

Ornith не сильнее Qwen во всех задачах: на SWE‑bench Verified, SWE‑bench Pro, NL2Repo и ClawEval Qwen немного впереди. Но для локального coding‑агента результат выглядел интересно. Ornith занимает на порядок меньше памяти и обучалась именно под agentic coding — работу с терминалом, инструментами, памятью и повторными попытками после ошибок.

Мы проверили модель на открытом срезе из 43 задач. В него вошли tool calling, генерация кода, исправление репозиториев, работа с памятью и длинным контекстом:

Класс

Результат Ornith

SimpleQA

4/5

BFCL Orion Safe

9/9

BFCL Memory

1/1

HumanEval+

17/17

PersistBench

3/3

NIAH

4/4

SWE‑bench Python

2/2

SWE‑bench Multilingual

0/1

TheAgentCompany

0,8333/1

Итого

40,8333/43

Локальный прогон подтвердил, что модель хорошо подходит для агентного кодинга.

После этого появилась другая мысль: Ornith явно можно ускорить на Ubuntu‑машине с Ryzen AI MAX+ 395, Radeon 8060S и пропускной способностью памяти до 256 ГБ/с.

Модель занимала около 19–21 ГБ, помещалась в общей памяти целиком, но выдавала только 68–74 ток/с.

Для увеличения скорости нужно было перебрать следующие гипотезы:

  • изменить K‑quant matvec и число строк, обрабатываемых одним Vulkan dispatch;

  • объединить MUL_MAT_ID dispatch и подобрать размер workgroup;

  • проверить graphics queue вместо compute queue;

  • подобрать batch и ubatch;

  • уменьшить KV‑cache с Q8 до Q4;

  • добавить MTP и подобрать глубину draft;

  • селективно переквантовать самые тяжёлые expert‑тензоры;

  • использовать exact n‑gram reuse на повторяющемся agent/code‑трафике.

Один Vulkan‑патч дал меньше процента. Рабочий результат получился только из комбинации MTP, n‑gram reuse, селективной квантизации и настроек runtime: 79,661 ток/с на полном 43‑task прогоне и 99,088 ток/с на повторяемом code‑потоке без потери балла.

Железо и исходная скорость

Для получения убедительного результата в работе участвовали две разные машины:

Компонент

Ubuntu‑сервер

Windows‑ноутбук

CPU/APU

AMD Ryzen AI MAX+ 395

Intel Core i7‑13650HX

GPU

Radeon 8060S, Vulkan gfx1151

RTX 4060 Laptop, 8 GiB

RAM

128 GiB DDR5 UMA

63,74 GiB

Runtime

llama.cpp b9994, Vulkan

llama.cpp b10066 CUDA; LM Studio 2.14.0

Контекст

131 072, один slot

16 384, один slot

Для чего использовалась

постоянный agent endpoint

локальная разработка и LM Studio

На сервере модель целиком помещалась в общей памяти CPU и Radeon. Это избавляло от PCIe paging, но не превращало DDR5 в HBM. Ornith — MoE‑модель: на каждом токене включается только часть экспертов, однако их веса всё равно приходится читать из памяти. При batch 1 арифметики относительно мало, а чтений много. В результате GPU может показывать высокую загрузку, пока decode ограничен трафиком весов, деквантизацией и маршрутизацией экспертов.

В первом общем замере исходный Ornith-1.0-35B-Q4_K_M выдавал около 68,7 ток/с на длинном decode. После обновления llama.cpp и выравнивания параметров короткий контрольный тест показал 74,066 ток/с. Напрямую сравнивать эти значения нельзя: бинарники и конфигурации различались. Все проценты дальше относятся только к парным A/B‑прогонам.

Целью был максимально быстрый batch‑1 decode при контексте 131 072 и сохранении качества на агентных задачах. Для сравнения изменений использовались только парные A/B‑прогоны.

Чтобы не получить красивую цифру только на коротком тесте, мы использовали одинаковые prompt, прогрев, повторные запуски и серверную метрику timings.predicted_per_second. На полном агентном прогоне считался weighted decode:

weighted_decode = сумма сгенерированных токенов / сумма decode-времени

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

Качество проверялось на том же срезе Orion с одинаковыми task ID и offset. Состав и исходный результат приведены в таблице в начале статьи.

Все изменения Vulkan перед замерами проходили test-backend-ops: 3160 успешных тестов для четырёх значений row merge и ещё 790 для отдельного MUL_MAT_ID‑патча.

Попытки ускорить Ornith на Ubuntu

Логично было начать с matvec: MoE постоянно читает квантованные expert weights, значит небольшой выигрыш в K‑quant ядре должен повторяться на каждом токене. В форк добавили runtime‑переключатель GGML_VK_DMMV_RM_KQ, чтобы проверять row merging 1/2/4/8 без пересборки.

В изолированном тесте без MTP rows=1 показал 75,886 против 75,400 ток/с чистого b9994 — всего +0,64%. После включения MTP профиль нагрузки изменился, и лучшим снова оказался исходный rows=2:

rm_kq

Mean, ток/с

Min

Max

1

87,029

86,583

87,832

2

87,552

87,162

87,779

4

86,717

86,285

87,167

8

86,120

85,270

87,502

Укрупнение tile уменьшает число dispatch, но увеличивает live state и давление на регистры. На RDNA это снижает число одновременно работающих waves, поэтому «больше строк за раз» не обязательно быстрее.

Другие варианты тоже не дали нужного скачка:

Эксперимент

Результат

Что сделал

MUL_MAT_ID token‑batch Z‑dispatch fusion

87,214 против 87,591 ток/с

отклонил

RDNA3 workgroup 256

74,556 ток/с, выше power

отклонил

rows=4, без MTP

74,418 ток/с

отклонил

rows=8, без MTP

73,099 ток/с

отклонил

Q4_K/Q6_K activation hoist

75,535 ток/с

положительно, но слабее rows=1

Один Vulkan‑патч не мог дать целевые 96 ток/с, потому что почти не менял главный объём работы — чтение весов экспертов. Измерительный переключатель и отрицательные результаты остались в форке, но выдавать доли процента за большой прорыв не имело смысла.

Затем проверили очередь и размер ubatch. При ubatch=1024 graphics queue оказалась быстрее стандартной compute queue:

Тест

Compute queue

Graphics queue

Изменение

PP 8K

1053,044 ток/с

1080,212 ток/с

+2,58%

Decode 512

73,079 ток/с

75,886 ток/с

+3,84%

Самый большой ubatch не победил:

Ubatch

PP 8K

Средняя мощность

256

833,183 ток/с

138,0 Вт

512

1011,601 ток/с

131,7 Вт

1024

1080,212 ток/с

120,4 Вт

2048

1010,463 ток/с

106,4 Вт

В рабочем профиле остались:

GGML_VK_ALLOW_GRAPHICS_QUEUE=1GGML_VK_DISABLE_COOPMAT=1GGML_VK_MAX_NODES_PER_SUBMIT=50
--ctx-size 131072--parallel 1--batch-size 2048--ubatch-size 1024--flash-attn on--cache-type-k q8_0--cache-type-v q8_0--n-gpu-layers all--device Vulkan0--no-mmap

Логично было попробовать и Q4 KV‑cache: меньше байт должно помочь на длинном контексте. На 62 630 входных и 512 выходных токенах результат оказался хуже:

KV‑cache

Prefill

Decode

Q8_0/Q8_0

524,16 ток/с

70,29 ток/с

Q4_0/Q4_0

484,73 ток/с

67,67 ток/с

Q4 проиграл 7,5% по prefill и 3,7% по decode. Для этого Vulkan backend экономия памяти не покрыла стоимость деквантизации и работы с scale/metadata, поэтому в production остался Q8. На CUDA, HIP или Metal результат может быть другим.

Что действительно сработало

После опытов с ядрами стало понятно, что нужно не быстрее выполнять тот же объём работы, а уменьшать его.

Первой частью стала совместимая MTP‑головка. Ornith и Qwen3.6‑35B‑A3B MTP‑only используют архитектуру qwen35moe. Скрипт graft-ornith-mtp.py берёт дополнительный prediction‑слой из MTP‑GGUF, проверяет имена тензоров, копирует базовые блоки и записывает nextn_predict_layers=1. Это не fine‑tuning и не новая обученная модель: к target добавляется совместимая голова для speculative decoding.

Глубокий draft здесь оказался невыгоден. Для MoE лишние draft‑токены способны включить новые маршруты экспертов, которые target потом всё равно должен проверить. Лучшей стала глубина 2. Порог p_min тоже выбирался замерами:

p_min

Mean decode, ток/с

0,00

87,326

0,05

88,145

0,10

87,561

0,20

89,235

0,30

86,986

0,40

85,196

0,50

86,566

Финальные параметры speculation:

--spec-type ngram-mod,draft-mtp--spec-draft-n-max 2--spec-draft-n-min 1--spec-draft-p-min 0.20--spec-ngram-mod-n-min 48--spec-ngram-mod-n-max 64--spec-ngram-mod-n-match 24

Вторая часть — селективная квантизация. Двадцать ffn_down_exps‑тензоров перевели в Q4_0, остальные оставили по плану Q4_K_M. Target уменьшился с 20 681,07 до 19 377,09 MiB, средняя плотность — с 4,89 до 4,58 BPW.

llama-quantize \  --tensor-type-file scripts/ornith-down-experts-q4_0.txt \  ornith-1.0-35b-MTP-graft-Q4_K_M.gguf \  ornith-1.0-35b-MTP-graft-down-Q4_0.gguf \  Q4_K_M

Финальный файл:

ornith-1.0-35b-MTP-graft-down-Q4_0.gguf20 329 342 112 bytes (18,933 GiB)SHA-256: 365a7c02dfd320b9696f189d6dc12bd2b0eabb9f8e58ba9fc8cab3af93c0234b

Третья часть — exact n‑gram reuse. Агент постоянно повторяет system prompt, tool schemas, фрагменты кода и вывод инструментов. ngram-mod предлагает уже встречавшуюся последовательность, а target её проверяет. В одном SWE‑turn aggregate acceptance достиг 93,5%, в среднем принимался 61 n‑gram токен, а короткие интервалы доходили до 155–176 ток/с. На новом нерегулярном 11K‑ответе было около 65 ток/с. Поэтому 177,48 ток/с — реальный пик, но не постоянная скорость модели.

Speculative decoding остаётся target‑verified: ошибочный draft отклоняется. Селективная квантизация, напротив, меняет сам target численно, поэтому после неё обязательно требовался отдельный тест качества.

Результат на Ubuntu

По шагам скорость складывалась так:

Конфигурация

Mean

Min

Max

К baseline

Исходная Q4_K_M, speculation off

74,066

baseline

Integrated MTP, depth 2

89,235

89,125

89,344

+20,5%

Hybrid experts + MTP

90,252

89,612

91,936

+21,8%

Hybrid + MTP + n‑gram, repeated code

99,088

90,367

116,945

+33,8%

99 ток/с — среднее пяти прогонов повторяемого code‑потока, а не новая нижняя граница. Для production важнее полный одинаковый прогон 43 задач:

Метрика

Архивный Q4_K_M

Hybrid MTP

Изменение

Requests

232

223

траектории агента различались

Prompt tokens

699 947

668 344

−4,5%

Generated tokens

68 447

73 700

+7,7%

Weighted decode

61,446 ток/с

79,661 ток/с

+29,6%

Mean request decode

65,681 ток/с

83,944 ток/с

+27,8%

P50 request decode

68,45 ток/с

84,84 ток/с

+23,9%

P10 request decode

56,58 ток/с

56,51 ток/с

−0,1%

Maximum request decode

75,03 ток/с

177,48 ток/с

зависит от запроса

Wall time

72 мин 42 с

36 мин 11 с

−50,2%

Score

40,8333/43

40,8333/43

одинаково

Wall time сократился вдвое, но приписывать все 50,2% только быстрому decode нельзя. Агент прошёл разные траектории: запросов было 232 против 223, изменилось число входных и выходных токенов. Сопоставимая цифра здесь — +29,6% weighted decode.

По классам качество не изменилось:

Класс

Архивный

Hybrid MTP

SimpleQA

4/5

4/5

BFCL Orion Safe

9/9

9/9

BFCL Memory

1/1

1/1

HumanEval+

17/17

17/17

PersistBench

3/3

3/3

NIAH

4/4

4/4

SWE‑bench Python

2/2

2/2

SWE‑bench Multilingual

0/1 (AGENT_DEAD)

0/1 (INCORRECT)

TheAgentCompany

0,8333/1

0,8333/1

В SWE Multilingual hybrid хотя бы дошёл до grader вместо AGENT_DEAD, но балл остался нулевым. Общий итог обеих версий — 40,8333/43.

После Ubuntu — перенос на Windows

Когда профиль стабильно заработал на Ubuntu, логично было попробовать ту же модель на обычном ноутбуке. Здесь задача была уже не приблизиться к пропускной способности памяти, а вообще разумно разместить 35B при 8 ГБ VRAM и получить пригодную для работы скорость.

Сначала собрали и запустили патченный Vulkan‑вариант llama.cpp под Windows. Модель загрузилась, а настройки дали небольшой прирост, но повторить Ubuntu‑эффект было невозможно: здесь всё определяло размещение 35B между 8 ГБ VRAM и системной памятью. Для численного A/B использовали официальный CUDA‑контейнер b10066 — так baseline и hybrid сравнивались на одном runtime.

Обычный split на 15 GPU layers выглядел терпимо только на коротком prompt. На реалистичном 1K скорость падала примерно до 1,2 ток/с: MoE‑эксперты оказывались разрезаны между CPU и GPU, и обмен данными съедал весь throughput.

Рабочей оказалась другая схема:

--ctx-size 16384--parallel 1--n-gpu-layers all--cpu-moe--no-mmap--batch-size 2048--ubatch-size 512--flash-attn on--cache-type-k q8_0--cache-type-v q8_0

Attention и плотные части остаются на RTX 4060, крупные MoE‑эксперты — в host RAM. Модель не помещается целиком в VRAM, но перестаёт постоянно гонять промежуточные данные через неудачную границу слоёв.

На официальном CUDA‑контейнере llama.cpp b10066 (86a9c79f8) получились такие значения; везде генерировалось по 128 токенов:

Профиль

Decode 1K

Decode 8K

Repeated code

PP 8K

Baseline Ornith Q4_K_M

5,57 ток/с

23,90 ток/с

21,95 ток/с

341,48 ток/с

Hybrid, speculation off

29,70 ток/с

29,59 ток/с

26,75 ток/с

365,26 ток/с

Hybrid, MTP + n‑gram

28,74 ток/с

66,88 ток/с

67,23 ток/с

354,19 ток/с

Первый baseline 1K — холодный выброс из‑за expert cache/thrashing, поэтому обещать ускорение в 5,3 раза по нему нельзя. На более показательных 8K hybrid без speculation быстрее baseline на 23,8%, а на repeated code — на 21,8%. MTP особенно хорошо работает на предсказуемом потоке, но на коротком неповторяемом 1K оказался на 3,2% медленнее no‑spec.

По ресурсам схема спокойно уложилась в доступные 8 ГБ VRAM. Host RAM ниже — память всей системы, не только RSS процесса:

Профиль

Температура

GPU util

VRAM

Мощность

Host RAM peak

Baseline Q4_K_M

71 °C

98%

2342 MiB

49,34 Вт

44,48 GiB

Hybrid, speculation off

69 °C

98%

2234 MiB

45,80 Вт

42,64 GiB

Hybrid, MTP + n‑gram

69 °C

98%

2672 MiB

46,47 Вт

42,77 GiB

Hybrid‑файл на 798,6 MiB меньше baseline. MTP добавил около 438 MiB VRAM относительно no‑spec, но общий расход остался небольшим.

После raw‑тестов прогнали четыре одинаковых workspace с теми же prompt, verifier, approval=auto, лимитом 24 шага и timeout 900 секунд:

Сценарий

Baseline

Hybrid

Baseline time

Hybrid time

Экономия

Java service coverage

80%, 8/10

80%, 8/10

323,9 с

322,4 с

0,4%

Kafka Node order pipeline

75%, 6/8

75%, 6/8

282,6 с

185,7 с

34,3%

Rabbit retry / DLQ

75%, 6/8

75%, 6/8

466,0 с

324,7 с

30,3%

Kafka Java outbox

80%, 8/10

80%, 8/10

382,4 с

313,4 с

18,1%

Итого

77,5%, 28/36

77,5%, 28/36

1454,9 с

1146,2 с

21,2%

Обе версии пропустили одни и те же проверки cli_exit_zero и final_contract: изменения и deterministic tests были выполнены, но агент завершил цикл без обязательного финального контракта Orion. Это общий сбой завершения агента, а не регрессия новых весов.

LM Studio не приняла полную модель

После удачного запуска через llama.cpp попробовали открыть тот же GGUF в LM Studio. Полная версия не загрузилась: runtime LM Studio 2.14.0 не поддержал MTP metadata и дополнительный prediction‑слой.

Просто переименовать metadata было нельзя. Получился бы файл, который, возможно, открывается, но неверно интерпретирует структуру модели. Поэтому для LM Studio пришлось убрать MTP‑голову и оставить тот же селективно оптимизированный 40‑слойный target:

Артефакт

Блоков

Тензоров

Размер

Integrated MTP

Full MTP

40 target + MTP layer

base + MTP

20 329 342 112 bytes

да

LM Studio compatible

40

733

19 782 637 440 bytes

нет

Это не другая обученная модель и не ухудшенная копия весов. Из неё удалена только часть, которую текущий runtime LM Studio не умеет использовать. Цена совместимости — отсутствие speculative boost.

Нативный /api/v0/completions в LM Studio показал:

Prompt

Decode mean

Cold TTFT

Warm‑cache TTFT

1056 токенов

33,48 ток/с

4,296 с

0,139 с

8448 токенов

34,77 ток/с

23,160 с

0,166 с

Repeated 8K code

33,25 ток/с

0,151–0,170 с

Температура

GPU util

VRAM

Мощность

Host RAM used

66 °C

91%

2329 MiB

45,1 Вт

44,10 GiB

LM Studio был быстрее b10066 no‑spec на том же наборе тензоров по raw decode: +12,7% на 1K, +17,5% на 8K и +24,3% на repeated code. Но в Orion картина оказалась другой:

Сценарий

Score

Checks

Wall time

Java service coverage

80%

8/10

900,0 с, outer timeout

Kafka Node order pipeline

75%

6/8

383,2 с

Rabbit retry / DLQ

75%

6/8

363,4 с

Kafka Java outbox

80%

8/10

530,9 с

Итого

77,5%

28/36

2177,6 с (36:17,6)

Качество снова совпало, но end‑to‑end время выросло. Orion повторял verification‑guard шаги, а Java‑сценарий дошёл до внешнего timeout уже после изменений и тестов. Это одна из причин не ранжировать агентные runtime только по tok/s.

Все финальные профили в одной таблице

Платформа / профиль

Типичный измеренный результат

Лучший предсказуемый поток

Quality

Ubuntu, baseline Q4_K_M

61,446 weighted tok/s

74,066 в microbench

40,8333/43

Ubuntu, Hybrid MTP

79,661 weighted tok/s

99,088 mean, 177,48 peak

40,8333/43

Windows CUDA, baseline

23,90 tok/s на 8K

21,95 repeated

77,5%, 28/36

Windows CUDA, Hybrid no‑spec

29,59 tok/s на 8K

26,75 repeated

77,5%, 28/36

Windows CUDA, Hybrid MTP/ngram

28,74–66,88 в зависимости от acceptance

67,23 repeated

77,5%, 28/36

Windows LM Studio, no‑MTP

34,77 tok/s на 8K

33,25 repeated

77,5%, 28/36

Где результаты могут отличаться

Тестировались один Strix Halo и один ноутбук; на Ubuntu использовался llama.cpp b9994, на Windows — b10066 и LM Studio 2.14.0. Основной quality‑срез содержит 43 задачи, ноутбучный — четыре coding‑сценария. Повторяемый code‑тест запускался пять раз.

99 ток/с — результат предсказуемого потока, не скорость на любом запросе. При prompt 62,6K получилось 70,29 ток/с, а на 128K значение может быть ещё ниже. Время агентной задачи зависит также от числа шагов, инструментов, verifier и timeout.

Target‑verified speculation не меняет принятый вывод модели, но селективная квантизация меняет сам target — поэтому понадобился отдельный quality A/B. Полностью сопоставимого энергетического A/B для финального Ubuntu‑профиля нет, поэтому улучшение энергоэффективности не заявляется.

Код, модели и логи

Новые замеры и практические рецепты запуска, cсылки на полную MTP‑версию и отдельный GGUF для LM Studio публикуются в Telegram‑канале Vibe Orion.

Что получилось в итоге

В начале мы ожидали, что основное ускорение придёт из Vulkan. Оно оказалось почти незаметным. Для batch‑1 MoE важнее было сократить движение весов и не заставлять target заново генерировать то, что можно безопасно предсказать или точно взять из контекста.

На Ubuntu комбинация MTP, n‑gram reuse, селективной квантизации и настроек runtime дала +29,6% weighted decode без изменения балла 43‑task среза. После этого ту же модель удалось запустить на Windows‑ноутбуке с 8 ГБ VRAM: --cpu-moe сократил время четырёх coding‑задач на 21,2% при том же качестве. Для LM Studio пришлось убрать встроенную MTP‑голову и выпустить отдельный совместимый GGUF.

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

ссылка на оригинал статьи https://habr.com/ru/articles/1060632/