# ADR 0023. Ударение задаётся знаком U+0301, и модель дообучена его слушаться

**Дата:** 2026-09-23
**Статус:** принято; в Rust-сервере с 2026-09-24

## Контекст

Qwen3-TTS местами ставит ударение неверно, а разметку ударений не понимает:
`+` читает вслух как «плюс», U+0301 коверкает слово ([ADR 0015](0015-stress-control-in-qwen3-tts.md)).
Управлять ударением получалось только выбором слов, а словарь произношений
([ADR 0016](0016-automatic-pronunciation-dictionary.md)) правит написание,
но не ударение.

Модель — Apache-2.0: дообучать и выпускать производные веса можно.

## Что проверено

Всё — в [experiments/23](../../experiments/23-stress-finetune/README.md).

**Измеритель.** Ударение без человека в цикле меряется сопоставлением контура
громкости слова с эталонами Silero во фразе. Проверен на известных ударениях
до того, как ему поверили: 10–12% ложных тревог, 71% сдвигов находит. Первый
вариант (эталоны — отдельные слова) давал 45% ложных тревог и был отброшен.

**Дообучение.** LoRA ранга 16 на основном трансформере (~1% параметров), в той
раскладке, в какой voicy генерирует (клонирование по образцу). Данные:
12 ч аудиокниг RuLS (общественное достояние) с разметкой RUAccent и 3000 фраз,
где 1–3 слова уведены на другой слог, — без таких примеров знак почти всегда
повторяет привычку модели, и учить его читать нечему.

| Знак на ненормативном слоге | Модель следует знаку |
|---|---|
| исходная | 21% (≈ случайно) |
| только аудиокниги | 40% |
| + сдвиги, озвученные Silero (CC BY-NC) | 55% |
| **+ сдвиги, озвученные ESpeech (Apache-2.0)** | **49%**, p = 0.002 |
| потолок измерителя (Silero / ESpeech) | 66% / 59% |

Голос (0.983 против 0.985), разборчивость (CER 4.1% против 4.4%) и полоса
частот не пострадали. На обычном тексте измеритель разницы не видит: модель
и так ставит верно около 85% слов, а он по одному слову шумит.

То же, что выпускается, — через Rust-сервер: говорящая часть q5_k на llama.cpp,
знаки от RUAccent в Rust, три сида. Знаку на ненормативном слоге следует 43%
(93 из 215) против 26% у исходной, p = 0.0008; f16 даёт 44% — квантование
адаптер не ослабляет. CER на обычном тексте 4.3% против 3.6%, ДИ разницы
−0.15…+1.65 п.п.; худшие фразы — английские термины кириллицей, где ударение
RUAccent угадывает.

## Решение

1. **Ударение — это разметка текста**, а не знание модели. Где стоит ударение,
   решает подготовка текста: запись словаря произношений проекта → RUAccent →
   без знака. Модель только выполняет знак.
2. **Знак — U+0301 после ударной гласной**: один токен в словаре Qwen,
   стандартная типографика, `ё` не размечается.
3. **Модель — Qwen3-TTS с влитым адаптером v3.** Выпускается только вариант,
   обученный на данных со свободными лицензиями; адаптер на речи Silero остаётся
   исследовательским.

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

## Последствия

- Слитые веса той же формы: `scripts/convert_qwen.py --weights` переводит их
  в GGUF как официальные, меняется только файл talker, скорость та же. Выпуск —
  [sknyazev/qwen3-tts-12hz-1.7b-ru-stress-gguf](https://huggingface.co/sknyazev/qwen3-tts-12hz-1.7b-ru-stress-gguf),
  `voicy setup` качает её по умолчанию.
- RUAccent перенесён в Rust (`rust/src/native/accent.rs`): на 2022 предложениях
  выход совпал с Python целиком. Словарь на 3.2 млн словоформ — FST на 4.7 МБ,
  разметка ~9 мс на фразу, модели ~1 ГБ памяти.
- Знаки ставятся в исходный текст: нормализованный текст RUAccent теряет
  «/», «%», «…». По умолчанию включены, отключаются `stress: false` /
  `--no-stress`, и только модели с `stress.json` рядом с весами — исходной
  Qwen знак вредит.
- Словарь произношений получает поле ударения; запись словаря главнее RUAccent —
  это и путь исправления жаргона («дже́нерики»), где RUAccent даёт норму,
  а не речь разработчиков.
- Остаётся открытым: на словах с сильной привычкой модели знак перебивает её
  в меньшинстве случаев; как поднять послушность к потолку источника данных,
  и нужно ли, — решается слуховой проверкой, а не этим измерителем.
