Вот что сам автор доклада говорит о нем:
“В последние годы всё больше внимания уделяется мобильным приложениям, использующим машинное обучение, а также эффективным аппаратным архитектурам для нейросетевых вычислений. Задача распознавания речи, отличающаяся рекуррентной структурой вычислений и нерегулярным доступом в память, хорошо демонстрирует возникающие тут проблемы.
В докладе мы рассмотрим подходы к реализации универсальной библиотеки распознавания речи для мобильных устройств, поставим проблему оптимальности вычислений. Рассмотрим, как современные нейросетевые методы сочетаются с классическими алгоритмами оптимальных вычислений в условиях ограниченных ресурсов.”
Презентация: https://drive.google.com/drive/u/1/folders/1TgSWkEyqreaQwjnPsgduOXiAJcEo2C_3
Запись вебинара: https://youtu.be/VFeSCp958E4
(00:00:00) (Начало записи)
И: Здравствуйте, друзья! Сегодняшний доклад будет о достаточно общих вещах по нейронным сетям, которые возникают при реализации задач распознавания речи, но также они возникают и при реализации других нейросетей на мобильных устройствах. Так что надеюсь, это будет полезно в общем смысле.
Итак, почему одна из важных тем сейчас – это работа искусственного интеллекта, в частности, распознавание речи на мобильных устройствах? Здесь часто упоминают, что полезно сохранять данные, не отсылая их куда-то в сеть. Но в то же время есть и другие важные преимущества, такие как скорость и предсказуемость получаемого результата, независимость от проблем сети, а также простота реализации системы в целом для разработчика. В частности, вам не нужно поддерживать огромную серверную инфраструктуру для того, чтобы реализовать приложение и обрабатывать огромное количество информации.
Как недостаток здесь можно упомянуть то, что вы, к сожалению, не собираете данные пользователей, но этот недостаток можно нивелировать, в частности, отсылать некоторые данные, которые вам особенно интересны, или использовать другие данные из других источников.
Здесь есть плюсы и минусы, но в целом эта область развивается в последнее время. И мне кажется, она достаточно интересна и будет использоваться с большим интересом в последнее время, когда уже возможности серверной инфраструктуры распознавания достигли максимума.
Немножко расскажу о нас. Мы делаем библиотеку Vosk – библиотеку для распознавания речи, которая работает на мобильных устройствах (Android, iOS), поддерживает несколько языков. И в то же время она хороша среди существующих систем: мы стремимся делать ее достаточно компактной. Модели – всего 50 Мб, что более или менее соответствует ожиданиям мобильных разработчиков, потому что модели большего размера они пока еще не готовы принимать. Конечно, это все происходит за счет точности распознавания, но приходится находить баланс.
Библиотека поддерживает несколько языков. Можно скачать примерное приложение, чтобы ее попробовать запустить у себя на телефоне. Должно работать неплохо.
Сейчас мы поговорим в целом, как системы распознавания реализуются на мобильных устройствах и не только, и что у нас здесь важно и что нужно настроить.
Несколько примеров приложений, в которых используется библиотека вроде Vosk, в которых используется Pocketsphinx. Одно интересное приложение – это мобильная игра, где надо произносить заклинания голосом. Таким образом увеличивается вовлеченность. Вообще, голосовые приложения интересны тем, что эмоциональная связь пользователя с приложением гораздо выше, чем просто когда пользователь нажимает на кнопки или тыкает джойстик.
Еще одно интересное приложение с использованием похожей голосовой библиотеки – это приложение по тренировке пения. Тоже очень эмоциональное, очень вовлекающее. Советую скачать и ознакомиться. Оно находится в App Store. Можно попробовать попеть зажигательные песни караоке с помощью мобильного узнавания.
Кроме того, есть и более серьезные приложения для предприятий. В частности, приложение по управлению складом, когда сотрудник работает на складе, ему нужно находить необходимый товар. Он сканирует его, получает инструкции с помощью голосовых команд. С помощью голосовых же команд эти инструкции подтверждает. И таким образом значительно увеличивается производительность работы. Много таких приложений в последнее время. Есть еще и такие приложения, которые я не очень люблю. В частности, всякие бейджики, которые следят за сотрудниками в ритейле.
(00:05:00)
Надеюсь, что будут и более интересные и более помогающие приложения для обычных людей.
Конечно же, все это реализовано на нейросетях. И сейчас очень развивается область распознавания и, вообще, нейросетей на мобильных устройствах. Каждый производитель делает свою специальную библиотеку для того, чтобы работать с нейросетями. Во многих телефонах, во многих платформах есть свои специальные железки, в частности, Samsung, Huawei, Qualcomm Snapdragon – все они сейчас соревнуются в том, чтобы сделать нейросети с наилучшей производительностью на мобильных платформах.
К сожалению, все эти библиотеки, все эти платформы достаточно закрытые и не так легко интегрируются в общие задачи распознавания речи. В частности, программный интерфейс не всегда доступен. Или эти системы часто оптимизируются до каких-то фотозадач, скажем, для обнаружения лица, для обнаружения улыбки. И для более сложных задач, таких, как НЛП, или для распознавания речи, они не совсем подходят. В частности, не поддерживают нужные слои или нужные вещи.
Поэтому в целом говорить о поддержке мобильной аппаратуры достаточно интересно, но не совсем продуктивно, в частности, для нас, потому что мы хотим поддержать все платформы. И кроме того, мы хотим поддержать и другие устройства, которые также мобильны и также обладают ограниченным ресурсом, но в то же время не обладают графическими ускорителями. На мой взгляд, перспективное направление развитие у таких нейросетевых библиотек, библиотек с искусственным интеллектом – это системы, которые поддерживают все универсальные мобильные устройства, нежели системы, которые заточены на конкретную платформу.
Поэтому пока не приходится ждать того, что какой-то производитель сделает стандарт, и все будут этим стандартом пользоваться. Скорее всего, ожидать мобильной платформы для нейросетей, такой, как Hulu (00:07:41), не приходится. Hulu поддерживать вряд ли будут.
То, что я буду говорить дальше, на самом деле не относится к оптимизации на мобильных платформах, а относится к оптимизации на более общих устройствах, таких как Raspberry Pi или обычный телефон без какого-то графического ускорителя. Может быть, это будет перспективно, может, нет, не совсем понятно, но увидим в будущем.
Оптимизируя сети на мобильный телефон, мы, конечно, имеем дело с большим количеством параметров. Это и точность нейросети, устойчивость к шуму, скорость ее работы, задержка получения результатов, то есть мы имеем стандартную задачу оптимизации, где с помощью нашего алгоритма, нашей архитектуры мы пытаемся получить наилучшие результаты, наилучшее распознавание, балансируя между различными факторами. Факторов таких много. И сейчас даже популярно такое дело, как hardware/software codesign, когда мы модифицируем не только алгоритм, но и аппаратную часть.
Важно выяснить, какой из этих факторов сейчас главный, и каким фактором стоит заниматься. И вот как можно утверждать, самый главный фактор в последнее время – это использование памяти, а именно коммуникационные проблемы.
Чтобы проиллюстрировать этот факт, скажу несколько слов об общих характеристиках нейросетевых и, вообще, моделей искусственного интеллекта. Суть заключается в том, что чем больше у нас модель, чем больше данных мы в эту модель собираем, тем более точно она работает. Это подтверждается многими экспериментами: и экспериментами на речи, экспериментами обработки текстов, экспериментами на видео и так далее.
Суть заключается в том, что пока не получается создать маленькие модели, которые будут описывать весь окружающий мир. Чем больше внешней информации об окружающем сложном мире мы интегрируем, тем лучше наша модель работает.
(00:10:10)
В частности, для текста, для создания языковых моделей нам нужно обработать терабайты информации, для того чтобы отловить эти длинные хвосты, то есть специальные случаи в текстовом материале. На данный момент нейросетевые алгоритмы, конечно, сложные и не всегда могут отловить такое количество текста. Я думаю, что в будущем такие компании, как Google, будут стремиться к обработке большего и большего количества текста.
Если сейчас говорить о моделях типа GPT-3 и подобных, они тренируются в основном на «Википедии», некоторые на _____ (00:10:56) тексте. Но в целом в интернете доступны терабайты и терабайты текстов, которые нужно интегрировать, для того чтобы получить большую точность.
Для распознавания, для того чтобы применять эти нейросетевые модели, нам нужно иметь доступ ко всей этой большой информации, то есть объем памяти, к которому мы должны получить доступ, значителен.
Похожая ситуация и в распознавании речи. Расскажу немного подробнее, как происходит распознавание речи, чтобы вы понимали, что там задача доступа к памяти еще более актуальна.
Здесь мы рассмотрим простую задачу распознавания в простейшем случае, когда у нас есть всего одно слово, и нам нужно получить времена (00:11:46). Мы используем алгоритм _____, который выравнивает с помощью алгоритма динамического программирования обнаруженный звуковой сигнал относительно нашей речевой модели. В данном случае, вы видите, это стандартный алгоритм динамического программирования. Мы начинаем сначала. И дальше по каждому этапу мы продвигаемся либо на следующую клеточку по вертикали, либо двигаемся по горизонтали в зависимости от того, какая у нас акустическая оценка в данный момент.
И в конце концов, мы получаем этот путь, это наилучшее решение, которое мы принимаем за конечное решение. Для того чтобы правильно распознать речь, естественно, нужен не один этот путь, а обычно тысяча путей, две тысячи путей. И таким образом на каждом этапе мы обращаемся в память, для того чтобы получить информацию от нашей модели о том, что у нас в памяти хранится. Поэтому на каждом шаге распознавания речи обычно у нас огромное количество обращений в память и огромное количество дополнительной информации. И вот это взаимодействие с памятью достаточно серьезное.
На самом деле то, что здесь изображено – это модельная задача. Если мы в реальности рассмотрим граф поиска, то он состоит из миллионов вершин и миллионов ребер между ними. То есть вот эта простейшая задача, которая здесь нарисована, усложняется значительно. Особенно много таких ветвлений в таком графе приходится на стыке между словами.
Когда мы распознаем слово, в середине этого слова мы примерно представляем, что за слово мы распознаем, то есть мы понимаем, что сейчас мы говорим слово «молоко», и когда мы сказали «мо», мы можем дополнить «молоко». А вот когда мы «молоко» закончили и не знаем еще, какое слово будет следующим, в этот момент возникает очень большое ветвление графа, которое приводит к огромному количеству путей, то есть до миллиона путей мы должны протестировать, для того чтобы понять, куда идти дальше.
Конечно, с этим научились бороться, и есть какие-то библиотеки оптимизации графов. Но все равно это ветвление присутствует. И на любом шаге распознавания мы имеем большое количество обращений к памяти, то есть ситуация еще хуже, чем в тексте. У нас огромное количество взаимодействий с памятью и на самом деле не очень большое количество вычислений, потому что нейросеть единым проходом быстро все обрабатывает, а вот работать с памятью достаточно сложно.
Поэтому, кстати, часто возникают проблемы при потоковой обработке нескольких потоков, потому что когда мы измеряем один поток, это одна схема обращения к памяти, а когда у нас есть сервер, обрабатывающий десять потоков параллельно, это совсем другие требования по обращению к памяти.
(00:15:12)
И там обычно наблюдается затык, то есть система начинает проседать по производительности именно в этом месте. Поэтому даже когда говорят, что наша система работает в сто раз быстрее виаптайма (00:15:27), обрабатывая один поток, тут еще надо выяснить, как она будет работать, когда она будет обрабатывать сто потоков параллельно. Обычно проседание там значительное.
Понятно это все примерно?
Мужчина: Да, пока все понятно. По крайней мере, мне. Коллеги, призываем вас задавать вопросы по мере их возникновения.
Николай Шмырев: Если непонятно или слишком…
Мужчина: Коллеги говорят, что понятно. Давайте продолжать.
Николай Шмырев: Итак, если _____ (00:16:15) количество параллельных потоков? Объясняю. Ветвления просто повышают требования к доступу к памяти. И если шина памяти у нас один гигабит, и один поток требует 500 мегабит из этой шины, то десять потоков все равно будут конкурировать за один мегабит этой шины. И получаются, они будут ждать, пока остальные потоки из памяти получат нужную информацию. Сейчас как раз об этом и поговорим.
Стандартная архитектура нашего процессора – это некоторые процессоры. Есть у нас память. И для того чтобы получать доступ к этой памяти, у нас есть кеши, то есть это некоторые кусочки памяти, которые находятся на самом процессоре, и доступ к ним быстрее. Причем эти кеши имеют ограниченный размер, потому что на процессоре больше памяти не умещается.
И времена доступа у нас выглядят следующим образом. Вычисления у нас занимают наносекунды. Доступ к первому кешу – это полнаносекунды. Доступ ко второму кешу уже гораздо больше – это семь наносекунд. А доступ к основной памяти – уже до 100 наносекунд. Это цифры для Pentium, для процессора Intel, но в принципе для мобильных систем они еще хуже, потому что у мобильных систем еще меньше кеш и еще хуже доступ к памяти.
Таким образом, мы получаем, что в этой системе наш процессор может пересчитать гораздо больше, пока он ждет доступ данных от памяти. И критическое узкое горлышко здесь – именно закачка данных из памяти. По крайней мере, в текущей архитектуре. Хотя архитектуры, конечно, меняются. Может быть, в других архитектурах, где память находится совместно с вычислительными элементами, это уже будет неверно.
Если мы рассмотрим, как с годами менялись вычислительные способности процессора относительно пропускной способности памяти, мы можем видеть, что пропускная способность памяти существенно отстает от процессора. Процессоры у нас разгоняются, они могут обрабатывать все больше и больше информации. А пропускная способность памяти тоже растет, но все же остается достаточно низкой.
Эту проблему понимают и в NVIDIA. В частности, когда они говорят о своих архитектурах, они всегда сейчас говорят о ширине шины памяти и пытаются оптимизировать эту ширину шины памяти. Но как вы видите, все равно ширина шин памяти значительно меньше, чем производительность вычислительных _____ (00:19:38). В частности, характеристики последних архитектур NVIDIA, уже последние работы – 19 терафлопов, то есть 19 терабайт, 19 тера плавающих чисел, а пропускная способность – всего 1,5 терабайта, то есть примерно в двадцать раз. То есть мы можем обработать двадцать чисел и в это время только получить одно из памяти или записать одно в память.
(00:20:20)
Проблема взаимодействия с памятью стоит достаточно остро. В частности, в прошлый месяц была анонсирована более подробная информация об усилиях Google в этой области. Это достаточно интересная статья, которую всем интересующимся советую посмотреть, как Google борется с этой проблемой. Они в целом переделали всю программную и аппаратную архитектуру, для того чтобы попытаться решить эту проблему доступа к памяти. В частности, они отбросили кеши, потому что кеши замедляют взаимодействие с памятью. Оставили только одно большое ядро, которое напрямую взаимодействует с памятью, не используя какие-то примитивы синхронизации, не используя примитивы межпоточной синхронизации. Просто забирая полностью поток из памяти и обрабатывая ее.
Другое решение, которое они упоминают – это использование так называемого systolic array – это массив из вычислительных элементов, которые сидят над памятью и эффективно, без использования переноса данных могут умножать пока еще небольшие матрицы, но я думаю, они будут умножать матрицы и большего размера. Вот такой вычислительный элемент, который обеспечивает локальность данных там же, где мы вычисляем данные, там же мы их и храним, позволяет огромные массивы матриц прогонять через эту систему и не думать о кешах, о задержках памяти и подобных вещах.
В целом они, конечно, существенно переработали современную программную архитектуру, для того чтобы получить, опять же, существенный прирост производительности. Но такого рода системы на мобильных системах появятся не скоро. Пока они есть только у Google. Хотя эта работа по ссылке, которую можно почитать по ссылке, достаточно интересная, о том, куда все это движется с точки зрения Google.
А на обычных мобильных устройствах все достаточно прозаично. У нас есть несколько процессоров с обычными кешами, причем кешами небольшого размера. Есть некоторый разделяемый кеш. Кроме того, есть известная штука, как архитектура big.LITTLE, которая используется для экономии потребления батареи в мобильных устройствах, а именно система состоит из четырех не очень быстрых процессоров и четырех, которые значительно быстрее и которые подключаются в момент высокой загрузки. Причем этой загрузкой нельзя эффективно управлять, то есть решать, кто подключает ядра, кто нет. Это решает операционная система, а не обычный программист, который этим занимается.
В целом мы можем стремиться оптимизировать доступ к памяти, но не можем влиять на архитектуру, не можем планировать наши действия, потому что поначалу нашей работы мы будем работать на маленьком процессоре и только потом переключимся на большой.
Из-за такой закрытости, конечно, программирование на мобильных системах достаточно сложно. И по сути, это могут делать только разработчики мобильных _____ (00:24:17), у которых есть доступ к соответствующему инструментарию. Но обычные разработчики тоже могут что-то сделать. Ниже мы обсудим, что мы можем здесь сделать.
Итак, у нас есть данные, которые мы хотим обработать. И в качестве модельного примера, что делать с памятью в таком случае и что делать с нейросетями, мы рассмотрим пример умножения матриц.
Допустим, у нас есть две большие матрицы, которые нужно умножить оптимальным образом на процессоре. Как нам это сделать? Один из трюков, которые применяются всегда – это транспонирование второй матрицы. Здесь нарисовано, что мы умножаем строку на столбец.
(00:25:07)
Но на самом деле, так как у нас вторая матрица лежит в памяти, и мы забираем из памяти по строчкам кеша, то если мы будем забирать по строчкам кеша, то у нас во второй матрице мы постоянно будем промахиваться, потому что мы заберем первый блок, а остальное все мусорное. Заберем второй блок – остальное все мусорное. Что делают в данном случае? Вторую матрицу транспонируют в памяти перед умножением. Такая операция транспонирования позволяет эффективно по строчкам читать кеш, то есть и первую матрицу мы читаем по строчкам, и вторую матрицу мы читаем по строчкам.
Это один из трюков, который часто применяется при оптимизации доступа к кешу. Причем заметьте, вторую матрицу мы можем транспонировать, а можем так разработать наш алгоритм, что она уже будет транспонирована. В частности, если мы говорим о нейросетях, то мы можем специально перед запуском разложить наши матрицы в памяти таким образом, что они будут транспонированы, и входящие результаты будут правильно умножаться на них с правильным использованием кеша.
Второй трюк здесь заключается в том, что умножаем поблочно, то есть мы выделяем небольшой блочок, причем размеры этого блока зависят от аппаратной архитектуры процессора, и здесь выделяем блочок. Умножаем их, складываем результат. Потом идем к следующему блоку и так далее. Таким образом, если один блок у нас помещается в кеш, то мы правильно попадаем в нашу архитектуру и эффективно используем ее.
Размер блоков можно вычислять по-разному. Некоторые библиотеки, в частности, OpenBLAS, подбирают блок в зависимости от процессора. У них есть специальные размеры блоков, которые в зависимости от архитектуры процессора были протестированы и показывают наилучший результат.
Есть достаточно старые, но интересные библиотеки, как ATLAS. Они тестировали различные блоки на данном процессоре и выбирали тот, который работает оптимально. Таким образом, на YouTube мы подбирали наилучший результат. Есть и более оптимизированные библиотеки вроде интеловской библиотеки MKL, которая уже полностью заоптимизирована под их архитектуру с помощью эмуляторов, симуляторов и так далее и дает оптимальную производительность.
Похожий результат, когда мы используем умножение матриц, когда мы используем локальность данных в нейросетях. У нас есть несколько входящих элементов. Мы можем обработать их вместе и запихать в тест таким образом, что мы не забираем каждый раз блок из памяти. Обычно в нейросетевых библиотеках мы оптимизируем не просто по элементам матрицы, а еще и набираем входящие элементы, – так называемые минипатчи, – для того чтобы они более эффективно обрабатывались.
Минипатч нужно подбирать таким образом, чтобы он влезал в кеш, и мы не вылезали по промахам в памяти. Это можно отследить. И это можно отследить, запустив тестовый пример с различными размерами минипатчей и посмотреть, какой у нас обеспечивает попадание в кеш, какой не обеспечивает.
Пример такой оптимизированной библиотеки для мобильных устройств – это библиотека QNNPack от Facebook. Тоже по ссылке она описана. Можно взять презентацию, прочитать.
Интересная библиотека. Они описывают, как они оптимизировали раскладку и умножение матриц в нейросетях, для того чтобы обеспечить наилучший прирост. В частности, до двух раз они получают прирост в производительности библиотеки по сравнению даже с библиотеками Google просто за счет того, что правильно раскладывают все эти элементы в памяти и правильно их умножают.
Причем эта библиотека, что интересно, оптимизирована, опять же, на компьютерное зрение, потому что компьютерное зрение пользуется большей популярностью в мобильной разработке. Поэтому она оптимизировалась для таких стандартных матриц в компьютерном зрении, как 64 на 64, 132 на 132.
(00:30:13)
Для более общих задач, таких как задача распознавания речи, задача НЛП, нужно смотреть, подходим ли мы по размеру к этому QNNPack или не подходим. Вот такая интересная библиотека.
Кроме того, одним из стандартных способов снижения нагрузки на память является квантование входящих данных. В частности, сейчас, если вы посчитаете современные статьи по мобильной оптимизации и, вообще, по оптимизации нейросетевых вычислений, первым делом все обсуждают формат плавающих чисел. Задача состоит в том, чтобы впихнуть большее плавающее число в меньшее число бит. И таким образом мы не только упрощаем аппаратную архитектуру на вычисления, но, что существенно важно, используя новые форматы данных, вот такие, как _____ 16 (00:31:20), как сейчас используются в NVIDIA, или BF16, которые используются в _____, мы впихиваем большее количество информации в меньшее количество памяти и таким образом существенно снижаем нагрузку на память, повышая производительность.
И здесь мы играем как раз на том, что мы получаем меньшие требования по памяти, меньше промахов кеша, а не на том, что делаем какие-то сложные вычисления.
Развивая эту идею для матриц, можно говорить о такой интересной статье, как использование блочных матриц. Идея здесь состоит в том, что просто разряженные матрицы не обеспечивают нужной точности. Что мы можем сделать? Мы можем представить матрицу в виде суммы матриц различных блоков, причем эти блоки разного размера. И опять же, такое разряженное представление позволяет описать значительное количество матриц с использованием небольшого числа плавающих чисел и умножать быстро, и более быстрое вычисление, и точность при этом получается весьма неплохой. Одна из интересных статей в этой области приведена по ссылке.
Вот, пожалуй, и все, о чем обычно говорят в нейросетевой оптимизации. Обычно говорят только об этих вещах. Во-первых, это использование более гибкого, плавающего формата для данных. И различные блочные штуки, как мы раскладываем блоки, чтобы помещаться в кеш. Разряженные матрицы и как мы их умножаем, то есть как мы их раскладываем перед умножением.
В целом более сложные алгоритмы оптимизации недостаточно популярны.
Есть вопросы какие-то на данный момент?
Мужчина: Кажется, пока вопросов нет. Я призываю участников писать в чат. Можно также поднимать руку.
Посмотрите, пожалуйста, вопрос в чате.
Николай Шмырев: Вопрос. Все _____ (00:34:16) мощности подошли к пределу, но приведены примеры _____ ампер. _____ В пределах серверных систем, и нужно ли их _____ на мобильные устройства?
Да, я считаю, что это предел, потому что эти серверные системы типа TPU-3 уже недоступны небольшим компаниям и обычным производителям. Обычным программистам. Они есть, и вы можете GPT-3 через разный интерфейс попробовать. Но сами с ними поиграться не можете. В частности, сами вы уже не можете повлиять на их архитектуру или что-то сделать в библиотеке по работе с TPU-3, поэтому да, уже предел достигнут. Поэтому и Google уже бросил работать с NVIDIA, а начал разрабатывать свое аппаратное обеспечение.
(00:35:26)
Второй вопрос. Правда ли, что проблемы памяти сейчас стоят гораздо острее, чем энергопотребление?
Да, для нейросетей, я считаю, что проблемы с памятью гораздо важнее, чем с энергопотреблением. Но здесь все завязано. И как раз об этом мы будем говорить.
Если в целом поставить эту задачу и рассмотреть задачу работы с памятью более модельную, например, задачу бинарного поиска, можно поставить такую задачу: а являются ли алгоритмы, с которыми я работаю, оптимальными? Мы говорим о каких-то оптимизациях, но правда ли они нам обеспечивают тот порядок вычислений, который мы можем достичь? К сожалению, на этот вопрос пока можно ответить отрицательно.
В качестве модели задачи рассмотрим простую задачу бинарного поиска. Допустим, у нас есть целочисленный массив, в котором мы хотим найти нужный элемент. Это классическая задача доступа к памяти. В данном случае простейший алгоритм у нас заключается в том, что это алгоритм бинарного поиска, который, опять же, построен на интересной парадигме «разделяй и властвуй», то есть мы сначала разделяем наш массив пополам, потом еще другие половинки, другие половинки и дальше.
Таким образом, за счет иерархического доступа мы получаем огромный прирост производительности. В частности, оценка (00:37:07) такого алгоритма – это O от логарифма числа элементов. То есть мы можем обрабатывать огромное число элементов с помощью небольшого числа операций доступа в память. И мы уже можем говорить, что такой алгоритм более или менее оптимален.
Сравните это с предыдущей оптимизацией. Оптимизировав флоаты с 32 бит до 16 бит, вы получаете прирост максимум в два раза, может быть, в полтора. А здесь уже у вас логарифм, то есть это гораздо более существенный прирост, который возникает за счет декомпозиции задачи на подзадачи и такого иерархического подхода к задаче. Причем этот иерархический подход к задаче можно еще развить.
В частности, если мы применяем некоторую более сложную индексированную структуру, это такой алгоритм с y деревьев, который, вместо того чтобы раскладывать этот массив целиком в бинарное дерево, делает некий индекс сначала, а потом внутри этого индекса ссылается на бинарные деревья, мы можем получить при незначительном увеличении памяти, при хеширующем индексе, огромную производительность по числу операций по _____ (00:38:45) нужного элемента.
Применив идею иерархичности, мы на самом деле можем еще существенно снизить число операций для доступа к памяти для поиска нужного нам элемента. В целом такой подход иерархичности пока не применяется в нейросетях, хотя он может значительно ускорить нейросети, в частности, снизив электропотребление, снизив такие вещи, как задержки кеш, и решив многие проблемы, о которых мы говорили раньше.
Вот пример иерархического подхода. Это подход, который упоминается некоторыми исследователями, но пока еще не полностью применен – это плавающая арифметика на основе позитов. Это есть такая область, которая достаточно известна. Идея заключается в следующем. Вместо того чтобы паковать мантиссу и экспоненту в плавающее число, давайте мы еще запакуем суперэкспоненту – это экспонента экспоненты.
(00:40:08)
Образуя таким образом иерархическую структуру, мы можем запихать гораздо больше динамический интервал в меньшее число b. Сравните это с подходом в том же FP16, где мы просто играем с числом битов в экспоненте. Мы можем играться с такой иерархической структурой экспоненты экспоненты, собственно экспоненты и мантиссы.
Таким образом, мы можем еще больше снизить потребности на пропускную шину данных и еще больше повысить пропускную способность нашей системы.
Пока эта арифметика на позитах не очень популярна, но уже есть некоторые работы, которые показывают хороший прирост производительности, в частности, из-за использования этих позитов.
Я знаю, команда MIPS, известный Юрий Панчул, они про эти позиты читают, изучают. Может быть, в скором будущем эти позиты появятся в архитектуре NVIDIA и TPU-3. Здесь как раз дело за малым.
Еще один пример такой иерархической структуры, которая, кстати, используется в нашей программе Vosk для кодирования памяти и в различных других применениях – это компрессия модели языка с помощью исчезающих деревьев. Алгоритм исчезающих деревьев обеспечивает оптимальную компрессию структуры графа в память, где каждая вершина графа, каждое ребро кодируется всего лишь почти одним битом. На самом деле там есть оценки. Там где-то 1,1 бит тратится на вершину графа. Таким образом, структура упаковки дерева в эту битовую структуру «Исчезающее дерево», может быть доказано, что она более или менее даже оптимальная, то есть можно получить, как оценки снизу, так и оценки сверху.
Оценки снизу у нас – один бит на вершину. Оценка сверху – это где-то 1,1 бита на вершину.
Как эта структура выглядит? Допустим, у нас есть дерево. Мы начинаем с его вершины и дальше делаем следующее. Мы для каждого потомка ставим единичку, просто отмечая их число. У нас здесь 19 вершин. Мы ставим единичку и нолик. Единичка – это значит у нас всего один потомок у этой вершины. Эта строчка в презентации на самом деле не относится к этой картинке. Но я попытаюсь все равно объяснить. А нет, она относится, я думаю.
Дальше у нас идут потоки: раз, два, три, четыре. Мы ставим раз, два, три, четыре единички и нолик. Нолик завершает эту последовательность. У первой вершины нет потомков – ставим нолик. У второй вершины два потомка – ставим две единички и нолик. У третьей вершины нет потомков – ставим нолик. У четвертой вершины один потомок – ставим единичку и нолик. Дальше вершины без потомков – это нолик. Вершины с одним потомком – единичка и нолик. И дальше две вершины без потомков – вот эта и эта, H и I без потомков.
Таким образом мы получаем битовую строку, которая описывает структуру нашего дерева очень компактно. Используя индексы в этой структуре, мы можем переходить от индексов элементов к индексам верхних элементов, к индексам нижних элементов и так далее. Операции перехода к элементам следующие. Можно ввести такие функции, как rank0(n) и rank1(n) – это число битов в строке до нужного индекса n, то есть число нулевых битов и число единичных битов.
(00:45:02)
И например, переход к родителю – это найти такой элемент, у которого единичек столько же, сколько у нас ноликов. То есть вот такими битовыми операциями мы можем быстро в нашем дереве переходить. Причем отмечу, что операции здесь – это просто подсчет битов. Они, конечно, не очень оптимизированы на современных процессорах, но они могут быть реализованы достаточно быстро. И что еще важно в этой структуре – то, что она дает очень хороший доступ к кешу за счет того, что хорошие показатели по доступу к кешу, за счет того, что она очень компактная.
Скажем, у нас есть такое дерево из миллиона элементов. Вопрос к аудитории. Как мы будем вычислять функцию rank0(n)? Как ее эффективно вычислить? Какие есть предположения? Скажем, у нас есть строка из битов и нам нужно найти позицию в ней, тот элемент, до которого находится 500 тысяч единичек.
Вот пишут _____ (00:46:50).
На самом деле что здесь важно. Здесь присутствует такая же идея иерархичности. Для того чтобы быстро вычислить эту функцию, нам не нужно проходить по полумиллиону байтов, подсчитывая единички и нолики. Что мы делаем? Похоже на эту идею. Мы вводим еще дополнительный индекс, который нам говорит, приближенно на какой позиции у нас какое количество единичек. И нам достаточно искать по этому индексу, а потом уже подсчитывать единички, для того чтобы найти нужную единичку. То есть за счет дополнительного индекса мы получаем очень быстрый подсчет единичек и очень быстрый доступ к нужному нам элементу.
Правильно пишет _____ (00:48:06). То есть кроме этой структуры мы еще вводим несколько индексов этой структуры. И эти индексы позволяют быстро искать в этой структуре и перемещаться по ее элементам. Вот такая система используется в Vosk для кодирования энграммной модели. Она кодирует, работает, но, к сожалению, она работает на С++, и не совсем понятно, почему она работает медленно. Вот одна из практических задач в Vosk – это посмотреть, что там на самом деле происходит, и почему она работает медленнее, чем обычный _____ (00:48:44), хотя теоретически она должна работать гораздо быстрее.
Похожая идея применялась и в нейросетях, в частности, для кодирования разрядных нейросетей. Есть соответствующая работа, не очень популярная. Но если есть интерес, ее можно посмотреть. За счет такого минимального кодирования мы можем эффективно представлять разряды нейросети. И результаты там достаточно неплохие, но пока эта тема не очень популярная.
И похожая система используется в Kaldi, в частности, в новой версии, которая только развивается. Пока еще не точно, пока еще нет определенных результатов, но общая идея Kaldi заключается в том, что эти конечные автоматы мы представляем в _____ (00:49:47) индексированной структуре списка список и будем их дифференцировать, оптимизировать и тренировать с использованием _____ (00:49:58). Почему такая система эффективна?
(00:50:01)
Как раз она обеспечивает иерархический и очень эффективный доступ к кешу. Мы можем быстро перемещаться между элементами и ВТО же время можем последовательно по ним проходить, обрабатывая нужную нам информацию. Так что эта идея будет еще развиваться и, может быть, будет Kaldi меняться эффективно. Но пока ждем вторую версию Kaldi. Какие-то наработки там есть. Скорее всего, скоро будут более интересные результаты.
Итак, в целом говоря про эту инфраструктуру, можно выделить такой общий подход, про который я уже говорил. Это, прежде всего, иерархическое вычисление, когда мы сначала вычисляем что-то приближенно, потом пытаемся уточнять. В частности, эти операции с индексом в бинарном поиске и с индексом исчезающих деревьев. Кроме того, мы можем использовать более сложные структуры, потому что на самом деле больше вычислений нам не сильно вредят. Мы больше задерживаемся по ожиданию памяти.
И третий _____ (00:51:20), который я хотел бы отметить – это рандомизированные алгоритмы, когда мы можем автоматически доказать, что случайная функция у нас вычисляет результат достаточно точно. Мы можем использовать такие случайные функции, для того чтобы быстро вычислить нужные нам значения. Это отдельный класс алгоритмов, которые тоже очень эффективно работают. Но нужно их применять, нужно знать, может быть, какие идеи здесь можно получить. В частности, случайное вычисление, популярный ______ (00:52:01), такие вещи. Метод Монте-Карло сюда же.
Метод Монте-Карло, если его правильно применять, может позволить быстро вычислить тот же интеграл, просто бросив несколько случайных точек.
Такие идеи, к сожалению, пока не совсем применимы в нейросетях. По крайней мере, не широко анонсированы, хотя они дают обычно логарифмически значительный рост производительности вычислений и могут служить основой для очень эффективных алгоритмов.
Еще один пример таких подходов. Хотел еще немножко поговорить про Pocketsphinx. Это старая система. Но в ней, что интересно, есть много таких алгоритмов, таких ухищрений, которые сейчас, конечно, неактуальны, но делали эту систему очень быстрой. И в частности, хотелось бы, чтобы они применялись и в современных системах.
Одна из популярных оптимизаций, которая есть в Pocketsphinx, которая сейчас не применяется – это использование предыдущего звукового отрезка для следующего отрезка. Если мы произносим какую-то фонему, мы можем ожидать, что она еще продлится, поэтому при акустической оценке следующей фонемы мы можем результаты предыдущей фонемы использовать, то есть отбрасывать какое-то существенное количество вычислений на следующем шаге. Такая оптимизация в Pocketsphinx и в других системах, в декодерах IBM очень эффективно работала и давала достаточно прирост производительности.
Дальше в Pocketsphinx везде используется десятибитное квантование весов. Это обеспечивает прекрасную точность при значительно меньших доступах к памяти. К сожалению, сейчас в Vosk, Kaldi квантование все еще не используется. Но надеюсь, когда-нибудь мы до этого доживем и будем квантовать модель языка и сами акустические параметры, и так далее.
Далее важная тема, которую все еще хотелось обсудить – это приближенный поиск в моментах ветвлений. Помните, когда я говорил про характеристики этого графа, я говорил, что между словами у нас есть большие ветвления. Внутри слов мы идем достаточно равномерно, а между словами у нас возникает большой разлад. В Pocketsphinx эта особенность учитывалась. В частности, для алгоритма _____ (00:54:57) именно поиска, мы регулировали.
(00:55:00)
Мы делали разную ширину поиска для элементов внутри слова. И для элементов в конце слова мы делали более глубокую ширину поиска, так как у нас там более глубокое ветвление. Вот так играться с ширинами поиска можно и можно получать хорошие результаты.
И более сложный алгоритм – это поиск в глубину, поиск с эвристикой. И он в Kaldi есть, но пока активно не применяется, хотя это тоже алгоритм эвристический, который может давать значительный прирост производительности, когда мы сначала декодируем слово целиком, а потом уже возвращаемся и пытаемся найти альтернативы, вместо того чтобы на каждом шаге смотреть все возможные слова.
Много таких интересных вещей в Pocketsphinx, которые заслуживают не только анализа, но и применения в современных системах.
В современных нейросетях тоже есть некоторые попытки применить подобные алгоритмы. Но как я говорил, они недостаточно на слуху, хотя их можно изучать, можно применять и можно с помощью них достигать хороших результатов.
В частности, Reformer – это достаточно известная работа, когда мы вычисления длинных элементов заменяем на хеширование. Это работа в правильном направлении, которая пока еще не пошла никуда, но в принципе, наверное, будет продолжена в Google.
Есть такие, например, работы вроде Adaptive Computation Time for Recurrent Neural Networks. Это как раз работа Алекса Грейвза, то есть основного гугловского алгоритмиста, которая использует эту идею ширины поиска в зависимости от глубины ветвления. Там получены неплохие результаты, но пока она нечасто применяется, хотя может применяться.
И несколько других работ, о которых тоже хотелось упомянуть. Работа об иерархическом, но, прежде всего, вычислении, которое не использует лишние элементы. И работа об отбрасывании путей, когда мы знаем, что нам точно что-то будет не нужно, и за счет этого достигаем более высокой производительности. Эти работы можно посмотреть в качестве идей в том направлении, которое мы обсуждаем.
Несколько слов о том, как отлаживать все эти проблемы. К сожалению, на мобильных устройствах отлаживать не очень удобно, потому что инструментарий там недостаточно широк. Но что-то можно получить. В частности, можно сначала эти алгоритмы отладить просто на десктопе, понять, как они взаимодействуют с кешем, понять, какие там есть ограничения, для того чтобы оптимизировать алгоритмы. И такие инструменты, как Valgrind или Oprofile, очень полезны, для того чтобы понимать, что у нас происходит.
В частности, у Valgrind есть плагин Cachegrind, который показывает использование кеша. И кроме того, есть Oprofile, который позволяет вам отслеживать задержки по кешу. К сожалению, Oprofile не устанавливается на Debian, но на _____ (00:58:46) зато он очень неплохо работает и позволяет запускать программу. Там с помощью аппаратных счетчиков показывается, где происходят задержки кеша, и этот момент можно отслеживать.
На Android можно запускать гугловскую библиотеку Google gprof, но она не использует аппаратные возможности. Она просто использует профилирование на основе прерываний. Хотя тоже какие-то подсказки по поводу того, где у нас задержки, она может дать.
И производители этих процессоров пользуются гораздо более сложным инструментарием, симуляторами, которые могут им показать, что там происходит. Но, к сожалению, такие вещи не всегда доступны. Приходится гадать в темную с помощью Google gprof. Или какие-то вещи можно отследить с помощью профайла на _____ (00:59:42) сначала. Таким образом, я призываю использовать эти инструменты. Может быть, они будут полезны.
Итак, заключение. Основа производительности современных алгоритмов – это повышение локальных вычислений и снижение коммуникаций, что достигается за счет следующих мер. Это индексирование информации, то есть иерархическое вычисление, сжатие информации, для того чтобы передавать меньше, и локализация вычислений и алгоритмов, то есть это блокирование, для того чтобы не передавать большие объемы данных через шину памяти.
(01:00:24)
Оптимальные алгоритмы, которые я перечислил, реализованы в нейросетях, но могут быть сделаны более популярными. Может быть, в следующие годы они будут более популярны. И конечно же, нужно следить за аппаратными решениями.
Про аппаратные решения я тоже хотел бы сказать. Одна из интересных работ в этой области – это, как всегда, корейцы, которые сделали умножитель матриц на основе напряжений. И есть соответствующая статья. Причем это все уже применяется в реальных устройствах для поиска ключевых слов. И ее можно посчитать. Они там обеспечивают просто поразительный уровень энергопотребления, что-то вроде пяти милливатт всего схема потребляет для отслеживания ключевых слов. А если это переводить в устройство с батарейкой, то можно годами таким устройством слушать ключевое слово и потом привести в действие то, что нужно.
Интересная система. Хотя я думаю, проблема будет не в том, что эта система будет долго работать, а в том, что будут очень большие ложные срабатывания течение года.
Кроме того, развиваются оптические системы. Еще зимой это было в новинку, а сейчас, оказывается, в прошлом месяце анонсировали чип, который с помощью оптических технологий перемножает матрицы. Причем я думаю, что эта технология гораздо более перспективная и практичная, чем квантовые компьютеры, хотя, по сути, это тоже квантовый компьютер на основе матрицы. Кому интересно, посмотрите, что натворила эта компания по созданию оптического умножителя матриц и интегрированию его в реальную систему.
Кстати, если здесь показано, что они делали на 3D-принтере этот оптический умножитель матриц, то сейчас там современная система делается перепрограммируемом, то есть эти матрицы не просто кодируются сразу. Они могут быть перепрограммированы с помощью термоэлементов. То есть там нагревается часть платы и повышается пропускная способность лазера. Можно любую матрицу в такую штуку запихать, и он ее будет считать.
И по энергии, конечно, эта технология гораздо более эффективна, чем просто цифровое умножение.
У меня все.
Мужчина: У нас есть вопросы в Q&N.
«Николай, каким вы видите развитие Vosk? Спасибо за нее. Как можно поучаствовать?» Константин Доричев спрашивает.
Николай Шмырев: По развитию Vosk у нас стоит огромное количество задач. В частности, у нас большая пробелам состоит в том, что мы пока еще не можем адаптировать словарь к новым словам. Это просто огромная проблема, над которой все плюются. Из-за того что граф у нас собран статически, мы не можем новые слова быстро вносить в эту систему. Приходится перекомпилировать граф. Фамилии, имена – все это, к сожалению, пока не очень поддерживается. Я потом анонсирую, будет еще один доклад на эту тему. Может быть, поговорим об этом подробно.
Кроме этого, конечно же, у нас еще хромает точность. В частности, только вчера Игорь Гончаровский прислал мне результаты по телефонии. Там пока еще все плохо. Если мы даем процент ошибок 40% на реальной телефонии, то Tinkoff и «Яндекс» приближаются к двадцати. По точности там еще огромная работа. Это работа и по тренировке, и по созданию базы. И по другим вещам вроде применения распознавания говорящих, тоже есть работа.
Как можно помочь, присоединиться? Пожалуйста, мы будем рады какой-то поддержке, алгоритмам, помощи.
(01:05:12)
В частности, можно взять под себя какую-то задачу, например, задачу определения говорящего. У нас там пока не все еще работает. Отладить ее правильно, правильно реализовать в Vosk, прислать в главный репозитарий. И уже это будет очень полезно нам. Или какие-то другие задачи, которые важны. Например, у нас на GitHub отмечены несколько проблем, с которыми требуется помощью. Любую их них можно брать. Например, там есть интересная проблема с использованием нейросетевых языковых моделей для первого прохода, которые должны дать хороший прирост точности распознавания. Нужно попробовать реализовать эту систему, и там будет очень хороший прирост.
Несколько там задач по мобильнику тоже есть. Задач много. В принципе, буду рад любой помощи.
Мужчина: Спасибо. Коллеги, пожалуйста, еще вопросы?
Расскажите про тестирование на мобильных устройствах.
Николай Шмырев: Тестирование Vosk или, вообще, тестирование? Не совсем понятно.
В целом пока еще тестирование у нас идет достаточно… Small testing вроде это называется. Попробовал – вроде работает, вроде хорошо.
Вот у меня спрашивают: какие тесты запускаете, что вы меряете?
Мы меряем производительность _____ (01:07:57). Некоторые вещи можно бы мерить. Можно бы мерить энергопотребление, можно мерить много вещей. Просто пока не доходят руки, к сожалению. Если есть желание, это тоже интересная работа, которой нужно заняться – померить, как это все будет работать, реализовать.
Кстати, сейчас у нас тоже идет запрос – реализация сервиса, который будет постоянно слушать речь на заднем фоне. Запустить такой сервис, тоже померить результат. Пока мы делаем только простейшее тестирование – производительность и точность. Ну, и чтобы система работала.
Кстати, здесь же есть интересный вопрос с поиском ключевых слов. Это достаточно часто требуемая возможность, чтобы можно было искать эффектно, без потребления батареи, ключевые слова. Я сам считаю, что поиск ключевых слов – это немножко отжившая вещь, потому что люди на самом деле не всегда хотят говорить эти «Окей, Google», «Окей, еще кто-то». Для более естественного общения достаточно просто задавать нужные вопросы. И хотелось бы сделать такую нейросеть, которая бы эффективно отлавливала эти вопросы, но при этом не была поиском ключевых слов. В этой иерархической структуре мы хотим создать нейросеть, которая более легкая по сравнению с большой нейросетью, но в то же время позволяет отлавливать хотя бы главные вопросы.
Я думаю, что реализация ключевых слов в Vosk будет двигаться в эту область, а не в область аккуратных сеток просто для отлова конкретного ключевого слова. Если мы такую сетку сделаем, нам, конечно, надо будет больше делать по отслеживанию энергопотребления такой сети, по отслеживанию того, как она работает и так далее..
(01:10:17)
Мужчина: Нам еще задают вопрос.
«Вроде не было про парсификацию. Перспективно ли?» – спрашивает неизвестный посетитель.
Николай Шмырев: _____ (01:10:37) слайд про эти разряженные модели. Да, большая разряженность – это перспективно, конечно. Это просто один из методов в том направлении, которое я описывал. Хотелось бы, чтобы попробовали другие методы. И может быть, какие-то интересные результаты получатся.
Мужчина: Коллеги, пожалуйста, еще вопросы. Наверное, вопросы иссякли. Время у нас тоже вышло. Через час вебинара некоторое количество людей у нас отвалилось. Максимум сегодня было 38 человек, что очень и очень неплохо. Я со своей стороны Николая благодарю за интересный доклад. У меня возник целый ряд смежных вопросов, но мы их сейчас не будем выносить на текущий разговор, потому что время уже заканчивается.
Пользуясь возможностью, я анонсирую следующий вебинар, который пройдет через две недели, и у нас будет Анна Куртукова, которая будет рассказывать определение принадлежности кода, то есть определение авторства кода на основе данных GitHub, с помощью нейронных сетей. Так что будет понятно, кто сделал этот баг.
Я еще раз благодарю Николая. Рассказ очень интересный. Большое спасибо и до новых встреч. Надеюсь, что вы к нам еще придете рассказывать о новых результатах.
Николай Шмырев: Спасибо, уважаемые слушатели и коллеги.
(01:13:08) (Конец записи.)