В Neural Networks HAL 1.2 введено понятие пакетных выполнений. Пакетные выполнения — это последовательность быстрых выполнений одной и той же подготовленной модели, например, при работе с кадрами, полученными с камеры, или последовательными аудиосэмплами. Объект Burst используется для управления набором пакетных выполнений и для экономии ресурсов между выполнениями, что позволяет снизить накладные расходы. Объекты 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 если очередь неблокирующая.
Интерфейсы пакетной передачи
Интерфейсы пакетной обработки данных для 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();
}