Когда я замерил это в первый раз, я не поверил числу. Я удержал горячую клавишу, произнёс фразу, отпустил и в уме запустил секундомер до момента, когда текст приземлится у курсора. Потом я сделал то же самое с облачным инструментом диктовки. Облачный полный цикл был не вдвое медленнее. Утром с плохим кофейным Wi-Fi он был на порядок медленнее, и хуже того — он был непредсказуемым: иногда 300 мс, иногда две секунды, изредка спиннер, который разрешался в ничто.
Эта дисперсия — вся суть истории. Диктовка — это интерфейсная поверхность реального времени, а интерфейсные поверхности реального времени умирают от хвостовой задержки, а не от средней. Поэтому когда мы строили Vext — наше закрытое приложение голос-в-текст для macOS — решение запускать всё на устройстве не было идеологическим решением о приватности. Оно началось как решение о задержке. Аргумент о приватности выпал из него бесплатно.
Этот пост — про математику и архитектуру за тем выбором. Он написан для инженера, стоящего на той же развилке: у вас есть аудио- или ИИ-функция, вы смотрите на облачный STT API с дружелюбным SDK, и голос в вашей голове спрашивает, не стоит ли просто запустить модель локально. Я хочу дать вам способ ответить на этот вопрос числами, а не ощущениями.
Одно уточнение сразу. Сам Vext — закрытый. Инструменты разработки Muvon — Octomind, Octocode и остальной стек агентов — с открытым кодом под пермиссивными лицензиями, и про их внутренности мы пишем свободно. Vext — это коммерческое приложение, которое оплачивает открытую работу, поэтому дальше речь про подход и компромиссы, такие, о которых мог бы рассуждать любой инженер, а не про экскурсию по нашему дереву исходников.
Диктовка — Это Нагрузка, Враждебная к Задержке
Большинство аудиофункций снисходительны. Транскрибировать подкаст за ночь? Никто не смотрит на часы. Сгенерировать протокол встречи после окончания звонка? Несколько секунд обработки невидимы. Это пакетные нагрузки, и для пакетных нагрузок облако часто и есть правильный ответ — вы получаете чужие GPU, чужую команду эксплуатации, и задержка просто не имеет значения, потому что ни один человек не ждёт на критическом пути.
Диктовка — противоположность. Человек пристально смотрит на курсор, посреди мысли, ожидая, когда появятся слова. Цикл взаимодействия такой: сказать, отпустить, ждать текст сейчас. Каждая миллисекунда между «отпустил» и «появился текст» — это мёртвый воздух, который пользователь чувствует в руках. И в отличие от загрузки страницы, нет спиннера, который сделал бы это приемлемым: ментальная модель — «я печатаю голосом», а печать не буферизуется.
Жестокая часть в том, что это стоимость на каждое высказывание. Вы платите не один раз. Вы платите на каждой диктовке, пятьдесят или сто раз в день для активного пользователя. Штраф в 700 мс, который вы никогда не заметили бы на загрузке страницы, за рабочий день становится разницей между инструментом, который исчезает в потоке, и инструментом, с которым вы боретесь.
Так что бюджет тесный, и бюджет — это первое, о чём надо рассуждать.
Математика Полного Цикла
Построим бюджет облачной задержки честно, из частей, которые нельзя избежать.
Облачный STT-запрос обязан сделать как минимум следующее до того, как вернётся хоть одно слово:
- Захватить и закодировать аудио на устройстве.
- Установить или переиспользовать соединение — TLS-рукопожатие, если холодное, или хотя бы стоимость удерживаемого сокета.
- Загрузить байты аудио по сети.
- Поставить в очередь на стороне провайдера, пока воркер её не подхватит.
- Выполнить инференс на их железе.
- Сериализовать и скачать результат обратно к вам.
Вот часть, которую люди недооценивают: шаги 2, 3, 4 и 6 — это чистые накладные расходы, не имеющие никакого отношения к тому, насколько хороша модель. Это налог. И это шаги с худшей дисперсией.
Время сетевого полного цикла само по себе редко вам друг. Тот же регион, тёплое соединение, хорошая сеть — вы можете увидеть RTT в десятки миллисекунд. Но «тот же регион» тут несёт большую нагрузку. Пользователь в Бангкоке, бьющийся в эндпоинт в us-east, платит пару сотен миллисекунд RTT прежде, чем произойдёт что-то полезное, за один полный цикл — а холодное TLS-рукопожатие — это несколько циклов. Добавьте перегрузку, добавьте нестабильную гостиничную сеть, добавьте глубину очереди провайдера в часы пик, и ваш p99 полностью отрывается от p50.
Вот концептуальный бюджет. Я намеренно не привожу жёсткие числа так, будто их измерили в вашей среде — они зависят от вашей сети и вашего провайдера — но важна именно форма:
Cloud STT latency = encode
+ connection_setup # 0 if warm, 1-3 RTT if cold
+ upload_time # audio_bytes / uplink_bandwidth
+ provider_queue_wait # the variance killer
+ inference_time
+ download_time
+ deserialize
On-device STT latency = capture_to_buffer
+ inference_time # on local Neural Engine / GPU
Посмотрите, чего у пути на устройстве нет. Нет установки соединения. Нет загрузки. Нет очереди. Нет скачивания. Весь сетевой столбец схлопывается до нуля. Остаётся захват плюс инференс, и на Apple Silicon инференс достаточно быстр, чтобы член, доминирующий в облачном бюджете — сеть — просто не входил в уравнение.
Поэтому сравнение — не «облако немного медленнее». Для интерактивной нагрузки диктовки оно структурно иное. Облачный бюджет доминируют члены с высокой дисперсией, которые вы не контролируете. Бюджет на устройстве доминирует один член, который вы контролируете, выполняясь на железе, которое вы можете измерить.
И сама модель не из слабых. Речевая модель, которую мы используем по умолчанию — Parakeet от NVIDIA, из семейства NeMo — работает гораздо быстрее реального времени на чипах серии M, примерно на два порядка быстрее реального времени, когда она отображается на Neural Engine. Когда транскрипция настолько быстрее речи, которая её породила, член инференса в вашем бюджете становится мал относительно аудио, которое вам и так пришлось ждать, пока его произнесут. Узким местом перестаёт быть модель и становится физика: вы не можете транскрибировать фразу прежде, чем человек закончит её произносить.
Приватность Была Побочным Эффектом, Потом Стала Целью
Мы выбрали устройство ради задержки. Потом мы заметили, что купили по случайности.
Если аудио никогда не покидает машину, нет аудио, которое можно слить, истребовать по суду, взломать, удержать или использовать для обучения. Это не политика приватности — политика это обещание, что сервер мог бы делать что-то другое. Это архитектура приватности: данным физически некуда деться. Нет ведра ваших голосовых записей на чьей-то инфраструктуре, потому что запись была обработана и отброшена в нескольких сантиметрах от микрофона.
Для инструмента диктовки это важнее, чем кажется на первый взгляд, из-за того, что люди диктуют. Понаблюдайте за своим использованием день. Вы диктуете пароли, которые сбрасываете. Вы диктуете архитектуру систем под NDA. Вы диктуете содержание сообщения юристу, врачу, сооснователю в разгар поглощения. Микрофон на машине разработчика направлен на одни из самых чувствительных сырых материалов в его жизни, а позиция по умолчанию у большинства голосовых инструментов — стримить всё это третьей стороне на обработку.
Есть и измерение комплаенса, и оно не абстрактное. «Аудио никогда не покидает устройство» — это фраза, которая закрывает множество тикетов в ревью безопасности. Никакого соглашения об обработке данных для согласования, никакого списка субпроцессоров для аудита, никакого вопроса о трансграничной передаче данных, никакой поверхности уведомления о взломе для голосовых данных, потому что нет голосовых данных ни на каком сервере, который можно взломать. Для команды, пытающейся вывести инструмент в регулируемую среду, «оно работает локально» часто и есть разница между «одобрено» и «бессрочно на рассмотрении».
Честная версия: мы не сделали бы приватность заголовком, если бы математика задержки не указывала в ту же сторону. Но она указала, и раз уж вы решили, что аудио остаётся дома по причинам производительности, отказ выпускать облачный фолбэк, который тихо это отменяет — лёгкое, правильное решение.
Две Модели, Один Пайплайн, Обе Локальные
Speech-to-text даёт вам слова. Слова — не то, чего хочет пользователь.
То, что он сказал, было «э ну в общем нам типа надо перенести эту эту логику ретраев в в воркер, ну, тот который э обрабатывает вебхуки». То, что он хочет видеть вставленным — «Перенеси логику ретраев в воркер, который обрабатывает вебхуки». Убрать слова-паразиты, исправить фальстарты и придать форму результату — это другая работа, чем транскрипция, и это работа с языком, а не с речью. Поэтому пайплайн имеет две стадии, и интересным архитектурным решением было держать обе на устройстве.
Концептуально это передача эстафеты:
audio ──▶ [ speech model ] ──▶ raw transcript
(Parakeet) "uh so basically we need to..."
│
▼
[ small local LLM ] ──▶ clean text
(Gemma 3 4B, MLX) "Move the retry logic..."
│
┌─────────────┼─────────────┐
▼ ▼ ▼
cleanup translation summary
Стадия один — специализированная речевая модель: маленькая, быстрая, узкоцелевая, оптимизированная под одно. Стадия два — маленький LLM общего назначения — мы используем Gemma 3 4B по умолчанию (также можно выбрать Qwen 3, LLaMA 3.2 и Phi-3.5), выполняющийся через фреймворк MLX от Apple на GPU — делающий лингвистическую очистку, и та же модель с другой инструкцией обрабатывает живой перевод и суммаризацию встреч. Одна модель, три работы, переключаемые промптом, а не отгрузкой трёх разных бинарников.
Причина, по которой это разбиение на две модели — правильная форма: каждая стадия может быть хороша в своей работе. Вы не хотите, чтобы ваша речевая модель пыталась ещё и переписывать прозу, и вы не хотите, чтобы общий LLM пытался делать акустическое моделирование. Специализированное-потом-общее — это чистое разделение, и оно держит каждую модель достаточно маленькой, чтобы влезть в память и работать быстро. Гигантская сквозная модель, делающая и то и другое, была бы медленнее, прожорливее и труднее для рассуждения — и вы потеряли бы возможность менять модель очистки независимо от распознавателя.
Держать вторую стадию локальной — это та часть, которую соблазнительно пропустить. Речевая модель должна быть локальной ради задержки; уж очистка-то наверняка может быть быстрым вызовом API к большой облачной модели? Может — и тогда вы выбросили всё свойство приватности для стадии, которая обрабатывает самую читаемую версию слов пользователя. Сырая транскрипция беспорядочна; очищенный текст — это ясная, цитируемая, полностью сформированная мысль. Это ровно то, что меньше всего хочется отправлять на сервер. Если вторая стадия уходит в облако, то локальность первой не купила вам ничего. Поэтому обе остаются дома.
Где Устройство Начинает Болеть
Я бы соврал, продавая это как бесплатное. У инференса на устройстве есть реальные издержки, и притворяться обратным — это как выпустить нечто элегантное в демо и жалкое в поле. Вот где кусается.
Размер модели и скачивание. Локальные модели — это файлы, а хорошие локальные модели — большие файлы. Первый запуск обязан их забрать — многогигабайтное скачивание для скомпилированных весов речевой модели, плюс сравнимое по размеру скачивание для LLM — и это опыт первого запуска, который надо проектировать вокруг, потому что «скачать 3 ГБ прежде, чем вы сможете сказать первое слово» — это жестокий онбординг, если обращаться с ним плохо. На облачной стороне эквивалента нет; их модель уже тёплая на их железе.
Давление на память. Обе модели, загруженные и резидентные, стоят RAM. На машине с 64 ГБ — кому какое дело. На базовом ноутбуке, который ещё и крутит браузер с девяноста вкладками, IDE и три Electron-приложения, вы теперь конкурируете за память со всем, ради чего пользователь вообще открыл ноутбук. Надо быть осмотрительным насчёт того, когда модели загружаются, когда освобождаются и какого размера модель вы готовы ставить по умолчанию.
Вы наследуете нижнюю планку железа. Облачный STT работает на одних и тех же датацентровых GPU и для флагманского телефона, и для пятилетнего ноутбука. На устройстве ваша производительность и есть кремний пользователя. Мы требуем Apple Silicon именно потому, что Neural Engine и унифицированная память — это то, что заставляет математику задержки работать; на старом или слабом железе весь аргумент смягчается. Вы делаете ставку на нижнюю планку железа и отсекаете всех, кто ниже неё.
Квантизация — ручка с зубами. Чтобы модели влезли и работали быстро, вы их квантизуете — представляете веса меньшим числом бит, обменивая немного точности на много скорости и памяти. Сделано хорошо — это почти бесплатно. Доведено слишком далеко — это смерть от тысячи мелких неправильностей: слегка неверная транскрипция здесь, тонко исковерканная очистка там. Выбор уровня квантизации — это выбор точки на кривой точность-против-ресурсов, и это настоящее инженерное решение, а не галочка.
Вы отгружаете модель, значит, вы владеете обновлениями. Облачный провайдер улучшает свою модель, и каждый пользователь получает это молча. Ваша локальная модель улучшается, когда вы выпускаете обновление приложения, а пользователь скачивает новые веса. Вы обменяли «чужое непрерывное улучшение» на «контроль и предсказуемость», что и есть правильный обмен для инструмента, который должен вести себя идентично офлайн в самолёте — но это обмен.
Ни одна из этих вещей не топит подход на устройстве для интерактивного инструмента диктовки. Выигрыши по задержке и приватности слишком велики. Но каждая из них — причина, по которой другая функция могла бы правильно выбрать облако.
Фреймворк Принятия Решения, Который Реально Можно Использовать
Так что вот что взять к вашей собственной аудиофункции. Вопрос никогда не «лучше ли устройство». Он «какой столбец издержек волнует мою нагрузку». Сопоставьте свою функцию с этим:
| Сигнал | Склоняйтесь к устройству | Склоняйтесь к облаку |
|---|---|---|
| Модель взаимодействия | Реальное время, человек ждёт результат | Пакетная, обрабатывается в фоне |
| Толерантность к задержке | Хвостовая задержка ощущается; важен p99 | Секунды нормальны; никто не смотрит |
| Чувствительность данных | Аудио приватное, регулируемое или под NDA | Публичный или уже расшаренный контент |
| Требование офлайн | Должно работать в самолёте / плохой сети | Всегда-онлайн — безопасное допущение |
| Частота использования | Много раз в день, на пользователя | Изредка |
| Целевое железо | Можете требовать современную нижнюю планку | Должно работать везде, включая слабые устройства |
| Свежесть модели | Выигрывает предсказуемое, идентичное, офлайн-поведение | Выигрывает непрерывное молчаливое улучшение |
Читайте таблицу сверху вниз. Если ваши ответы кучкуются в левом столбце — интерактивно, чувствительно к задержке, приватно, часто, на способном железе — у вас задача формы диктовки, и устройство очень вероятно правильная архитектура, даже если это больше работы. Если они кучкуются справа — пакетно, толерантно к задержке, нечувствительно, изредка, должно-работать-везде — облако делает вам одолжение, а уход в локальное — самопричинённая боль.
Ошибка, которую я вижу чаще всего — трактовать это как единое глобальное решение для всего продукта. Это не так. Это по функциям. Инструмент вполне может крутить интерактивную диктовку на устройстве ради задержки и при этом отдавать двухчасовую архивную запись облачному пакетному заданию за ночь, потому что эти две нагрузки сидят в противоположных столбцах. Мы ставим всё локально по умолчанию в Vext, потому что наши нагрузки оказались слева — но красть стоит фреймворк, а не вывод.
Что Я Сказал Бы Себе Год Назад
Если бы я мог отправить одну записку версии себя, смотрящей на дружелюбный облачный STT SDK: дружелюбный SDK продаёт вам инференс и прячет налог. Качество модели редко бывает проблемой. Сетевой столбец — установка, загрузка, очередь, скачивание и дисперсия, живущая во всех четырёх — вот проблема, и это проблема, которую вы платите на каждом высказывании, навсегда, ровно в тот момент, когда человек ждёт ваше ПО с руками наготове.
Устройство убирает этот столбец целиком и вручает вам архитектуру приватности как побочный эффект. Это стоит вам большого скачивания, некоторой дисциплины памяти, нижней планки железа и решения по квантизации. Для интерактивной, чувствительной, высокочастотной нагрузки это обмен, который я делал бы каждый раз — и сделал.
Просчитайте математику своей собственной функции прежде, чем тянуться к SDK. Салфетка дешевле переписывания.
— Don
Vext — закрытое приложение голос-в-текст от Muvon, работающее на устройстве для Apple Silicon: распознавание речи и LLM для очистки работают локально, и ваше аудио никогда не покидает Mac. Инструменты разработки Muvon, включая Octomind, с открытым кодом. Описанные здесь архитектура и компромиссы — общие инженерные паттерны, а не разбор внутренностей Vext.



