В HAL нейронных сетей 1.2 представлено понятие пакетного выполнения. Серийные выполнения – это последовательность выполнений одной и той же подготовленной модели, которые происходят быстро друг за другом, например при обработке кадров с камеры или последовательных аудиозаписей. Объект burst используется для управления набором выполнений burst и сохранения ресурсов между выполнениями, что позволяет снизить накладные расходы. Объекты с быстрым показом позволяют выполнять три вида оптимизации:
- Объект Burst создается перед последовательностью выполнений и освобождается, когда она завершается. Поэтому время существования объекта Burst указывает драйверу, как долго он должен оставаться в высокопроизводительном состоянии.
- Объект Burst может сохранять ресурсы между выполнениями. Например, драйвер может сопоставить объект памяти при первом выполнении и кэшировать сопоставление в объекте пакета для повторного использования при последующих выполнениях. Любой ресурс, сохраненный в кеше, можно освободить, когда объект burst будет уничтожен или когда среда выполнения NNAPI уведомит объект burst о том, что ресурс больше не нужен.
- Объект Burst использует быстрые очереди сообщений (FMQ) для связи между процессами приложения и драйвера. Это позволяет снизить задержку, поскольку FMQ обходит HIDL и передает данные непосредственно другому процессу через атомарный циклический FIFO в общей памяти. Процесс потребителя узнает, что нужно извлечь элемент из очереди и начать его обработку, либо путем опроса количества элементов в FIFO, либо путем ожидания флага события FMQ, который устанавливается производителем. Этот флаг события является быстрым мьютексом пространства пользователя (futex).
FMQ – это низкоуровневая структура данных, которая не гарантирует доступность данных в течение всего времени работы процессов и не имеет встроенного механизма для определения того, работает ли процесс на другом конце FMQ должным образом. В результате, если производитель для FMQ перестанет работать, потребитель может застрять в ожидании данных, которые никогда не поступят. Чтобы решить эту проблему, драйвер должен связать FMQ с объектом более высокого уровня, чтобы определить, когда выполнение пакета завершится.
Поскольку пакетное выполнение использует те же аргументы и возвращает те же результаты, что и другие пути выполнения, базовые FMQ должны передавать одни и те же данные драйверам сервиса NNAPI. Однако FMQ могут передавать только простые типы данных. Передача сложных данных осуществляется путем сериализации и десериализации вложенных буферов (типов векторов) непосредственно в FMQ и использования объектов обратного вызова HIDL для передачи дескрипторов пула памяти по запросу. Производитель FMQ должен отправлять запросы или сообщения с результатами потребителю атомарно, используя MessageQueue::writeBlocking, если очередь блокирующая, или MessageQueue::write, если очередь неблокирующая.
Интерфейсы Burst
Интерфейсы для пакетной обработки в 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, который находится в сервисе Neural Networks.
IBurstContext– объект контекста для управления ресурсами серии.
IBurstCallback.hal
IBurstCallback.hal определяет объект интерфейса HIDL для обратного вызова, созданного средой выполнения Neural Networks, и используется сервисом Neural Networks для получения объектов hidl_memory, соответствующих идентификаторам слотов.
- IBurstCallback – объект обратного вызова, используемый сервисом для получения объектов памяти.
IPreparedModel.hal
В HAL 1.2 интерфейс IPreparedModel.hal дополнен методом для создания объекта IBurstContext на основе подготовленной модели.
configureExecutionBurst: Настраивает объект пакетной обработки, который используется для быстрого выполнения нескольких логических выводов на основе подготовленной модели.
Поддержка пакетного выполнения в драйвере
Самый простой способ реализовать поддержку объектов burst в сервисе HIDL NNAPI – использовать служебную функцию ::android::nn::ExecutionBurstServer::create, которая находится в ExecutionBurstServer.h и упакована в статические библиотеки libneuralnetworks_common и libneuralnetworks_util. У этой фабричной функции есть две перегрузки:
- Одна перегрузка принимает указатель на объект
IPreparedModel. Эта служебная функция использует методexecuteSynchronouslyв объектеIPreparedModelдля выполнения модели. - Одна перегрузка принимает настраиваемый объект
IBurstExecutorWithCache, который можно использовать для кеширования ресурсов (например, сопоставленийhidl_memory), сохраняемых при нескольких выполнениях.
Каждая перегрузка возвращает объект IBurstContext (который представляет объект burst), содержащий и управляющий собственным выделенным потоком прослушивателя. Этот поток получает запросы из очереди requestChannel, выполняет логический вывод и возвращает результаты через очередь resultChannel. Этот поток и все другие ресурсы, содержащиеся в объекте IBurstContext, автоматически освобождаются, когда клиент пакета теряет ссылку на IBurstContext.
Вы также можете создать собственную реализацию IBurstContext, которая будет понимать, как отправлять и получать сообщения через requestChannel и resultChannel FMQ, переданные в 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();
}