Esecuzioni burst e code di messaggi veloci

Reti neurali HAL 1.2 introduce il concetto di esecuzioni burst. Le esecuzioni a raffica sono una sequenza di esecuzioni dello stesso modello preparato che si verificano in rapida successione, come quelle che operano sui fotogrammi di una cattura della fotocamera o su campioni audio successivi. Un oggetto burst viene utilizzato per controllare una serie di esecuzioni burst e per preservare le risorse tra le esecuzioni, consentendo alle esecuzioni di avere un sovraccarico inferiore. Gli oggetti burst consentono tre ottimizzazioni:

  1. Un oggetto burst viene creato prima di una sequenza di esecuzioni e liberato al termine della sequenza. Per questo motivo, la durata dell'oggetto burst suggerisce al conducente quanto tempo dovrebbe rimanere in uno stato ad alte prestazioni.
  2. Un oggetto burst può preservare le risorse tra le esecuzioni. Ad esempio, un driver può mappare un oggetto memoria alla prima esecuzione e memorizzare nella cache la mappatura nell'oggetto burst per riutilizzarla nelle esecuzioni successive. Qualsiasi risorsa memorizzata nella cache può essere rilasciata quando l'oggetto burst viene distrutto o quando il runtime NNAPI notifica all'oggetto burst che la risorsa non è più necessaria.
  3. Un oggetto burst utilizza code di messaggi veloci (FMQ) per comunicare tra i processi dell'app e del driver. Ciò può ridurre la latenza perché FMQ bypassa HIDL e passa i dati direttamente a un altro processo attraverso un FIFO circolare atomico nella memoria condivisa. Il processo del consumatore sa come rimuovere dalla coda un articolo e iniziare l'elaborazione interrogando il numero di elementi nel FIFO o attendendo il flag di evento FMQ, che viene segnalato dal produttore. Questo flag di evento è un mutex veloce dello spazio utente (futex).

Una FMQ è una struttura dati di basso livello che non offre garanzie a vita tra i processi e non dispone di un meccanismo integrato per determinare se il processo all'altra estremità della FMQ funziona come previsto. Di conseguenza, se il produttore della FMQ muore, il consumatore potrebbe rimanere bloccato in attesa di dati che non arriveranno mai. Una soluzione a questo problema è che il driver associ gli FMQ all'oggetto burst di livello superiore per rilevare quando l'esecuzione del burst è terminata.

Poiché le esecuzioni burst operano sugli stessi argomenti e restituiscono gli stessi risultati di altri percorsi di esecuzione, gli FMQ sottostanti devono passare gli stessi dati da e verso i driver del servizio NNAPI. Tuttavia, gli FMQ possono trasferire solo tipi di dati semplici. Il trasferimento di dati complessi viene eseguito serializzando e deserializzando buffer nidificati (tipi di vettore) direttamente negli FMQ e utilizzando oggetti di callback HIDL per trasferire gli handle del pool di memoria su richiesta. Il lato produttore dell'FMQ deve inviare i messaggi di richiesta o di risultato al consumatore in modo atomico utilizzando MessageQueue::writeBlocking se la coda è bloccante oppure utilizzando MessageQueue::write se la coda non è bloccante.

Interfacce a raffica

Le interfacce burst per l'HAL delle reti neurali si trovano in hardware/interfaces/neuralnetworks/1.2/ e sono descritte di seguito. Per ulteriori informazioni sulle interfacce burst nel livello NDK, vedere frameworks/ml/nn/runtime/include/NeuralNetworks.h .

tipi.hal

types.hal definisce il tipo di dati inviati attraverso FMQ.

  • FmqRequestDatum : un singolo elemento di una rappresentazione serializzata di un oggetto Request di esecuzione e un valore MeasureTiming , che viene inviato attraverso la coda di messaggi veloce.
  • FmqResultDatum : un singolo elemento di una rappresentazione serializzata dei valori restituiti da un'esecuzione ( ErrorStatus , OutputShapes e Timing ), che viene restituito tramite la coda di messaggi veloce.

IBurstContext.hal

IBurstContext.hal definisce l'oggetto interfaccia HIDL che risiede nel servizio Reti neurali.

  • IBurstContext : oggetto contesto per gestire le risorse di un burst.

IBurstCallback.hal

IBurstCallback.hal definisce l'oggetto interfaccia HIDL per un callback creato dal runtime Neural Networks e viene utilizzato dal servizio Neural Networks per recuperare oggetti hidl_memory corrispondenti agli identificatori di slot.

  • IBurstCallback : oggetto callback utilizzato da un servizio per recuperare oggetti di memoria.

IPreparedModel.hal

IPreparedModel.hal è esteso in HAL 1.2 con un metodo per creare un oggetto IBurstContext da un modello preparato.

  • configureExecutionBurst : configura un oggetto burst utilizzato per eseguire più inferenze su un modello preparato in rapida successione.

Supporta esecuzioni burst in un driver

Il modo più semplice per supportare oggetti burst in un servizio HIDL NNAPI è utilizzare la funzione di utilità burst ::android::nn::ExecutionBurstServer::create , che si trova in ExecutionBurstServer.h e inclusa nelle librerie statiche libneuralnetworks_common e libneuralnetworks_util . Questa funzione di fabbrica ha due sovraccarichi:

  • Un sovraccarico accetta un puntatore a un oggetto IPreparedModel . Questa funzione di utilità utilizza il metodo executeSynchronously in un oggetto IPreparedModel per eseguire il modello.
  • Un sovraccarico accetta un oggetto IBurstExecutorWithCache personalizzabile, che può essere utilizzato per memorizzare nella cache le risorse (come i mapping hidl_memory ) che persistono tra più esecuzioni.

Ogni sovraccarico restituisce un oggetto IBurstContext (che rappresenta l'oggetto burst) che contiene e gestisce il proprio thread di ascolto dedicato. Questo thread riceve le richieste da requestChannel FMQ, esegue l'inferenza, quindi restituisce i risultati tramite resultChannel FMQ. Questo thread e tutte le altre risorse contenute nell'oggetto IBurstContext vengono rilasciate automaticamente quando il client del burst perde il riferimento a IBurstContext .

In alternativa, è possibile creare la propria implementazione di IBurstContext che comprenda come inviare e ricevere messaggi sugli FMQ requestChannel e resultChannel passati a IPreparedModel::configureExecutionBurst .

Le funzioni dell'utilità burst si trovano in 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);

Di seguito è riportata un'implementazione di riferimento di un'interfaccia burst trovata nel driver di esempio Neural Networks in 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();
}