Execuções de intermitência e filas de mensagens rápidas

Redes Neurais HAL 1.2 introduz o conceito de execuções de rajadas. As execuções em rajada são uma sequência de execuções do mesmo modelo preparado que ocorrem em rápida sucessão, como as que operam em quadros de captura de câmera ou amostras de áudio sucessivas. Um objeto de intermitência é usado para controlar um conjunto de execuções de intermitência e preservar recursos entre as execuções, permitindo que as execuções tenham uma sobrecarga menor. Os objetos Burst permitem três otimizações:

  1. Um objeto de intermitência é criado antes de uma sequência de execuções e liberado quando a sequência termina. Por causa disso, o tempo de vida do objeto de intermitência indica a um driver por quanto tempo ele deve permanecer em um estado de alto desempenho.
  2. Um objeto de intermitência pode preservar recursos entre execuções. Por exemplo, um driver pode mapear um objeto de memória na primeira execução e armazenar em cache o mapeamento no objeto de intermitência para reutilização em execuções subsequentes. Qualquer recurso armazenado em cache pode ser liberado quando o objeto de intermitência é destruído ou quando o runtime NNAPI notifica o objeto de intermitência de que o recurso não é mais necessário.
  3. Um objeto de intermitência usa filas de mensagens rápidas (FMQs) para se comunicar entre os processos do aplicativo e do driver. Isso pode reduzir a latência porque o FMQ ignora o HIDL e passa os dados diretamente para outro processo por meio de um FIFO circular atômico na memória compartilhada. O processo consumidor sabe como desenfileirar um item e iniciar o processamento por meio do polling do número de elementos no FIFO ou aguardando o sinalizador de evento do FMQ, que é sinalizado pelo produtor. Este sinalizador de evento é um mutex de espaço de usuário rápido (futex).

Um FMQ é uma estrutura de dados de baixo nível que não oferece garantias de vida útil entre os processos e não possui mecanismo integrado para determinar se o processo na outra extremidade do FMQ está sendo executado conforme o esperado. Conseqüentemente, se o produtor do FMQ morrer, o consumidor pode ficar parado esperando por dados que nunca chegam. Uma solução para esse problema é que o driver associe FMQs ao objeto de intermitência de nível superior para detectar quando a execução de intermitência terminou.

Como as execuções de intermitência operam nos mesmos argumentos e retornam os mesmos resultados que outros caminhos de execução, os FMQs subjacentes devem passar os mesmos dados para e dos drivers de serviço NNAPI. No entanto, FMQs só podem transferir tipos de dados antigos. A transferência de dados complexos é realizada pela serialização e desserialização de buffers aninhados (tipos de vetor) diretamente nos FMQs e usando objetos de retorno de chamada HIDL para transferir identificadores de pool de memória sob demanda. O lado produtor do FMQ deve enviar as mensagens de solicitação ou resultado para o consumidor atomicamente usando MessageQueue::writeBlocking se a fila estiver bloqueando ou usando MessageQueue::write se a fila não estiver bloqueando.

Interfaces de rajada

As interfaces de intermitência para o HAL de redes neurais são encontradas em hardware/interfaces/neuralnetworks/1.2/ e são descritas abaixo. Para obter mais informações sobre interfaces de intermitência na camada NDK, consulte frameworks/ml/nn/runtime/include/NeuralNetworks.h .

tipos.hal

types.hal define o tipo de dados que são enviados pelo FMQ.

  • FmqRequestDatum : Um único elemento de uma representação serializada de um objeto Request de execução e um valor MeasureTiming , 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 e Timing ), que é retornado por meio da fila de mensagens rápidas.

IburstContext.hal

IBurstContext.hal define o objeto de interface HIDL que reside no serviço Neural Networks.

  • IBurstContext : Objeto de contexto para gerenciar os recursos de uma rajada.

IBurstCallback.hal

IBurstCallback.hal define o objeto de interface HIDL para um retorno de chamada criado pelo tempo de execução do Neural Networks e é usado pelo serviço Neural Networks para recuperar objetos hidl_memory correspondentes aos identificadores de slot.

  • IBurstCallback : objeto de retorno de chamada usado por um serviço para recuperar objetos de memória.

IPreparedModel.hal

IPreparedModel.hal é estendido no HAL 1.2 com um método para criar um objeto IBurstContext a partir de um modelo preparado.

  • configureExecutionBurst : Configura um objeto de intermitência usado para executar várias inferências em um modelo preparado em rápida sucessão.

Suportando execuções de intermitência em um driver

A maneira mais simples de oferecer suporte a objetos de intermitência em um serviço HIDL NNAPI é usar a função do utilitário de intermitência ::android::nn::ExecutionBurstServer::create , que é encontrada em ExecutionBurstServer.h e empacotada nas bibliotecas estáticas libneuralnetworks_common e libneuralnetworks_util . Esta função de fábrica tem duas sobrecargas:

  • Uma sobrecarga aceita um ponteiro para um objeto IPreparedModel . Essa função de utilitário usa o método executeSynchronously em um objeto IPreparedModel para executar o modelo.
  • Uma sobrecarga aceita um objeto IBurstExecutorWithCache personalizável, que pode ser usado para armazenar em cache recursos (como mapeamentos hidl_memory ) que persistem em várias execuções.

Cada sobrecarga retorna um objeto IBurstContext (que representa o objeto de intermitência) que contém e gerencia seu próprio thread de ouvinte dedicado. Esse encadeamento recebe solicitações do requestChannel FMQ, executa a inferência e retorna os resultados por meio do resultChannel FMQ. Essa thread e todos os outros recursos contidos no objeto IBurstContext são liberados automaticamente quando o cliente do burst perde sua referência a IBurstContext .

Como alternativa, você pode criar sua própria implementação de IBurstContext que entende como enviar e receber mensagens pelos FMQs requestChannel e resultChannel passados ​​para IPreparedModel::configureExecutionBurst .

As funções do utilitário de intermitência são encontradas 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);

Veja a seguir uma implementação de referência de uma interface de intermitência encontrada no driver de amostra Neural Networks 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();
}