A HAL 1.2 de redes neurais introduz o conceito de execuções burst. As execuções em sequência são uma sequência do mesmo modelo preparado que ocorrem em sucessão rápida, como aquelas que ocorrem em frames de uma captura de câmera ou amostras de áudio sucessivas. Um objeto de burst é usado para controlar um conjunto de execuções de burst e para preservar recursos entre execuções, permitindo que elas tenham uma sobrecarga menor. Os objetos de explosão permitem três otimizações:
- Um objeto de explosão é criado antes de uma sequência de execuções e liberado quando a sequência termina. Por isso, o ciclo de vida do objeto de burst mostra ao driver quanto tempo ele precisa permanecer em um estado de alto desempenho.
- Um objeto de burst pode preservar recursos entre execuções. Por exemplo, um driver pode mapear um objeto de memória na primeira execução e armazenar o mapeamento em cache no objeto de explosão para reutilização em execuções subsequentes. Qualquer recurso armazenado em cache pode ser liberado quando o objeto de explosão é destruído ou quando o ambiente de execução da NNAPI notifica o objeto de explosão de que o recurso não é mais necessário.
- Um objeto de burst usa filas rápidas de mensagens (FMQs, na sigla em inglês) para se comunicar entre os processos do app e do driver. Isso pode reduzir a latência porque o FMQ ignora o HIDL e transmite dados diretamente para outro processo por meio de um FIFO circular atômico na memória compartilhada. O processo do consumidor sabe como retirar um item da fila e começar o processamento consultando o número de elementos no FIFO ou aguardando a flag de evento da FMQ, que é sinalizada pelo produtor. Essa flag de evento é um mutex rápido do espaço do usuário (futex).
Um FMQ é uma estrutura de dados de baixo nível que não oferece garantias de ciclo de vida nos processos e não tem mecanismo integrado para determinar se o processo na outra extremidade da FMQ está sendo executado conforme o esperado. Consequentemente, se o produtor do FMQ falhar, o consumidor poderá ficar preso à espera de dados que nunca chegam. Uma solução para esse problema é que o driver associe FMQs ao objeto de burst de nível mais alto para detectar quando a execução de burst terminar.
Como as execuções de pico operam com os mesmos argumentos e retornam os mesmos
resultados que outras rotas de execução, as FMQs subjacentes precisam transmitir os mesmos dados para
e dos drivers de serviço da NNAPI. No entanto, as FMQs só podem transferir
tipos de dados simples. A transferência de dados complexos é feita serializando
e desserializando buffers aninhados (tipos de vetor) diretamente nos FMQs e usando
objetos de callback do HIDL para transferir identificadores de pool de memória sob demanda. O lado
do produtor da FMQ precisa enviar as mensagens de solicitação ou resultado ao consumidor
atomicamente usando MessageQueue::writeBlocking
se a fila estiver bloqueada ou
MessageQueue::write
se a fila não estiver bloqueada.
Interfaces de burst
As interfaces de explosão para a HAL de redes neurais estão em
hardware/interfaces/neuralnetworks/1.2/
e são descritas abaixo. Para saber mais sobre interfaces com burst na camada
do NDK, consulte
frameworks/ml/nn/runtime/include/NeuralNetworks.h
.
types.hal
types.hal
define o tipo de dados enviados pelo FMQ.
FmqRequestDatum
: um único elemento de uma representação serializada de um objetoRequest
de execução e um valorMeasureTiming
, que é enviado pela fila de mensagens rápidas.FmqResultDatum
: um único elemento de uma representação serializada dos valores retornados de uma execução (ErrorStatus
,OutputShapes
eTiming
), que é retornado pela fila de mensagens rápidas.
IBurstContext.hal
IBurstContext.hal
define o objeto de interface HIDL que reside no serviço de redes neurais.
IBurstContext
: objeto de contexto para gerenciar os recursos de um burst.
IBurstCallback.hal
IBurstCallback.hal
define o objeto de interface HIDL para um callback criado pelo ambiente de execução de redes neurais
e é usado pelo serviço de redes neurais para recuperar objetos hidl_memory
correspondentes a identificadores de slot.
- IBurstCallback: objeto de callback usado por um serviço para recuperar objetos de memória.
IPreparedModel.hal
IPreparedModel.hal
é estendido na HAL 1.2 com um método para criar um objeto IBurstContext
usando um
modelo preparado.
configureExecutionBurst
: configura um objeto de explosão usado para executar várias inferências em um modelo preparado em sucessão rápida.
Oferecer suporte a execuções de burst em um driver
A maneira mais simples de oferecer suporte a objetos de explosão em um serviço de NNAPI HIDL é usar a
função de utilitário de explosão ::android::nn::ExecutionBurstServer::create
, que é
encontrada em
ExecutionBurstServer.h
e empacotada nas bibliotecas estáticas
libneuralnetworks_common
e libneuralnetworks_util
. Essa função de fábrica tem duas sobrecargas:
- Uma sobrecarga aceita um ponteiro para um objeto
IPreparedModel
. Essa função utilitária usa o métodoexecuteSynchronously
em um objetoIPreparedModel
para executar o modelo. - Uma sobrecarga aceita um objeto
IBurstExecutorWithCache
personalizável, que pode ser usado para armazenar recursos em cache, como mapeamentoshidl_memory
, que persistem em várias execuções.
Cada sobrecarga retorna um objeto IBurstContext
(que representa o objeto
de burst) que contém e gerencia a própria linha de execução de listener dedicada. Essa linha de execução
recebe solicitações do FMQ requestChannel
, executa a inferência e
retorna os resultados por meio do FMQ resultChannel
. Essa linha de execução e todos os outros
recursos contidos no objeto IBurstContext
são liberados automaticamente
quando o cliente do burst perde a referência a IBurstContext
.
Como alternativa, é possível criar sua própria implementação de IBurstContext
, que
entende como enviar e receber mensagens pelos FMQs requestChannel
e
resultChannel
transmitidos para IPreparedModel::configureExecutionBurst
.
As funções utilitárias de burst estão em
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);
Confira a seguir uma implementação de referência de uma interface de burst encontrada no
driver de amostra de redes neurais em
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();
}