Содержимое
Как OpenVINO оптимизирует memory-bound сценарии OpenVINO — open-source фреймворк от Intel для оптимизации и эффективного инференса нейронных сетей. Он позволяет запускать модели на x86/ARM(!)/RISC-V(!) CPU, GPU и NPU, используя C++, Python и другие языки программирования. 🚀 Инференс модели — это выполнение графа операций, таких как add, mul, relu, softmax и т.д. Выполнение каждой операции сводится к запуску низкоуровневых программных блоков — kernels. Каждый kernel загружает данные из ОЗУ в регистры процессора, считает результат и пишет его обратно. Одна из проблем при выполнении графа операций — простой процессора в связи с тем, что данные не успевают «доехать» до регистров. Этот сценарий называется memory-bound. Его появление зависит от скорости процессора, пропускной способности памяти и типа выполняемой операции. Естественное желание — минимизировать простой процессора. Для этого необходимо уменьшить количество обращений в память и выполнить больше операций с уже загруженными данными. Один из используемых подходов — fusing: объединение нескольких операций в один kernel. Для таких операций данные один раз загружаются в регистры, проходят цепочку вычислений и только потом выгружаются обратно в память. Это позволяет лучше загружать вычислительные блоки CPU за счёт уменьшения количества операций с памятью. Поддерживать десятки (а то и сотни) вариантов fusing’а вручную очень сложно, поэтому был разработан Snippets — JIT-компилятор внутри OpenVINO. Он находит подграфы модели, которые можно исполнить эффективнее, и компилирует для них оптимизированные kernels. И это не ограничено заранее зашитыми паттернами: Snippets может собрать подграфы с произвольными комбинациями поддерживаемых операций. Выделенные подграфы Snippets переводит в более удобное для оптимизации представление, оптимизирует работу с памятью и циклами, распределяет данные по регистрам процессора, чтобы лишний раз не гонять их в память. 🦾 Одна из новых фишек Snippets — поддержка dynamic shapes. Часть оптимизаций зависит от размера входных данных. Можно перекомпилировать kernel под новые входы, но на практике это получается медленно. Решение следующее: shape-зависимые части (например, шаги указателей или размеры буферов) выносятся в отдельный RuntimeConfig. Он получает параметры и подставляет их в уже скомпилированный kernel. Добавляется небольшой оверхед, но зато теперь можно работать с текстами разной длины или изображениями различных размеров без потери производительности. Итак, на практике мы имеем следующие результаты: ускорение трансформерных моделей, а именно — текстовые (BERT и не только) до 25%, Stable Diffusion до 40%, Whisper до 30%; а также экономию памяти в attention-блоках Stable Diffusion на 15-50%. 🏆 На курсе «Ускорение нейросетей» мы подробно разбираем OpenVINO и другие инструменты для оптимизации инференса. Записывайтесь на новый поток, который стартует 30 сентября! Вы можете присоединиться со скидкой до 20%, если запишитесь в лист ожидания до 19 сентября. Полезные ссылки: - Подробный разбор принципов работы Snippets - Статья про поддержку динамических шейпов Автор: Артур Панюков