Привет Хабр! Те из вас, кто следит за нашими публикациями про мобильное распознавание документов, знает, что мы придерживаемся принципа распознавания документов только на самом устройстве. Модуль, который отвечает за распознавание и ввод данных, не должен быть уязвимее того, что он в моменте заменяет (а именно, клавиатуру). Наши технологии легко встраиваются в мобильные приложения, но что делать, когда необходимо реализовать веб-приложение с возможностями ИИ? Уступать принципам не приходится - на помощь приходит WebAssembly (или Wasm). Под катом мы расскажем, как мы портировали наши решения по распознаванию документов, банковских карт, баркодов, и всего остального, для использования в Wasm. Уверены, что вам будет интересно. WebAssembly - формат бинарного кода для виртуальной машины, в который можно компилировать программы на C, C++, C#, Rust или Go и который браузеры могут транслировать в нативный бинарный код для той машины, на котором запущены. WebAssembly разрабатывается с 2015-го года и в данный момент развивается такими компаниями, как Mozilla, Microsoft, Google, Apple, Intel, Red Hat и пр. В настоящее время Wasm уже поддерживают (с разными ограничениями, но об этом позже) наиболее популярные браузеры: Firefox, Chrome, Microsoft Edge, Safari и другие. Наш основной интерес к технологии Wasm связан с возможностью обработки чувствительной информации в браузере без участия сервера. Обработка цифровых изображений, извлечение текстовых данных из документов, бланков, банковских карт, чтение баркодов происходят только в памяти браузера локальной машины без необходимости пересылки данных по сети, минимизируя риски утечек персональных, биометрических, и всяких других данных.  Благодаря Wasm, многие тяжелые или платформозависимые приложения теперь можно моментально разворачивать на любом устройстве с выходом в интернет и современным браузером: вместо скачивания и установки приложения пользователю теперь нужен только один клик по ссылке, чтобы получить готовую систему по оптическому извлечению данных с веб-камеры или сканера.  Более того, даже если целевой кейс - облачные вычисления, но предоставить такую возможность всем клиентам не представляется возможным (проблемы с горизонтальным масштабированием, поддержкой или арендой инфраструктуры и т.п.) Wasm выступает практически безальтернативной технологией. Ему удалось, по крайней мере в области намерений, найти баланс между удобством интеграции, производительностью и безопасностью, путь к которому прослеживается с java-апплетов, Flash, Silverlight, ActiveX и прочего. Одна из идей Wasm состояла (и состоит) в том, чтобы выполнять браузером вычисления со скоростью, максимально приближенной к нативному приложению. Хотя Wasm - технология достаточно молодая, современная реализация уже позволяет производить сложные вычисления, даже такие, как обработка изображений и распознавание, с достаточно высокой производительностью. В нашем случае, используя все оптимизации, модуль распознавания уже обрабатывает документы за время, немногим уступающее решению, скомпилированным под нативную платформу, как мы и продемонстрируем в конце статьи. Всякое ли технологическое решение, реализованное в виде той или иной нативной библиотеки, может быть портировано на Wasm так, чтобы все вычисления выполнялись кроссплатформенно, прямо в браузере? Вынося за скобки ряд деталей можно сказать, что да, если все компоненты решения имеются на уровне исходного кода. Нам это сделать удалось – т.к. наша библиотека не имеет никаких внешних зависимостей, весь код системы, алгоритмы обработки изображений, исполнение нейросетей и т.п. – все написано нами, а значит мы можем легко собрать все из исходников и модифицировать при необходимости. Обертки интерфейсов и модули для встраивания мы создаем всегда на основе С++ интерфейса нашей главной библиотеки. Это позволяет легко интегрироваться в системы, написанные на разных языках программирования и практически на любых платформах, сохраняя логику, заложенную в интерфейс. Существует множество инструментов для портирования существующей кодовой базы в Wasm модуль. Для языка С++  существует проект Emscripten, который включает в себя инструмент EmBind. Он позволяет максимально простым способом обернуть классы, их поля и методы в объекты, видимые из js. В принципе, это уже позволяет создать минимальное рабочее приложение или библиотеку, однако Wasm постепенно обрастает поддержкой разного рода оптимизаций, которые хотелось бы использовать по-максимуму.  Для Wasm существует список фич, которые поддерживаются лишь частью существующих браузеров. В их числе как раз используемые нами многопоточность и SIMD-инструкции. Конечно, не всe заработало сразу - процесс разработки быстро превратился в путь, полный чудес и новых открытий. Первым делом разбирались с дебагом и эксепшнами - exception’ы в васме реализованы через лютый костыль:  программе дают “упасть”, но не удаляют из памяти. Это позволяет взять адрес последнего возвращенного объекта и через cast представить как объект типа exception, после чего из него можно достать exception message и вернуть в консоль браузера. Пришлось заводить специальную функцию в модуле под это дело. В будущем обещают нормальную поддержку exception. Больше всего времени мы потратили на имплементацию многопоточности. Многопоточность в js эмулируется с помощью воркеров. На этапе инициализации создается пул воркеров, в которых и происходит параллельное выполнение разных частей кода. Методом проб и ошибок выяснилось эмпирическое правило: количество одновременно выполняемых тредов внутри системы не может превышать количество созданных воркеров. Другими словами, создать сотню тредов, открепить их и дождаться результатов выполнения, вы не сможете. Для себя мы решили эту проблему, приравняв количество создаваемых для задачи воркеров и количество одновременно выполняющихся внутри библиотеки тредов, к количеству потоков, доступных системе. Для этого пришлось переделать весь механизм обеспечения многопоточности и внедрить очередь из задач, ожидающих выполнения на доступных системе потоках. Неполная поддержка SIMD-инструкций, проблемы с i64 пойнтерами на старых архитектурах, слабая документация - но мы в итоге победили. Для того, чтобы сделать наше распознавание доступным для максимального количества браузеров, в принципе поддерживающих Wasm, мы готовим сразу четыре варианта сборки:  simd.threads (симды и многопоточность); simd.nothreads (симды без многопоточности); nosimd.threads (многопоточность без симдов); nosimd.nothreads (ну сами понимаете). Для определения поддерживаемых браузером фич и, соответственно, загрузки с сервера только нужной версии модуля распознавания, существуют две маленькие опенсорсные библиотеки: https://github.com/GoogleChromeLabs/wasm-feature-detect (Apache 2). https://github.com/MaxGraey/wasm-check (MIT). Для загрузки подходящей сборки мы рекомендуем использовать importScripts(), поскольку новые динамические импорты (ESM) не работают в воркерах в Firefox без полифила. Вот как это выглядит на iPhone 8 и на OnePlus 8T: Основные выводы, конечно, же не могут обойтись без бенчмарка. Измерим производительность модуля распознавания на различных платформах с разными оптимизациями (в таблицах указаны средние значения). simd.threads Устройство barcode card mrz Chrome 100 onOnePlus 8T (Qualcomm 865 Octa-core) 0.30с 0.30 с 0.70с Chrome 100 onRyzen 7 3700X 8-Core 0.20с 0.25с 0.30с Chrome 100 onmicrosoft surface (Pentium 4415Y dual-core) 0.50с 0.90с 2.00с nosimd.nothreads: Устройство barcode card mrz Chrome 100 on OnePlus 8T (Qualcomm 865 Octa-core) 0.55с 0.35с 1.70с Chrome 100 on Ryzen 7 3700X 8-Core 0.60с 0.30с 0.90с Safari 14 on Apple iPhone 8 0.25с 0.45с 1.65с Chrome 100 on Microsoft Surface (Pentium 4415Y dual-core) 0.40с 1.30с 4.50с В таблицах выше мы представили результаты замеров двух крайних случаев: работу с оптимизациями (SIMD, многопоточность) и без, на самых разных машинах.  Планшет и мобильные телефоны работали со штатной камерой, компьютер же был в связке с Microsoft LifeCam Studio. Изображение ограничено 640х480 (размер canvas, на который мы выводили видеопоток).  iPhone представлен только в колонке без оптимизаций - работу с SIMD-инструкциями на айфоны еще не подвезли, многопоточность присутствует только на старших моделях, да и то работает с ограничениями.  Surface - машинка с посредственной камерой и довольно средним железом выступает довольно типичным примером кейса работы на тонком клиенте (кассы или рабочие места банковских служащих). В целом, Wasm показал себя прекрасным решением для безопасного кроссплатформенного распознавания. Производительность его уже сегодня вполне достаточна для выполнения даже вычислительно сложных задач, таких, как распознавание документов. Таким образом, у всех наших организаций теперь имеется хороший инструмент сделать удобные веб-приложения, обладающие привычными функционалом на базе ИИ, без привязки к магазинам приложений (App Store, Google Play и т.д.).