Один Beelink GTR9 Pro на Ryzen AI Max+ 395 выдал 236 tok/s суммарной генерации на 32 одновременных запросах в коротких прогонах и удержал в среднем 226 tok/s за 30 минут непрерывной нагрузки без тротлинга. По дороге к этим числам я нашёл воспроизводимый провал пропускной способности, который сначала выглядел свойством одной модели, увидел, как спекулятивный декодинг на моём стеке превращается из ускорителя в налог, и трижды чуть не опубликовал неверные выводы. Каждый раз спасали контрольные замеры, все три истории здесь, в статье.

Харнессы, конфиги, полные таблицы, значения по каждому ключевому и финальному прогону, поминутные ряды выносливости и телеметрия лежат в открытом репозитории. Логи отдельных запросов харнесс не вёл, поэтому пересчитать можно всё до уровня прогона, но не глубже. Числа сняты на одном конкретном стенде; что из этого переносится на другие стеки, а что нет, оговорено по ходу текста.

С чего это началось

В ленте попался разбор от ciru.ai: они выжали из такого же Strix Halo 242 tok/s в своей метрике Active weighted TG/s, запустив Gemma 4 26B в 32 серверных слота со спекулятивным драфтом. А под моей прошлой статьёй про эти коробки сидел комментатор с одним аргументом: ваша железка не потянет много параллельных запросов, это вам не серверный GPU. С классификацией спорить не о чем, Radeon 8060S в серверной стойке не живёт. Но «не потянет» уже утверждение о числах: сколько запросов, на какой модели, с какой скоростью и где предел. А числа проверяются замером, и эта коробка, забегая вперёд, оказалась заметно интереснее своего ярлыка.

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

Сразу о терминах, чтобы не путаться. Клиентами я называю параллельные потоки нагрузки со стороны тестирующей машины: 32 клиента дают 32 одновременных запроса. Слотами называю параллельные обработчики внутри llama‑server, их число задаёт флаг -np. Скорость на запрос означает темп генерации после начала ответа, который видит один пользователь. Агрегат показывает, что выдаёт узел целиком. И важная оговорка: клиенты в тестах непрерывные, закончив запрос, тут же шлют следующий. Живые люди читают и печатают с паузами, поэтому таких клиентов сервису нужно меньше, чем живых пользователей; во сколько раз, из этого теста не следует.

Стенд

Параметр

Значение

Машина

Beelink GTR9 Pro

APU

Ryzen AI Max+ 395 (Strix Halo)

CPU

16C/32T Zen 5

GPU

Radeon 8060S, 40 CU RDNA 3.5, gfx1151

Память

128 ГБ LPDDR5X, единый пул

ОС

Ubuntu, ядро 6.17.0–35

Графика

Mesa / RADV

Рантайм

llama.cpp server-vulkan, пин по digest sha256:25932f…bea6df

Нагрузку подаёт соседняя машина по локальной сети. Инференс идёт через Vulkan/RADV: это мой основной проверенный путь на gfx1151, сравнение с ROCm‑путём в эту серию не входило. Весь рантайм берётся готовым Docker‑образом.

Правила замеров

  • Каждый промпт уникален и посолен случайным префиксом поверх небольшого пула из шести коротких заданий, ответы по 128 токенов, cache_prompt: false. Иначе легко намерить скорость кэша вместо скорости модели. Задания выбираются случайно без фиксации seed: воспроизводится процедура, а не точная последовательность запросов конкретного прогона.

  • Метрики беру из серверных timings. Агрегат считаю как сумму сгенерированных токенов, делённую на фактическую длительность прогона: хвост запросов, стартовавших до конца окна, в неё входит. Поле prompt_ms дальше по тексту называется обработкой промпта, потому что оно измеряет именно серверную работу над промптом, без времени ожидания в очереди. Настоящее время до первого токена, каким его видит клиент, я мерил отдельным стриминговым харнессом с меткой первого чанка, оно появится в разделе про длинные промпты.

  • Скрининг конфигов: один прогон по 75 секунд. Финальные числа: три прогона, медиана. Там, где прогонов меньше, таблица говорит об этом прямо.

  • Если не сказано иное, таблицы сняты после тюнинга узла, о котором отдельная глава ниже. Исключения помечены на месте.

  • Провалы и опровергнутые гипотезы публикуются наравне с успехами, включая три моих собственных ошибки.

