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

Cache de compilação

A partir do Android 10, a Neural Networks API (NNAPI) fornece funções para dar suporte ao armazenamento em cache de artefatos de compilação, o que reduz o tempo usado para compilação quando um aplicativo é iniciado. Usando esta funcionalidade de cache, o driver não precisa gerenciar ou limpar os arquivos em cache. Este é um recurso opcional que pode ser implementado com o NN HAL 1.2. Para mais informações sobre esta função, consulte ANeuralNetworksCompilation_setCaching .

O driver também pode implementar o cache de compilação independente do NNAPI. Isso pode ser implementado se os recursos de cache NNAPI NDK e HAL forem usados ​​ou não. O AOSP fornece uma biblioteca de utilitários de baixo nível (um mecanismo de cache). Para mais informações, consulte Implementar um mecanismo de cache .

Visão geral do fluxo de trabalho

Esta seção descreve fluxos de trabalho gerais com o recurso de cache de compilação implementado.

Informações de cache fornecidas e acertos de cache

  1. O aplicativo passa um diretório de cache e uma soma de verificação exclusiva para o modelo.
  2. O tempo de execução NNAPI procura os arquivos de cache com base na soma de verificação, a preferência de execução e o resultado do particionamento e encontra os arquivos.
  3. O NNAPI abre os arquivos de cache e passa os identificadores para o motorista com prepareModelFromCache .
  4. O driver prepara o modelo diretamente dos arquivos de cache e retorna o modelo preparado.

Informações de cache fornecidas e perda de cache

  1. O aplicativo passa uma soma de verificação exclusiva para o modelo e um diretório de armazenamento em cache.
  2. O tempo de execução NNAPI procura os arquivos de cache com base na soma de verificação, a preferência de execução e o resultado do particionamento e não encontra os arquivos de cache.
  3. O NNAPI cria arquivos de cache vazias com base na soma de verificação, a preferência de execução, e o particionamento, abre os arquivos de cache, e passa as alças e o modelo para o motorista com prepareModel_1_2 .
  4. O driver compila o modelo, grava as informações de cache nos arquivos de cache e retorna o modelo preparado.

Informações de cache não fornecidas

  1. O aplicativo invoca a compilação sem fornecer nenhuma informação de cache.
  2. O aplicativo não passa nada relacionado ao armazenamento em cache.
  3. O tempo de execução NNAPI passa o modelo para o motorista com prepareModel_1_2 .
  4. O driver compila o modelo e retorna o modelo preparado.

Informação de cache

As informações de cache fornecidas a um driver consistem em um token e identificadores de arquivo de cache.

Símbolo

O sinal é um sinal de cache comprimento Constant::BYTE_SIZE_OF_CACHE_TOKEN que identifica o modelo preparado. Da mesma forma é fornecido ao salvar os arquivos de cache com prepareModel_1_2 e recuperar o modelo preparado com prepareModelFromCache . O cliente do driver deve escolher um token com baixa taxa de colisão. O driver não consegue detectar uma colisão de token. Uma colisão resulta em uma execução com falha ou em uma execução bem-sucedida que produz valores de saída incorretos.

Identificadores de arquivo de cache (dois tipos de arquivos de cache)

Os dois tipos de arquivos de cache são cache de dados e cache de modelo.

  • Cache de dados: Use para armazenar em cache os dados constantes incluindo buffers tensor pré-processados e transformados. Uma modificação no cache de dados não deve resultar em nenhum efeito pior do que gerar valores de saída ruins em tempo de execução.
  • Cache de modelo: Use para o cache de dados sensíveis à segurança, como código de máquina executável compilado em formato binário nativo do dispositivo. Uma modificação no cache do modelo pode afetar o comportamento de execução do driver, e um cliente malicioso pode fazer uso disso para executar além da permissão concedida. Assim, o driver deve verificar se o cache do modelo está corrompido antes de preparar o modelo a partir do cache. Para mais informações, consulte Segurança .

O motorista deve decidir como informações de cache é distribuído entre os dois tipos de arquivos de cache, e reportar quantos arquivos de cache que precisa para cada tipo com getNumberOfCacheFilesNeeded .

O tempo de execução NNAPI sempre abre identificadores de arquivo de cache com permissão de leitura e gravação.

Segurança

