Wykonywanie serii i szybkie kolejki wiadomości

Neural Networks HAL 1.2 zawiera koncepcję wykonań burstowych. Uruchomienia serii to sekwencja wykonań tego samego gotowego modelu w krótkich odstępach czasu, np. w ramach ujęcia z kamery lub kilku kolejnych próbek audio. Obiekt burst służy do sterowania zestawem wykonań burst i do przechowywania zasobów między wykonaniami, dzięki czemu wykonania mają mniejsze wymagania. W przypadku obiektów serii można włączyć 3 optymalizacje:

  1. Obiekt burstowy jest tworzony przed sekwencją wykonań i uwalniany po jej zakończeniu. Z tego powodu czas życia obiektu serii wskazuje sterownikowi, jak długo ma on pozostawać w stanie wysokiej wydajności.
  2. Obiekt burstowy może zachowywać zasoby między uruchomieniami. Na przykład sterownik może zmapować obiekt pamięci przy pierwszym wykonaniu i buforować mapowanie w obiekcie burst do ponownego wykorzystania w kolejnych wykonaniach. Każdy zasób w pamięci podręcznej może zostać zwolniony po zniszczeniu obiektu burst lub gdy środowisko wykonawcze NNAPI powiadomi obiekt burst, że zasób nie jest już wymagany.
  3. Obiekt serii używa szybkich kolejek wiadomości (FMQ) do komunikacji między procesami aplikacji i sterowników. Może to zmniejszyć opóźnienie, ponieważ FMQ omija HIDL i przekazuje dane bezpośrednio do innego procesu przez niepodzielny okrągły obiekt FIFO w pamięci współdzielonej. Proces konsumenta wie, że powinien usunąć produkt z kolejki i rozpocząć przetwarzanie przez sprawdzenie liczby elementów w FIFO lub oczekiwanie na flagę zdarzenia FMQ sygnalizowaną przez producenta. To szybkie mutex (futex) w przestrzeni użytkownika.

FMQ to niskopoziomowa struktura danych, która nie daje żadnych gwarancji bezterminowych gwarancji dotyczących różnych procesów i nie ma wbudowanego mechanizmu, który określałby, czy proces po drugiej stronie FMQ działa zgodnie z oczekiwaniami. W związku z tym, jeśli producent FMQ umrze, konsument może utknąć w oczekiwaniu na dane, które nigdy nie zostaną dostarczone. Jednym z rozwiązań tego problemu jest powiązanie przez sterownika FMQ z obiektem burst wyższego poziomu w celu wykrycia zakończenia wykonywania serii.

Ponieważ wykonania burstowe działają na tych samych argumentach i zwracają te same wyniki co inne ścieżki wykonania, bazowe moduły FMQ muszą przekazywać te same dane do i od sterowników usługi NNAPI. FMQ mogą jednak przesyłać tylko stare typy danych. Przenoszenie złożonych danych odbywa się przez serializację i deserializację zagnieżdżonych buforów (typów wektorów) bezpośrednio w FMQ, a także przez użycie obiektów wywołań zwrotnych HIDL do przesyłania uchwytów puli pamięci na żądanie. FMQ po stronie producenta musi wysyłać do konsumenta komunikaty żądania lub wyników osobno, używając MessageQueue::writeBlocking, jeśli kolejka jest blokowana, lub MessageQueue::write, jeśli kolejka nie jest blokowana.

Interfejsy serii

Interfejsy burst dla interfejsu HAL sieci neuronowych znajdziesz w sekcji hardware/interfaces/neuralnetworks/1.2/ i opisane poniżej. Więcej informacji o interfejsach burst w warstwie NDK znajdziesz w frameworks/ml/nn/runtime/include/NeuralNetworks.h.

type.hal

types.hal określa typ danych, które są wysyłane przez FMQ.

  • FmqRequestDatum: pojedynczy element zserializowanej reprezentacji obiektu Request wykonania i wartości MeasureTiming, która jest wysyłana w kolejce szybkich wiadomości.
  • FmqResultDatum: pojedynczy element zserializowanej reprezentacji wartości zwracanych przez wykonanie (ErrorStatus, OutputShapes i Timing), które są zwracane przez szybką kolejkę wiadomości.

IBurstContext.hal

IBurstContext.hal definiuje obiekt interfejsu HIDL, który funkcjonuje w usłudze sieci neuronowych.

  • IBurstContext: obiekt kontekstu do zarządzania zasobami serii.

IBurstCallback.hal

IBurstCallback.hal definiuje obiekt interfejsu HIDL dla wywołania zwrotnego utworzonego przez środowisko wykonawcze sieci neuronowych i jest używany przez usługę sieci neuronowych do pobierania obiektów hidl_memory odpowiadających identyfikatorom przedziałów.

  • IBurstCallback: obiekt wywołania zwrotnego używany przez usługę do pobierania obiektów pamięci.

IPreparedModel.hal

Metoda IPreparedModel.hal jest rozszerzona w HAL 1.2 o metodę tworzenia obiektu IBurstContext z gotowego modelu.

  • configureExecutionBurst: konfiguruje obiekt burst używany do wykonywania w krótkim czasie wielu wnioskowania na gotowym modelu.

Obsługa wykonań burst w sterowniku

Najprostszym sposobem obsługi obiektów burst w usłudze HIDL NNAPI jest użycie funkcji burstowej ::android::nn::ExecutionBurstServer::create, którą można znaleźć w ExecutionBurstServer.h i spakować do bibliotek statycznych libneuralnetworks_common i libneuralnetworks_util. Ta funkcja fabryczna ma 2 przeciążenia:

  • Jedno przeciążenie akceptuje wskaźnik do obiektu IPreparedModel. Ta funkcja narzędziowa korzysta z metody executeSynchronously w obiekcie IPreparedModel do wykonywania modelu.
  • W ramach jednego przeciążenia możliwy jest konfigurowalny obiekt IBurstExecutorWithCache, który może być używany do buforowania zasobów (np. mapowań hidl_memory), które są przechowywane w wielu wykonaniach.

Każde przeciążenie zwraca obiekt IBurstContext (reprezentujący obiekt Burst), który zawiera własny wątek detektora i nim zarządza. Ten wątek odbiera żądania od FMQ requestChannel, przeprowadza wnioskowanie, a następnie zwraca wyniki przez FMQ resultChannel. Ten wątek i wszystkie pozostałe zasoby zawarte w obiekcie IBurstContext są automatycznie zwalniane, gdy klient serii utraci odniesienie do IBurstContext.

Możesz też utworzyć własną implementację IBurstContext, która rozumie, jak wysyłać i odbierać wiadomości przez requestChannel i resultChannel FMQ przekazywane do IPreparedModel::configureExecutionBurst.

Funkcje burstowe znajdują się w 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);

Poniżej znajduje się referencyjna implementacja interfejsu bursta w przykładowym sterowniku sieci neuronowych w 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();
}