Neural Networks HAL 1.2 introduce il concetto di esecuzioni in burst. Le esecuzioni in burst sono una sequenza di esecuzioni dello stesso modello preparato che si verificano in rapida successione, ad esempio quelle che operano sui frame di un'acquisizione della videocamera o su campioni audio successivi. Un oggetto burst viene utilizzato per controllare un insieme di esecuzioni in burst e per conservare le risorse tra le esecuzioni, consentendo alle esecuzioni di avere un overhead inferiore. Gli oggetti burst consentono tre ottimizzazioni:
- 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 indica a un driver per quanto tempo deve rimanere in uno stato di prestazioni elevate.
- Un oggetto burst può conservare le risorse tra le esecuzioni. Ad esempio, un driver può mappare un oggetto di 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 eliminato o quando il runtime NNAPI notifica all'oggetto burst che la risorsa non è più necessaria.
- Un oggetto burst utilizza le code di messaggi veloci (FMQ) per comunicare tra i processi dell'app e del driver. Questo può ridurre la latenza perché l'FMQ ignora HIDL e passa i dati direttamente a un altro processo tramite un FIFO circolare atomico in memoria condivisa. Il processo consumer sa di dover rimuovere un elemento dalla coda e iniziare l'elaborazione eseguendo il polling del numero di elementi nel FIFO o attendendo il flag di evento dell'FMQ, segnalato dal producer. Questo flag di evento è un mutex veloce dello spazio utente (futex).
Un FMQ è una struttura di dati di basso livello che non offre garanzie di durata tra i processi e non dispone di un meccanismo integrato per determinare se il processo all'altra estremità dell'FMQ è in esecuzione come previsto. Di conseguenza, se il producer dell'FMQ si arresta, il consumer potrebbe rimanere in attesa di dati che non arrivano mai. Una soluzione a questo problema è che il driver associ gli FMQ all'oggetto burst di livello superiore per rilevare quando l'esecuzione in burst è terminata.
Poiché le esecuzioni in burst operano sugli stessi argomenti e restituiscono gli stessi risultati degli altri percorsi di esecuzione, gli FMQ sottostanti devono passare gli stessi dati ai driver del servizio NNAPI e da questi. Tuttavia, gli FMQ possono trasferire solo tipi di dati semplici. Il trasferimento di dati complessi viene eseguito serializzando e deserializzando i buffer nidificati (tipi di vettori) direttamente negli FMQ e utilizzando gli oggetti di callback HIDL per trasferire gli handle del pool di memoria su richiesta. Il lato producer
dell'FMQ deve inviare i messaggi di richiesta o di risultato al consumer
in modo atomico utilizzando MessageQueue::writeBlocking se la coda è bloccante o
utilizzando MessageQueue::write se la coda non è bloccante.
Interfacce burst
Le interfacce burst per Neural Networks HAL si trovano in
hardware/interfaces/neuralnetworks/1.2/
e sono descritte di seguito. Per ulteriori informazioni sulle interfacce burst nel livello NDK, consulta
frameworks/ml/nn/runtime/include/NeuralNetworks.h.
types.hal
types.hal
definisce il tipo di dati inviati tramite l'FMQ.
FmqRequestDatum: Un singolo elemento di una rappresentazione serializzata di un oggettoRequestdi esecuzione e un valoreMeasureTiming, che viene inviato tramite la coda di messaggi veloci.FmqResultDatum: un singolo elemento di una rappresentazione serializzata dei valori restituiti da un'esecuzione (ErrorStatus,OutputShapeseTiming), che viene restituito tramite la coda di messaggi veloci.
IBurstContext.hal
IBurstContext.hal
definisce l'oggetto dell'interfaccia HIDL che si trova nel servizio Neural Networks.
IBurstContext: oggetto di contesto per gestire le risorse di un burst.
IBurstCallback.hal
IBurstCallback.hal
definisce l'oggetto dell'interfaccia HIDL per un callback creato dal runtime Neural Networks e viene utilizzato dal servizio Neural Networks per recuperare gli oggetti hidl_memory corrispondenti agli identificatori degli slot.
- IBurstCallback: oggetto di callback utilizzato da un servizio per recuperare gli oggetti di memoria.
IPreparedModel.hal
IPreparedModel.hal
viene 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.
Supportare le esecuzioni in burst in un driver
Il modo più semplice per supportare gli oggetti burst in un servizio NNAPI HIDL è utilizzare la
funzione di utilità burst ::android::nn::ExecutionBurstServer::create, che si
trova in
ExecutionBurstServer.h
e viene inclusa nelle libneuralnetworks_common e libneuralnetworks_util
librerie statiche. Questa funzione factory ha due overload:
- Un overload accetta un puntatore a un oggetto
IPreparedModel. Questa funzione di utilità utilizza il metodoexecuteSynchronouslyin un oggettoIPreparedModelper eseguire il modello. - Un overload accetta un oggetto
IBurstExecutorWithCachepersonalizzabile, che può essere utilizzato per memorizzare nella cache le risorse (ad esempio le mappaturehidl_memory) che persistono tra più esecuzioni.
Ogni overload restituisce un oggetto IBurstContext (che rappresenta l'oggetto burst) che contiene e gestisce il proprio thread di listener dedicato. Questo thread riceve le richieste dall'FMQ requestChannel, esegue l'inferenza e poi restituisce i risultati tramite l'FMQ resultChannel. 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, puoi creare la tua implementazione di IBurstContext che sappia come inviare e ricevere messaggi tramite gli FMQ requestChannel e resultChannel passati a IPreparedModel::configureExecutionBurst.
Le funzioni di 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 che si trova nel
driver di esempio di 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();
}