No cache de compilação, o cache do modelo pode conter dados sensíveis à segurança, como código de máquina executável compilado no formato binário nativo do dispositivo. Se não estiver devidamente protegido, uma modificação no cache do modelo pode afetar o comportamento de execução do driver. Como o conteúdo do cache é armazenado no diretório do aplicativo, os arquivos de cache podem ser modificados pelo cliente. Um cliente com erros pode corromper acidentalmente o cache e um cliente mal-intencionado pode fazer uso disso intencionalmente para executar código não verificado no dispositivo. Dependendo das características do dispositivo, isso pode ser um problema de segurança. Portanto, o driver deve ser capaz de detectar uma possível corrupção do cache do modelo antes de preparar o modelo a partir do cache.

Uma maneira de fazer isso é o driver manter um mapa do token para um hash criptográfico do cache do modelo. O driver pode armazenar o token e o hash de seu cache de modelo ao salvar a compilação no cache. O driver verifica o novo hash do cache do modelo com o token registrado e o par de hash ao recuperar a compilação do cache. Esse mapeamento deve ser persistente nas reinicializações do sistema. O motorista pode usar o serviço Android armazenamento de chaves , a biblioteca de utilitários no framework/ml/nn/driver/cache , ou qualquer outro mecanismo adequado para implementar um gestor de mapeamento. Após a atualização do driver, este gerenciador de mapeamento deve ser reinicializado para evitar a preparação de arquivos de cache de uma versão anterior.

Para evitar time-of-seleção para o tempo de uso ataques (TOCTOU), o condutor deve calcular o hash registrado antes de salvar para arquivo e calcular o novo hash depois de copiar o conteúdo do arquivo para um buffer interno.

Este código de exemplo demonstra como implementar essa lógica.

bool saveToCache(const sp<V1_2::IPreparedModel> preparedModel,
                 const hidl_vec<hidl_handle>& modelFds, const hidl_vec<hidl_handle>& dataFds,
                 const HidlToken& token) {
    // Serialize the prepared model to internal buffers.
    auto buffers = serialize(preparedModel);

    // This implementation detail is important: the cache hash must be computed from internal
    // buffers instead of cache files to prevent time-of-check to time-of-use (TOCTOU) attacks.
    auto hash = computeHash(buffers);

    // Store the {token, hash} pair to a mapping manager that is persistent across reboots.
    CacheManager::get()->store(token, hash);

    // Write the cache contents from internal buffers to cache files.
    return writeToFds(buffers, modelFds, dataFds);
}

sp<V1_2::IPreparedModel> prepareFromCache(const hidl_vec<hidl_handle>& modelFds,
                                          const hidl_vec<hidl_handle>& dataFds,
                                          const HidlToken& token) {
    // Copy the cache contents from cache files to internal buffers.
    auto buffers = readFromFds(modelFds, dataFds);

    // This implementation detail is important: the cache hash must be computed from internal
    // buffers instead of cache files to prevent time-of-check to time-of-use (TOCTOU) attacks.
    auto hash = computeHash(buffers);

    // Validate the {token, hash} pair by a mapping manager that is persistent across reboots.
    if (CacheManager::get()->validate(token, hash)) {
        // Retrieve the prepared model from internal buffers.
        return deserialize<V1_2::IPreparedModel>(buffers);
    } else {
        return nullptr;
    }
}

Casos de uso avançados

Em certos casos de uso avançado, um driver requer acesso ao conteúdo do cache (leitura ou gravação) após a chamada de compilação. Os exemplos de casos de uso incluem:

  • Just-in-time de compilação: A compilação é adiada para a primeira execução.
  • Compilação de vários estágios: Uma compilação rápido é realizado inicialmente e uma compilação otimizada opcional é realizada em um momento posterior, dependendo da frequência de utilização.

Para acessar o conteúdo do cache (leitura ou gravação) após a chamada de compilação, certifique-se de que o driver:

  • Duplica os identificadores de arquivo durante a invocação de prepareModel_1_2 ou prepareModelFromCache e lê / atualiza o conteúdo do cache em um momento posterior.
  • Implementa a lógica de bloqueio de arquivo fora da chamada de compilação comum para evitar que uma gravação ocorra simultaneamente com uma leitura ou outra gravação.

Implementando um mecanismo de cache

Além da interface de caching 1.2 compilação NN HAL, você também pode encontrar uma biblioteca de utilitário de cache na frameworks/ml/nn/driver/cache diretório. O nnCache subdiretório contém código de armazenamento persistente para o motorista para implementar cache compilação sem usar as características NNAPI cache. Esta forma de cache de compilação pode ser implementada com qualquer versão do NN HAL. Se o driver optar por implementar o cache desconectado da interface HAL, o driver será responsável por liberar os artefatos armazenados em cache quando não forem mais necessários.