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 kontrolowania zestawu wykonań burst i do zachowywania zasobów między wykonaniami, co pozwala zmniejszyć obciążenie. W przypadku obiektów serii można włączyć 3 optymalizacje:

  1. Obiekt burst jest tworzony przed sekwencją wykonywania, a zwalniany po jej zakończeniu. Z tego powodu czas trwania obiektu burst wskazuje sterownikowi, jak długo powinien on pozostawać w stanie wysokiej wydajności.
  2. Obiekt burst może zachować zasoby między wykonaniami. 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, gdy obiekt burst zostanie zniszczony lub gdy środowisko uruchomieniowe NNAPI powiadomi obiekt burst, że zasób nie jest już potrzebny.
  3. Obiekt burst używa kolejek szybkich wiadomości (FMQ) do komunikacji między procesami aplikacji a sterownika. 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 ma pobrać element z kolejki i rozpocząć przetwarzanie, albo przez sprawdzenie liczby elementów w kolejce FIFO, albo przez oczekiwanie na flagę zdarzenia kolejki FMQ, która jest sygnalizowana 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 konsekwencji, jeśli producent FMQ przestanie działać, konsument może czekać na dane, które nigdy nie dotrą. Jednym z rozwiązań tego problemu jest powiązanie kolejek FMQ z obiektem burst wyższego poziomu, aby wykryć, kiedy zakończyło się wykonywanie burstu.

Ponieważ wykonania burstowe działają na tych samych argumentach i zwracają te same wyniki co inne ścieżki wykonania, bazowe zapytania FMQ muszą przekazywać te same dane do i od sterowników usługi NNAPI. FMQ mogą jednak przesyłać tylko stare typy danych. Przesyłanie złożonych danych odbywa się przez serializację i deserializację zagnieżdżonych buforów (typów wektorów) bezpośrednio w obiektach FMQ oraz przez używanie obiektów wywołania obsługi zdarzeń HIDL do przenoszenia uchwytów puli pamięci na żądanie. Producent w ramach kolejki FMQ musi wysłać żądanie lub wiadomości z wynikiem do konsumenta w sposób atomowy, używając funkcji MessageQueue::writeBlocking, jeśli kolejka jest blokująca, lub funkcji MessageQueue::write, jeśli kolejka nie jest blokująca.

Interfejsy burst

Interfejsy burst dla HAL-a sieci neuronowych znajdują się w hardware/interfaces/neuralnetworks/1.2/ i są opisane poniżej. Więcej informacji o interfejsach burst w warstwie NDK znajdziesz w artykule frameworks/ml/nn/runtime/include/NeuralNetworks.h (w języku angielskim).

types.hal

types.halokreśla typ danych przesyłanych przez FMQ.

  • FmqRequestDatum: pojedynczy element serializowanej reprezentacji obiektu Request wykonania i wartość MeasureTiming, które są wysyłane za pomocą kolejki 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 określa obiekt interfejsu HIDL, który znajduje się w usłudze Neural Networks.

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

IBurstCallback.hal

IBurstCallback.hal określa interfejs HIDL dla wywołania zwrotnego utworzonego przez środowisko wykonawcze Neural Networks i jest używany przez usługę Neural Networks do pobierania obiektów hidl_memory odpowiadających identyfikatorom slotó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 wykonywania burst w sterowniku

Najprostszym sposobem obsługi obiektów burst w usłudze HIDL NNAPI jest użycie funkcji narzędzia burst ::android::nn::ExecutionBurstServer::create, która znajduje się w ExecutionBurstServer.h i jest pakowana w bibliotekach statycznych libneuralnetworks_commonlibneuralnetworks_util. Ta funkcja fabryczna ma 2 przeciążenia:

  • Jedno przeciążenie akceptuje wskaźnik do obiektu IPreparedModel. Ta funkcja narzędzia wykorzystuje metodę executeSynchronously w obiekcie IPreparedModel do wykonania modelu.
  • Jedna z przeciążeń przyjmuje obiekt IBurstExecutorWithCache, który można wykorzystać do przechowywania w pamięci podręcznej zasobów (np. mapowań hidl_memory), które są trwałe w przypadku wielu wykonań.

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 za pomocą 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ę interfejsu IBurstContext, która potrafi wysyłać i odbierać wiadomości za pomocą interfejsów FMQ requestChannelresultChannel przekazywanych do IPreparedModel::configureExecutionBurst.

Funkcje narzędzia burst znajdziesz 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();
}