LiteRT를 사용한 NPU 가속

LiteRT는 공급업체별 컴파일러, 런타임 또는 라이브러리 종속 항목을 탐색하지 않고도 신경망 처리 장치 (NPU)를 사용할 수 있는 통합 인터페이스를 제공합니다. NPU 가속에 LiteRT를 사용하면 실시간 및 대형 모델 추론의 성능이 향상되고 제로 카피 하드웨어 버퍼 사용을 통해 메모리 복사가 최소화됩니다.

시작하기

시작하려면 NPU 개요 가이드를 참고하세요.

NPU 지원을 사용하는 LiteRT의 구현 예는 다음 데모 애플리케이션을 참고하세요.

NPU 공급업체

LiteRT는 다음 공급업체의 NPU 가속을 지원합니다.

Qualcomm AI Engine Direct

  • AOT 및 기기 내 컴파일 실행 경로는 컴파일된 모델 API를 통해 지원됩니다.
  • 설정 세부정보는 Qualcomm AI Engine Direct를 참고하세요.

MediaTek NeuroPilot

  • AOT 및 JIT 실행 경로는 컴파일된 모델 API를 통해 지원됩니다.
  • 설정 세부정보는 MediaTek NeuroPilot을 참고하세요.

NPU용 모델 변환 및 컴파일

LiteRT로 NPU 가속을 사용하려면 모델을 LiteRT 파일 형식으로 변환하고 기기 내 NPU 사용을 위해 컴파일해야 합니다. LiteRT AOT (사전) 컴파일러를 사용하여 모델을 AI 팩으로 컴파일할 수 있습니다. AI 팩은 컴파일된 모델을 기기 타겟팅 구성과 번들로 묶습니다. 이를 통해 특정 SoC에 장착되었는지 또는 최적화되었는지에 따라 모델이 기기에 올바르게 제공되는지 확인할 수 있습니다.

모델을 변환하고 컴파일한 후 온디바이스 AI용 Play(PODAI)를 사용하여 모델을 Google Play에 업로드하고 온디맨드 AI 프레임워크를 통해 기기에 모델을 제공할 수 있습니다.

LiteRT AOT 컴파일 노트북을 사용하여 NPU용 모델을 변환하고 컴파일하는 엔드 투 엔드 가이드를 확인하세요.

[AOT만 해당] Play AI Pack으로 배포

모델을 변환하고 AI Pack을 컴파일한 후 다음 단계에 따라 Google Play로 AI Pack을 배포합니다.

Gradle 프로젝트로 AI 팩 가져오기

AI 팩을 Gradle 프로젝트의 루트 디렉터리에 복사합니다. 예를 들면 다음과 같습니다.

my_app/
    ...
    ai_packs/
        my_model/...
        my_model_mtk/...

각 AI Pack을 Gradle 빌드 구성에 추가합니다.

// my_app/ai_packs/my_model/build.gradle.kts

plugins { id("com.android.ai-pack") }

aiPack {
  packName = "my_model"  // ai pack dir name
  dynamicDelivery { deliveryType = "on-demand" }
}

// Add another build.gradle.kts for my_model_mtk/ as well

프로젝트에 NPU 런타임 라이브러리 추가

AOT의 경우 litert_npu_runtime_libraries.zip을, JIT의 경우 litert_npu_runtime_libraries_jit.zip을 다운로드하고 프로젝트의 루트 디렉터리에 압축을 풉니다.

my_app/
    ...
    litert_npu_runtime_libraries/
        mediatek_runtime/...
        qualcomm_runtime_v69/...
        qualcomm_runtime_v73/...
        qualcomm_runtime_v75/...
        qualcomm_runtime_v79/...
        qualcomm_runtime_v81/...
        fetch_qualcomm_library.sh

스크립트를 실행하여 NPU 지원 라이브러리를 다운로드합니다. 예를 들어 Qualcomm NPU의 경우 다음을 실행합니다.

$ ./litert_npu_runtime_libraries/fetch_qualcomm_library.sh

Gradle 구성에 AI 팩 및 NPU 런타임 라이브러리 추가

생성된 AI 팩에서 device_targeting_configuration.xml를 기본 앱 모듈의 디렉터리에 복사합니다. 그런 다음 settings.gradle.kts를 업데이트합니다.

// my_app/setting.gradle.kts

...

// [AOT only]
// AI Packs
include(":ai_packs:my_model")
include(":ai_packs:my_model_mtk")

// NPU runtime libraries
include(":litert_npu_runtime_libraries:runtime_strings")

