W HAL 1.2 sieci neuronowych wprowadzono pojęcie wykonywania pakietowego. Wykonywanie w serii to sekwencja wykonań tego samego przygotowanego modelu, które następują po sobie w szybkim tempie, np. w przypadku klatek przechwytywanych przez kamerę lub kolejnych próbek audio. Obiekt burst służy do kontrolowania zestawu wykonań burst i zachowywania zasobów między wykonaniami, co pozwala zmniejszyć obciążenie. Obiekty burst umożliwiają 3 rodzaje optymalizacji:
- Obiekt burst jest tworzony przed sekwencją wykonań i zwalniany po jej zakończeniu. Dlatego czas życia obiektu burst podpowiada sterownikowi, jak długo powinien on pozostawać w stanie wysokiej wydajności.
- Obiekt burst może zachowywać zasoby między wykonaniami. Na przykład sterownik może zmapować obiekt pamięci podczas pierwszego wykonania i zapisać mapowanie w obiekcie pakietu, aby użyć go ponownie w kolejnych wykonaniach. Każdy zasób w pamięci podręcznej może zostać zwolniony, gdy obiekt burst zostanie zniszczony lub gdy środowisko wykonawcze NNAPI powiadomi obiekt burst, że zasób nie jest już potrzebny.
- Obiekt burst korzysta z szybkich kolejek wiadomości (FMQ) do komunikacji między procesami aplikacji i sterownika. Może to skrócić czas oczekiwania, ponieważ FMQ pomija HIDL i przekazuje dane bezpośrednio do innego procesu za pomocą atomowej okrągłej kolejki FIFO w pamięci współdzielonej. Proces konsumujący wie, że ma usunąć element z kolejki i rozpocząć przetwarzanie, sprawdzając liczbę elementów w kolejce FIFO lub czekając na flagę zdarzenia FMQ, która jest sygnalizowana przez producenta. Ten flaga zdarzenia to szybki mutex przestrzeni użytkownika (futex).
FMQ to struktura danych niskiego poziomu, która nie gwarantuje czasu życia w różnych procesach i nie ma wbudowanego mechanizmu sprawdzania, czy proces po drugiej stronie FMQ działa zgodnie z oczekiwaniami. W konsekwencji, jeśli producent FMQ przestanie działać, konsument może utknąć w oczekiwaniu na dane, które nigdy nie nadejdą. Jednym z rozwiązań tego problemu jest powiązanie przez sterownik kolejek FMQ z obiektem wyższego poziomu, aby wykryć, kiedy zakończy się wykonanie serii.
Ponieważ wykonania pakietowe działają na tych samych argumentach i zwracają te same wyniki co inne ścieżki wykonania, podstawowe kolejki FMQ muszą przekazywać te same dane do sterowników usług NNAPI i z nich. Kolejki FMQ mogą jednak przesyłać tylko zwykłe typy danych. Przesyłanie złożonych danych odbywa się przez serializację i deserializację zagnieżdżonych buforów (typów wektorowych) bezpośrednio w FMQ oraz używanie obiektów wywołania zwrotnego HIDL do przesyłania uchwytów puli pamięci na żądanie. Producent FMQ musi wysyłać żądania lub wiadomości z wynikami do konsumenta w sposób niepodzielny, używając MessageQueue::writeBlocking
, jeśli kolejka jest blokująca, lub MessageQueue::write
, jeśli kolejka nie jest blokująca.
Interfejsy serii
Interfejsy burst dla HAL sieci neuronowych znajdują się w hardware/interfaces/neuralnetworks/1.2/
i są opisane poniżej. Więcej informacji o interfejsach pakietowych w warstwie NDK znajdziesz w frameworks/ml/nn/runtime/include/NeuralNetworks.h
.
types.hal
types.hal
określa typ danych przesyłanych przez FMQ.
FmqRequestDatum
: pojedynczy element zserializowanej reprezentacji obiektu wykonaniaRequest
i wartośćMeasureTiming
, która jest wysyłana przez szybką kolejkę wiadomości.FmqResultDatum
: pojedynczy element serializowanej reprezentacji wartości zwracanych z wykonania (ErrorStatus
,OutputShapes
iTiming
), który jest zwracany przez szybką kolejkę wiadomości.
IBurstContext.hal
IBurstContext.hal
określa obiekt interfejsu HIDL, który znajduje się 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 gniazd.
- IBurstCallback: Obiekt wywołania zwrotnego używany przez usługę do pobierania obiektów pamięci.
IPreparedModel.hal
IPreparedModel.hal
w HAL 1.2 jest rozszerzony o metodę tworzenia obiektu IBurstContext
z przygotowanego modelu.
configureExecutionBurst
: Konfiguruje obiekt burst, który służy do szybkiego wykonywania wielu wnioskowań na przygotowanym modelu.
Obsługa wykonywania serii 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 spakowana w bibliotekach statycznych libneuralnetworks_common
i libneuralnetworks_util
. Ta funkcja fabryczna ma 2 przeciążenia:
- Jeden z nich akceptuje wskaźnik do obiektu
IPreparedModel
. Ta funkcja narzędziowa używa metodyexecuteSynchronously
w obiekcieIPreparedModel
do wykonania modelu. - Jeden z nich akceptuje konfigurowalny obiekt
IBurstExecutorWithCache
, którego można używać do buforowania zasobów (np. mapowańhidl_memory
), które są zachowywane w wielu wykonaniach.
Każde przeciążenie zwraca obiekt IBurstContext
(który reprezentuje obiekt burst), który zawiera i zarządza własnym wątkiem odbiornika. Ten wątek odbiera żądania z kolejki requestChannel
FMQ, przeprowadza wnioskowanie, a następnie zwraca wyniki za pomocą kolejki resultChannel
FMQ. Ten wątek i wszystkie inne 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 będzie wiedzieć, jak wysyłać i odbierać wiadomości za pomocą kolejek FMQ requestChannel
i resultChannel
przekazywanych do IPreparedModel::configureExecutionBurst
.
Funkcje narzędziowe serii 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 znajdziesz przykładową implementację interfejsu burst w przykładowym sterowniku sieci neuronowych dostępnym pod adresem 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();
}