Личный опыт настройки NPU AMD XDNA2, сборки новостного агента на FastFlowLM и Piper TTS.


Ошибки, восстановление, рабочий код. Актуально на 2026 год.

Часть 1 из 4

Утро. Ноутбук ещё тёплый после ночи в спящем режиме. Я нажимаю одну клавишу, и из динамиков раздаётся спокойный женский голос: «Сегодня в мире технологий: компания из Купертино представила новый чип для носимых устройств, а в сегменте мобильных процессоров наметился сдвиг в сторону энергоэффективности». Голос ровный, без металлических нот, без пауз между словами. Это не облачный сервис. Не подписка за девятьсот рублей в месяц. Не сервер в чужом дата-центре. Это мой ноутбук, мой NPU, моя модель на четыре миллиарда параметров, работающая локально, без единого пакета данных, ушедшего в интернет.

А три дня назад этот же самый ноутбук лежал на столе с чёрным экраном терминала, где красовалась строчка: probe with driver amdxdna failed with error -22. Нейросетевой процессор — тот самый хвалёный AMD XDNA2, ради которого я, собственно, и брал этот ноутбук, — был мёртв. Контроллер управления питанием завис в состоянии, из которого его не мог вытащить ни один программный сброс. Только полное выключение и повторный старт. И виноват в этом был я сам — точнее, одна небрежная команда, которую я ввёл, не подумав.

Эта статья — не инструкция из документации. Это рассказ человека, который прошёл путь от «у меня есть ноутбук с NPU, и что с ним делать» до работающего голосового агента, читающего утреннюю сводку новостей. По дороге я сломал железо, потратил вечер на патчи, которые не могли сработать, перечитал форумы на трёх языках и в итоге собрал систему, которая каждый день экономит мне пятнадцать минут. Я расскажу всё как было: с ошибками, с тупиками, с моментами, когда хотелось закрыть крышку ноутбука и больше не открывать. Потому что чужой опыт экономит время. А чужой опыт поломки экономит ещё и нервы.

Информация в статье актуальна на сентябрь 2026 года. Ядро — 7.2.7, дистрибутив — Fedora 44 с KDE Plasma, драйвер — DKMS-модуль xrt-amdxdna версии 2.26.0, прошивка NPU — 1.1.2.64. Если вы читаете это позже, некоторые номера версий могли измениться, но архитектура решений и принципы, скорее всего, останутся прежними.


Зачем мне понадобился локальный AI-агент для новостей

Начну с предыстории, потому что без неё непонятно, зачем вообще городить огород с NPU, локальными моделями и самописными скриптами, когда есть готовые приложения.

Я читаю новости каждый день. Не все подряд, а выборочно: технологии, железо, искусственный интеллект, иногда космос. Раньше это выглядело так: утром открываю три-четыре сайта, пролистываю заголовки, кликаю на интересное, трачу время на рекламу и всплывающие окна. Минут двадцать-двадцать пять, если не залипну. Вечером — то же самое. Итого почти час в день на потребление информации, которая на восемьдесят процентов мне не нужна.

Подписки на агрегаторы решают проблему частично, но добавляют свою: уведомления, пуши, бесконечная лента, в которой тонет действительно важное. Облачные AI-суммаризаторы появились, но у них два недостатка, которые для меня критичны. Первый — приватность. Я не хочу, чтобы мои запросы, мои интересы, мои паттерны чтения оседали на чужих серверах. Второй — зависимость. Нет интернета — нет новостей. Нет подписки — нет новостей. Изменили условия — нет новостей.

И тут я подумал: а что если собрать цепочку целиком на своём железе? Заголовок и текст новости забираются по RSS. Суммаризацию делает локальная LLM. Озвучку — локальный синтезатор речи. Никакого облака. Никакой подписки. Никаких данных, покидающих мой ноутбук.

