O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

Pools de memória

Esta página descreve as estruturas de dados e métodos usados ​​para comunicar eficientemente os buffers de operando entre o driver e a estrutura.

No momento da compilação do modelo, o framework fornece os valores dos operandos constantes para o driver. Dependendo do tempo de vida do operando constante, seus valores estão localizados em um vetor HIDL ou em um pool de memória compartilhada.

  • Se o tempo de vida é CONSTANT_COPY , os valores estão localizados no operandValues campo do modelo de estrutura. Uma vez que os valores no vector HIDL são copiados durante a comunicação entre processos (IPC), este é normalmente utilizado apenas para manter uma pequena quantidade de dados, tais como operandos escalares (por exemplo, o escalar activação em ADD ) e parâmetros de tensores pequenas (por exemplo, o tensor de forma a RESHAPE ).
  • Se o tempo de vida é CONSTANT_REFERENCE , os valores estão localizados no pools campo do modelo de estrutura. Apenas os identificadores dos pools de memória compartilhada são duplicados durante o IPC, em vez de copiar os valores brutos. Portanto, é mais eficiente manter uma grande quantidade de dados (por exemplo, os parâmetros de peso em convoluções) usando pools de memória compartilhada do que vetores HIDL.

No tempo de execução do modelo, o framework fornece os buffers dos operandos de entrada e saída para o driver. Ao contrário das constantes de tempo de compilação que podem ser enviadas em um vetor HIDL, os dados de entrada e saída de uma execução são sempre comunicados por meio de uma coleção de pools de memória.

O HIDL tipo de dados hidl_memory é utilizada tanto na compilação e execução para representar um pool de memória compartilhada não mapeado. O motorista deve mapear a memória de acordo para torná-lo utilizável com base no nome do hidl_memory tipo de dados. Os nomes de memória suportados são:

  • ashmem : Android memória compartilhada. Para mais detalhes, veja memória .
  • mmap_fd : memória compartilhada apoiado por um descritor de arquivo através mmap .
  • hardware_buffer_blob : memória compartilhada apoiado por uma AHardwareBuffer com o formato AHARDWARE_BUFFER_FORMAT_BLOB . Disponível em Neural Networks (NN) HAL 1.2. Para mais detalhes, consulte AHardwareBuffer .
  • hardware_buffer : memória compartilhada apoiado por uma AHardwareBuffer geral que não usa o formato AHARDWARE_BUFFER_FORMAT_BLOB . O buffer de hardware do modo não BLOB só é compatível com a execução do modelo. Disponível em NN HAL 1.2. Para mais detalhes, consulte AHardwareBuffer .

A partir do NN HAL 1.3, o NNAPI oferece suporte a domínios de memória que fornecem interfaces de alocador para buffers gerenciados por driver. Os buffers gerenciados pelo driver também podem ser usados ​​como entradas ou saídas de execução. Para mais detalhes, consulte domínios de memória .

Motoristas NNAPI deve suportar o mapeamento de ashmem e mmap_fd nomes de memória. De NN HAL 1.3, os motoristas também deve suportar o mapeamento de hardware_buffer_blob . Suporte para gerais modo não-BLOB hardware_buffer domínios e de memória é opcional.

AHardwareBuffer

AHardwareBuffer é um tipo de memória compartilhada que envolve um tampão Gralloc . No Android 10, o API Redes Neurais (NNAPI) utilizando suportes AHardwareBuffer , permitindo que o condutor para realizar as execuções sem cópia de dados, o que melhora o desempenho e o consumo de energia para aplicações. Por exemplo, uma pilha de HAL de câmera pode passar objetos AHardwareBuffer para a NNAPI para cargas de trabalho de aprendizado de máquina usando identificadores AHardwareBuffer gerados por NDK de câmera e APIs de NDK de mídia. Para mais informações, consulte ANeuralNetworksMemory_createFromAHardwareBuffer .

AHardwareBuffer objetos usados no NNAPI são passados para o motorista através de um hidl_memory struct chamado quer hardware_buffer ou hardware_buffer_blob . O hidl_memory struct hardware_buffer_blob representa apenas objetos AHardwareBuffer com o AHARDWAREBUFFER_FORMAT_BLOB formato.

As informações requeridas pela estrutura está codificado no hidl_handle campo do hidl_memory struct. O hidl_handle campo envolve native_handle , que codifica todos os metadados necessários cerca AHardwareBuffer ou tampão Gralloc.

