В HAL 1.2 для нейронных сетей представлена концепция пакетных вычислений (burst executions). Пакетные вычисления представляют собой последовательность выполнений одной и той же подготовленной модели, выполняемых в быстром темпе, например, при обработке кадров с камеры или последовательных аудиофрагментов. Объект burst используется для управления набором пакетных вычислений и экономии ресурсов между выполнениями, что позволяет снизить накладные расходы. Объекты burst обеспечивают три оптимизации:
- Объект burst создается перед последовательностью выполнения и освобождается после ее завершения. Таким образом, время жизни объекта burst подсказывает драйверу, как долго он должен оставаться в состоянии высокой производительности.
- Объект burst может сохранять ресурсы между выполнениями. Например, драйвер может отобразить объект памяти при первом выполнении и кэшировать это отображение в объекте burst для повторного использования при последующих выполнениях. Любой кэшированный ресурс может быть освобожден при уничтожении объекта burst или когда среда выполнения NNAPI уведомляет объект burst о том, что ресурс больше не требуется.
- Объект burst использует быстрые очереди сообщений (FMQ) для взаимодействия между процессами приложения и драйвера. Это может сократить задержку, поскольку FMQ обходит HIDL и передаёт данные напрямую другому процессу через атомарный циклический FIFO в общей памяти. Процесс-потребитель знает, что нужно извлечь элемент из очереди и начать обработку, либо опрашивая количество элементов в FIFO, либо ожидая флага события FMQ, который поступает от поставщика. Этот флаг события представляет собой быстрый мьютекс (фьютекс) пользовательского пространства.
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
.
типы.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
IPreparedModel.hal
расширен в HAL 1.2 методом создания объекта 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), который содержит и управляет собственным выделенным потоком прослушивания. Этот поток получает запросы из requestChannel
FMQ, выполняет вывод, а затем возвращает результаты через resultChannel
FMQ. Этот поток и все остальные ресурсы, содержащиеся в объекте IBurstContext
, автоматически освобождаются, когда клиент burst теряет ссылку на IBurstContext
.
В качестве альтернативы вы можете создать собственную реализацию IBurstContext
, которая понимает, как отправлять и получать сообщения через FMQ requestChannel
и resultChannel
, передаваемые в IPreparedModel::configureExecutionBurst
.
Функции утилиты burst находятся в 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);
Ниже приведена эталонная реализация интерфейса burst, найденного в примере драйвера нейронных сетей по адресу 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();
}