Проблема была одна: локальные LLM на CPU работают медленно. Модель на четыре миллиарда параметров на шестнадцати ядрах Ryzen генерирует текст со скоростью три-пять токенов в секунду. Для суммаризации десяти новостей это две-три минуты ожидания. Терпимо, но неприятно. А вот на NPU — на специализированном нейросетевом процессоре — та же модель выдаёт двадцать-тридцать токенов в секунду. Разница в шесть-восемь раз. И вот ради этой разницы я и взял ноутбук с AMD XDNA2.


Железо: что внутри и почему NPU — это не GPU

Мой ноутбук — Pulse A16 AI+ C3HWFKG. Внутри — процессор AMD Ryzen AI 7 350 на шестнадцать ядер, встроенная графика Radeon 860M, тридцать два гигабайта оперативной памяти и тот самый нейросетевой процессор: AMD XDNA2, кодовое имя Krackan 1, PCI-идентификатор 1022:17f0, ревизия 0x20.

Тут важно сделать отступление, потому что путаница между NPU и GPU встречается даже у людей, которые разбираются в железе. Графический процессор — это универсальный параллельный вычислитель. Он умеет рисовать треугольники, считать матрицы, рендерить видео, обучать нейросети. Он гибкий, мощный, но прожорливый: под нагрузкой Radeon 860M потребляет десятки ватт, греется, шумит вентиляторами.

NPU устроен иначе. Это массив из фиксированных вычислительных блоков — в случае XDNA2 это топология шесть на восемь, архитектура aie2p. Он не умеет рисовать треугольники. Он не умеет запускать произвольный код. Он умеет одно: гонять тензоры через матричные умножители с минимальным энергопотреблением. Пять-шесть ватт под нагрузкой вместо тридцати-сорока у GPU. При этом для инференса небольших моделей — до восьми миллиардов параметров — скорость сопоставима или выше, потому что нет накладных расходов на универсальность.

Малоизвестный факт: архитектура AIE (AI Engine) в чипах AMD XDNA2 ведёт родословную не из мира настольных процессоров, а из мира программируемых логических матриц. В две тысячи шестнадцатом году компания Xilinx, которую AMD впоследствии купила, разработала концепцию реконфигурируемых вычислительных ядер для задач машинного обучения встраиваемых систем. То, что сейчас стоит в моём ноутбуке, — это прямой потомок тех решений, адаптированный для потребительского рынка. Отсюда и название драйвера — xrt, Xilinx Runtime. Отсюда и каталог /opt/xilinx/xrt/. Для тех, кто помнит, как выглядели платы Xilinx Virtex в лабораториях десять лет назад, эта преемственность выглядит почти сюрреалистично.

Ещё один факт, который я обнаружил уже в процессе: для Krackan Point с ревизией 0x20 директория прошивки называется 17f0_10, а не 17f0_20, как логично было бы ожидать. Это не баг. Драйвер amdxdna намеренно использует общую директорию для ревизий 0x10 (Strix Point) и 0x20 (Krackan Point), потому что формат прошивки у них идентичный. Различия — на уровне конфигурации массива ядер, а не на уровне микрокода. Я потратил сорок минут, пытаясь понять, почему прошивка грузится из «неправильной» папки, прежде чем разобрался.


Первые шаги: Fedora, драйверы и танцы с DKMS

На ноутбук я поставил Fedora 44. Ядро 7.2.7-200. Из коробки NPU не работал — и это нормально. Для работы нужен стек из трёх компонентов:

Первый — XRT, Xilinx Runtime. Это пользовательская библиотека, которая общается с драйвером и предоставляет API для приложений. Устанавливается в /opt/xilinx/xrt/. После установки нужно выполнить source /opt/xilinx/xrt/setup.sh, чтобы прописать переменные окружения.

Второй — модуль ядра amdxdna. Это драйвер, который непосредственно управляет железом: загружает прошивку, инициализирует SMU (контроллер управления питанием), создаёт устройство /dev/accel/accel0. В моём случае он установлен через DKMS — систему, которая пересобирает модуль при обновлении ядра. Версия: 2.26.0_20260924.

