Wykonywanie serii i szybkie kolejki komunikatów

Sieci neuronowe HAL 1.2 wprowadza koncepcję wykonywania serii. Egzekucje seryjne to sekwencja wykonań tego samego przygotowanego modelu, które występują w krótkich odstępach czasu, np. te działające na klatkach przechwytywania kamery lub kolejnych próbkach audio. Obiekt serii służy do kontrolowania zestawu wykonań serii i do zachowania zasobów pomiędzy wykonaniami, dzięki czemu wykonania mają niższy narzut. Obiekty rozrywające umożliwiają trzy optymalizacje:

  1. Obiekt rozerwany jest tworzony przed sekwencją egzekucji i zwalniany po zakończeniu sekwencji. Z tego powodu czas życia obiektu, który uległ eksplozji, wskazuje kierowcy, jak długo powinien on pozostawać w stanie wysokiej wydajności.
  2. Obiekt serii może zachować zasoby pomiędzy wykonaniami. Na przykład sterownik może mapować obiekt pamięci przy pierwszym wykonaniu i buforować mapowanie w obiekcie seryjnym do ponownego użycia w kolejnych wykonaniach. Każdy zasób buforowany może zostać zwolniony, gdy obiekt serii zostanie zniszczony lub gdy środowisko wykonawcze NNAPI powiadomi obiekt serii, że zasób nie jest już potrzebny.
  3. Obiekt serii wykorzystuje szybkie kolejki komunikatów (FMQ) do komunikacji między procesami aplikacji i sterowników. Może to zmniejszyć opóźnienia, ponieważ FMQ omija HIDL i przekazuje dane bezpośrednio do innego procesu za pośrednictwem atomowego kołowego FIFO w pamięci współdzielonej. Proces konsumencki wie, że należy usunąć element z kolejki i rozpocząć przetwarzanie albo poprzez odpytywanie liczby elementów w FIFO, albo poprzez oczekiwanie na flagę zdarzenia FMQ, która jest sygnalizowana przez producenta. Ta flaga zdarzenia jest szybkim muteksem przestrzeni użytkownika (futex).

FMQ to struktura danych niskiego poziomu, która nie oferuje dożywotnich gwarancji dla procesów i nie ma wbudowanego mechanizmu określania, czy proces na drugim końcu FMQ działa zgodnie z oczekiwaniami. W rezultacie, jeśli producent FMQ umrze, konsument może utknąć w oczekiwaniu na dane, które nigdy nie dotrą. Jednym z rozwiązań tego problemu jest powiązanie przez sterownik FMQ z obiektem serii wyższego poziomu w celu wykrycia zakończenia wykonywania serii.

Ponieważ wykonania seryjne działają na tych samych argumentach i zwracają te same wyniki, co inne ścieżki wykonania, podstawowe FMQ muszą przekazywać te same dane do i ze sterowników usług NNAPI. Jednak FMQ mogą przesyłać tylko zwykłe, stare typy danych. Przesyłanie złożonych danych odbywa się poprzez serializację i deserializację zagnieżdżonych buforów (typów wektorowych) bezpośrednio w FMQ oraz użycie obiektów wywołania zwrotnego HIDL do przesyłania uchwytów puli pamięci na żądanie. Strona producenta FMQ musi wysyłać komunikaty żądań lub wyników do konsumenta niepodzielnie, używając MessageQueue::writeBlocking , jeśli kolejka jest blokowana, lub używając MessageQueue::write jeśli kolejka nie blokuje.

Interfejsy seryjne

Interfejsy impulsowe dla sieci neuronowych HAL można znaleźć w hardware/interfaces/neuralnetworks/1.2/ i opisano je poniżej. Więcej informacji na temat interfejsów seryjnych w warstwie NDK można znaleźć w frameworks/ml/nn/runtime/include/NeuralNetworks.h .

typy.hal

types.hal definiuje typ danych przesyłanych przez FMQ.

  • FmqRequestDatum : Pojedynczy element serializowanej reprezentacji obiektu Request wykonania i wartości MeasureTiming , która jest wysyłana przez szybką kolejkę komunikatów.
  • FmqResultDatum : Pojedynczy element serializowanej reprezentacji wartości zwróconych w wyniku wykonania ( ErrorStatus , OutputShapes i Timing ), który jest zwracany przez szybką kolejkę komunikatów.

IBurstContext.hal

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

  • IBurstContext : Obiekt kontekstowy do zarządzania zasobami serii.

IBurstCallback.hal

IBurstCallback.hal definiuje obiekt interfejsu 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 gniazd.

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

IPreparedModel.hal

IPreparedModel.hal został rozszerzony w HAL 1.2 o metodę tworzenia obiektu IBurstContext z przygotowanego modelu.

  • configureExecutionBurst : Konfiguruje obiekt serii używany do szybkiego wykonywania wielu wniosków na przygotowanym modelu.

Obsługa wykonywania serii w sterowniku

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

  • Jedno Przeciążenie akceptuje wskaźnik do obiektu IPreparedModel . Ta funkcja narzędziowa używa metody executeSynchronously w obiekcie IPreparedModel w celu wykonania modelu.
  • Jedno przeciążenie akceptuje dostosowywalny obiekt IBurstExecutorWithCache , którego można użyć do buforowania zasobów (takich jak mapowania hidl_memory ), które utrzymują się w wielu wykonaniach.

Każde przeciążenie zwraca obiekt IBurstContext (który reprezentuje obiekt serii), który zawiera własny dedykowany wątek odbiornika i zarządza nim. Ten wątek odbiera żądania z requestChannel FMQ, przeprowadza wnioskowanie, a następnie zwraca wyniki za pośrednictwem resultChannel FMQ. Ten wątek i wszystkie inne zasoby zawarte w obiekcie IBurstContext są automatycznie zwalniane, gdy klient serii traci odwołanie do IBurstContext .

Alternatywnie możesz utworzyć własną implementację IBurstContext , która rozumie, jak wysyłać i odbierać komunikaty za pośrednictwem requestChannel i resultChannel FMQ przekazywanych do IPreparedModel::configureExecutionBurst .

Funkcje narzędziowe serii można znaleźć 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 seryjnego, którą można znaleźć w przykładowym sterowniku sieci neuronowych pod 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();
}