Базовая кривая и первая странность

Начал с рабочей модели своего стека: Qwen3.6–35B‑A3B (A3B значит 3 млрд активных параметров на токен) в кванте Q4_K_M, сервер на 32 слота, нагрузка растёт ступенями. Кривая снята одним прогоном на уровень, опорные точки 1, 8, 16 и 32 клиента дополнительно прогнаны трижды, в таблице их медианы.

Клиентов

1

4

8

10

12

16

24

32

Агрегат tok/s

67

131

149

99

104

121

141

160

Смотрите на переход от 8 к 10 клиентам. Суммарная пропускная не растёт, а обваливается со 149 до 99, и только к 32 клиентам выкарабкивается обратно. Я гонял этот участок мелкими шагами, на двух сборках образа, до и после тюнинга узла, в разные дни. Провал воспроизводится каждый раз, и форма кривой всюду одна. Попутная сноска: на одном клиенте агрегат за окно ниже скорости самого запроса (75,7 в следующей главе), потому что окно включает паузы между последовательными запросами.

Первым делом я подумал на рантайм и флаги. Перебрал размеры батча, типы KV‑кэша, переменные окружения Vulkan. Кривая не сдвинулась. Тогда я пошёл менять модель, честно понимая, что вместе с ней неизбежно меняются квант, тип KV и батчинг: чистого однофакторного контроля тут не бывает.

Эффект, который казался модельным

Контрольной моделью стала Gemma 4 26B A4B в QAT‑кванте UD‑Q4_K_XL, около 16 ГБ. Модель максимально непохожая: у Qwen3.6 большинство слоёв работает как линейное внимание Gated DeltaNet и лишь часть как обычное softmax‑внимание, у Gemma 4 линейного внимания нет вовсе, зато это MoE с 3,8 млрд активных параметров из 25,2 млрд общих и чередованием скользящего окна с глобальным вниманием пять к одному.

Опорные точки 1, 2, 4, 8, 16 и 32 клиента у Gemma обвала не показали, и вывод «эффект следует за моделью» уже стоял в черновике этой статьи. Спасла финальная проверка перед публикацией: я всё‑таки снял зону 8–16 мелким шагом, единой серией, в одном режиме узла. И вот она, третья пойманная ошибка:

Клиентов

8

10

12

16

32

Qwen Q4_K_M

149,3

101,5

104

120,8

160,4

Qwen Q4_0

158,7

114,8

123,7

137,3

178,1

Qwen IQ4_NL

153,7

108,4

114,9

130,2

166,9

Gemma 4 QAT

161,4

123,7

134,8

160,0

236

Столбцы 8, 10 и 32 здесь медианы трёх прогонов, столбцы 12 и 16 одиночные скрининги (у Q4_K_M на 16 тоже три прогона). Долина есть у всех: две разные архитектуры, все три кванта Qwen, один и тот же обрыв на переходе от 8 к 10 клиентам. Опорные точки Gemma его просто перепрыгивали: на 8 ещё до обрыва, к 16 она уже успевает восстановиться. Отличается не наличие долины, а её глубина (от −23 процентов у Gemma до −32 у Qwen Q4_K_M) и скорость выхода: Gemma отыгрывается к 14–16 клиентам и дальше растёт без провалов (179,9 на 20, 200,3 на 24, 215,6 на 28, всё по одному прогону, и 236 на 32), Qwen выкарабкивается до самого верха и упирается в потолок 160–178.

Долина, выходит, не принадлежит ни одной модели: она общая для всех четырёх проверенных конфигураций этого стенда. Дальше этого наблюдения не иду: обе модели, замечу, MoE, харнесс и форма нагрузки у меня одни на все тесты, так что причина может сидеть где угодно в цепочке от класса операций модели до llama‑server, Vulkan‑бэкенда, RADV и железа. Профилирования ядер не делал; если у кого‑то из читателей есть такой опыт, в репозитории есть всё, чтобы копнуть глубже.

Практических вывода теперь два, и они разные для разных моделей. Потолки по‑прежнему различаются на треть, 236 у Gemma против 178 у лучшего кванта Qwen, так что модель выбирается по её кривой. А вот лечение долины зависит от кривой выхода: для Qwen Q4_K_M на 16 клиентах ограничение восемью активными запросами на процесс подтверждено лестницей из следующей главы; для других квантов и уровней нагрузки это пока гипотеза, а Gemma после 14 клиентов растёт сама, и кап на восьми отрезал бы ей весь потолок.

