Wykonywanie zadań w krótkich odstępach czasu i szybkie kolejki wiadomości

W wersji Neural Networks HAL 1.2 wprowadzono koncepcję wykonywania w setek. Burst wykonania to sekwencja wykonanych w szybkiej kolejności operacji na tym samym przygotowanym modelu, np. operacje na klatkach obrazu z kamery lub kolejnych próbkach dźwięku. Obiekt burst służy do kontrolowania zbioru wykonań burst i do zachowywania zasobów między wykonaniami, dzięki czemu wykonania te mają mniejszy nakład. Obiekty burst umożliwiają 3 optymalizacje:

  1. Obiekt burst jest tworzony przed sekwencją wykonywania, a zwalniany po jej zakończeniu. Z tego powodu czas trwania obiektu burst sugeruje kierowcy, 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 mapować obiekt pamięci podczas pierwszego wykonania i przechowywać w pamięci podręcznej mapowanie w obiekcie burst, aby można było go ponownie użyć 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 za pomocą atomowego kołowego FIFO w wspólnej pamięci. Proces konsumenta wie, że ma pobrać element z kolejki i rozpocząć przetwarzanie, albo przez odczytywanie liczby elementów w FIFO, albo przez oczekiwanie na flagę zdarzenia FMQ, która jest sygnalizowana przez producenta. Ten parametr to szybki semafor w przestrzeni użytkownika (futex).

FMQ to struktura danych niskiego poziomu, która nie gwarantuje trwałości w przypadku różnych procesów i nie ma wbudowanego mechanizmu umożliwiającego określenie, 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ą do odbiorcy. 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 w trybie burst działają na podstawie tych samych argumentów i zwracają te same wyniki co inne ścieżki wykonania, podstawowe obiekty FMQ muszą przekazywać te same dane do i z sterowników usług NNAPI. Jednak w przypadku interfejsów FMQ można przesyłać tylko typy danych plain-old. 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 serii

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.

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 serializowanej reprezentacji wartości zwracanych przez wykonanie (ErrorStatus, OutputShapesTiming), zwracany przez kolejkę szybkich 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

IPreparedModel.hal w HAL 1.2 został rozszerzony o metodę umożliwiającą utworzenie obiektu IBurstContext na podstawie przygotowanego modelu.

  • configureExecutionBurst: Konfiguruje obiekt burst używany do wykonywania wielu wnioskowań na przygotowanym modelu w szybkiej kolejności.

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:

  • Jedna z przeciążeń przyjmuje wskaźnik do obiektu IPreparedModel. Ta funkcja pomocnicza używa metody 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 wielu wykonaniach.

Każda z przeciążeń zwraca obiekt IBurstContext (który reprezentuje obiekt burst), który zawiera i zarządza własnym wątkiem odsłuchu. Ten wątek odbiera żądania z poziomu requestChannel FMQ, wykonuje wnioskowanie, a następnie zwraca wyniki przez resultChannel FMQ. Ten wątek i wszystkie inne zasoby zawarte w obiekcie IBurstContext są automatycznie uwalniane, gdy klient bursta traci odwołanie 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 burst, która znajduje się w przykładowym sterowniku Neural Networks w pliku 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();
}