include(":litert_npu_runtime_libraries:mediatek_runtime")
include(":litert_npu_runtime_libraries:qualcomm_runtime_v69")
include(":litert_npu_runtime_libraries:qualcomm_runtime_v73")
include(":litert_npu_runtime_libraries:qualcomm_runtime_v75")
include(":litert_npu_runtime_libraries:qualcomm_runtime_v79")
include(":litert_npu_runtime_libraries:qualcomm_runtime_v81")

build.gradle.kts를 업데이트합니다.

// my_app/build.gradle.kts

android {
 ...

 defaultConfig {
    ...

    // API level 31+ is required for NPU support.
    minSdk = 31

    // NPU only supports arm64-v8a
    ndk { abiFilters.add("arm64-v8a") }
    // Needed for Qualcomm NPU runtime libraries
    packaging { jniLibs { useLegacyPackaging = true } }
  }

  // Device targeting
  bundle {
      deviceTargetingConfig = file("device_targeting_configuration.xml")
      deviceGroup {
        enableSplit = true // split bundle by #group
        defaultGroup = "other" // group used for standalone APKs
      }
  }

  // [AOT Only]
  // AI Packs
  assetPacks.add(":ai_packs:my_model")
  assetPacks.add(":ai_packs:my_model_mtk")

  // NPU runtime libraries
  dynamicFeatures.add(":litert_npu_runtime_libraries:mediatek_runtime")
  dynamicFeatures.add(":litert_npu_runtime_libraries:qualcomm_runtime_v69")
  dynamicFeatures.add(":litert_npu_runtime_libraries:qualcomm_runtime_v73")
  dynamicFeatures.add(":litert_npu_runtime_libraries:qualcomm_runtime_v75")
  dynamicFeatures.add(":litert_npu_runtime_libraries:qualcomm_runtime_v79")
  dynamicFeatures.add(":litert_npu_runtime_libraries:qualcomm_runtime_v81")
}

dependencies {
  // Dependencies for strings used in the runtime library modules.
  implementation(project(":litert_npu_runtime_libraries:runtime_strings"))
  ...
}

[AOT만 해당] 주문형 배포 사용

build.gradle.kts 파일에 구성된 Android AI Pack 기능을 사용하여 기기 기능을 확인하고 지원되는 기기에서 NPU를 사용합니다. GPU와 CPU는 대체로 사용합니다.

val env = Environment.create(BuiltinNpuAcceleratorProvider(context))

val modelProvider = AiPackModelProvider(
    context, "my_model", "model/my_model.tflite") {
    if (NpuCompatibilityChecker.Qualcomm.isDeviceSupported())
      setOf(Accelerator.NPU) else setOf(Accelerator.CPU, Accelerator.GPU)
}
val mtkModelProvider = AiPackModelProvider(
    context, "my_model_mtk", "model/my_model_mtk.tflite") {
    if (NpuCompatibilityChecker.Mediatek.isDeviceSupported())
      setOf(Accelerator.NPU) else setOf()
}
val modelSelector = ModelSelector(modelProvider, mtkModelProvider)
val model = modelSelector.selectModel(env)

val compiledModel = CompiledModel.create(
    model.getPath(),
    CompiledModel.Options(model.getCompatibleAccelerators()),
    env,
)

JIT 모드의 CompiledModel 생성

val env = Environment.create(BuiltinNpuAcceleratorProvider(context))

val compiledModel = CompiledModel.create(
    "model/my_model.tflite",
    CompiledModel.Options(Accelerator.NPU),
    env,
)

Kotlin에서 LiteRT를 사용하여 NPU에서 추론

NPU 가속기를 사용하려면 컴파일된 모델 (CompiledModel)을 만들 때 NPU 매개변수를 전달하세요.

다음 코드 스니펫은 Kotlin에서 전체 프로세스의 기본 구현을 보여줍니다.

val inputBuffers = model.createInputBuffers()
val outputBuffers = model.createOutputBuffers()

inputBuffers[0].writeFloat(FloatArray(data_size) { data_value })
model.run(inputBuffers, outputBuffers)
val outputFloatArray = outputBuffers[0].readFloat()

inputBuffers.forEach { it.close() }
outputBuffers.forEach { it.close() }
model.close()

C++에서 LiteRT를 사용하여 NPU에서 추론

빌드 종속 항목

