Локальный LLM-сервер на AMD Strix Halo: запуск Qwen3.8-Flash-Next через HALOGEN в Fedora 43

—

от автора

Ещё недавно «запустить LLM у себя» почти всегда означало одно: discrete GPU с парой десятков гигабайт VRAM. AMD Strix Halo с 128 ГБ унифицированной памяти эту привычку ломает — крупные MoE-модели на нём заводятся спокойно, если inference-движок умеет работать с общей памятью, memory mapping и своеобразием встроенной графики.

Ниже — практическая настройка Qwen3.8-Flash-Next на Fedora 43 Server через HALOGEN: OpenAI-совместимый API, ключ, четыре параллельные сессии и доступ снаружи через порт 9091.

Что за зверь Qwen3.8-Flash-Next

Это не «обычная» модель даже по меркам MoE. Внутри — смесь экспертов (MoE), гибридное внимание и отдельные N-gram embeddings. Если коротко:

Параметр

Значение

Основная модель

125 млрд параметров

Активируется на токен

около 6 млрд параметров

N-gram embedding table

около 51 млрд параметров

MTP-head

около 4 млрд параметров

Количество слоёв

48

MoE-экспертов

512 routed + 1 shared

Активных routed-экспертов

10

Нативный контекст

262 144 токена

Максимальный расширенный контекст

до 1 000 000 токенов

Возможности

текст и опционально изображения

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

Почему HALOGEN, а не llama.cpp

У Strix Halo unified memory architecture: CPU и встроенный GPU делят одну LPDDR5X-память. Отдельной физической VRAM, как у дискретных NVIDIA или Radeon, здесь просто нет.

HALOGEN рассчитан именно на такое устройство:

  • заточен под GPU gfx1151;

  • хранит веса в собственном формате .hgn;

  • не пытается копировать весь комплект весов в «VRAM»;

  • держит N-gram lookup table через memory mapping;

  • ускоряет генерацию с помощью MTP/speculative decoding;

  • отдаёт наружу OpenAI-совместимый HTTP API.

Это не универсальная замена llama.cpp или vLLM — все его преимущества вытекают из узкой заточки под одну архитектуру GPU и одну линейку моделей.

И сразу важный момент про BIOS: параметр UMA Frame Buffer выставляйте в минимальное фиксированное значение, а не оставляйте Auto. HALOGEN нужен доступ к обычной памяти хоста, а большой статический резерв под iGPU только съедает доступную RAM.

Сколько места нужно на диске

Стандартный набор весов HALOGEN выглядит так:

Файл

Размер

Назначение

qwen38-flash-next-v2.hgn

62,1 GiB

Основной checkpoint

qwen38-flash-next-ngram.hgn

47,7 GiB

N-gram lookup table

tokenizer/

небольшой

Токенизатор

Всего

около 110 GiB

Текстовая модель

qwen38-flash-next-vision.hgn

0,84 GiB

Опциональная поддержка изображений

qwen38-flash-next-mtp.hgn

1,42 GiB

Draft-head для стороннего GGUF

При первом запуске HALOGEN сам скачает нужные файлы, если передать ему:

-e HALOGEN_DOWNLOAD=peonist-ai/halogen-qwen3.8-flash-next

Свободного места закладывайте не меньше 120–140 ГБ: помимо самих весов понадобятся служебные файлы и запас под обновления.

Какую скорость ждать

По опубликованным результатам HALOGEN на Strix Halo ориентиры такие:

Сценарий

Производительность

Генерация текста

около 54 токенов/с

Благоприятный сценарий speculative decoding

до 94 токенов/с

Генерация при редактировании кода

до 81 токена/с

Генерация tool calls

около 71 токена/с

Prefill: промпт 8K токенов

около 1 821 токена/с

Prefill: промпт 32K токенов

около 1 740 токенов/с

Prefill: промпт 131K токенов

около 1 675 токенов/с

Что это значит на практике? Новый документ на 32K токенов обработается примерно за 19 секунд. Уже закэшированная история диалога продолжится заметно быстрее. Ответ на 1 000 токенов при 54 токенах/с будет крутиться секунд 18–19. А реальная скорость, конечно, зависит от текста, длины контекста, температуры, speculative decoding и того, что ещё грузит машину параллельно.