Дуэль квантов

Следом проверил, влияет ли квант. Три кванта Qwen3.6, одна и та же нагрузка, по три прогона на каждую клетку, в таблице медианы.

Квант

1 клиент, tok/s на запрос

8 клиентов, агрегат

32 клиента, агрегат

Q4_K_M

75,7

149,3

160,4

Q4_0

79,5

158,7

178,1

IQ4_NL

76,3

153,7

166,9

Q4_0 впереди на всех уровнях, под батчем отрыв доходит до одиннадцати процентов. Мои первые одиночные прогоны, к слову, показывали, что на одном клиенте якобы выигрывает Q4_K_M. Повторы этого не подтвердили, и правило трёх прогонов отработало как рутинная страховка, ещё до всяких ревью. И прежде чем менять прод‑квант по этой табличке, добавьте к скорости оценку качества на своих задачах. Скорость без качества выбор не решает.

Главная цифра и полчаса под полной нагрузкой

Лучший агрегат кампании принадлежит Gemma 4 26B в том же QAT‑кванте UD‑Q4_K_XL: 236 tok/s на 32 одновременных запросах, медиана прогонов по 75 секунд. Разброс между прогонами составил от 235,3 до 246,5, почти пять процентов, причём два прогона сняты подряд, а верхний пришёлся на другой день, после перезагрузки узла. Это наблюдавшийся межпрогонный разброс, а не оценённая погрешность всех измерений, но одиночные числа в статье стоит читать с поправкой такого порядка.

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

  • Средний агрегат за 30 минут: 226,4 tok/s. Прошло 411 803 токена на 3223 запросах, около 7 tok/s на запрос в среднем: полный ответ на 128 токенов под такой нагрузкой занимает около 18 секунд.

  • Температурная полка 78 °C, мощность около 113 Вт, частоты стабильны.

  • Первая минута дала 202,7, тридцатая 200,5, между ними ряд ходит двумя полками: примерно каждая третья минута даёт 258–271, остальные идут по 200–210. Так выглядит учёт по минуте завершения запроса: клиенты стартовали одновременно и заканчивают ответы волнами. Деградации от начала к концу нет.

  • 1,4 процента ответов пришли без блока timings, харнесс посчитал их ошибками; была ли в них пригодная генерация, я не проверял, харнесс смотрит только на телеметрию. Наблюдал это только в прогонах Gemma под параллельной нагрузкой на этом билде; причину не разбирал. В среднем агрегате эти запросы не участвуют.

Короткие прогоны дают медиану 236, полчаса под нагрузкой дают 226. Смешивать эти числа нельзя.

Спекулятивный декодинг: на моём стеке он для одного‑двух клиентов

Теперь про MTP (multi‑token prediction), ради которого всё затевалось у ciru. Это вариант спекулятивного декодинга: лёгкая драфт‑голова предсказывает несколько токенов вперёд, основная модель проверяет их разом, и при удачных догадках вы получаете их пачкой по цене одного прохода.

Сначала драфт вообще не включался: сервер падал на этапе оценки памяти под драфт‑модель. Похожая ошибка описана в открытом, пока не подтверждённом issue llama.cpp про другую платформу, и предложенный там обход сработал и у меня: флаг -fit off. Дальше начались числа для Gemma с её отдельной драфт‑моделью, и они меня удивили. Серия скрининговая, по одному прогону на уровень, снята до тюнинга узла, поэтому якорь на 32 клиентах здесь 219,8, а не 236: сравнение внутри таблицы от этого не страдает, обе колонки в равных условиях. Строка одного клиента показывает скорость на запрос, остальные показывают агрегат.

Клиентов

Без MTP, tok/s

С MTP, tok/s

Эффект

1 (на запрос)

53,3

75,6

+42%

2

78,9

95,2

+21%

4

120,8

83,0

−31%

8

155,0

115,6

−25%

32

219,8

146,9

−33%

На одном клиенте плюс сорок два процента. На четырёх уже минус тридцать один. Доля принятых токенов драфта (acceptance) при этом держалась около 74 процентов, от 56 на прозе до 90 на SQL, то есть высокая приёмка сама по себе выгоду на батче не спасла.

