Привет, меня зовут Витя Хаймоненко, я разработчик в команде голосовой активации Алисы. Сегодня я расскажу, как мы прошли путь от инференса на чистом C корутинами до современных движков и квантизованных моделей — и какой инженерный опыт из этого вынесли.
На колонке одновременно крутится несколько нейросетей:
Все они работают в реальном времени и должны укладываться в жёсткие лимиты по latency, CPU и RAM. Докинуть ресурсы на устройство нельзя, поэтому единственный путь — оптимизация инференса.
В 2014 году не было ни готовых инференс-фреймворков, ни повсеместной поддержки C++ на чипах. Мы писали на чистом C, а пайплайн описывали корутинами. Композиция получалась гибкой, но за это мы расплачивались накладными расходами на переключение контекстов и динамическими аллокациями памяти на каждом шаге.
Уже тогда появились оптимизации, которые живут до сих пор. Во-первых, батчинг: вместо обработки одного фрейма звука за раз мы ждём, пока накопится батч из нескольких фреймов, и обрабатываем их разом. Это позволяет эффективно использовать SIMD-инструкции процессора и даёт серьёзный выигрыш по скорости, жертвуя лишь примерно полусотней миллисекунд latency.
Во-вторых, стриминг: мы не пересчитываем целиком всё «окно» заново, а только обновляем его с приходом новых фреймов. Для матричных умножений использовали одну из реализаций BLAS, которая отлично утилизирует кеш процессора.
С появлением нормальной поддержки C++ на чипах мы стали переписывать рантайм. На смену корутинам пришла наша внутренняя библиотека YNMT, изначально сделанная для моделей перевода. Она представляет модель как статический вычислительный граф, из-за чего отпала необходимость в корутинах. А потом, когда YNMT стала уступать по скорости опенсорсному TFLite, мы переехали на него. Он поддерживает встроенные оптимизации под разные архитектуры чипов.
Первый подход был простым и безопасным: мы сжимали веса модели с FP32 до INT8, но перед вычислением разжимали их обратно. Затем научились квантовать и активации. Теперь мы на лету сжимаем вход до INT8, перемножаем матрицы в INT16 и деквантизуем выход. Выигрываем и по памяти, и по скорости за счёт более быстрых целочисленных SIMD-инструкций.
Раньше каждая модель тащила за собой свои буфера и предобработку звука, что вело к дублированию вычислений. Теперь общая часть вынесена в отдельную сущность, и все модели пользуются ею совместно.
Не стоит бояться использовать опенсорс-решения и адаптировать их под своё железо. Мы взяли стандартную реализацию свёртки в TFLite, переработали её под конкретный чип и получили огромный прирост производительности. К тому же у опенсорсных подходов есть неплохой плюс: зачастую они поддерживаются широким сообществом.
Сейчас мы в процессе миграции на TFLite, готовимся использовать встроенную квантизацию вместо ручной и продолжаем пилить кастомные операции. Всё это позволяет делать колонки умнее и при этом сохранять доступность для пользователей.
Подписывайтесь:



