Rozszerzenia dostawców

Rozszerzenia dostawcy interfejsu Neural Networks API (NNAPI), wprowadzone w Androidzie 10, to zbiory operacji i typów danych zdefiniowanych przez dostawcę. Na urządzeniach z systemem NN HAL w wersji 1.2 lub nowszej, sterowniki mogą umożliwiać niestandardowe przyspieszanie sprzętowe operacji przez obsługujące odpowiednie rozszerzenia dostawców. Rozszerzenia dostawcy nie modyfikują działania dotychczasowych operacji.

Rozszerzenia dostawcy stanowią bardziej uporządkowaną alternatywę dla operacji OEM i typów danych, które zostały wycofane w Androidzie 10. Więcej informacji: Operacje OEM i typy danych.

Lista dozwolonych na temat użycia rozszerzeń

Rozszerzeń dostawcy mogą używać tylko wyraźnie określone aplikacje na Androida i natywne pliki binarne na partycjach /product, /vendor, /odm/data. Aplikacje i natywne pliki binarne znajdujące się na partycji /system nie mogą używać rozszerzeń dostawców.

Lista aplikacji na Androida i plików binarnych, które mogą używać rozszerzeń dostawcy NNAPI, to przechowywane w /vendor/etc/nnapi_extensions_app_allowlist. Każdy wiersz w pliku zawiera nowy wpis. Element może być ścieżką do pliku binarnego z prefiksem ukośnika (/), np. /data/foo, lub nazwą pakietu aplikacji na Androida, np. com.foo.bar.

Lista dozwolonych jest wymuszana przez bibliotekę współdzieloną NNAPI. Ta biblioteka chroni przed przypadkowym użyciem, ale nie przed bezpośrednio przez interfejs HAL sterownika NNAPI.

Definicja rozszerzenia z danymi o dostawcy

Dostawca tworzy i utrzymuje plik nagłówka z definicją rozszerzenia. Pełny przykład definicji rozszerzenia znajdziesz w example/fibonacci/FibonacciExtension.h.

Każde rozszerzenie musi mieć unikalną nazwę, która zaczyna się od odwróconej nazwy domeny dostawcy.

const char EXAMPLE_EXTENSION_NAME[] = "com.example.my_extension";

Jest to przestrzeń nazw operacji i typów danych. NNAPI używa tego nazwy pozwalającej rozróżnić rozszerzenia dostawcy.

Operacje i typy danych są deklarowane w sposób podobny do tego, który jest stosowany w runtime/include/NeuralNetworks.h.

enum {
    /**
     * A custom scalar type.
     */
    EXAMPLE_SCALAR = 0,

    /**
     * A custom tensor type.
     *
     * Attached to this tensor is {@link ExampleTensorParams}.
     */
    EXAMPLE_TENSOR = 1,
};

enum {
    /**
     * Computes example function.
     *
     * Inputs:
     * * 0: A scalar of {@link EXAMPLE_SCALAR}.
     *
     * Outputs:
     * * 0: A tensor of {@link EXAMPLE_TENSOR}.
     */
    EXAMPLE_FUNCTION = 0,
};

Operacja rozszerzenia może używać dowolnego typu operandu, w tym typów operandów spoza rozszerzenia i typów operandów z innych rozszerzeń. Jeśli używasz typu operanda z innego rozszerzenia, sterownik musi obsługiwać to rozszerzenie.

Rozszerzenia mogą też deklarować niestandardowe struktury, aby towarzyszyć operandom rozszerzenia.

/**
 * Quantization parameters for {@link EXAMPLE_TENSOR}.
 */
typedef struct ExampleTensorParams {
    double scale;
    int64_t zeroPoint;
} ExampleTensorParams;

Korzystanie z rozszerzeń w klientach NNAPI

runtime/include/NeuralNetworksExtensions.h (C API) zapewnia obsługę rozszerzenia środowiska wykonawczego. W tej sekcji znajdziesz o C API.

Aby sprawdzić, czy urządzenie obsługuje rozszerzenie, użyj ANeuralNetworksDevice_getExtensionSupport.

bool isExtensionSupported;
CHECK_EQ(ANeuralNetworksDevice_getExtensionSupport(device, EXAMPLE_EXTENSION_NAME,
                                                   &isExtensionSupported),
         ANEURALNETWORKS_NO_ERROR);
if (isExtensionSupported) {
    // The device supports the extension.
    ...
}

Aby utworzyć model z operandem rozszerzenia, użyj funkcji ANeuralNetworksModel_getExtensionOperandType, aby uzyskać typ operandu, a następnie wywołaj funkcję ANeuralNetworksModel_addOperand.

int32_t type;
CHECK_EQ(ANeuralNetworksModel_getExtensionOperandType(model, EXAMPLE_EXTENSION_NAME, EXAMPLE_TENSOR, &type),
         ANEURALNETWORKS_NO_ERROR);
ANeuralNetworksOperandType operandType{
        .type = type,
        .dimensionCount = dimensionCount,
        .dimensions = dimensions,
};
CHECK_EQ(ANeuralNetworksModel_addOperand(model, &operandType), ANEURALNETWORKS_NO_ERROR);

Możesz też użyć funkcji ANeuralNetworksModel_setOperandExtensionData , aby powiązać dodatkowe dane z operandem rozszerzenia.

ExampleTensorParams params{
        .scale = 0.5,
        .zeroPoint = 128,
};
CHECK_EQ(ANeuralNetworksModel_setOperandExtensionData(model, operandIndex, &params, sizeof(params)),
         ANEURALNETWORKS_NO_ERROR);

Aby utworzyć model z operacją rozszerzenia, użyj funkcji ANeuralNetworksModel_getExtensionOperationType, aby uzyskać typ operacji, a potem wywołaj funkcję ANeuralNetworksModel_addOperation.

ANeuralNetworksOperationType type;
CHECK_EQ(ANeuralNetworksModel_getExtensionOperationType(model, EXAMPLE_EXTENSION_NAME, EXAMPLE_FUNCTION,
                                                        &type),
         ANEURALNETWORKS_NO_ERROR);
CHECK_EQ(ANeuralNetworksModel_addOperation(model, type, inputCount, inputs, outputCount, outputs),
         ANEURALNETWORKS_NO_ERROR);

Dodawanie obsługi rozszerzenia do sterownika NNAPI

Sterowniki zgłaszają obsługiwane rozszerzenia za pomocą metody IDevice::getSupportedExtensions. Zwrócona lista musi zawierać wpis opisujący każde obsługiwane rozszerzenie.

Extension {
    .name = EXAMPLE_EXTENSION_NAME,
    .operandTypes = {
        {
            .type = EXAMPLE_SCALAR,
            .isTensor = false,
            .byteSize = 8,
        },
        {
            .type = EXAMPLE_TENSOR,
            .isTensor = true,
            .byteSize = 8,
        },
    },
}

Z 32 bitów używanych do identyfikowania typów i operacji wysoka Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX bity to rozszerzenie prefiks i niski Model::ExtensionTypeEncoding::LOW_BITS_TYPE liczba bitów oznacza typ lub operację rozszerzenia.

Podczas obsługi operacji lub typu operandu sterownik musi sprawdzić rozszerzenie . Jeśli prefiks rozszerzenia ma wartość różną od 0, typ operacji lub operand jest typem rozszerzenia. Jeśli wartość to 0, jest to typ operacji lub argumentu. Nie jest typem rozszerzenia.

Aby zmapować prefiks na nazwę rozszerzenia, wyszukaj go w sekcji model.extensionNameToPrefix. Mapowanie z prefiksu na nazwę rozszerzenia jest korespondencją „jeden do jednego”. (bijekcjom) danego modelu. Różne wartości prefiksów mogą odpowiadać tej samej nazwie rozszerzenia w różnych modelach.

Sterownik musi zweryfikować operacje rozszerzenia i typy danych, ponieważ NNAPI środowisko wykonawcze nie może sprawdzać konkretnych operacji rozszerzeń ani typów danych.

Operandy rozszerzeń mogą mieć powiązane dane w operand.extraParams.extension, które środowisko wykonawcze traktuje jako surowy blok danych o dowolnym rozmiarze.

Typy danych i działania OEM

NNAPI zawiera operacje i typy danych OEM, aby umożliwić producentom urządzeń udostępnianie niestandardowych funkcji związanych z sterownikami. Te operacji i typów danych są używane tylko przez aplikacje OEM. Semantyka operacji OEM i typów danych zależy od producenta OEM i może się w dowolnym momencie zmienić. Operacja OEM i typy danych są kodowane za pomocą wartości OperationType::OEM_OPERATION, OperandType::OEMOperandType::TENSOR_OEM_BYTE.