Голосовые системы

Потоки и LLM: ограничить очередь до создания задач

Статус: предложение, не реализовано. Инвазивность: Средняя. Основа: webrtc-komaroff@772de4a, 16.09.2026.

WhisperStreamNode.handle_frame создаёт task на каждый кадр. Semaphore ограничивает число одновременно выполняемых publish, но все остальные tasks уже существуют и удерживают payload. Такая же форма есть в LLM BaseService: входящие сообщения превращаются в tasks до semaphore.

Где накапливается работа

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

Предлагаемая правка

Для аудио ввести один publisher-worker на stream и bounded queue с учётом байтов и возраста. Кадр поступает в очередь без отдельной task. При заполнении нужна явная политика: краткое ожидание в пределах latency budget, затем сигнал перегрузки и корректное завершение потока либо согласованный gap protocol. Молчаливое выпадение PCM нельзя считать безопасным: оно меняет временную шкалу и распознавание.

В LLM adapter использовать ограниченную очередь запросов и фиксированное число workers. До дорогостоящего inference проверять дедлайн. В контракты добавить deadline/cancellation совместимым способом: Pydantic сейчас запрещает лишние поля. Не полагаться на timeout ожидающего NATS request как на отмену работы responder.

Время очереди измерять отдельно. Текущий duration_ms LLM начинает отсчёт уже внутри semaphore и скрывает ожидание.

Масштабирование звука требует ещё одного решения

Whisper хранит буфер в RAM по stream subject и получает wildcard без queue. Обычные реплики дублируют обработку. Случайная queue group для каждого PCM frame разрушит целостность потока. Для живого аудио закрепить stream за worker: ingress directory, стабильный shard-key или отдельный session owner. Core peer connection также остаётся у создавшего его процесса до завершения.

Эта часть пересекает оба репозитория и не должна попадать в MR только для WebRTC producer без совместимого consumer.

Проверяемые пределы

При 16 кГц mono s16 один поток создаёт 50 кадров/с и 32 000 байт PCM/с. Например, предлагаемый, ещё не принятый бюджет 0.5 с — 25 кадров и 16 000 байт PCM на stream без overhead. Выбирать его нужно по допустимой задержке и тестам качества.

Приёмка: размер очереди никогда не превосходит заданного предела; количество tasks перестаёт зависеть от числа входных frames; очередь не содержит просроченных запросов после drain. Нагрузочный прогон: 1/10/50 потоков, искусственная пауза NATS/backend, reconnect и отсутствие end. Снять RSS, pending bytes, p95 queue wait, возраст старейшего кадра и число отказов admission.

Для deadline проверить согласованность source defaults и DRS: frontend default 70 с, DRS frontend 300 с; Agent default 60 с, DRS 270 с; Core DRS request timeout 600 с. Это разные конфигурации, а не доказанный production timeout-инцидент.

Цена и откат

Появится явный отказ при перегрузке там, где раньше росло ожидание. Это нужно показать в UI. Вводить limits постепенно с метрикой would-reject, затем включать enforcement. При rollback протокол frame/end должен остаться совместимым; новые поля требуют versioned rollout.

Основания

Audio tasks, LLM BaseService, settings и DRS. Python asyncio.Queue документирует maxsize и блокирующий put; Queue.shutdown появился только в Python 3.13, тогда как образы проекта — 3.11. Для текущего runtime нужен совместимый drain/cancel. Context7 NATS queue groups подтверждает случайное распределение сообщений внутри группы: оно не обеспечивает affinity сессии.

Все предложения · Фактическая архитектура

Исходник

Скачать Markdown · К материалам исследования