Burst-Ausführungen und schnelle Nachrichtenwarteschlangen

In Neural Networks HAL 1.2 wird das Konzept der Burst-Ausführungen eingeführt. Burst-Ausführungen sind eine Abfolge von Ausführungen desselben vorbereiteten Modells, die in schneller Folge erfolgen, z. B. bei der Verarbeitung von Frames einer Kameraaufnahme oder aufeinanderfolgenden Audiosamples. Ein Burst-Objekt wird verwendet, um eine Reihe von Burst-Ausführungen zu steuern Ressourcen zwischen den Ausführungen aufrechtzuerhalten, sodass Ausführungen koordiniert. Mit Burst-Objekten sind drei Optimierungen möglich:

  1. Ein Burst-Objekt wird vor einer Abfolge von Ausführungen erstellt und freigegeben. wenn die Sequenz beendet ist. Daher gibt die Lebensdauer des Burst-Objekts einem Treiber einen Hinweis darauf, wie lange er sich in einem Hochleistungsstatus befinden sollte.
  2. Ein Burst-Objekt kann Ressourcen zwischen Ausführungen beibehalten. Ein Treiber kann beispielsweise ein Arbeitsspeicherobjekt bei der ersten Ausführung zuordnen und die Zuordnung im Burst-Objekt für die Wiederverwendung bei nachfolgenden Ausführungen im Cache speichern. Alle im Cache gespeicherten Ressourcen können freigegeben werden, wenn das Burst-Objekt zerstört wird oder die NNAPI-Laufzeit das Burst-Objekt darüber informiert, dass die Ressource nicht mehr benötigt wird.
  3. Ein Burst-Objekt verwendet schnelle Nachrichtenwarteschlangen (FMQs) für die Kommunikation zwischen Anwendungs- und Treiberprozessen. Dies kann da FMQ HIDL umgeht und Daten direkt an durch einen atomaren kreisförmigen FIFO im gemeinsamen Speicher weiter. Die Verbraucherprozess weiß, dass ein Artikel aus der Warteschlange entfernt und die Verarbeitung entweder durch Abfragen der Anzahl von Elementen im FIFO oder Warten auf das FMQ-Ereignis , das vom Ersteller signalisiert wird. Dieses Ereignisflag ist ein schneller Mutex im Userspace (futex).

Ein FMQ ist eine Low-Level-Datenstruktur, die keine lebenslangen Garantien für und verfügt über keinen integrierten Mechanismus, um festzustellen, ob der Prozess anderen Ende des FMQ wie erwartet ausgeführt wird. Wenn also der Produzent wenn der FMQ abbricht, könnte der Nutzer auf Daten warten, die nie ankommen. Eins Die Lösung für dieses Problem besteht darin, dass der Treiber FMQs höherem Burst-Objekt, um zu erkennen, wann die Burst-Ausführung beendet ist.

Da Burst-Ausführungen mit denselben Argumenten arbeiten und dieselben Ergebnisse wie andere Ausführungspfade zurückgeben, müssen die zugrunde liegenden FMQs dieselben Daten an die NNAPI-Diensttreiber und von diesen weitergeben. FMQs können jedoch nur altbekannte Datentypen. Die Übertragung komplexer Daten erfolgt durch Serialisierung und Deserialisieren verschachtelter Puffer (Vektortypen) direkt in den FMQs und verwenden HIDL-Callback-Objekte zum Übertragen von Arbeitsspeicherpool-Handles bei Bedarf. Die Produzentenseite der FMQ muss die Anfrage- oder Ergebnisnachrichten atomically an den Verbraucher senden. Verwenden Sie dazu MessageQueue::writeBlocking, wenn die Warteschlange blockiert, oder MessageQueue::write, wenn die Warteschlange nicht blockiert.

Burst-Schnittstellen

Die Burst-Schnittstellen für die HAL für neuronale Netze finden Sie unter hardware/interfaces/neuralnetworks/1.2/ und werden unten beschrieben. Weitere Informationen zu Burst-Schnittstellen in der NDK-Ebene finden Sie unter frameworks/ml/nn/runtime/include/NeuralNetworks.h.

Typen.hal

types.hal definiert den Datentyp, der über die FMQ gesendet wird.

  • FmqRequestDatum: Ein einzelnes Element einer serialisierten Darstellung einer Ausführung Request und ein MeasureTiming-Wert, der über die schnelle Nachricht gesendet wird, in die Warteschlange stellen.
  • FmqResultDatum: Ein einzelnes Element einer serialisierten Darstellung der von eine Ausführung (ErrorStatus, OutputShapes und Timing), die die von der Warteschlange für schnelle Nachrichten zurückgegeben werden.

IBurstContext.hal

IBurstContext.hal definiert das HIDL-Schnittstellenobjekt im Dienst für neuronale Netzwerke.

  • IBurstContext: Kontextobjekt zum Verwalten der Ressourcen eines Bursts.

IBurstCallback.hal

IBurstCallback.hal definiert das HIDL-Schnittstellenobjekt für einen Callback, der von der Neural Networks-Laufzeit erstellt wird. Der Neural Networks-Dienst verwendet es, um hidl_memory-Objekte abzurufen, die den Steckplatz-IDs entsprechen.

  • IBurstCallback: Callback-Objekt, das von einem Dienst zum Abrufen von Speicherobjekten verwendet wird.

iPreparedModel.hal

IPreparedModel.hal wird in HAL 1.2 um eine Methode zum Erstellen eines IBurstContext-Objekts aus einem vorbereiteten Modell verwenden.

  • configureExecutionBurst: Konfiguriert ein Burst-Objekt, mit dem mehrere Inferenzen in schneller Folge auf einem vorbereiteten Modell ausgeführt werden.

Unterstützung für Burst-Ausführungen in einem Treiber

Die einfachste Möglichkeit, Burst-Objekte in einem HIDL NNAPI-Dienst zu unterstützen, ist die Verwendung der Burst-Hilfsfunktion ::android::nn::ExecutionBurstServer::create, die sich in ExecutionBurstServer.h befindet und in den statischen Bibliotheken libneuralnetworks_common und libneuralnetworks_util verpackt ist. Diese Factory-Funktion hat zwei Überlastungen:

  • Eine Überladung akzeptiert einen Zeiger auf ein IPreparedModel-Objekt. Dieses die Methode executeSynchronously in einer IPreparedModel-Objekt, um das Modell auszuführen.
  • Eine Überladung akzeptiert ein anpassbares IBurstExecutorWithCache-Objekt, mit dem Ressourcen (z. B. hidl_memory-Zuordnungen) im Cache gespeichert werden können, die über mehrere Ausführungen hinweg bestehen bleiben.

Jede Überladung gibt ein IBurstContext-Objekt zurück (das das Burst-Objekt darstellt), das einen eigenen Listener-Thread enthält und verwaltet. Dieser Thread empfängt Anfragen von der requestChannel-FMQ, führt die Inferenz durch und gibt die Ergebnisse dann über die resultChannel-FMQ zurück. Dieser Thread und alle anderen Ressourcen im IBurstContext-Objekt werden automatisch freigegeben, wenn der Client des Bursts seine Referenz auf IBurstContext verliert.

Alternativ können Sie eine eigene Implementierung von IBurstContext erstellen, die weiß, wie Nachrichten über die requestChannel- und resultChannel-FMQs gesendet und empfangen werden, die an IPreparedModel::configureExecutionBurst übergeben werden.

Die Burst-Hilfsfunktionen finden Sie unter 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);

Im Folgenden finden Sie eine Referenzimplementierung einer Burst-Schnittstelle aus dem Beispieltreiber für neuronale Netze unter 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();
}