Neural Networks HAL 1.2 introduit le concept d'exécutions en rafale. Les exécutions en rafale sont une séquence d'exécutions du même modèle préparé qui se succèdent rapidement, comme celles opérant sur des images d'une capture de caméra ou sur des échantillons audio successifs. Un objet rafale est utilisé pour contrôler un ensemble d'exécutions en rafale et pour préserver les ressources entre les exécutions, permettant aux exécutions d'avoir une surcharge inférieure. Les objets Burst permettent trois optimisations :
- Un objet burst est créé avant une séquence d'exécutions, et libéré une fois la séquence terminée. Pour cette raison, la durée de vie de l’objet éclaté indique au conducteur combien de temps il doit rester dans un état de haute performance.
- Un objet en rafale peut préserver les ressources entre les exécutions. Par exemple, un pilote peut mapper un objet mémoire lors de la première exécution et mettre en cache le mappage dans l'objet rafale pour une réutilisation lors des exécutions ultérieures. Toute ressource mise en cache peut être libérée lorsque l'objet en rafale est détruit ou lorsque le runtime NNAPI informe l'objet en rafale que la ressource n'est plus nécessaire.
- Un objet en rafale utilise des files d'attente de messages rapides (FMQ) pour communiquer entre les processus d'application et de pilote. Cela peut réduire la latence car le FMQ contourne HIDL et transmet les données directement à un autre processus via une FIFO circulaire atomique dans la mémoire partagée. Le processus consommateur sait retirer un élément de la file d'attente et commencer le traitement soit en interrogeant le nombre d'éléments dans le FIFO, soit en attendant le drapeau d'événement de la FMQ, qui est signalé par le producteur. Cet indicateur d'événement est un mutex rapide de l'espace utilisateur (futex).
Un FMQ est une structure de données de bas niveau qui n'offre aucune garantie de durée de vie entre les processus et ne possède aucun mécanisme intégré pour déterminer si le processus à l'autre extrémité du FMQ s'exécute comme prévu. Par conséquent, si le producteur de la FMQ décède, le consommateur risque de devoir attendre des données qui n'arrivent jamais. Une solution à ce problème consiste pour le pilote à associer les FMQ à l'objet de rafale de niveau supérieur pour détecter la fin de l'exécution de la rafale.
Étant donné que les exécutions en rafale fonctionnent sur les mêmes arguments et renvoient les mêmes résultats que les autres chemins d'exécution, les FMQ sous-jacentes doivent transmettre les mêmes données vers et depuis les pilotes de service NNAPI. Cependant, les FMQ ne peuvent transférer que des types de données simples. Le transfert de données complexes s'effectue en sérialisant et désérialisant les tampons imbriqués (types vectoriels) directement dans les FMQ et en utilisant des objets de rappel HIDL pour transférer les descripteurs de pool de mémoire à la demande. Le côté producteur de la FMQ doit envoyer la demande ou les messages de résultat au consommateur de manière atomique en utilisant MessageQueue::writeBlocking
si la file d'attente est bloquante, ou en utilisant MessageQueue::write
si la file d'attente est non bloquante.
Interfaces en rafale
Les interfaces en rafale pour le HAL des réseaux de neurones se trouvent dans hardware/interfaces/neuralnetworks/1.2/
et sont décrites ci-dessous. Pour plus d'informations sur les interfaces en rafale dans la couche NDK, consultez frameworks/ml/nn/runtime/include/NeuralNetworks.h
.
types.hal
types.hal
définit le type de données envoyées via la FMQ.
-
FmqRequestDatum
: élément unique d'une représentation sérialisée d'un objetRequest
d'exécution et d'une valeurMeasureTiming
, qui est envoyé via la file d'attente de messages rapide. -
FmqResultDatum
: élément unique d'une représentation sérialisée des valeurs renvoyées par une exécution (ErrorStatus
,OutputShapes
etTiming
), qui est renvoyé via la file d'attente de messages rapide.
IBurstContext.hal
IBurstContext.hal
définit l'objet d'interface HIDL qui réside dans le service Neural Networks.
-
IBurstContext
: Objet de contexte pour gérer les ressources d'un burst.
IBurstCallback.hal
IBurstCallback.hal
définit l'objet d'interface HIDL pour un rappel créé par le runtime Neural Networks et est utilisé par le service Neural Networks pour récupérer les objets hidl_memory
correspondant aux identifiants d'emplacement.
- IBurstCallback : Objet de rappel utilisé par un service pour récupérer des objets mémoire.
IPreparedModel.hal
IPreparedModel.hal
est étendu dans HAL 1.2 avec une méthode permettant de créer un objet IBurstContext
à partir d'un modèle préparé.
-
configureExecutionBurst
: configure un objet burst utilisé pour exécuter plusieurs inférences sur un modèle préparé en succession rapide.
Prise en charge des exécutions en rafale dans un pilote
Le moyen le plus simple de prendre en charge les objets burst dans un service HIDL NNAPI consiste à utiliser la fonction utilitaire burst ::android::nn::ExecutionBurstServer::create
, qui se trouve dans ExecutionBurstServer.h
et packagée dans les bibliothèques statiques libneuralnetworks_common
et libneuralnetworks_util
. Cette fonction d'usine présente deux surcharges :
- Une surcharge accepte un pointeur vers un objet
IPreparedModel
. Cette fonction utilitaire utilise la méthodeexecuteSynchronously
dans un objetIPreparedModel
pour exécuter le modèle. - Une surcharge accepte un objet
IBurstExecutorWithCache
personnalisable, qui peut être utilisé pour mettre en cache des ressources (telles que les mappageshidl_memory
) qui persistent sur plusieurs exécutions.
Chaque surcharge renvoie un objet IBurstContext
(qui représente l'objet burst) qui contient et gère son propre thread d'écoute dédié. Ce thread reçoit les requêtes du requestChannel
FMQ, effectue l'inférence, puis renvoie les résultats via le resultChannel
FMQ. Ce thread et toutes les autres ressources contenues dans l'objet IBurstContext
sont automatiquement libérés lorsque le client de la rafale perd sa référence à IBurstContext
.
Vous pouvez également créer votre propre implémentation de IBurstContext
qui comprend comment envoyer et recevoir des messages via les FMQ requestChannel
et resultChannel
transmises à IPreparedModel::configureExecutionBurst
.
Les fonctions de l'utilitaire Burst se trouvent dans 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);
Ce qui suit est une implémentation de référence d'une interface burst trouvée dans l'exemple de pilote Neural Networks à 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();
}