Armazenamento em cache da compilação

No Android 10, a API Neural Networks (NNAPI). fornece funções para auxiliar armazenamento em cache de artefatos de compilação, o que reduz o tempo usado para compilação quando um app é iniciado. Com essa funcionalidade de armazenamento em cache, o driver não necessárias para gerenciar ou limpar os arquivos armazenados em cache. Esse é um recurso opcional que podem ser implementadas com a NN HAL 1.2. Para mais informações sobre essa função, ver ANeuralNetworksCompilation_setCaching

O driver também pode implementar o armazenamento em cache de compilação independente da NNAPI. Isso podem ser implementados independentemente de os recursos de armazenamento em cache da NNAPI e da HAL serem usados ou não. O AOSP oferece uma biblioteca de utilitários de baixo nível (um mecanismo de armazenamento em cache). Para mais informações, consulte Como implementar um mecanismo de armazenamento em cache.

Visão geral do fluxo de trabalho

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

Informações de cache fornecidas e ocorrência em cache

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

Informações de cache fornecidas e ausência no cache

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

Informações de cache não fornecidas

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

Armazenar informações em cache

As informações de armazenamento em cache fornecidas a um driver consistem em um token e gerenciadores de arquivos em cache.

Token

A token é um token de armazenamento em cache Constant::BYTE_SIZE_OF_CACHE_TOKEN que identifica o modelo preparado. O mesmo token é fornecido ao salvar o armazenar arquivos em cache com prepareModel_1_2 e recuperar o modelo preparado com prepareModelFromCache. O cliente do driver deve escolher um token com um e baixa taxa de colisão. O driver não detecta uma colisão de tokens. Uma colisão resulta em uma execução com falha ou bem-sucedida que produz valores de saída incorretos.

Processamentos de arquivos 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 dados constantes em cache, incluindo pré-processados e buffers de tensor transformados. Uma modificação no cache de dados não deve resultar em qualquer efeito pior do que gerar valores de saída ruins na execução tempo de resposta.
  • Cache de modelo: use para armazenar em cache dados confidenciais de segurança, como compilados. código de máquina executável no formato binário nativo do dispositivo. Um modificações no cache do modelo podem afetar a execução do driver e um cliente malicioso poderia usá-lo para executar além a permissão concedida. Assim, o driver precisa verificar se o cache do modelo está corrompida antes de preparar o modelo a partir do cache. Para mais informações, consulte Segurança.

O driver precisa decidir como as informações de cache são distribuídas entre os dois de arquivos de cache e informa quantos arquivos de cache são necessários para cada tipo com getNumberOfCacheFilesNeeded

O tempo de execução da NNAPI sempre abre gerenciadores de arquivos de cache com leitura e gravação permissão.

Segurança

No armazenamento em cache da compilação, o cache do modelo pode conter dados sensíveis à segurança, como como código de máquina executável compilado no formato binário nativo do dispositivo. Caso contrário corretamente protegidos, uma modificação no cache do modelo pode afetar comportamento de execução. Como o conteúdo do cache é armazenado no app os arquivos de cache são modificáveis pelo cliente. Um cliente com bugs pode acidentalmente o cache e um cliente malicioso poderia Ele pode ser usado para executar um código não verificado no dispositivo. Dependendo do dispositivo, isso pode ser um problema de segurança. Assim, o o motorista precisa conseguir detectar potencial corrupção do cache do modelo antes de prepará-lo a partir do cache.

Uma forma de fazer isso é manter um mapa do token para um hash criptográfico do cache do modelo. O driver pode armazenar o token e a do cache do modelo ao salvar a compilação no cache. O motorista verifica o novo hash do cache do modelo com o token registrado e o par de hash quando recuperar a compilação do cache. Esse mapeamento precisa ser persistente o sistema é reinicializado. O motorista pode usar Serviço de armazenamento de chaves do Android, a biblioteca de utilitários do framework/ml/nn/driver/cache, ou qualquer outro mecanismo adequado para implementar um gerenciador de mapeamento. Depois do motorista atualização, este gerenciador de mapeamento deve ser reinicializado para impedir a preparação do cache arquivos de uma versão anterior.

Para evitar entre o horário da verificação e o horário de uso (TOCTOU), o driver precisa calcular o hash gravado antes de salvar no e calcular o novo hash depois de copiar o conteúdo do arquivo para um sistema tempo extra.

Este exemplo de código 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 determinados casos de uso avançados, um driver exige acesso ao conteúdo do cache (leitura ou gravação) após a chamada de compilação. Estes são alguns exemplos de casos de uso:

  • Compilação just-in-time:a compilação é adiada até que o a primeira execução.
  • Compilação em vários estágios:uma compilação rápida é realizada inicialmente. e uma compilação otimizada opcional é executada posteriormente de acordo com a frequência de uso.

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

  • Duplica os identificadores de arquivo durante a invocação de prepareModel_1_2 ou prepareModelFromCache e lê/atualiza o cache conteúdo mais tarde.
  • implementa a lógica de bloqueio de arquivos fora da chamada de compilação comum; para evitar que uma gravação ocorra simultaneamente a uma leitura ou a outra gravação.

Implementar um mecanismo de armazenamento em cache

Além da interface de armazenamento em cache da compilação NN HAL 1.2, também é possível encontrar uma biblioteca de utilitários de armazenamento em cache frameworks/ml/nn/driver/cache diretório. A nnCache o subdiretório contém um código de armazenamento permanente para o driver implementar armazenamento em cache de compilação sem usar os recursos de armazenamento em cache da NNAPI. Essa forma de o armazenamento em cache da compilação pode ser implementado com qualquer versão da NN HAL. Se o opta por implementar o armazenamento em cache desconectado da interface HAL, o motorista é responsável por liberar artefatos armazenados em cache quando eles não forem mais necessários.