Третий — прошивка. Файл npu.dev.sbin, который драйвер загружает в NPU при инициализации. Лежит в /lib/firmware/amdnpu/17f0_10/. Версия 1.1.2.64.

Установка прошла штатно. Я перезагрузился, открыл терминал, ввёл:

source /opt/xilinx/xrt/setup.sh
xrt-smi examine

И увидел заветное: Device(s) Present, NPU Krackan 1. Сердце ёкнуло. Работает.

Дальше — проверка глубже:

ls -la /dev/accel/accel0

Ответ: crw-rw-rw-. root render — устройство существует, права на чтение и запись есть, группа — render. Отлично.

lsmod | grep amdxdna

Модуль загружен.

sudo dmesg | grep -i "amdxdna.*firmware"

Ответ: Load firmware amdnpu/17f0_10/npu.dev.sbin. Прошивка загрузилась.

Я выдохнул. NPU жив. Можно ставить FastFlowLM.


FastFlowLM: первый запуск и первый восторг

FastFlowLM — это локальный LLM-сервер, оптимизированный для работы на NPU AMD. Он предоставляет OpenAI-совместимый API, то есть любое приложение, умеющее работать с OpenAI, будет работать и с ним — достаточно подменить адрес сервера.

Установка заняла минуты. Я скачал бинарник в /opt/fastflowlm/, прописал его в PATH, загрузил первую модель:

flm pull qwen3:4b

Три гигабайта моделей в итоге легли в ~/.config/flm/models/. Модель на NPU грузится в память один раз, а дальше работает быстро.

Проверка:

flm validate

Пять строк, ни одной ошибки.

flm list

Сорок две модели доступны для загрузки. Я выбрал Qwen3-4B — и не пожалел. Но об этом позже.

Первый запуск сервера:

flm serve qwen3:4b

Терминал показал, что сервер слушает на порту 52625. Я открыл второй терминал и отправил тестовый запрос через curl:

curl -s http://localhost:52625/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model": "qwen3:4b", "messages": [{"role": "user", "content": "Привет!"}], "max_tokens": 100}'

Ответ пришёл через полторы секунды. Модель поздоровалась, представилась, спросила, чем помочь. На NPU. Локально. Без интернета. Я сидел и улыбался.


Инцидент: как одна команда убила нейросетевой процессор

А потом случился тот самый вечер, который я теперь вспоминаю с содроганием и с благодарностью — потому что он научил меня главному правилу работы с NPU.

Контекст: я пытался установить Silero TTS. Идея была простая — озвучивать новости естественным голосом. Но pip install silero-tts тянет за собой PyTorch. А PyTorch на Fedora 44 с Python 3.14 — это пятьсот пятьдесят четыре мегабайта пакетов плюс CUDA-зависимости, которые мне на данном этапе пока не нужны. Установка шла медленно. Я потерял терпение и нажал Ctrl+C.

Часть пакетов уже установилась. Система в лёгком беспорядке, но это полбеды. Я решил, что разберусь потом, и переключился на запуск FastFlowLM. Написал скрипт активации окружения, попробовал запустить — файл не нашёлся, потому что я забыл его создать. Ладно. Создал. Запустил flm serve. Сервер поднялся.

А потом мне понадобилось его перезапустить. И вместо того чтобы нажать Ctrl+C в терминале, где работал сервер, я ввёл в другом окне:

pkill -9 -f flm

Девятый сигнал. SIGKILL. Мгновенная смерть процесса без возможности корректного завершения. Процесс умер. Драйвер не получил шанса освободить NPU-контекст. И через три секунды, когда я попытался запустить flm serve снова, система выдала:

amdxdna 0000:66:00.1: [drm] *ERROR* Access power failed, ret -22
amdxdna 0000:66:00.1: [drm] *ERROR* aie2_hw_start: failed to init smu, ret -22
amdxdna 0000:66:00.1: [drm] *ERROR* probe with driver amdxdna failed with error -22

xrt-smi examine ответил: 0 devices found. NPU исчез. Из системы. Из реальности. Как будто его физически вынули из материнской платы.

