Как я перестал верить в «мы договаривались» и написал систему с Whisper, которая помнит всё
Или история о том, как я собрал AI-арбитра для совещаний, потерял три дня на аудиодрайверах и подружил Python с RTX 5080.
Привет! Меня зовут Сергей, и последние пару месяцев я проектировал и писал систему, которая умеет слушать рабочие встречи, превращать их в текст и — самое главное — находить доказательства в спорах за секунды. Не «находить похожие слова», а реально понимать, что «расходы на серверы» и «бюджет на ЦОД» — это одно и то же.
Если вы хоть раз участвовали в совещании, где через неделю никто не помнит, о чём договорились — добро пожаловать под кат. Здесь будут схемы на Graphviz, куски Python-кода и пара историй о том, как я наступил на грабли с CUDA, аудиодрайверами и PyTorch.
О чём этот лонгрид
- Почему я не взял готовое решение (и вам не советую, если работаете с русским языком)
- Архитектура, которая не ломается при смене модели распознавания
- Как подружить микрофон, системный звук, очередь и Whisper на GPU
- Почему WASAPI — это боль, но Stereo Mix — ещё больнее
- Что делать, если PyTorch говорит «Key already registered with the same priority: C10» и молча падает
- RAG-поиск на 21 000+ фразах и почему RTX 5080 для эмбеддингов — пока бесполезна
- Что получилось в итоге: цифры, метрики и планы
О чём этот лонгрид
- Почему я не взял готовое решение (и вам не советую, если работаете с русским языком)
- Архитектура, которая не ломается при смене модели распознавания
- Как подружить микрофон, системный звук, очередь и Whisper на GPU
- Почему WASAPI — это боль, но Stereo Mix — ещё больнее
- Что делать, если PyTorch говорит «Key already registered with the same priority: C10» и молча падает
- RAG-поиск на 21 000+ фразах и почему RTX 5080 для эмбеддингов — пока бесполезна
- Что получилось в итоге: цифры, метрики и планы
То же самое с источниками звука:
AudioConsumer — сердце системы — работает только с этими абстракциями. Ему всё равно, откуда взялся звук и кто его распознаёт. Это как розетка: можно воткнуть утюг или дрель, главное — чтобы вилка подходила.
Мораль: потратьте час на проектирование интерфейсов в начале — сэкономите дни на переделках потом.
Конвейер: как аудио превращается в текст (и не только)
Центральный конвейер — это классический Producer-Consumer с асинхронной очередью:
- Producer (микрофон или WASAPI) нарезает непрерывный аудиопоток на 2-секундные чанки и складывает в asyncio.Queue
- Буфер (очередь) разделяет быстрый источник (микрофон выдаёт данные равномерно) и медленный обработчик (GPU думает 0.3 секунды)
- Consumer забирает чанки, прогоняет через VAD-фильтр (отсев тишины), отправляет в Whisper и сохраняет результат
Отдельного упоминания заслуживает VAD-фильтр (Voice Activity Detection). Без него Whisper на тишине начинает галлюцинировать: «Продолжение следует...», «Субтитры сделал DimaTorzok». Я отсекаю тишину по среднеквадратичной громкости (RMS < 0.01). Подобрал порог экспериментально: записал тишину, измерил RMS ≈ 0.005, речь даёт 0.1–0.5. Порог 0.01 — золотая середина. Это экономит ~95% ресурсов GPU.
Грабли №1: Stereo Mix vs WASAPI vs USB-наушники
Первая неожиданность: системный звук из браузера или Zoom не захватывается.
Что я сделал сначала: Stereo Mix — классический loopback в Windows. Работает? Да. Но он слушает аналоговый выход материнской платы. А у меня USB-наушники — Windows определяет их как отдельное аудиоустройство. Stereo Mix их просто не видит.
Попытка №2: VB-Cable — виртуальный аудиокабель. Настраивается, но требует, чтобы пользователь вручную перенаправлял звук. Неудобно для тиражирования.
Решение: Перешёл на WASAPI Loopback через библиотеку soundcard. Эта технология умеет захватывать звук конкретного устройства вывода напрямую, без аналоговых петель. В диспетчере устройств нашел свои USB-наушники (они определяются как «Динамики (3- USB Audio Device)») и направил WASAPI на них:
Теперь «Советник» захватывает звук из любого приложения: Chrome, Zoom, Discord — без единой настройки со стороны пользователя.
Грабли №2: PyTorch, CUDA 13 и загадочная ошибка C10
Самая болезненная проблема ждала меня при установке pyannote.audio для Voice ID (диаризации спикеров).
У меня стоит CUDA 13.2 (последняя на момент установки). faster-whisper через ctranslate2 ищет cublas64_12.dll. А у меня в папке CUDA\v13.1\bin\x64 лежит только cublas64_13.dll. Whisper падает с ошибкой «Library not found».
Решение: Копируем cublas64_13.dll → переименовываем в cublas64_12.dll. Грязный хак, но работает. Не делайте так в продакшене, но для локальной разработки — почему нет?
Дальше — больше. При установке pyannote.audio PyTorch молча обновился с 2.5.1 до 2.12.0. И всё. Процесс Python начал падать при импорте с загадочным:
Ни трейсбека, ни объяснения. Просто молча умирает. Я потратил несколько часов: переустанавливал PyTorch раз пять, чистил кеш pip, пересоздавал venv. Оказалось:
- PyTorch 2.12.0 (CPU) конфликтует с остатками CUDA-версии в кеше
- Windows блокирует DLL при удалении ([WinError 5] Отказано в доступе) — приходится убивать процессы через taskkill
- pyannote.audio 4.x требует PyTorch 2.8+, который не поддерживает RTX 5080 (архитектура Blackwell, sm_120)
Финальное решение: Пересоздал venv с Python 3.11 (системный был 3.8 — тоже сюрприз!), установил PyTorch 2.5.1+cu121 первым, затем всё остальное. Voice ID временно отложил до выхода стабильного PyTorch с поддержкой RTX 5080. Код для Voice ID готов, лежит в репозитории, ждёт своего часа.
Мораль: python --version — самая важная команда перед созданием venv. И pip freeze > requirements.txt — сразу после установки всего.
Хранение: почему одной базы данных категорически мало
Я использую три разных хранилища. Не потому что «модно», а потому что каждое решает свою задачу:
Отдельная история про WAV-файлы. Первая версия сохраняла сырые PCM-байты. Ни один плеер не мог их открыть — он просто не знал частоту дискретизации и битность. Пришлось добавить метод _add_wav_header(), который формирует правильный 44-байтный RIFF-заголовок. Теперь файлы открываются в любом плеере.
RAG-поиск: когда «найти по слову» — это провал
Обычный полнотекстовый поиск по ключевым словам не работает для разговорной речи. Человек может сказать «расходы на серверы» вместо «бюджет», и LIKE '%бюджет%' ничего не найдёт.
Я использую семантический поиск: каждая фраза превращается в 1024-мерный вектор (эмбеддинг) через модель intfloat/multilingual-e5-large. При поиске запрос тоже векторизуется, и Qdrant находит ближайшие векторы по косинусному сходству.
Результат: запрос «кластер» находит «Начальное состояние кластера» с релевантностью 91%. Запрос «отказоустойчивость» — «Тест наш заключался в том, что у нас проверяется отказоустойчивость кластера» с релевантностью 89%.
Но есть нюанс. RTX 5080 не поддерживается текущей версией PyTorch для эмбеддингов (архитектура Blackwell, sm_120). Поэтому эмбеддинги считаются на CPU. Это медленнее, но работает. Ждём обновления PyTorch.
Что получилось в итоге
Система работает полностью локально на Windows 11 с RTX 5080 (16 ГБ VRAM), 128 ГБ RAM:
Что бы я сделал по-другому
- Сразу взял WASAPI, а не Stereo Mix. Потерял пару дней на отладку loopback, который принципиально не работает с USB-наушниками.
- Закрепил версию PyTorch в requirements.txt с первой минуты. pyannote.audio молча обновил torch, и я потратил часы на восстановление.
- Сделал веб-дашборд с первого дня. Первую неделю управлял системой через командную строку. Веб-интерфейс с кнопками «Старт/Стоп» и поиском сделал жизнь в разы удобнее.
- Не хранил бы модель Whisper в Git. 3 ГБ бинарных данных в репозитории — сомнительное удовольствие. Для личного GitLab сойдёт, но для командной работы лучше вынести в отдельное хранилище.
Что дальше
В планах — Voice ID (диаризация спикеров), «Арбитр» с LLM для автоматического разрешения споров и автоматический протокол встреч. Код для Voice ID уже написан и ждёт обновления PyTorch.
А пока система работает, записывает совещания и честно ищет доказательства. Если вы тоже устали от «мы это не обсуждали» — добро пожаловать в issues.
Теги (хабы): python, machine learning, ASR, Whisper, RAG, архитектура, разработка под Windows, open source