Skip to main content

litert-runtime

Creates an Android app that runs a .tflite model on the CPU or the GPU with the LiteRT CompiledModel API in Kotlin. Use this skill to build a new app around a vision, audio or embedding model, or to add on-device inference to an existing app - the dependency, where the model file goes, the inference class, the ViewModel and screen, and checking the output on a device.

Source facts

Repository
google-ai-edge/litert-samples
Last source activity
October 9, 2026 at 05:47
Detected SKILL.md language
English
Stars
465
Forks
122

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
4 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
litert-runtime
description
Creates an Android app that runs a .tflite model on the CPU or the GPU with the LiteRT CompiledModel API in Kotlin. Use this skill to build a new app around a vision, audio or embedding model, or to add on-device inference to an existing app - the dependency, where the model file goes, the inference class, the ViewModel and screen, and checking the output on a device.
license
Apache-2.0
metadata
{"last-updated":"2026-10-09","keywords":["LiteRT","CompiledModel","tflite","Android app","GPU"]}
This skill provides step-by-step guidance for building an Android app that runs a `.tflite` model with the LiteRT CompiledModel API (`com.google.ai.edge.litert:litert` 2.x; overview: https://ai.google.dev/edge/litert/android, sources: https://github.com/google-ai-edge/litert/blob/main/README.md) on the CPU or the GPU. The Interpreter API is not covered. For language models, use the `litert-lm` skill: https://github.com/google-ai-edge/litert-samples/blob/main/skills/litert-lm/SKILL.md (LiteRT-LM: https://github.com/google-ai-edge/LiteRT-LM/blob/main/README.md). ## Prerequisites - A Kotlin Android project (Android Studio's Empty Activity template is enough). The LiteRT 2.3.0 AARs declare `minSdk` 24. - The dependencies in the app-level `build.gradle.kts`, from Google Maven: `implementation("com.google.ai.edge.litert:litert:2.3.0")` and `implementation("com.google.ai.edge.litert:litert-gpu:2.3.0")`. From 2.3.0 the GPU accelerator is the second artifact: with `litert` alone the app builds, and `CompiledModel.create` with `Accelerator.GPU` throws `LiteRtException` because the APK has no GPU accelerator library (logcat: `GPU accelerator could not be loaded and registered`). No manifest entry is needed: `litert-api`, which `litert` depends on, declares the OpenCL driver libraries (`uses-native-library`) in its own manifest. - A `.tflite` model and its input requirements (input size, mean/std, channel order). Models with a LiteRT recipe: https://github.com/google-ai-edge/litert-samples/blob/main/models/README.md ## Detailed steps ### 1. Set up the project and place the model In the app-level `build.gradle.kts`, set `minSdk = 24` in `defaultConfig`. Put the model at `app/src/main/assets/model.tflite` (AGP stores `.tflite` files uncompressed by default). A model too large for the APK is delivered by Play for On-device AI (beta) as an AI pack, or downloaded by the app into `context.filesDir`: https://developer.android.com/google/play/on-device-ai (its bundletool `--local-testing` installs the packs without the store). An install-time AI pack is read through the `AssetManager`, as that page shows. A fast-follow or on-demand pack and a downloaded file are loaded from their path with `CompiledModel.create(filePath, CompiledModel.Options(accelerator), environment)` in place of the asset call in step 2. The pack's directory comes from the AI Delivery library (`com.google.android.play:ai-delivery`): `AiPackManagerFactory.getInstance(context).getPackLocation(name)?.assetsPath()`, null until the pack has downloaded. With 2.3.0 nothing is added to `gradle.properties`: `litert` 2.3.0 declares the namespace `com.google.ai.edge.litert.impl` and its dependency `litert-api` 2.3.0 declares `com.google.ai.edge.litert`, so AGP 9.x builds the project. With 2.2.0 or 2.1.6 both libraries declare `com.google.ai.edge.litert`, and AGP 9.x stops at `processDebugMainManifest` (https://github.com/google-ai-edge/LiteRT/issues/8474; AGP 8.x reports it as a warning and builds); for those two versions add `android.uniquePackageNames=false` to `gradle.properties`, and remove it when moving to 2.3.0, because it also hides the same clash between any other two libraries. Excluding `litert-api` instead does not work: `CompiledModel` and `Accelerator` are in it, and the build then fails at `compileDebugKotlin`. ### 2. Write the inference class ```kotlin import android.content.Context import com.google.ai.edge.litert.Accelerator import com.google.ai.edge.litert.CompiledModel import com.google.ai.edge.litert.Environment import com.google.ai.edge.litert.TensorBuffer const val INPUT_SIZE = 224 * 224 * 3 private val environment by lazy { Environment.create() } class Classifier(context: Context, accelerator: Accelerator) : AutoCloseable { private val model = CompiledModel.create(context.assets, "model.tflite", CompiledModel.Options(accelerator), environment) private val inputs = mutableListOf<TensorBuffer>() private val outputs = mutableListOf<TensorBuffer>() init { try { inputs += model.createInputBuffers() outputs += model.createOutputBuffers() infer(FloatArray(INPUT_SIZE)) } catch (e: Exception) { close() throw e } } fun infer(input: FloatArray): FloatArray { inputs[0].writeFloat(input) model.run(inputs, outputs) return outputs[0].readFloat() } override fun close() { (inputs + outputs).forEach { it.close() } model.close() } } ``` `Accelerator.CPU` needs nothing from the device. `Accelerator.GPU` compiles the graph for the GPU; when the GPU cannot take the model, the constructor throws `LiteRtException`: from `create` for an op the GPU does not support, and on the Android emulator (API 36, arm64) from `create` or from the buffers. A missing asset and a file that is not a model also throw `LiteRtException` from `create`. The buffers are created once with the model, reused for every inference and closed before the model. One `Environment` serves every model in the process and stays open, so LiteRT loads the GPU accelerator library once and not on every `create`. The constructor ends with one inference as the warm-up (the first GPU run includes shader compilation) and closes the model and its buffers if any step throws. `INPUT_SIZE` is the model's input element count: `writeFloat` throws on a longer array and writes a shorter one without an error, so a wrong size shows up as a wrong result. ### 3. Wire a ViewModel and the screen ```kotlin data class UiState(val ready: Boolean = false, val result: List<Float>? = null, val error: String? = null) class MainViewModel(app: Application) : AndroidViewModel(app) { private val executor = Executors.newSingleThreadExecutor() private val scope = CoroutineScope(SupervisorJob() + executor.asCoroutineDispatcher()) private var classifier: Classifier? = null private val _uiState = MutableStateFlow(UiState()) val uiState: StateFlow<UiState> = _uiState.asStateFlow() fun load(accelerator: Accelerator = Accelerator.CPU) { scope.launch { if (classifier != null) return@launch try { classifier = Classifier(getApplication<Application>(), accelerator) _uiState.value = UiState(ready = true) } catch (e: LiteRtException) { _uiState.value = UiState(error = e.message) } } } fun classify(input: FloatArray) { scope.launch { try { classifier?.infer(input)?.let { result -> _uiState.update { it.copy(result = result.toList(), error = null) } } } catch (e: LiteRtException) { _uiState.update { it.copy(error = e.message) } } } } override fun onCleared() { scope.launch { classifier?.close() }.invokeOnCompletion { scope.cancel() } executor.shutdown() } } ``` One single-thread executor owns the model: create, run and close happen only on it, never on the main thread. The ViewModel keeps its own scope because `viewModelScope` is cancelled before `onCleared()` runs, so a close launched there would not run; `onCleared()` cancels the scope once the close is done. `load()` does nothing when a model is already loaded, so the screen can call it again after a rotation; a `load()` that fails leaves `classifier` `null`. `classify()` reports a `LiteRtException` (package `com.google.ai.edge.litert`) in `error` and keeps the model. The screen below takes the picture (`bitmap`) from the photo picker or CameraX and runs `preprocess()` in the click handler (a few milliseconds for 224 by 224; move it onto the executor for bigger pictures); it does not fall back on its own: if `load(Accelerator.GPU)` ends in `error`, call `load(Accelerator.CPU)`. `viewModel()` and `collectAsStateWithLifecycle()` come from `androidx.lifecycle:lifecycle-viewmodel-compose` and `androidx.lifecycle:lifecycle-runtime-compose` (2.10.0); every import the code blocks need is listed in [imports](references/imports.md): ```kotlin @Composable fun MainScreen(bitmap: Bitmap, viewModel: MainViewModel = viewModel()) { val state by viewModel.uiState.collectAsStateWithLifecycle() LaunchedEffect(Unit) { viewModel.load(Accelerator.GPU) } Column { state.error?.let { Text(it) } Button(onClick = { viewModel.classify(preprocess(bitmap)) }, enabled = state.ready) { Text("Run") } state.result?.let { Text("Top class: ${it.indices.maxBy { i -> it[i] }}") } } } ``` ### 4. Preprocess by the model's input requirements Turn the bitmap into the model's input with [`preprocess()`](references/preprocess.md). Use the model's own size, mean/std, channel order and layout (that example is NHWC, RGB, scaled to -1..1); a wrong mean/std looks exactly like a broken model. An audio or embedding model replaces `preprocess()` with its own input encoding and keeps the inference class. ### 5. Run on a device and check the output Run the app on a physical device from Android Studio. Compare the app's output with the model's reference output for one fixed input, on the CPU first and then on the GPU; follow [verification](references/verify.md). Moving an existing Interpreter-API app to the CompiledModel API is a separate skill: https://github.com/google-ai-edge/litert-samples/blob/main/skills/litert-compiled-model-migration/SKILL.md
View on GitHub