Пакетное выполнение и быстрые очереди сообщений

В Neural Networks HAL 1.2 введено понятие пакетных выполнений. Пакетные выполнения — это последовательность быстрых выполнений одной и той же подготовленной модели, например, при работе с кадрами, полученными с камеры, или последовательными аудиосэмплами. Объект Burst используется для управления набором пакетных выполнений и для экономии ресурсов между выполнениями, что позволяет снизить накладные расходы. Объекты Burst обеспечивают три оптимизации:

  1. Объект Burst создается перед последовательностью выполнений и освобождается по ее завершении. Благодаря этому время жизни объекта Burst указывает драйверу, как долго он должен оставаться в высокопроизводительном состоянии.
  2. Объект Burst может сохранять ресурсы между выполнениями. Например, драйвер может отобразить объект памяти при первом выполнении и кэшировать это отображение в объекте Burst для повторного использования в последующих выполнениях. Любой кэшированный ресурс может быть освобожден при уничтожении объекта Burst или когда среда выполнения NNAPI уведомит объект Burst о том, что ресурс больше не требуется.
  3. Объект Burst использует быстрые очереди сообщений (FMQ) для связи между процессами приложения и драйвера. Это может уменьшить задержку, поскольку FMQ обходит HIDL и передает данные напрямую другому процессу через атомарный кольцевой FIFO в общей памяти. Процесс-потребитель знает, когда нужно извлечь элемент из очереди и начать обработку, либо опрашивая количество элементов в FIFO, либо ожидая флага события FMQ, который подается производителем. Этот флаг события представляет собой быстрый мьютекс пользовательского пространства (futex).

FMQ — это низкоуровневая структура данных, которая не гарантирует времени жизни между процессами и не имеет встроенного механизма для определения того, работает ли процесс на другом конце FMQ должным образом. Следовательно, если производитель FMQ завершает работу, потребитель может застрять в ожидании данных, которые так и не поступят. Одним из решений этой проблемы является связывание драйвером FMQ с объектом пакетной обработки более высокого уровня для определения момента завершения пакетного выполнения.

Поскольку пакетное выполнение работает с теми же аргументами и возвращает те же результаты, что и другие пути выполнения, базовые FMQ должны передавать одни и те же данные драйверам служб NNAPI и получать их от них. Однако FMQ могут передавать только обычные типы данных. Передача сложных данных осуществляется путем сериализации и десериализации вложенных буферов (векторных типов) непосредственно в FMQ, а также с использованием объектов обратного вызова HIDL для передачи дескрипторов пула памяти по запросу. Сторона-производитель FMQ должна атомарно отправлять сообщения запроса или результата потребителю, используя MessageQueue::writeBlocking если очередь блокирующая, или MessageQueue::write если очередь неблокирующая.

Интерфейсы пакетной передачи

Интерфейсы пакетной обработки данных для HAL нейронных сетей находятся в каталоге hardware/interfaces/neuralnetworks/1.2/ и описаны ниже. Для получения дополнительной информации об интерфейсах пакетной обработки данных в слое NDK см. frameworks/ml/nn/runtime/include/NeuralNetworks.h .

types.hal

types.hal определяет тип данных, передаваемых через FMQ.

  • FmqRequestDatum : Отдельный элемент сериализованного представления объекта Request на выполнение и значения MeasureTiming , передаваемого через быструю очередь сообщений.
  • FmqResultDatum : Отдельный элемент сериализованного представления значений, возвращаемых в результате выполнения ( ErrorStatus , OutputShapes и Timing ), которые передаются через быструю очередь сообщений.

IBurstContext.hal

IBurstContext.hal определяет объект интерфейса HIDL, который находится в службе нейронных сетей.

  • IBurstContext : Объект контекста для управления ресурсами всплеска активности.

IBurstCallback.hal

IBurstCallback.hal определяет объект интерфейса HIDL для функции обратного вызова, созданной средой выполнения нейронных сетей, и используется службой нейронных сетей для получения объектов hidl_memory соответствующих идентификаторам слотов.

  • IBurstCallback : Объект обратного вызова, используемый службой для получения объектов памяти.

IPreparedModel.hal

В HAL 1.2 класс IPreparedModel.hal расширен методом для создания объекта IBurstContext из подготовленной модели.

  • configureExecutionBurst : Настраивает объект Burst, используемый для выполнения нескольких вычислений на подготовленной модели в быстрой последовательности.

Поддержка пакетного выполнения в драйвере