C++ 사용자는 LiteRT NPU 가속으로 애플리케이션의 종속 항목을 빌드해야 합니다. 핵심 애플리케이션 로직(예:cc_binary main.cc)에는 다음 런타임 구성요소가 필요합니다.

  • LiteRT C API 공유 라이브러리: data 속성에는 LiteRT C API 공유 라이브러리 (//litert/c:litert_runtime_c_api_shared_lib)와 NPU의 공급업체별 디스패치 공유 객체(//litert/vendors/qualcomm/dispatch:dispatch_api_so)가 포함되어야 합니다.
  • NPU 관련 백엔드 라이브러리: Android 호스트 (예: libQnnHtp.so, libQnnHtpPrepare.so)용 Qualcomm AI RT (QAIRT) 라이브러리 및 해당 Hexagon DSP 라이브러리 (libQnnHtpV79Skel.so)입니다. 이를 통해 LiteRT 런타임이 NPU에 계산을 오프로드할 수 있습니다.
  • 속성 종속 항목: deps 속성은 LiteRT의 텐서 버퍼(//litert/cc:litert_tensor_buffer) 및 NPU 디스패치 레이어용 API(//litert/vendors/qualcomm/dispatch:dispatch_api)와 같은 필수 컴파일 시간 종속 항목에 연결됩니다. 이를 통해 애플리케이션 코드가 LiteRT를 통해 NPU와 상호작용할 수 있습니다.
  • 모델 파일 및 기타 애셋: data 속성을 통해 포함됩니다.

이 설정을 사용하면 컴파일된 바이너리가 가속화된 머신러닝 추론을 위해 NPU를 동적으로 로드하고 사용할 수 있습니다.

NPU 환경 설정

일부 NPU 백엔드에는 런타임 종속 항목이나 라이브러리가 필요합니다. 컴파일된 모델 API를 사용할 때 LiteRT는 Environment 객체를 통해 이러한 요구사항을 정리합니다. 다음 코드를 사용하여 적절한 NPU 라이브러리나 드라이버를 찾습니다.

// Provide a dispatch library directory (following is a hypothetical path) for the NPU
std::vector<Environment::Option> environment_options = {
    {
      Environment::OptionTag::DispatchLibraryDir,
      "/usr/lib64/npu_dispatch/"
    }
};

LITERT_ASSIGN_OR_RETURN(auto env, Environment::Create(absl::MakeConstSpan(environment_options)));

런타임 통합

다음 코드 스니펫은 C++에서 전체 프로세스의 기본 구현을 보여줍니다.

// 1. Load the model that has NPU-compatible ops
LITERT_ASSIGN_OR_RETURN(auto model, Model::Load("mymodel_npu.tflite"));

// 2. Create a compiled model with NPU acceleration
//    See following section on how to set up NPU environment
LITERT_ASSIGN_OR_RETURN(auto compiled_model,
  CompiledModel::Create(env, model, kLiteRtHwAcceleratorNpu));

// 3. Allocate I/O buffers
LITERT_ASSIGN_OR_RETURN(auto input_buffers, compiled_model.CreateInputBuffers());
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());

// 4. Fill model inputs (CPU array -> NPU buffers)
float input_data[] = { /* your input data */ };
input_buffers[0].Write<float>(absl::MakeConstSpan(input_data, /*size*/));

// 5. Run inference
compiled_model.Run(input_buffers, output_buffers);

// 6. Access model output
std::vector<float> data(output_data_size);
output_buffers[0].Read<float>(absl::MakeSpan(data));

NPU 가속을 통한 제로 카피

무사본을 사용하면 CPU가 해당 데이터를 명시적으로 복사할 필요 없이 NPU가 자체 메모리에서 직접 데이터에 액세스할 수 있습니다. CPU 메모리로 데이터를 복사하지 않으므로 제로 카피는 엔드 투 엔드 지연 시간을 크게 줄일 수 있습니다.

다음 코드는 AHardwareBuffer를 사용하여 NPU에 직접 데이터를 전달하는 제로 카피 NPU의 구현 예입니다. 이 구현은 CPU 메모리로의 비용이 많이 드는 왕복을 방지하여 추론 오버헤드를 크게 줄입니다.

// Suppose you have AHardwareBuffer* ahw_buffer

LITERT_ASSIGN_OR_RETURN(auto tensor_type, model.GetInputTensorType("input_tensor"));

LITERT_ASSIGN_OR_RETURN(auto npu_input_buffer, TensorBuffer::CreateFromAhwb(
    env,
    tensor_type,
    ahw_buffer,
    /* offset = */ 0
));

std::vector<TensorBuffer> input_buffers{npu_input_buffer};

LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());

// Execute the model
compiled_model.Run(input_buffers, output_buffers);

