Наговори и Saylo: как устроен сервис расшифровки на своих GPU
Пользователь загружает запись созвона — через несколько минут получает текст и протокол встречи. Всё остальное в этом посте — про то, что стоит за этими двумя предложениями.
#Файл не проходит через бэкенд
Первое решение — самое дешёвое и самое полезное. Аудио бывает на несколько часов и гигабайты; гнать его через приложение означает держать соединение открытым всё время загрузки, есть память на буферах и упираться в лимиты прокси.
Бэкенд на Go выдаёт presigned-ссылку и на этом самоустраняется: файл едет из браузера прямо в бакет. Приложение узнаёт о записи, только когда фронт приходит создавать задачу. Побочный эффект — бэкенд можно перевыкатывать посреди чужой часовой загрузки, и ничего не сломается.
#Одна видеокарта, несколько воркеров
На GPU-хосте живёт не один процесс: ASR, TTS, что-нибудь ещё. Видеокарта одна. Если два воркера одновременно возьмут по задаче, обоим не хватит памяти, и упадут оба — причём не сразу, а на середине чужой часовой записи.
Поэтому перед тем как взять задачу, воркер берёт в Redis лок слота хоста и держит его до конца работы. Все воркеры одного хоста сериализуются через этот лок, сколько бы их ни было. Планировщик при этом ничего не знает про GPU: Temporal просто раздаёт активности, а физическое ограничение живёт там, где оно физически есть — на хосте.
Сама модель вызывается не напрямую, а через LiteLLM по OpenAI-совместимому API. Это выглядит как лишний слой, пока не понадобится сменить модель: id модели приходит в задаче, а какой именно чекпоинт за ним стоит — вопрос конфигурации шлюза, а не выкатки воркера.
#Биллинг: сначала резерв, потом факт
Поминутная оплата ставит неудобный вопрос: сколько списать, если длительность станет известна только в конце?
При старте задачи резервируется оценка, поэтому баланс не может уйти в минус, пока крутится воркфлоу. На финише оценка заменяется фактической длительностью, а разница возвращается. Если воркфлоу упал — возвращается всё: за сбой сервиса платит сервис.
Такой порядок — единственный, который переживает падение в середине. Списание по факту в конце даёт бесплатную работу тому, кто запустит десять задач разом на пустом балансе.
#Два бренда, один код
Наговори и Saylo — буквально один и тот же код. Различия вынесены в конфигурацию, привязанную к организации пользователя: своя организация в провайдере идентичности, свой платёжный аккаунт, своя валюта, свой объём бесплатных минут, свой язык интерфейса. Запрос с домена saylo.pro попадает на свою биллинг-конфигурацию, и на этом всё отличие заканчивается.
Соблазн форкнуть код под второй рынок велик и обходится дорого через полгода: любая правка становится двумя правками, а расхождение обнаруживается на клиенте. Разделение по конфигу требует один раз аккуратно провести границу — и дальше оно бесплатно.
| Решение | Что оно покупает |
|---|---|
| Presigned-загрузка мимо бэкенда | деплой не роняет чужие загрузки |
| Лок слота хоста в Redis | одна задача на карту без знания планировщика о GPU |
| Модель за OpenAI-совместимым шлюзом | смена модели — конфигурация, а не выкатка |
| Резерв, потом перерасчёт | баланс не уходит в минус даже при падении |
| Бренд как конфиг, а не форк | одна правка вместо двух |
#Где сейчас граница
Российский контур в проде целиком: оплата, поминутный учёт, счета для юрлиц. По международному контуру карточный приём платежей ещё доделывается — всё остальное, от входа до расшифровки, работает.