Я смотрел на эти строчки и чувствовал, как внутри что-то холодеет. Ноутбуку три месяца. Я ещё не разобрался со всеми возможностями этого куска кремния, а уже, похоже, его убил.


Хроника восстановления: семь тупиков

Дальше начался вечер, который я не пожелаю никому. Семь попыток. Семь тупиков. Я опишу каждую, чтобы вы не повторили мой путь.

Попытка первая: перезагрузка модуля.

sudo rmmod amdxdna
sudo modprobe amdxdna

Модуль выгрузился. Загрузился. И снова: failed to init smu, ret -22. SMU не отвечает. Контроллер управления питанием завис. Драйвер стучится в дверь, а за дверью тишина.

Попытка вторая: пересборка DKMS.

Подумал: вдруг модуль повреждён. Удалил, пересобрал, установил заново:

sudo dkms remove xrt-amdxdna/2.26.0 --all
sudo dkms install xrt-amdxdna/2.26.0 -k $(uname -r)
sudo depmod -a

Перезагрузил модуль. Тот же результат. Конечно. Проблема не в модуле.

Попытка третья: кастомный драйвер.

Нашёл на форуме патч под названием «amdxdna-strix-fix». Автор — некто, кто столкнулся с похожей проблемой на Strix Point. Скачал, собрал, установил. Модуль загрузился. SMU всё равно упал. Потому что патч написан для ревизии 0x10, а у меня 0x20. Я понял это позже, а тогда просто видел ту же ошибку и не понимал почему.

Попытка четвёртая: обход инициализации SMU.

Патч, который делает ошибку инициализации SMU нефатальной. Драйвер загружается, пропускает SMU, пытается работать дальше. Но firmware не становится alive. Потому что без инициализированного SMU прошивка не может поднять канал управления. Тупик.

Попытка пятая: перестановка порядка инициализации.

Идея: а что если сначала загрузить firmware через PSP, а потом инициализировать SMU? Вдруг firmware сама поднимет контроллер питания? Нет. Не поднимет. SMU — это часть firmware. Замкнутый круг.

Попытка шестая: подмена прошивки.

Попробовал заменить файл прошивки на другой из соседней директории. Не помогло. Драйвер ждёт конкретный формат, конкретную сигнатуру. Чужой файл не подходит.

Попытка седьмая: патч ошибки POWER_OFF.

Сделал ошибку при выключении питания нефатальной. Драйвер прошёл дальше. POWER_ON сработал. Но firmware не ответила на management-запросы. Мёртвый канал. Мёртвый процессор.

Я сидел в два часа ночи, смотрел в терминал и думал: ну всё, попал. Завтра писать в поддержку. Говорить, что убил NPU командой pkill -9. Как это будет звучать?


Спасение: одна кнопка

А потом я сделал то, что нужно было сделать с самого начала. Я нажал кнопку перезагрузки.

sudo reboot

Экран погас. Ноутбук выключился. Я досчитал до десяти. Включил.

Загрузка Fedora. Экран входа в KDE. Открываю терминал. Ввожу:

sudo dmesg | grep -i amdxdna

И вижу:

[15.197616] amdxdna 0000:66:00.1: [drm] Load firmware amdnpu/17f0_10/npu.dev.sbin
[15.335325] [drm] Initialized amdxdna_accel_driver 0.17.0 for 0000:66:00.1 on minor 0

Живой. Загрузился. Прошивка встала. Драйвер инициализировался.

xrt-smi examine

NPU Krackan 1. Device(s) Present.

Я выдохнул. Потом выдохнул ещё раз. Потом налил себе чай и полчаса просто сидел, глядя в стену.


Корневая причина: анатомия зависания

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

Драйвер amdxdna при загрузке проходит строго определённую последовательность. Сначала вызывается pci_enable_device — включается PCI-устройство. Потом aie_smu_init — инициализируется контроллер управления питанием. Затем aie_psp_start — через PSP (Platform Security Processor) загружается прошивка. Потом aie2_get_mgmt_chann_info — драйвер ждёт, пока прошивка поднимет канал управления. И наконец aie2_mgmt_fw_query — конфигурация.