При четырёх одновременных сессиях HALOGEN просто делит вычислительный ресурс между потоками. Ориентир из документации: примерно 76 токенов/с суммарно и 11–19 токенов/с на поток. Так что четыре слота полезны для API-сервера с несколькими клиентами, но единичный запрос они не ускоряют.

Синтетические тесты: проверяем на живом сервере

Цифры из readme — это хорошо, но интереснее погонять бенчмарк по реальному эндпоинту. Подошёл LLMPerf от ray-project: он собирает промпты из случайных кусков Шекспира и меряет по каждому запросу время до первого токена (TTFT) и интервалы между токенами (ITL) — из них легко получить скорость чтения и записи.

По ходу дела выяснились две грабли, о которых стоит знать заранее:

  • SSE-парсер llmperf пришлось патчить. На длинных контекстах при c=4 стрим молчит достаточно долго, и прокси начинает слать keep-alive (:ping), от которых наивный парсер падает. Лечится просто: игнорировать комментарии, разбирать только строки data:.

  • Модель — reasoning-модель. Она «размышляет» перед ответом (поле reasoning_content), и эти паузы искажают метрики: на 128k свободная генерация показывала жалкие 2.6 ток/с записи. Отключается штатным для Qwen3 параметром chat_template_kwargs: {"enable_thinking": false}, а чтобы модель не останавливалась по EOS после пары сотен токенов, добавляли ignore_eos: true.

Финальная серия — reasoning OFF, один поток, выход = 1/10 контекста, 12 запросов без единой ошибки:

Контекст

Выход (факт)

TTFT p50

Чтение

Запись

E2E p50

8k

888

4.2 с

1 965 ток/с

96.4 ток/с

13.3 с

16k

1 775

8.1 с

2 040 ток/с

98.1 ток/с

26.6 с

32k

3 571

16.1 с

2 048 ток/с

96.5 ток/с

53.4 с

64k

7 203

32.3 с

2 035 ток/с

96.5 ток/с

121 с

128k

14 518

66.0 с

1 992 ток/с

94.5 ток/с

230 с

Что из этого следует:

  • Запись — константа ~95–98 ток/с на всём диапазоне контекстов. «Деградации от длины контекста» не существует — её имитировали паузы размышлений (разница до 36×). И обещанные до 94 токенов/с в благоприятном сценарии — не маркетинг: форсированный стрим стабильно выдаёт ~96.

  • Чтение — линейное, ~2 000 ток/с. TTFT растёт с контекстом (4.2 с на 8k → 66 с на 128k), и именно чтение здесь узкое место.

  • Параллелизм ведёт себя как заявлено. Короткие контексты при c=4 дают максимум суммарного throughput (+27% к одиночному потоку), но батчинг замедляет каждый запрос по TTFT в 2–5 раз, а c=16 сервер уже не переваривает. Длинные одиночные запросы выгоднее слать по одному.

  • Для агентского сценария (прочитать контекст + ответ ~150 токенов) шаг целиком занимает ~6 с при 8k, ~18 с при 32k и ~68 с при 128k — комфортный рабочий контекст где-то до 32k.

А если оставить reasoning включённым?

Для полноты картины ту же матрицу прогнали и с включёнными размышлениями. Картинка получается занятная:

Метрика

Reasoning ON

Reasoning OFF

Запись @ 8k, форсированный вывод

~90–99 ток/с

96.4 ток/с

Запись @ 8k, свободный вывод

26.6 ток/с

—

Запись @ 128k, свободный вывод

2.6 ток/с

94.5 ток/с

TTFT до контента на тривиальном вопросе

6.76 с

0.18–0.35 с

Тонкость в том, что метрики здесь искажает сам способ измерения. TTFT отсчитывается до первого контентного токена, то есть включает все размышления целиком. А между контентными токенами вклиниваются паузы «подумать» — на 128k интервал доходит до 378 мс, что и выглядело как деградация до 2.6 ток/с. При форсированном выводе, когда паузы вымываются сплошным стримом, скорость записи почти не отличается от reasoning OFF (~90–99 против 96.4 ток/с): токены генерируются так же быстро — модель просто думает между ними.

Практический вывод: нужен быстрый и предсказуемый стрим — отключайте thinking. Если качество важнее latency — оставляйте, но помните, что свободная генерация с reasoning останавливается по EOS уже на 85–365 токенах, так что «честную» скорость записи вы в этом режиме всё равно не увидите.