Тут нужна оговорка про область применимости, потому что официальные материалы Google рисуют другую картину: они заявляют ускорение от MTP до 2,2 раза именно на батчах от 4 до 8 на Apple Silicon и сопоставимые эффекты на A100, а документация MTP связывает это с тем, что при больших батчах активируемые эксперты MoE сильнее перекрываются между последовательностями и загруженные веса переиспользуются. Мой минус тридцать три и их плюс сто двадцать не противоречат друг другу: это разные стеки. Моё предположение, профилированием не проверенное: Vulkan/RADV на APU и Metal на Apple Silicon по‑разному оплачивают лишние проходы драфта. Вывод формулирую по проверенным точкам: на связке Strix Halo, RADV и этой сборки llama.cpp спекулятивный декодинг в плюсе на одном и двух клиентах и в минусе на четырёх, восьми и тридцати двух. Промежуточные значения не мерил. На своём стеке меряйте сами, точка перегиба поедет.

Отдельно от Gemma я прогнал Qwen3.6 со встроенной MTP‑головой из свежих GGUF Unsloth. Гонял строго в один слот: README Unsloth рекомендует np=1, в апстриме встречаются запуски с двумя слотами с переменным успехом, и на своём стенде многослотный режим с MTP я не проверял. Это другой GGUF (UD‑Q4_K_M сборки Unsloth) и однослотный сервер, поэтому его база не совпадает с дуэлью квантов, сравнивать честно только внутри пары. Якорь без драфта: 63,5 tok/s на запрос, два тюненных прогона плюс ранний нетюненный с 62,1. С драфтом: 89,5, медиана трёх, плюс 41 процент. Это лучшая одиночная скорость всей кампании. Побочное наблюдение из репозитория: на тюненном узле серверная обработка промпта у этой однослотной конфигурации держится около 420 миллисекунд против 130–140 на нетюненном, одинаково с драфтом и без; причину не выяснил.

Два сервера против одного

У Qwen долина глубже и длиннее всех, и я попробовал обойти её топологией: вместо одного сервера на 32 слота поднять два по 8 на одном GPU и раздать клиентов пополам. Модель при этом загружается в память дважды, так что это размен памяти на планирование.

Сравнивать такое можно только при равном числе клиентов и равных условиях: модель, тип KV, батчинг и контекст на слот здесь одинаковы, а общий размер контекста менялся пропорционально числу слотов, чтобы каждому слоту доставалось те же 8192 токена. Для Qwen я в итоге прогнал целую лестницу конфигураций, по три прогона каждая, и она рассказывает историю лучше слов:

Qwen Q4_K_M, 16 клиентов

Агрегат tok/s, медиана 3

Один сервер, 32 слота

120,8

Один сервер, 16 слотов

122,5

Один сервер, 8 слотов, остальные клиенты в очереди

149,4

Два сервера по 8 слотов

170,7

Два нижних ряда появились по вопросам рецензента, и оба того стоили. Сначала он спросил, в процессах ли дело: вдруг выигрывает просто меньшее число слотов. Нет: 16 слотов дают ту же долину, что и 32. Потом спросил, что будет, если один сервер просто ограничить восемью слотами, а лишних клиентов оставить ждать в очереди. А вот это сработало: 149,4, большая часть потери вернулась без второго процесса. Пара серверов добирает сверху ещё 14 процентов.

Читается это так: у Q4_K_M на этой нагрузке долина завязана на число одновременно обрабатываемых запросов в процессе, и держать его не выше восьми полезно уже само по себе, хоть очередью, хоть вторым сервером. На другие кванты и уровни нагрузки эту лечебную схему я не переносил. Почему пара добирает ещё, точно не знаю: планировщики, аллокации и копии модели меняются вместе, без профилирования виновника не назвать. И трезвая оговорка про пару: 170,7 на двоих означает в среднем около 85 на процесс, это далеко не две полные скорости соло‑сервера, GPU один на всех. У Gemma в двух прогонах пара вышла на пять‑шесть процентов выше одиночного сервера, но эта разница сопоставима с межпрогонным разбросом, так что уверенного выигрыша нет. На 32 клиентах пару не гонял.