Когда процесс flm работает, он держит контекст на NPU. Драйвер знает, что устройство занято, и при нормальном завершении процесса (через SIGTERM или Ctrl+C) корректно освобождает ресурсы. Контекст закрывается, SMU получает команду на переход в режим ожидания, всё чисто.

Когда процесс убивается через SIGKILL, он умирает мгновенно. Без вызова деструкторов. Без освобождения ресурсов. Без уведомления драйвера. Драйвер видит, что процесс исчез, но не успевает корректно закрыть контекст. И — вот тут ключевой момент — SMU остаётся в состоянии «занят». Не в режиме ожидания. Не в режиме ожидания с ошибкой. В состоянии «я обслуживаю запрос, ждите». Навсегда.

При следующей загрузке модуля aie_smu_init отправляет команду контроллеру питания. И получает в ответ 0xff — невалидный ответ, потому что контроллер всё ещё ждёт завершения предыдущей операции. Драйвер интерпретирует это как EINVAL, код -22, и прекращает инициализацию.

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

При выключении питания сбрасывается всё. Конденсаторы разряжаются. Контроллеры теряют состояние. BIOS при следующем старте заново инициализирует PCI-шину, подаёт питание на NPU, и тот просыпается чистым. Как будто ничего не было.

Аналогия: представьте, что вы позвонили в дверь, вам не открыли, и вы ушли. А человек за дверью всё ещё стоит и ждёт, что вы скажете, кто вы. И будет ждать, пока не выключат свет во всём доме. Только тогда он забудет, что кто-то звонил, и будет готов услышать новый звонок.

Сравнение с GPU здесь показательно. Для NVIDIA есть команда nvidia-smi --gpu-reset, которая аппаратно сбрасывает состояние чипа. Для AMD GPU можно перезагрузить драйвер через rmmod/modprobe, и GPU обычно оживает. Для NPU XDNA2 такой возможности нет. Никакой команды сброса. Никакого программного восстановления. Только перезагрузка. Это аппаратная особенность, и в документации она не описана ни единым словом. Я выяснил это эмпирически.


Что категорически не работает для Krackan Point

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

Патч amdxdna-strix-fix. Написан для Strix Point (ревизия 0x10). Обходит ошибку инициализации SMU как нефатальную. На Krackan Point firmware не поднимается без SMU. Патч бесполезен.

Перестановка порядка PSP и SMU. Идея: сначала firmware, потом питание. Не работает, потому что прошивка не стартует без инициализированного контроллера питания. Замкнутый круг.

Патч «нефатальный POWER_OFF». Позволяет продолжить после ошибки выключения. Но включение проходит, а прошивка не отвечает. Канал управления не поднимается.

Подмена прошивки на старую версию. Драйвер 2.26.0 ожидает новый протокол взаимодействия. Старая прошивка несовместима.

Создание отдельной директории 17f0_20 для прошивки. Не нужно. Драйвер использует 17f0_10 для обеих ревизий. Это не ошибка, а архитектурное решение.

Единственное, что работает на Krackan Point — штатный DKMS-модуль версии 2.26.0 без патчей. Если он не грузится — проблема не в нём. Проблема в железе. И лечится перезагрузкой.


Рабочая конфигурация: как не сломать снова

После инцидента я выработал набор правил и скриптов, которые защищают от повторения. Делюсь ими целиком.

Скрипт активации окружения. Файл ~/activate_npu.sh:

#!/usr/bin/env bash
if [ -z "$NPU_ENV_ACTIVATED" ]; then
    source /opt/xilinx/xrt/setup.sh
    export PATH="/opt/fastflowlm/bin:$PATH"
    if [ -d "/opt/fastflowlm/lib64" ]; then
        export LD_LIBRARY_PATH="/opt/fastflowlm/lib64${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
    fi
    export NPU_ENV_ACTIVATED=1
fi