// Retrieve the output (possibly also an AHWB or other specialized buffer)
auto ahwb_output = output_buffers[0].GetAhwb();

여러 NPU 추론 연결

복잡한 파이프라인의 경우 여러 NPU 추론을 연결할 수 있습니다. 각 단계에서 가속기 친화적인 버퍼를 사용하므로 파이프라인은 대부분 NPU 관리 메모리에 유지됩니다.

// compiled_model1 outputs into an AHWB
compiled_model1.Run(input_buffers, intermediate_buffers);

// compiled_model2 consumes that same AHWB
compiled_model2.Run(intermediate_buffers, final_outputs);

NPU 적시 컴파일 캐싱

LiteRT는 .tflite 모델의 NPU 적시 (JIT) 컴파일을 지원합니다. JIT 컴파일은 모델을 미리 컴파일하는 것이 불가능한 상황에서 특히 유용합니다.

하지만 JIT 컴파일에는 사용자 제공 모델을 주문형으로 NPU 바이트코드 명령어로 변환하는 데 약간의 지연 시간과 메모리 오버헤드가 발생할 수 있습니다. 성능 영향을 최소화하기 위해 NPU 컴파일 아티팩트를 캐시할 수 있습니다.

캐싱이 사용 설정된 경우 LiteRT는 다음과 같은 경우에만 모델의 재컴파일을 트리거합니다.

  • 공급업체의 NPU 컴파일러 플러그인 버전이 변경됨
  • Android 빌드 지문이 변경되었습니다.
  • 사용자가 제공한 모델이 변경되었습니다.
  • 컴파일 옵션이 변경되었습니다.

NPU 컴파일 캐싱을 사용 설정하려면 환경 옵션에서 CompilerCacheDir 환경 태그를 지정하세요. 이 값은 애플리케이션의 기존 쓰기 가능한 경로로 설정해야 합니다.

   const std::array environment_options = {
        litert::Environment::Option{
            /*.tag=*/litert::Environment::OptionTag::CompilerPluginLibraryDir,
            /*.value=*/kCompilerPluginLibSearchPath,
        },
        litert::Environment::Option{
            litert::Environment::OptionTag::DispatchLibraryDir,
            kDispatchLibraryDir,
        },
        // 'kCompilerCacheDir' will be used to store NPU-compiled model
        // artifacts.
        litert::Environment::Option{
            litert::Environment::OptionTag::CompilerCacheDir,
            kCompilerCacheDir,
        },
    };

    // Create an environment.
    LITERT_ASSERT_OK_AND_ASSIGN(
        auto environment, litert::Environment::Create(environment_options));

    // Load a model.
    auto model_path = litert::testing::GetTestFilePath(kModelFileName);
    LITERT_ASSERT_OK_AND_ASSIGN(auto model,
                                litert::Model::CreateFromFile(model_path));

    // Create a compiled model, which only triggers NPU compilation if
    // required.
    LITERT_ASSERT_OK_AND_ASSIGN(
        auto compiled_model, litert::CompiledModel::Create(
                                 environment, model, kLiteRtHwAcceleratorNpu));

지연 시간 및 메모리 절약 예:

NPU 컴파일에 필요한 시간과 메모리는 기본 NPU 칩, 입력 모델의 복잡성 등 여러 요인에 따라 달라질 수 있습니다.

다음 표에서는 NPU 컴파일이 필요한 경우와 캐싱으로 인해 컴파일을 건너뛸 수 있는 경우의 런타임 초기화 시간과 메모리 소비를 비교합니다. 한 샘플 기기에서 다음을 획득합니다.

TFLite 모델 NPU 컴파일을 사용한 모델 초기화 캐시된 컴파일로 모델 초기화 NPU 컴파일을 사용한 메모리 사용량 초기화 캐시된 컴파일로 메모리 초기화
torchvision_resnet152.tflite 7465.22 ms 198.34 ms 1525.24 MB 355.07 MB
torchvision_lraspp_mobilenet_v3_large.tflite 1592.54 ms 166.47 ms 254.90 MB 33.78 MB

Google은 다른 기기에서 다음 정보를 획득합니다.

TFLite 모델 NPU 컴파일을 사용한 모델 초기화 캐시된 컴파일로 모델 초기화 NPU 컴파일을 사용한 메모리 사용량 초기화 캐시된 컴파일로 메모리 초기화
torchvision_resnet152.tflite 2,766.44ms 379.86 ms 653.54 MB 501.21 MB
torchvision_lraspp_mobilenet_v3_large.tflite 784.14 ms 231.76 ms 113.14 MB 67.49 MB