Признаюсь: в ранней версии этого текста стояло «два сервера вредят Gemma на 19 процентов». Число родилось из сравнения двух серверов при 16 клиентах с одним сервером при 32, вдобавок с разными настройками KV‑кэша. Рецензент ткнул в это место, я снял корректный A/B, и знак эффекта поменялся. Таблица выше уже из правильного эксперимента, а старое число отозвано в репозитории.

И про пределы: третий тяжёлый инстанс на GPU при стандартном лимите памяти падает с vk::DeviceLostError. С расширенным GTT‑пулом теоретически поместится, но этот тест я пока не гонял.

Тюнинг ядра и открытие, которого не было

У Strix Halo память общая для CPU и GPU, и по умолчанию драйверный лимит разрешает GPU замапить примерно её половину. Тяжёлым сочетаниям этого мало: две крупные модели вместе с KV‑кэшами и рабочими буферами подбираются к дефолтному лимиту вплотную, а третий инстанс у меня в него уже не влез. Поэтому существует известный пакет параметров ядра: ttm.pages_limit и ttm.page_pool_size на 94 процента RAM, amd_iommu=offzswap.enabled=0.

Пока я готовил эти замеры, мне показалось, что я поймал сенсацию. Реранкер на узле с тюнингом выдавал 7 запросов в секунду, а конфиг, который я считал идентичным, на узле без тюнинга давал 2,7. Разница в 2,6 раза! Я прогнал контроли: образ тот же, ядро то же, профили питания совпадают. Оставалась одна переменная, параметры ядра, и глава про «тюнинг ускоряет в разы» уже писалась в голове.

Спасла привычка сверять не команду запуска, а реальное состояние сервера. Запрос /props показал, что боевой реранкер крутит четыре слота, хотя флага --parallel в его командной строке нет. Дефолт этого флага равен минус единице, режиму auto, а auto в текущих исходниках server.cpp захардкожен в четыре слота, оба моих образа это подтвердили. Гарантий, что так останется, никаких, поэтому сверять надо живой /props. А свой контрольный сервер я запускал с явной единицей. Всё это время я сравнивал четыре слота с одним и приписывал разницу параметрам ядра.

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

Замер

Без тюнинга

С тюнингом

Gemma, 32 клиента

219,8

235,7

Qwen Q4_0, 32 клиента

167,8

180,9

embed, 8 слотов

240 rps

255 rps

GTT‑лимит, системная память для GPU‑маппингов

62,5 ГиБ

117,4 ГиБ

Плюс шесть‑восемь процентов к генерации и почти вдвое больший пул памяти. Числа в таблице из парного эксперимента в день ребута, по одному‑два прогона на клетку; медианы трёх прогонов из соседних глав (178,1 у Q4_0, 236 у Gemma) живут внутри того же разброса. Вклад отдельных параметров я не изолировал, эффект относится к пакету целиком. И это настройки выделенной инференс‑ноды: IOMMU обеспечивает DMA‑изоляцию, на которой держится безопасный проброс устройств в виртуалки, так что на хосте с виртуализацией и пробросом его не трогайте.

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

Эмбеддинги и реранкер

RAG состоит не только из генерации. Прогнал оба вспомогательных сервиса по числу слотов: эмбеддинги считает bge‑m3 в кванте Q8_0, реранкует bge‑reranker‑v2-m3 Q8_0. Окна по 45 секунд, по одному прогону на клетку; нагрузку давили 16 клиентов на эмбеддинги и 4 на реранкер.

Слотов

1

4

8

16

embed, rps

не мерил

194

255

242 (до тюнинга; после не мерил)

rerank, rps

2,7–2,8

7,3

7,3

не мерил

Эмбеддингам восемь слотов дают плюс 30 процентов к четырём. Насыщение на восьми подтверждает нетюненный ряд, снятый в одинаковых условиях: 171 на четырёх, 240 на восьми, 242 на шестнадцати. Реранкер насыщается уже на четырёх. Пропускная реранкера в документах в секунду держится постоянной, 143–144 на трёх проверенных размерах пачки (замер из ранней серии, до тюнинга): пачка в 10, 20 или 40 документов меняет латентность запроса, а не суммарную скорость. Из операционного стоит знать про хвост: на батчёванных embed‑запросах при восьми и больше слотах у меня воспроизводился одиночный столл около 45 секунд, ровно один запрос за прогон, при чистом p95 и нетронутой пропускной. Клиенту с жёстким таймаутом нужен ретрай; детали в репозитории.

Длинные промпты, самое слабое место

