Искусственный интеллект (ИИ) прочно вошел в современную жизнь, проникая во все сферы – от компьютеров до встраиваемых систем и микроконтроллеров. Однако, работа ИИ на столь ограниченных по ресурсам устройствах, как микроконтроллеры, вызывает множество вопросов. Одним из ключевых аспектов является процесс регистрации операторов — тех самых базовых «инструкций», которые составляют вычислительный граф модели. Понимание этого процесса помогает разобраться, как происходит запуск и выполнение ИИ-моделей на микроконтроллерах в условиях жестких ограничений по памяти и производительности. Рассмотрим подробнее, как это реализовано, какие механизмы используются и почему регистрация операторов – важнейшая часть эффективного инференса на микроконтроллерах.
Для начала важно понять, что ИИ-модель представляет собой набор операторов и вычислительных ядер — маленьких атомарных функций, обрабатывающих данные в определенной последовательности. Эти операции могут включать свертки, активации, полносвязные вычисления и многое другое. В TensorFlow Lite и его микро-версии TensorFlow Lite Micro, используемой для микроконтроллеров, этот набор операторов должен быть явно зарегистрирован и доступен во время выполнения. Регистрация операторов — это процесс связывания кода, реализующего каждую из операций, с интерпретатором, который исполняет модель. В традиционной среде разработки с достаточным объемом памяти и вычислительных ресурсов удобно загружать полную библиотеку операторов и всегда предоставлять их в интерфейсе.
Однако на микроконтроллерах ресурсы очень ограничены, память построена на флэш-чипах и оперативной памяти с малыми размером и скоростью. Чтобы уменьшить размер исполняемого кода и расход памяти, необходимо регистрировать строго только те операторы, которые нужны для запуска конкретной модели. Благодаря такой селективности появляется возможность запускать успешный инференс даже на системах с десятками килобайт оперативки и скромным объемом флэш-памяти. Одним из главных инструментов для управления этой задачей является класс MicroMutableOpResolver, который реализует хранение зарегистрированных операторов в фиксированном массиве, размер которого определяется во время компиляции. Это кардинально отличается от более гибкой, но ресурсоемкой реализации на базе динамических контейнеров std::unordered_map, используемых в стандартном TensorFlow Lite.
Использование массива фиксированной длины позволяет избавить программу от динамического выделения памяти, что способствует уменьшению потребления ресурсов и повышенной стабильности на «бедных» платформах. Процесс регистрации оператора в MicroMutableOpResolver сводится к вызову соответствующей функции, например AddFullyConnected(), которая добавляет конкретный оператор и связывает его с набором функций и методов инициализации, подготовки, выполнения и освобождения памяти. Каждая из этих функций получает определенные данные структуры TfLiteRegistration, которая содержит указатели на эти функции, версию оператора и другую служебную информацию. Именно эта структура является связующим звеном между компилируемой функцией и интерпретатором, позволяя последнему корректно вызывать необходимые методы во время инференса. При добавлении оператора в резолвер соблюдается строгость: нельзя добавить одну и ту же операцию несколько раз, чтобы избежать конфликтов и неопределенного поведения.
Если массив зарегистрированных операторов заполнен или операция уже добавлена, возвращается ошибка. Такая жесткая проверка критична для обеспечения надежности и сохранения целостности выполнения модели. В случае правильной регистрации операции интерпретатор получает доступ к точным адресам функций, отвечающих за операции, и при вызове узла вычислений выполняет именно нужный код. В контексте микроконтроллеров важным аспектом является жизненный цикл объектов TfLiteRegistration. Они должны существовать все время работы интерпретатора, так как он ссылается на них при выполнении различных этапов: инициализации, подготовки и самой активации вычислений.
Поэтому объекты регистрации обычно размещаются в статически выделенной памяти или областях с продолжительным временем жизни, например, в глобальных переменных или статических объектах внутри функций setup(). С точки зрения организации кода в таких компонентах, как tflite::MicroMutableOpResolver, сохраняются массивы зарегистрированных операторов и служебные массивы для кодов операторов и функций парсинга, которые применяются во время загрузки и инициализации модели. Это позволяет эффективно находить нужную функцию по коду оператора на этапе выполнения и минимизировать затраты по времени и памяти. Отдельно стоит отметить, что помимо встроенных операторов (Builtin operators), система поддерживает регистрацию и пользовательских операторов (Custom operators). Такие операции могут понадобиться для специализированных моделей или функций, не покрываемых стандартной библиотекой.
При этом для работы с кастомными операторами предусмотрен отдельный путь регистрации, где по имени оператора связывается набор функций, обеспечивающих необходимый функционал. В микроконтроллерных реализациях также применяется выделение массива фиксированной длины для хранения пользовательских операторов, чтобы избежать использования динамической памяти. Многие примеры из реальных проектов подтверждают, как важно точно подбирать набор операторов. Например, в самом простом Hello World примере tflite-micro для микроконтроллеров используется только оператор FULLY_CONNECTED. Благодаря этому приложение занимает всего около 56 килобайт флэш-памяти, из которых значительная часть — строго необходимый код TensorFlow Lite Micro и RTOS Zephyr.
Если же добавить гораздо больший набор операторов, например, те, что используются в более сложном примере person_detection (AVERAGE_POOL_2D, CONV_2D, DEPTHWISE_CONV_2D, RESHAPE, SOFTMAX), размер кода увеличится более чем на 20 килобайт. Такой рост неприемлем для большинства микроконтроллерных платформ с ограничениями по памяти. Взгляд внутрь процесса регистрации операторов показывает, что на этапе вызова AddFullyConnected() происходит вызов функции Register_FULLY_CONNECTED(), возвращающей объект TfLiteRegistration. Этот объект содержит указатели на функции инициализации, подготовки и выполнения операции, после чего он копируется в фиксированный массив регистратора. Таким образом, во время интерпретации модели при необходимости вызова данного оператора система знает точно, какой код использовать.
Кроме того, особенностью tflite-micro является отказ от динамического выделения памяти и использование шаблонов C++ для установки размера операторного резолвера на этапе компиляции. Подобный подход снижает оверхед и исключает ошибки, связанные с нехваткой памяти в рантайме. В то же время он требует от разработчика сознательного выбора операторов заранее, что идеально вписывается в программирование под микроконтроллеры – где строгое управление ресурсами и отказ от избыточности являются нормой. Таким образом, регистрация операторов находит свое место в архитектуре ИИ на микроконтроллерах как одна из ключевых точек оптимизации и гарантии стабильности запуска и работы модели. Глубокое понимание этого процесса помогает инженерам и разработчикам правильно конфигурировать инференс-среду для специфичных задач и сложных устройств с ограниченными ресурсами.