O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.
Esta página foi traduzida pela API Cloud Translation.
Switch to English

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 for CONSTANT_COPY , os valores estão localizados no campo operandValues da estrutura do modelo. Como os valores no vetor HIDL são copiados durante a comunicação entre processos (IPC), isso é normalmente usado apenas para manter uma pequena quantidade de dados, como operandos escalares (por exemplo, o escalar de ativação em ADD ) e pequenos parâmetros de tensor (por exemplo, o tensor de forma em RESHAPE ).
  • Se o tempo de vida for CONSTANT_REFERENCE , os valores estarão localizados no campo pools da estrutura do modelo. 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 tipo de dados hidl_memory é usado tanto na compilação quanto na execução para representar um pool de memória compartilhada não mapeada. O driver deve mapear a memória de acordo para torná-la utilizável com base no nome do tipo de dados hidl_memory . Os nomes de memória suportados são:

  • ashmem : memória compartilhada do Android. Para mais detalhes, consulte a memória .
  • mmap_fd : Memória compartilhada apoiada por um descritor de arquivo por meio de mmap .
  • hardware_buffer_blob : Memória compartilhada apoiada por um AHardwareBuffer com o formato AHARDWARE_BUFFER_FORMAT_BLOB . Disponível em Neural Networks (NN) HAL 1.2. Para obter mais detalhes, consulte AHardwareBuffer .
  • hardware_buffer : Memória compartilhada apoiada por um 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 obter mais detalhes, consulte AHardwareBuffer .

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 obter mais detalhes, consulte Domínios de memória .

Motoristas NNAPI deve suportar o mapeamento de ashmem e mmap_fd nomes de memória. No NN HAL 1.3, os drivers também devem oferecer suporte ao mapeamento de hardware_buffer_blob . O suporte para hardware_buffer geral de modo não BLOB e domínios de memória é opcional.

AHardwareBuffer

AHardwareBuffer é um tipo de memória compartilhada que envolve um buffer Gralloc . No Android 10, a API Neural Networks (NNAPI) suporta o uso de AHardwareBuffer , permitindo que o driver execute execuções sem copiar dados, o que melhora o desempenho e o consumo de energia dos aplicativos. Por exemplo, uma pilha 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 obter mais informações, consulte ANeuralNetworksMemory_createFromAHardwareBuffer .

Objetos AHardwareBuffer usados ​​em NNAPI são passados ​​para o driver por meio de uma estrutura hidl_memory chamada hardware_buffer ou hardware_buffer_blob . A estrutura hidl_memory hardware_buffer_blob representa apenas objetos AHardwareBuffer com o formato AHARDWAREBUFFER_FORMAT_BLOB .

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

O driver deve decodificar corretamente o campo hidl_handle fornecido e acessar a memória descrita por hidl_handle . Quando o getSupportedOperations_1_2 , getSupportedOperations_1_1 ou getSupportedOperations é chamado, o driver deve detectar se pode decodificar o hidl_handle fornecido e acessar a memória descrita por hidl_handle . A preparação do modelo deve falhar se o campo hidl_handle usado para um operando constante não for compatível. A execução deve falhar se o campo hidl_handle usado para um operando de entrada ou saída da execução não for compatível. É recomendado que o driver retorne um código de erro GENERAL_FAILURE se a preparação ou execução do modelo falhar.

Domínios de memória

Para dispositivos com 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
Figura 1. Fluxo de dados do buffer usando 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 oferecer suporte ao recurso de domínio de memória, implemente IDevice::allocate para permitir que a estrutura solicite a alocação de buffer gerenciada pelo driver. 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 buffer.
  • BufferRole descreve o padrão de uso potencial do buffer como uma entrada ou saída de um modelo preparado. Várias 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 fazer referência ou interagir com o buffer usando o token retornado ou o objeto IBuffer .

O token de IDevice::allocate é fornecido ao fazer referência ao buffer como um dos objetos MemoryPool na estrutura Request 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 driver deve validar se o uso do buffer é uma das funções BufferRole fornecidas durante a alocação e deve falhar a execução imediatamente se o uso for ilegal.

O objeto IBuffer é usado para cópia de 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 oferecer suporte a esses casos de uso, o driver deve implementar IBuffer::copyTo e IBuffer::copyFrom com ashmem , mmap_fd e hardware_buffer_blob se ele suportar alocação de domínio de memória. É opcional que o driver suporte hardware_buffer modo não BLOB.

Durante a alocação do buffer, as dimensões do buffer podem ser deduzidas dos operandos do modelo correspondentes de todas as funções especificadas por BufferRole e das 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 usadas 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.