В ~/.bashrc добавлена строка: [ -f ~/activate_npu.sh ] && source ~/activate_npu.sh. Теперь при каждом открытии терминала окружение поднимается автоматически.

Правила остановки сервера. Никогда не убивать flm через kill -9 или pkill -9. Только через pkill -TERM -f flm или через Ctrl+C в терминале. После отправки сигнала подождать три секунды и проверить: pgrep -a flm. Должно быть пусто. Если не пусто — подождать ещё. Если через десять секунд процесс жив — что-то не так, но всё равно не убивать девяткой. Лучше перезагрузиться.

Один сервер — один экземпляр. Не запускать два flm serve одновременно. NPU не поддерживает два контекста FastFlowLM без конфликтов.

Проверка после каждого эксперимента. Любая правка — и сразу xrt-smi examine. Убедиться, что устройство на месте. Если нет — не продолжать эксперименты, а разбираться.


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

Теперь, когда железо работает и я знаю, как его не ломать, пора строить то, ради чего всё затевалось. Агент.

Логика простая. Есть цепочка из четырёх звеньев. Первое: загрузка новостей по RSS. Второе: суммаризация через LLM. Третье: синтез речи. Четвёртое: воспроизведение аудио. Каждое звено — отдельный модуль, который можно менять независимо от остальных.

Вот как это выглядит схематически:

RSS-лента 3DNews → feedparser → текст новостей
                                    ↓
                              Qwen3-4B на NPU
                              (через FastFlowLM)
                                    ↓
                              Сводка на русском
                                    ↓
                              Piper TTS (на CPU)
                                    ↓
                              .wav файл
                                    ↓
                              aplay → динамики

Всё это оркестрируется одним Python-скриптом. Никакого фреймворка. Никакого Docker. Один файл, три зависимости, одна команда для запуска.

Почему именно такая архитектура? Потому что она прозрачная. Если что-то ломается, я вижу, на каком этапе. Если RSS не отдаёт новости — проблема в сети или в источнике. Если LLM отвечает бессвязно — проблема в промпте или в модели. Если голос звучит странно — проблема в TTS. Каждый слой изолирован.


Выбор источника: почему 3DNews и почему RSS

Первый вопрос: откуда брать новости. Мне нужны технологии, железо, ИИ. Русскоязычный источник. С работающим RSS.

Я перепробовал несколько вариантов. Лента одного крупного государственного новостного портала не работает с 2023 года — отдаёт пустой каркас страницы, а контент рендерится через JavaScript. Парсить это через requests бесполезно: получаешь пустой <div> без единого текста. Можно было бы использовать headless-браузер, но это утяжеляет агента на порядок и ломает идею лёгкости.

Другой вариант — агрегаторы. Но они отдают контент в своём формате, с ограничениями, иногда с задержкой. И тоже бывает, что RSS у них тихо умирает.

3DNews оказался идеальным. Адрес их ленты для срочных новостей отдаёт стабильно десять-пятнадцать записей в день. Формат стандартный, без причуд. Заголовок, аннотация, ссылка, дата. Всё, что нужно для суммаризации. Парсится через feedparser за полсекунды.

Код загрузки тривиальный:

import feedparser

def fetch_news():
    feed = feedparser.parse("https://3dnews.ru/breaking/rss/")
    return [
        {"title": e.title, "summary": e.summary, "link": e.link}
        for e in feed.entries[:10]
    ]

Десять записей — оптимально. Меньше — неинформативно. Больше — модель начинает путаться и терять фокус. Десять новостей, каждая по одному-двум предложениям, укладываются в полторы-две тысячи токенов. Для Qwen3-4B это комфортный объём.


Выбор модели: почему Qwen3-4B

Это был неочевидный выбор, и я потратил вечер на сравнение. В FastFlowLM доступны десятки моделей. Для суммаризации новостей на русском языке мне нужна была модель, которая: понимает русский на уровне носителя; следует инструкциям без самодеятельности; работает быстро на NPU; не генерирует разметку, потому что результат пойдёт в TTS.

Я перепробовал четыре варианта.