Все числа выше сняты на коротких промптах. RAG так не живёт, поэтому отдельный тест на Qwen3.6 Q4_0 с 32 слотами: промпты около 3,4 тысячи токенов, ответ 128 токенов. Здесь я мерил стримингом, с клиентской меткой первого чанка, так что время до первого токена в таблице настоящее: с очередью и сетью. Окна по 90 секунд, один прогон на уровень. Сразу скажу о пределах теста: корпус синтетический из восьми абзацев, вопрос фиксированный, так что это микробенчмарк нагрузки на обработку промптов, а не имитация продакшна.

Клиентов

Первый токен, p50

Первый токен, p95

Полный ответ, p50

Агрегат выдачи, tok/s

1

3,0 с

3,0 с

4,6 с

25,0

4

3,2 с

9,9 с

15,9 с

27,8

8

7,3 с

22,8 с

33,0 с

29,6

16

16,0 с

49,3 с

64,4 с

30,1

Столбец p95 показывает то, чего в серверных метриках не видно. При 16 клиентах медиана ожидания первого токена 16 секунд при серверной медиане обработки 9,3 секунды. Вычитать одну медиану из другой и называть результат очередью нельзя, это медианы разных распределений, и в клиентское время входят ещё сеть и первый шаг генерации. Но зазор растёт с числом клиентов, и это поведение совместимо с накоплением очереди при насыщении, хотя прямого серверного замера очереди у меня нет. Хвост тяжелее: p95 первого токена доходит до 49 секунд, и это наблюдение одного 90-секундного прогона, а не устойчивая оценка. Агрегат при этом стоит на месте около 25–30, и наблюдаемый ограничитель здесь обработка промптов: её суммарный темп, посчитанный как сумма входных токенов на фактическую длительность прогона, держится в районе 760–810 токенов в секунду на всех уровнях; повторная серия с этим расчётом лежит в репозитории. Комфортная зона заканчивается на четырёх клиентах: первый токен через 3,2 секунды в медиане, полный ответ за 16 секунд. На восьми уже ждёшь полминуты.

В финале я собрал всё вместе на одной коробке: генерация на четырёх клиентах, эмбеддинги на шестнадцати, реранкер на четырёх, всё одновременно. Этот тест из ранней серии, до тюнинга узла и без стриминга, так что время тут серверная обработка промпта, а не клиентское начало ответа. Работает стабильно, ни одной ошибки GPU, но каждый сервис теряет от 40 до 70 процентов пропускной: обработка промпта у генерации выросла до пяти секунд. Выходит такой ориентир, именно ориентир, а не измеренная граница ёмкости: два‑четыре одновременных клиента тяжёлого RAG на коробку. Если клиентов нужно больше, генерация выносится на отдельный узел, и именно так у меня устроен прод.

Комментатор из прошлой статьи в этой части был прав, и спорить тут не с чем. Разница между нами в том, что теперь у этой правоты есть измеренные ориентиры.

Что не сработало

Ручки, которые я крутил и которые не дали ничего или сделали хуже.

  • Размеры батча из чужого рецепта, 4096/1024 против моих 2048/2048: ноль на decode‑нагрузке.

  • f16 против q8_0 в KV‑кэше: плюс два процента ценой удвоения памяти кэша.

  • Форсирование профиля питания GPU: плюс два процента, в пределах разброса.

  • Число CPU‑потоков при полном оффлоде: ноль.

  • GGML_VK_PREFER_HOST_MEMORY=1: минус 38 процентов и семикратное замедление обработки промпта. Единственная найденная анти‑ручка.

  • Отключение cooperative matrix: ноль на одиночном клиенте, минус 15–20 процентов на 32. Дефолт на этом стеке оптимален.

Про исходные 242

Итоговая сверка с отправной точкой. У ciru на этой топологии заявлено 242 tok/s со спекулятивным драфтом, и его страница оформлена добротно: метрика определена (Active weighted TG/s складывает скорости всех воркеров, каждая посчитана на активном времени своего воркера), опубликованы точная команда сервера и данные каждого воркера. Нет только кода клиентского харнесса. У меня получилось 236 без драфта, а с драфтом на 32 клиентах строго хуже.