И приятный бонус: за всё исследование — 150+ запросов, и после патча парсера ни одной ошибки и ни одного таймаута.

Готовим Fedora 43 Server

Ставим Podman и базовые утилиты:

sudo dnf upgrade --refresh -ysudo dnf install -y podman curl pciutilssudo reboot

После перезагрузки проверяем драйвер и GPU-устройства:

lspci -nnk | grep -A 4 -Ei 'VGA|Display|3D'ls -l /dev/kfd /dev/dri/renderD*free -h

Для работы с GPU пользователю обычно нужны группы render и video:

sudo usermod -aG render,video "$USER"

Дальше — важная мелочь, о которой часто забывают: полностью завершите SSH-сеанс и подключитесь заново, иначе новые группы не подхватятся.

Создаём API-ключ

HALOGEN умеет защищать API через переменную HALOGEN_API_KEY. Клиент передаёт её стандартным заголовком:

Authorization: Bearer ВАШ_КЛЮЧ

Создадим файл окружения с правами доступа только для текущего пользователя:

mkdir -p "$HOME/.config/halogen" "$HOME/halogen-models"chmod 700 "$HOME/.config/halogen"(  umask 077  if [ ! -f "$HOME/.config/halogen/server.env" ]; then    printf 'HALOGEN_API_KEY=%s\n' \      "$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')" \      > "$HOME/.config/halogen/server.env"  fi)chmod 600 "$HOME/.config/halogen/server.env"

Посмотреть ключ можно так:

cat "$HOME/.config/halogen/server.env"

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

Запускаем HALOGEN с четырьмя параллельными сессиями

HALOGEN слушает порт 8731 внутри контейнера. Если внешний API должен быть доступен через 9091, порт-маппинг выглядит так: 9091 на хосте → 8731 в контейнере.

Команда запуска:

podman run -d \  --name halogen \  -p 0.0.0.0:9091:8731 \  --device /dev/kfd \  --device /dev/dri \  --group-add keep-groups \  --security-opt label=disable \  --ipc=host \  --ulimit memlock=-1:-1 \  --env-file "$HOME/.config/halogen/server.env" \  -e HALOGEN_KV_SLOTS=4 \  -e HALOGEN_DOWNLOAD=peonist-ai/halogen-qwen3.8-flash-next \  -v "$HOME/halogen-models:/models" \  ghcr.io/peonist-ai/halogen-flash-server:0.18.5

Что здесь отвечает за что

Параметр

Назначение

-p 0.0.0.0:9091:8731

Публикует API наружу на порту 9091

--device /dev/kfd

Доступ к AMD Compute Runtime

--device /dev/dri

Доступ к DRM/GPU

--group-add keep-groups

Передаёт контейнеру права групп пользователя

--security-opt label=disable

Обход ограничений SELinux-разметки для GPU-доступа

--ipc=host

Общий IPC namespace, как рекомендует quickstart-конфигурация

--ulimit memlock=-1:-1

Снимает лимит блокируемой памяти

HALOGEN_KV_SLOTS=4

До четырёх одновременно генерирующих диалогов

HALOGEN_DOWNLOAD=...

Автоматическая загрузка весов при первом старте

--env-file

Подключает API-ключ, не светя им в командной строке

Посмотреть ход загрузки и инициализации модели:

podman logs -f halogen

И завершающий штрих — на мой взгляд, обязательная настройка. Разрешите пользователю работать в фоне, иначе при закрытии SSH-консоли сервер просто выключится:

sudo loginctl enable-linger "$USER"

Почему nvtop показывает всего ~27 ГБ «видеопамяти»

На Strix Halo показания nvtop регулярно вызывают вопросы. Модель занимает около 110 ГиБ на SSD, основной checkpoint требует примерно 62 ГиБ резидентной памяти — а nvtop показывает какие-то ~27 ГБ GPU memory.

Это нормально, и вот почему.

Отдельной физической VRAM у Strix Halo нет: iGPU использует RAM хоста, и HALOGEN не обязан отображать весь объём модели как классическую GPU VRAM. Память распределяется примерно так:

  • основные веса живут в резидентной памяти и занимают около 62 ГиБ;

  • N-gram table на 47,7 ГиБ подключена через mmap — Linux подтягивает её страницы по мере необходимости и греет их в page cache.

В nvtop при этом обычно хорошо видны буферы GPU, рабочие области вычислений, KV-cache и часть GPU memory mapping. При HALOGEN_KV_SLOTS=4 заметную долю тех самых ~27 ГБ съедает общий KV-pool и состояние слотов.

Так что цифра в nvtop не означает, что модель загрузилась «только на 27 ГБ» или работает не в полную силу.

Расширяем контекст до миллиона токенов через YaRN

Нативный контекст модели — 262 144 токена, но это не потолок. HALOGEN умеет растягивать позиционный контекст через YaRN, и задаётся это двумя переменными окружения:

-e HALOGEN_ROPE_YARN=4 \-e HALOGEN_CTX=1048576 \

В такой конфигурации максимальная длина контекста вырастает с 262 144 до 1 048 576 токенов. Важно: настройки читаются при запуске, поэтому контейнер нужно пересоздавать, а не просто перезапускать.

Сколько это съедает на практике? Живой замер при миллионном контексте и одном слоте: HALOGEN занимает 36,7 ГиБ по данным nvtop и 38,9 ГиБ по htop (для сравнения — около 27 ГиБ в nvtop на нативном контексте с четырьмя слотами). То есть сам по себе миллион запускается вполне уютно. Но «запускается» и «работает как станция» — вещи разные: если машина нужна именно как полноценная станция с миллионным контекстом, закладывайте порядка 96 ГиБ памяти — под веса, выросший KV-пул и запас под page cache. 128-гигабайтный Strix Halo такое закрывает с запасом, а на 64 ГБ замахиваться на миллион уже не стоит.

Для первого захода с таким контекстом советую один слот (HALOGEN_KV_SLOTS=1) — незачем делить и без того растянутый KV-пул между четырьмя запросами. Веса из $HOME/halogen-models и API-ключ из env-файла никуда не денутся, ничего заново качать не придётся. А чтобы не трогать исходный контейнер, второй заведём под другим именем — halogen-yarn:

podman run -d \  --name halogen-yarn \  -p 0.0.0.0:9091:8731 \  --device /dev/kfd \  --device /dev/dri \  --group-add keep-groups \  --security-opt label=disable \  --ipc=host \  --ulimit memlock=-1:-1 \  --env-file "$HOME/.config/halogen/server.env" \  -e HALOGEN_KV_SLOTS=1 \  -e HALOGEN_ROPE_YARN=4 \  -e HALOGEN_CTX=1048576 \  -e HALOGEN_DOWNLOAD=peonist-ai/halogen-qwen3.8-flash-next \  -v "$HOME/halogen-models:/models" \  ghcr.io/peonist-ai/halogen-flash-server:0.18.5

Единственный нюанс: если основной контейнер halogen всё ещё работает, порт 9091 у него занят — остановите его перед запуском второго, два контейнера не смогут слушать один и тот же порт.

Не публикуйте HTTP API в интернете без TLS

API-ключ защищает сервер от чужих запросов, но шифровать соединение он не умеет. По чистому HTTP токен и содержимое запросов может перехватить любой, кто стоит по пути.

Правильная production-схема выглядит так:

Интернет    ↓ HTTPS :9091Caddy / Nginx / Traefik    ↓ HTTP 127.0.0.1:8731HALOGEN

Вместо итога

Связка из AMD Strix Halo, 128 ГБ RAM, Fedora 43 Server и HALOGEN позволяет поднять быстрый локальный API для Qwen3.8-Flash-Next — без дискретной GPU. На выходе получается:

  • MoE-модель на 125B параметров с 6B активных на токен;

  • около 110 ГиБ весов на диске;

  • 50+ токенов/с для одного интерактивного потока на рабочих задачах при отключенном reasoning;

  • до четырёх параллельных генераций через HALOGEN_KV_SLOTS=4;

  • OpenAI-совместимые /v1/chat/completions, /v1/completions и /v1/responses;

  • доступ извне через порт 9091;

  • авторизация через HALOGEN_API_KEY.

Главное отличие этой конфигурации от «просто LLM-сервера» — правильная работа с UMA-памятью: низкий UMA carve-out в BIOS, понимание роли memory-mapped N-gram table и отказ от идеи читать показания nvtop как полный объём памяти, занятой моделью.

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