Qwen3-4B. Отличный русский. Хорошо следует инструкциям. Оптимизирована под NPU. Но есть нюанс: у модели есть режим рассуждения (reasoning), в котором она тратит токены на внутренние размышления перед ответом. Для суммаризации это лишнее. Решение: max_tokens=2000 с запасом и temperature=0.3 для минимума креативности. Если ответ всё равно обрывается, можно отключить режим рассуждения через параметр в запросе.

Qwen3-it-4B (instruction-tuned). Чуть лучше следует инструкциям, нет режима рассуждения. Но на сложных задачах чуть слабее базовой. Для суммаризации — рабочая альтернатива.

Llama-3.2-3B. Быстрая, стабильная. Но русский язык заметно слабее. Фразы иногда строятся неестественно, порядок слов хромает. Для новостной сводки, которую слушаешь ушами, это критично.

Phi4-mini-it-4B. Хороший баланс скорости и качества. Но русский не идеален. Отдельные формулировки звучат как машинный перевод.

Итог: Qwen3-4B. Она лучше всех работает с русским текстом на этой задаче.

Ещё один факт, который я обнаружил в процессе: при temperature выше 0.5 модель начинает «украшать» текст. Добавлять вводные слова, риторические вопросы, восклицания. Для сводки, которую озвучивает синтезатор, это катастрофа. Голос начинает звучать неестественно на риторических конструкциях. Поэтому temperature=0.3 — не просто рекомендация, а необходимость.


Промпт: неочевидная инженерия

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

Вот финальная версия:

Ты — редактор новостного дайджеста.
1. Выдели 3-5 самых важных новостей.
2. Каждую новость опиши одним предложением.
3. Числа пиши словами (TTS не любит цифры).
4. Без маркдауна, списков, нумерации.
5. Не обрывай предложения.

Разберу по пунктам, потому что каждый — это шрам от конкретной ошибки.

«Выдели три-пять самых важных». Без этого ограничения модель пытается пересказать все десять новостей. Получается длинно, скучно, и синтез речи растягивается на пять минут. Три-пять — это сорок-шестьдесят секунд аудио. Оптимально для утреннего прослушивания.

«Каждую новость опиши одним предложением». Без этого модель пишет по абзацу на новость. Сводка раздувается до тысячи слов. Мне нужна выжимка.

«Числа пиши словами». Это критично для TTS. Piper произносит «2026» как «двадцать двадцать шесть» или «две тысячи двадцать шесть» непредсказуемо. А «15%» может прочитать как «пятнадцать процентов», а может как «один пять процент». Если написать «пятнадцать процентов» словами — проблема исчезает.

«Без маркдауна, списков, нумерации». Модель обожает выводить текст в формате «1. Первая новость… 2. Вторая новость…». Для чтения глазами это удобно. Для озвучки — нет. Синтезатор произносит «один точка» или просто делает паузу. Слушается уродливо.

«Не обрывай предложения». Иногда модель останавливается на полуслове, если упирается в лимит токенов. Обрывок предложения, оборванный голосом TTS, звучит как помеха. Лучше пусть модель закончит мысль, даже если это займёт лишних двадцать токенов.

Код запроса к модели:

from openai import OpenAI

client = OpenAI(base_url="http://localhost:52625/v1", api_key="sk-local")

response = client.chat.completions.create(
    model="qwen3:4b",
    messages=[
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": f"Новости:\n{news_text}"}
    ],
    temperature=0.3,
    max_tokens=2000,
)
summary = response.choices[0].message.content.strip()

Обратите внимание: используется библиотека openai, но адрес сервера — локальный. Для FastFlowLM это неважно: он реализует тот же протокол. Ключ sk-local — формальность, сервер его не проверяет.


Часть 1 из 4.

В следующей части: выбор и настройка Piper TTS, полный код агента, подводные камни синтеза речи на русском языке, установка зависимостей, первый запуск и то, как звучит результат. А также малоизвестные факты о локальных нейросетях, которые я узнал в процессе.


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *