Burst-Ausführungen und schnelle Nachrichtenwarteschlangen

Neural Networks HAL 1.2 führt das Konzept der Burst-Ausführungen ein. Bei Burst-Ausführungen handelt es sich um eine Folge von Ausführungen desselben vorbereiteten Modells, die in schneller Folge erfolgen, beispielsweise solche, die auf Bildern einer Kameraaufnahme oder aufeinanderfolgenden Audio-Samples basieren. Ein Burst-Objekt wird verwendet, um eine Reihe von Burst-Ausführungen zu steuern und Ressourcen zwischen den Ausführungen zu schonen, sodass die Ausführungen einen geringeren Overhead haben. Burst-Objekte ermöglichen drei Optimierungen:

  1. Ein Burst-Objekt wird vor einer Ausführungssequenz erstellt und freigegeben, wenn die Sequenz beendet ist. Aus diesem Grund gibt die Lebensdauer des Burst-Objekts einem Treiber einen Hinweis darauf, wie lange es in einem Hochleistungszustand bleiben sollte.
  2. Ein Burst-Objekt kann Ressourcen zwischen Ausführungen bewahren. Beispielsweise kann ein Treiber ein Speicherobjekt bei der ersten Ausführung zuordnen und die Zuordnung im Burst-Objekt zwischenspeichern, um sie bei nachfolgenden Ausführungen wiederzuverwenden. Jede zwischengespeicherte Ressource kann freigegeben werden, wenn das Burst-Objekt zerstört wird oder wenn die NNAPI-Laufzeit das Burst-Objekt darüber benachrichtigt, dass die Ressource nicht mehr benötigt wird.
  3. Ein Burst-Objekt verwendet schnelle Nachrichtenwarteschlangen (FMQs), um zwischen App- und Treiberprozessen zu kommunizieren. Dies kann die Latenz reduzieren, da der FMQ HIDL umgeht und Daten über einen atomaren zirkulären FIFO im gemeinsam genutzten Speicher direkt an einen anderen Prozess weiterleitet. Der Verbraucherprozess kann ein Element aus der Warteschlange entfernen und mit der Verarbeitung beginnen, indem er entweder die Anzahl der Elemente im FIFO abfragt oder auf das Ereignisflag der FMQ wartet, das vom Produzenten signalisiert wird. Dieses Ereignisflag ist ein schneller Userspace-Mutex (Futex).

Ein FMQ ist eine Datenstruktur auf niedriger Ebene, die keine lebenslangen Garantien für alle Prozesse bietet und über keinen integrierten Mechanismus verfügt, um festzustellen, ob der Prozess am anderen Ende des FMQ wie erwartet ausgeführt wird. Wenn also der Produzent der FMQ ausfällt, muss der Konsument möglicherweise auf Daten warten, die nie eintreffen. Eine Lösung für dieses Problem besteht darin, dass der Treiber FMQs mit dem Burst-Objekt höherer Ebene verknüpft, um zu erkennen, wann die Burst-Ausführung beendet wurde.

Da Burst-Ausführungen mit denselben Argumenten arbeiten und dieselben Ergebnisse zurückgeben wie andere Ausführungspfade, müssen die zugrunde liegenden FMQs dieselben Daten an und von den NNAPI-Diensttreibern übergeben. Allerdings können FMQs nur Plain-Old-Data-Typen übertragen. Die Übertragung komplexer Daten erfolgt durch die Serialisierung und Deserialisierung verschachtelter Puffer (Vektortypen) direkt in den FMQs und die Verwendung von HIDL-Callback-Objekten zur bedarfsgesteuerten Übertragung von Speicherpool-Handles. Die Produzentenseite des FMQ muss die Anforderungs- oder Ergebnisnachrichten atomar an den Verbraucher senden, indem sie MessageQueue::writeBlocking verwendet, wenn die Warteschlange blockiert, oder indem sie MessageQueue::write verwendet, wenn die Warteschlange nicht blockiert.

Burst-Schnittstellen

Die Burst-Schnittstellen für das Neural Networks HAL 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 eines Request und eines MeasureTiming Werts, der über die schnelle Nachrichtenwarteschlange gesendet wird.
  • FmqResultDatum : Ein einzelnes Element einer serialisierten Darstellung der von einer Ausführung zurückgegebenen Werte ( ErrorStatus , OutputShapes und Timing ), das über die schnelle Nachrichtenwarteschlange zurückgegeben wird.

IBurstContext.hal

IBurstContext.hal definiert das HIDL-Schnittstellenobjekt, das im Neural Networks-Dienst vorhanden ist.

  • IBurstContext : Kontextobjekt zur Verwaltung der Ressourcen eines Bursts.

IBurstCallback.hal

IBurstCallback.hal definiert das HIDL-Schnittstellenobjekt für einen von der Neural Networks-Laufzeit erstellten Rückruf und wird vom Neural Networks-Dienst verwendet, um hidl_memory -Objekte abzurufen, die Slot-IDs entsprechen.

  • IBurstCallback : Rückrufobjekt, 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 erweitert.

  • configureExecutionBurst : Konfiguriert ein Burst-Objekt, das verwendet wird, um mehrere Rückschlüsse auf ein vorbereitetes Modell in schneller Folge auszuführen.

Unterstützt 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-Dienstprogrammfunktion ::android::nn::ExecutionBurstServer::create , die in ExecutionBurstServer.h zu finden und in den statischen Bibliotheken libneuralnetworks_common und libneuralnetworks_util gepackt ist. Diese Factory-Funktion verfügt über zwei Überladungen:

  • Eine Überladung akzeptiert einen Zeiger auf ein IPreparedModel Objekt. Diese Dienstprogrammfunktion verwendet die Methode executeSynchronously in einem IPreparedModel Objekt, um das Modell auszuführen.
  • Eine Überladung akzeptiert ein anpassbares IBurstExecutorWithCache -Objekt, das zum Zwischenspeichern von Ressourcen (z. B. hidl_memory Zuordnungen) verwendet werden kann, die über mehrere Ausführungen hinweg bestehen bleiben.

Jede Überladung gibt ein IBurstContext Objekt zurück (das das Burst-Objekt darstellt), das seinen eigenen dedizierten Listener-Thread enthält und verwaltet. Dieser Thread empfängt Anforderungen vom requestChannel FMQ, führt die Inferenz durch und gibt die Ergebnisse dann über den resultChannel FMQ zurück. Dieser Thread und alle anderen im IBurstContext Objekt enthaltenen Ressourcen werden automatisch freigegeben, wenn der Burst-Client seinen Verweis auf IBurstContext verliert.

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

Die Funktionen des Burst-Dienstprogramms finden Sie 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);

Im Folgenden finden Sie eine Referenzimplementierung einer Burst-Schnittstelle, die im Neural Networks-Beispieltreiber unter frameworks/ml/nn/driver/sample/SampleDriver.cpp zu finden ist.

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();
}