Простейший способ поддержки объектов Burst в сервисе HIDL NNAPI — использовать вспомогательную функцию Burst ::android::nn::ExecutionBurstServer::create , которая находится в файле ExecutionBurstServer.h и входит в состав статических библиотек libneuralnetworks_common и libneuralnetworks_util . Эта фабричная функция имеет две перегрузки:

  • Одна из перегруженных функций принимает указатель на объект IPreparedModel . Эта вспомогательная функция использует метод executeSynchronously объекта IPreparedModel для выполнения модели.
  • Одна из перегруженных функций принимает настраиваемый объект IBurstExecutorWithCache , который можно использовать для кэширования ресурсов (таких как сопоставления hidl_memory ), сохраняющихся между несколькими выполнениями.

Каждая перегрузка возвращает объект IBurstContext (который представляет собой объект Burst), содержащий и управляющий собственным выделенным потоком-слушателем. Этот поток получает запросы от FMQ- requestChannel , выполняет вывод, а затем возвращает результаты через FMQ resultChannel . Этот поток и все другие ресурсы, содержащиеся в объекте IBurstContext , автоматически освобождаются, когда клиент Burst теряет ссылку на IBurstContext .

В качестве альтернативы вы можете создать собственную реализацию IBurstContext , которая понимает, как отправлять и получать сообщения через FMQ-объекты requestChannel и resultChannel передаваемые в IPreparedModel::configureExecutionBurst .

Вспомогательные функции для выполнения пакетной обработки данных находятся в файле ExecutionBurstServer.h .

/**
 * Create automated context to manage FMQ-based executions.
 *
 * This function is intended to be used by a service to automatically:
 * 1) Receive data from a provided FMQ
 * 2) Execute a model with the given information
 * 3) Send the result to the created FMQ
 *
 * @param callback Callback used to retrieve memories corresponding to
 *     unrecognized slots.
 * @param requestChannel Input FMQ channel through which the client passes the
 *     request to the service.
 * @param resultChannel Output FMQ channel from which the client can retrieve
 *     the result of the execution.
 * @param executorWithCache Object which maintains a local cache of the
 *     memory pools and executes using the cached memory pools.
 * @result IBurstContext Handle to the burst context.
 */
static sp<ExecutionBurstServer> create(
        const sp<IBurstCallback>& callback, const FmqRequestDescriptor& requestChannel,
        const FmqResultDescriptor& resultChannel,
        std::shared_ptr<IBurstExecutorWithCache> executorWithCache);

/**
 * Create automated context to manage FMQ-based executions.
 *
 * This function is intended to be used by a service to automatically:
 * 1) Receive data from a provided FMQ
 * 2) Execute a model with the given information
 * 3) Send the result to the created FMQ
 *
 * @param callback Callback used to retrieve memories corresponding to
 *     unrecognized slots.
 * @param requestChannel Input FMQ channel through which the client passes the
 *     request to the service.
 * @param resultChannel Output FMQ channel from which the client can retrieve
 *     the result of the execution.
 * @param preparedModel PreparedModel that the burst object was created from.
 *     IPreparedModel::executeSynchronously will be used to perform the
 *     execution.
 * @result IBurstContext Handle to the burst context.
 */
  static sp<ExecutionBurstServer> create(const sp<IBurstCallback>& callback,
                                         const FmqRequestDescriptor& requestChannel,
                                         const FmqResultDescriptor& resultChannel,
                                         IPreparedModel* preparedModel);

Ниже представлена ​​эталонная реализация интерфейса пакетной обработки данных, найденная в примере драйвера нейронных сетей по адресу frameworks/ml/nn/driver/sample/SampleDriver.cpp .

Return<void> SamplePreparedModel::configureExecutionBurst(
        const sp<V1_2::IBurstCallback>& callback,
        const MQDescriptorSync<V1_2::FmqRequestDatum>& requestChannel,
        const MQDescriptorSync<V1_2::FmqResultDatum>& resultChannel,
        configureExecutionBurst_cb cb) {
    NNTRACE_FULL(NNTRACE_LAYER_DRIVER, NNTRACE_PHASE_EXECUTION,
                 "SampleDriver::configureExecutionBurst");
    // Alternatively, the burst could be configured via:
    // const sp<V1_2::IBurstContext> burst =
    //         ExecutionBurstServer::create(callback, requestChannel,
    //                                      resultChannel, this);
    //
    // However, this alternative representation does not include a memory map
    // caching optimization, and adds overhead.
    const std::shared_ptr<BurstExecutorWithCache> executorWithCache =
            std::make_shared<BurstExecutorWithCache>(mModel, mDriver, mPoolInfos);
    const sp<V1_2::IBurstContext> burst = ExecutionBurstServer::create(
            callback, requestChannel, resultChannel, executorWithCache);
    if (burst == nullptr) {
        cb(ErrorStatus::GENERAL_FAILURE, {});
    } else {
        cb(ErrorStatus::NONE, burst);
    }
    return Void();
}