LiteRT proporciona una interfaz unificada para usar las unidades de procesamiento neuronal (NPU) sin obligarte a navegar por compiladores, tiempos de ejecución o dependencias de bibliotecas específicos del proveedor. El uso de LiteRT para la aceleración de la NPU mejora el rendimiento de la inferencia en tiempo real y de modelos grandes, y minimiza las copias de memoria a través del uso de búferes de hardware de copia cero.
Comenzar
Para comenzar, consulta la guía de descripción general de la NPU:
- Para los modelos de AA clásicos, consulta las siguientes secciones para conocer los pasos de conversión, compilación y la implementación.
- Para los modelos de lenguaje grandes (LLM), usa nuestro framework LiteRT-LM:
Para ver ejemplos de implementaciones de LiteRT con compatibilidad con la NPU, consulta las siguientes aplicaciones de demostración:
Proveedores de NPU
LiteRT admite la aceleración de la NPU con los siguientes proveedores:
Qualcomm AI Engine Direct
- Las rutas de ejecución de compilación AOT y en el dispositivo se admiten a través de la API de Compiled Model.
- Consulta Qualcomm AI Engine Direct para obtener detalles sobre la configuración.
MediaTek NeuroPilot
- Las rutas de ejecución de AOT y JIT se admiten a través de la API de Compiled Model.
- Consulta MediaTek NeuroPilot para obtener detalles sobre la configuración.
Cómo convertir y compilar modelos para la NPU
Para usar la aceleración de la NPU con LiteRT, los modelos deben convertirse al formato de archivo de LiteRT y compilarse para el uso de la NPU en el dispositivo. Puedes usar el compilador AOT (adelantado en el tiempo) de LiteRT para compilar modelos en un paquete de IA, que incluye tus modelos compilados con configuraciones de segmentación por dispositivo. Esto verifica que los modelos se publiquen correctamente en los dispositivos, según si están equipados o optimizados para SoCs particulares.
Después de convertir y compilar los modelos, puedes usar Play for On-device AI (PODAI) para subir modelos a Google Play y entregarlos a los dispositivos a través del framework de On-Demand AI.
Usa el notebook de compilación AOT de LiteRT para obtener una guía integral sobre cómo convertir y compilar modelos para la NPU.
[Solo para AOT] Implementa con Play AI Pack
Después de convertir el modelo y compilar un paquete de IA, sigue estos pasos para implementar el paquete de IA con Google Play.
Importa paquetes de IA al proyecto de Gradle
Copia los paquetes de IA en el directorio raíz del proyecto de Gradle. Por ejemplo:
my_app/
...
ai_packs/
my_model/...
my_model_mtk/...
Agrega cada paquete de IA a la configuración de compilación de 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
Agrega bibliotecas de tiempo de ejecución de la NPU al proyecto
Descarga litert_npu_runtime_libraries.zip para AOT o litert_npu_runtime_libraries_jit.zip para JIT, y descomprímelo en el directorio raíz del proyecto:
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
Ejecuta la secuencia de comandos para descargar las bibliotecas de compatibilidad con la NPU. Por ejemplo, ejecuta lo siguiente para las NPUs de Qualcomm:
$ ./litert_npu_runtime_libraries/fetch_qualcomm_library.sh
Agrega paquetes de IA y bibliotecas de tiempo de ejecución de la NPU a la configuración de Gradle
Copia device_targeting_configuration.xml de los AI Packs generados en el directorio del módulo principal de la app. Luego, actualiza 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")
Actualiza 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"))
...
}
[Solo AOT] Usa la implementación a pedido
Con la función Android AI Pack configurada en el archivo build.gradle.kts, verifica las capacidades del dispositivo y usa la NPU en los dispositivos compatibles, con la GPU y la CPU como alternativa:
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,
)
Crea CompiledModel para el modo JIT
val env = Environment.create(BuiltinNpuAcceleratorProvider(context))
val compiledModel = CompiledModel.create(
"model/my_model.tflite",
CompiledModel.Options(Accelerator.NPU),
env,
)
Inferencia en la NPU con LiteRT en Kotlin
Para comenzar a usar el acelerador de NPU, pasa el parámetro de NPU cuando crees el modelo compilado (CompiledModel).
En el siguiente fragmento de código, se muestra una implementación básica de todo el proceso en 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()
Inferencia en la NPU con LiteRT en C++
Dependencias de la compilación
Los usuarios de C++ deben compilar las dependencias de la aplicación con la aceleración de la NPU de LiteRT. La regla cc_binary que empaqueta la lógica principal de la aplicación (p.ej., main.cc) requiere los siguientes componentes de tiempo de ejecución:
- Biblioteca compartida de la API de LiteRT en C: El atributo
datadebe incluir la biblioteca compartida de la API de LiteRT en C (//litert/c:litert_runtime_c_api_shared_lib) y el objeto compartido de envío específico del proveedor para la NPU (//litert/vendors/qualcomm/dispatch:dispatch_api_so). - Bibliotecas de backend específicas de la NPU: Por ejemplo, las bibliotecas de Qualcomm AI RT (QAIRT) para el host de Android (como
libQnnHtp.so,libQnnHtpPrepare.so) y la biblioteca de DSP de Hexagon correspondiente (libQnnHtpV79Skel.so). Esto garantiza que el tiempo de ejecución de LiteRT pueda descargar los cálculos en la NPU. - Dependencias de atributos: El atributo
depsse vincula con dependencias esenciales en tiempo de compilación, como el búfer de tensor de LiteRT (//litert/cc:litert_tensor_buffer) y la API para la capa de envío de la NPU (//litert/vendors/qualcomm/dispatch:dispatch_api). Esto permite que el código de tu aplicación interactúe con la NPU a través de LiteRT. - Archivos de modelos y otros recursos: Se incluyen a través del atributo
data.
Esta configuración permite que tu archivo binario compilado cargue y use de forma dinámica la NPU para la inferencia acelerada del aprendizaje automático.
Configura un entorno de NPU
Algunos backends de la NPU requieren dependencias o bibliotecas de tiempo de ejecución. Cuando se usa la API de modelos compilados, LiteRT organiza estos requisitos a través de un objeto Environment.
Usa el siguiente código para encontrar las bibliotecas o los controladores de la NPU adecuados:
// 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)));
Integración en el tiempo de ejecución
En el siguiente fragmento de código, se muestra una implementación básica de todo el proceso en 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));
Copia cero con aceleración de la NPU
El uso de la copia cero permite que una NPU acceda a los datos directamente en su propia memoria sin necesidad de que la CPU copie esos datos de forma explícita. Al no copiar datos hacia y desde la memoria de la CPU, la copia cero puede reducir significativamente la latencia de extremo a extremo.
El siguiente código es un ejemplo de implementación de la NPU de copia cero con AHardwareBuffer, que pasa datos directamente a la NPU. Esta implementación evita los viajes de ida y vuelta costosos a la memoria de la CPU, lo que reduce significativamente la sobrecarga de la inferencia.
// 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();
Encadena varias inferencias de la NPU
En el caso de las canalizaciones complejas, puedes encadenar varias inferencias de la NPU. Dado que cada paso usa un búfer compatible con el acelerador, tu canalización permanece principalmente en la memoria administrada por la 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);
Almacenamiento en caché de compilación justo a tiempo de la NPU
LiteRT admite la compilación justo a tiempo (JIT) de la NPU de los modelos .tflite. La compilación JIT puede ser especialmente útil en situaciones en las que no es factible compilar el modelo con anticipación.
Sin embargo, la compilación JIT puede generar cierta latencia y sobrecarga de memoria para traducir el modelo proporcionado por el usuario en instrucciones de código de bytes de la NPU a pedido. Para minimizar el impacto en el rendimiento, se pueden almacenar en caché los artefactos de compilación de la NPU.
Cuando el almacenamiento en caché está habilitado, LiteRT solo activará la recompilación del modelo cuando sea necesario, por ejemplo:
- Cambió la versión del complemento del compilador de la NPU del proveedor.
- Cambió la huella digital de compilación de Android.
- El modelo proporcionado por el usuario cambió.
- Cambiaron las opciones de compilación.
Para habilitar el almacenamiento en caché de la compilación de la NPU, especifica la etiqueta de entorno CompilerCacheDir en las opciones del entorno. El valor debe establecerse en una ruta de acceso grabable existente de la aplicación.
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));
Ejemplo de ahorro de latencia y memoria:
El tiempo y la memoria necesarios para la compilación de la NPU pueden variar según varios factores, como el chip de la NPU subyacente, la complejidad del modelo de entrada, etcétera.
En la siguiente tabla, se comparan el tiempo de inicialización del tiempo de ejecución y el consumo de memoria cuando se requiere la compilación de la NPU y cuando se puede omitir la compilación debido al almacenamiento en caché. En un dispositivo de muestra, obtenemos lo siguiente:
| Modelo de TFLite | Inicialización del modelo con compilación de la NPU | Inicialización del modelo con compilación almacenada en caché | Inicializa el espacio en memoria con la compilación de la NPU | Inicializa la memoria con la compilación almacenada en caché |
|---|---|---|---|---|
| torchvision_resnet152.tflite | 7465.22 ms | 198.34 ms | 1525.24 MB | 355.07 MB |
| torchvision_lraspp_mobilenet_v3_large.tflite | 1,592.54 ms | 166.47 ms | 254.90 MB | 33.78 MB |
En otro dispositivo, obtenemos lo siguiente:
| Modelo de TFLite | Inicialización del modelo con compilación de la NPU | Inicialización del modelo con compilación almacenada en caché | Inicializa el espacio en memoria con la compilación de la NPU | Inicializa la memoria con la compilación almacenada en caché |
|---|---|---|---|---|
| torchvision_resnet152.tflite | 2766.44 ms | 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 |