Прямо сравнивать эти числа нельзя по двум причинам сразу. Первая: метрики с разными знаменателями. Сумма индивидуальных скоростей за активное время каждого воркера математически не ниже агрегата по общей длительности, а на практике заметно выше. Из его же опубликованных данных это видно: 71 637 токенов при самом долгом воркере в 387 секунд дают в моём определении не больше ~185 tok/s для того же самого прогона. Вторая: нагрузка другой формы. У него 32 однократных длинных генерации по 1–3,5 тысячи токенов с приёмкой драфта 87,6 процента, у меня непрерывные циклы коротких ответов по 128 токенов с приёмкой около 74. Пересчитать обе метрики на моих данных пока нельзя: логов отдельных запросов мой харнесс не вёл, только агрегаты прогонов. Если добавить харнессу такой лог, обе метрики можно будет посчитать на одном и том же прогоне и увидеть разницу определений в чистом виде. Это следующий шаг, который напрашивается.

Итог

Коробка на Ryzen AI Max+ 395 держит 32 одновременных запроса: 236 tok/s по медиане коротких прогонов, лучший прогон 246,5, и 226 в получасовом среднем, без троттлинга и деградации. Одиночный клиент со спекулятивным драфтом получает под 90 tok/s. Эмбеддинги выходят на 255 rps. На длинных промптах (мерил на Qwen Q4_0) узел упирается в обработку промптов, и это главный ограничитель для тяжёлого RAG.

Уроки кампании, которые переживут конкретные числа:

  • Под параллельную нагрузку модель выбирается по её кривой масштабирования, и снимать кривую надо мелким шагом: долину на переходе через 8 одновременных запросов показали обе модели и все три кванта Qwen, а опорные точки её прятали. Лечение при этом у каждой кривой своё: Qwen Q4_K_M на измеренной нагрузке лечится капом в восемь активных запросов на процесс, Gemma быстрее всех отыгрывается и дальше растёт сама.

  • Спекулятивный декодинг требует замера на вашем стеке. Между моим минус 33 и официальными плюс 120 лежат разные железо, рантаймы, кванты и формы нагрузки, а не чья‑то ошибка.

  • Сервер llama.cpp без флага --parallel сегодня получает четыре слота через auto, и это может измениться в любой сборке. Сверяйте живую конфигурацию через /props, а не командную строку.

  • Один дополнительный контроль стоит дешевле одного опровержения. Проверено на себе трижды за неделю, последний раз за день до публикации.

Скрипты запуска, харнессы, полные таблицы, значения ключевых прогонов и телеметрия лежат в strix‑halo‑multislot. Повторяйте, перепроверяйте, и если ваши числа разойдутся с моими, это самый интересный исход из возможных.

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


  1. MountainGoat
    18.07.2026 21:34

    Интересно, а такое старое ядро Linux не тормозит ли всё шоу?


    1. AGmind Автор
      18.07.2026 21:34

      Замерил на 7.0.0-28. Генерация: Qwen на 8 клиентах 149 → 155, на 32 клиентах 160 → 159; Gemma на 32 клиентах 236 → 243. Embed 192 → 199 rps, rerank 7,0 → 7,3. Итого дельта ядра в пределах пары процентов, в рамках межпрогонного разброса, а провал на переходе через 8 одновременных запросов на новом ядре воспроизводится один в один. Сырые прогоны добавил в репо.


  1. WordEngineer
    18.07.2026 21:34

    Сильная методология, особенно история с реранкером на четырех слотах по умолчанию. Это ровно тот тип бага, который прячется, потому что никто не сверяет живой /props, а верят флагам командной строки. По долине на переходе от 8 к 10 клиентам: похоже на classic scheduler granularity в бэкенде. Если внутренний диспетчер Vulkan/RADV раскладывает работу батчами кратными восьми - типичное число для wavefront на RDNA - то на 9-10 запросах часть работы неизбежно фрагментируется на неполный батч, отсюда и просадка, а на 32 все снова ровно ложится по восемь. Про спекулятивный декодинг результат ожидаемый для APU с общей памятью: там пропускная способность на матричное умножение упирается в bandwidth, а не в compute, и лишние проходы драфта на этом стеке просто отъедают тот же bandwidth budget, который нужен основной модели под батчем. И отдельное спасибо, что не подчистили опровергнутую гипотезу про минус 19 процентов у Gemma, а оставили ее в тексте с объяснением - это редкость и на порядок ценнее глянцевой статьи без единой ошибки.