O condutor deve adequadamente decodificar o fornecido hidl_handle campo e acesso a memória descrito por hidl_handle . Quando o getSupportedOperations_1_2 , getSupportedOperations_1_1 , ou getSupportedOperations método é chamado, o motorista deve detectar se ele pode decodificar o fornecido hidl_handle e acessar a memória descrito por hidl_handle . A preparação do modelo deve falhar se o hidl_handle campo usado para um operando constante não é suportado. A execução deve falhar se o hidl_handle campo utilizado para um operando de entrada ou saída da execução não é suportado. É recomendado para o driver para retornar um GENERAL_FAILURE código de erro se a preparação ou execução do modelo falhar.

Domínios de memória

Para dispositivos que executam o Android 11 ou superior, a NNAPI oferece suporte a domínios de memória que fornecem interfaces de alocador para buffers gerenciados por driver. Isso permite a passagem de memórias nativas do dispositivo entre as execuções, suprimindo a cópia e transformação desnecessária de dados entre execuções consecutivas no mesmo driver. Este fluxo é ilustrado na Figura 1.

Fluxo de dados do buffer com e sem domínios de memória
Dados Figura 1. Tampão de fluxo utilizando domínios de memória

O recurso de domínio de memória é destinado a tensores que são principalmente internos ao driver e não precisam de acesso frequente no lado do cliente. Exemplos de tais tensores incluem os tensores de estado em modelos de sequência. Para tensores que precisam de acesso frequente à CPU no lado do cliente, é preferível usar pools de memória compartilhada.

Para suportar a funcionalidade de domínio de memória, implementar IDevice::allocate para permitir o enquadramento de pedido de alocação de buffer gerenciado pelo motorista. Durante a alocação, a estrutura fornece as seguintes propriedades e padrões de uso para o buffer:

  • BufferDesc descreve as propriedades necessárias do tampão.
  • BufferRole descreve o padrão de uso potencial do tampão como uma entrada ou saída de um modelo preparado. Múltiplas funções podem ser especificadas durante a alocação do buffer, e o buffer alocado pode ser usado apenas como aquelas funções especificadas.

O buffer alocado é interno ao driver. Um driver pode escolher qualquer local de buffer ou layout de dados. Quando o buffer é alocado com sucesso, o cliente do driver pode referenciar ou interagir com o tampão usando o token retornado ou o IBuffer objeto.

O sinal de IDevice::allocate é fornecida ao fazer referência a um tampão como do MemoryPool objetos no Request estrutura de uma execução. Para evitar que um processo tente acessar o buffer alocado em outro processo, o driver deve aplicar a validação adequada a cada uso do buffer. O condutor deve validar que o uso do tampão é uma das BufferRole papéis fornecidos durante a alocação e deve falhar a execução imediatamente se o uso é ilegal.

O IBuffer objeto é usado para copiar a memória explícita. Em certas situações, o cliente do driver deve inicializar o buffer gerenciado pelo driver de um pool de memória compartilhada ou copiar o buffer para um pool de memória compartilhada. Os exemplos de casos de uso incluem:

  • Inicialização do tensor de estado
  • Resultados intermediários de cache
  • Execução de fallback na CPU

Para apoiar estes casos de uso, o motorista deve implementar IBuffer::copyTo e IBuffer::copyFrom com ashmem , mmap_fd e hardware_buffer_blob se ele suporta alocação de domínio de memória. É opcional para o driver para suportar o modo não-BLOB hardware_buffer .

Durante tampão de atribuição, as dimensões do tampão podem ser deduzidos a partir dos correspondentes operandos modelo de todas as funções especificadas por BufferRole , e as dimensões fornecidas em BufferDesc . Com todas as informações dimensionais combinadas, o buffer pode ter dimensões ou classificação desconhecidas. Nesse caso, o buffer está em um estado flexível onde as dimensões são fixas quando usadas como uma entrada do modelo e em um estado dinâmico quando usado como uma saída do modelo. O mesmo buffer pode ser usado com diferentes formatos de saída em diferentes execuções e o driver deve lidar com o redimensionamento do buffer de forma adequada.

O domínio da memória é um recurso opcional. Um driver pode determinar que não dá suporte a uma determinada solicitação de alocação por vários motivos. Por exemplo:

  • O buffer solicitado tem um tamanho dinâmico.
  • O driver tem restrições de memória que o impedem de lidar com buffers grandes.

É possível que vários threads diferentes leiam simultaneamente o buffer gerenciado pelo driver. Acessar o buffer simultaneamente para gravação ou leitura / gravação é indefinido, mas não deve travar o serviço do driver ou bloquear o chamador indefinidamente. O driver pode retornar um erro ou deixar o conteúdo do buffer em um estado indeterminado.