Litert Compiled Model Migration logo

Litert Compiled Model Migration

Organization
google-ai-edge
litert-compiled-model-migration

Rapidly migrate an Android application from legacy TensorFlow Lite (TFLite) to the modern LiteRT CompiledModel API (2.x) in Open Source GitHub repositories, keeping the TensorFlow Lite Support Library as LiteRT Support (litert-support 2.3.0). Covers buffer reuse, NPU JIT compilation, a reference implementation, and a two-stage verification gate (compile check, then an on-device parity test against a golden output).

Overview

Publishergoogle-ai-edge
Repositorylitert-samples
Skill namelitert-compiled-model-migration
Stars
470
Forks
124
Bundled files
4
LicenseApache-2.0
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 4 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by google-ai-edge on GitHub. Read the source before you install it.

Installation

Install the Litert Compiled Model Migration AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/google-ai-edge/litert-samples/tree/main/skills/litert-compiled-model-migration
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/google-ai-edge/litert-samples.git /tmp/litert-samples
mkdir -p .claude/skills
cp -r /tmp/litert-samples/skills/litert-compiled-model-migration .claude/skills/litert-compiled-model-migration
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Litert Compiled Model Migration in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Litert Compiled Model Migration on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Litert Compiled Model Migration is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Skill: LiteRT Compiled Model Migration SKILL

Description

This skill guides an AI agent to rapidly migrate an Android application from legacy TensorFlow Lite (TFLite) to the modern LiteRT CompiledModel API (2.x) in Open Source GitHub repositories. It prioritizes a high-speed, 1st-pass "Like for Like" baseline migration with automated self-testing, and encourages advanced performance upgrades including off-main-thread execution with buffer reuse and NPU JIT compilation.

Read first: what the Kotlin CompiledModel API (com.google.ai.edge.litert, 2.3.0; 2.2.0 has the same API) has

  • CompiledModel.create(context.assets, assetName, CompiledModel.Options(Accelerator.CPU), env) and create(filePath, options, env); Options takes one or more of Accelerator.CPU / GPU / NPU (e.g. Options(Accelerator.GPU, Accelerator.CPU)); env is Environment.create(context) (optional; enables the compiler cache) or Environment.create(context, BuiltinNpuAcceleratorProvider(context)) for the NPU. Nothing takes a ByteBuffer or a mapped file.
  • Buffers come from the model (createInputBuffers() / createOutputBuffers(), List<TensorBuffer>; writeFloat(FloatArray) / writeInt8(ByteArray) / writeInt / writeLong, readFloat() and friends; create them once). run(inputs, outputs) is synchronous on the CPU; on the GPU it can return before the GPU finishes and read*() waits; there is no callback API. CompiledModel, TensorBuffer and Environment are AutoCloseable: close buffers, then the model, then an Environment the app created.
  • CompiledModel.Options() with no accelerator compiles, but create then throws LiteRtException. Interpreter, GpuDelegate, NnApiDelegate and the Support library's Model (com.google.ai.edge.litert.support.model.Model, which runs on InterpreterApi) do not appear in migrated code; the rest of LiteRT Support does, under com.google.ai.edge.litert.support (Step 1), with org.tensorflow.lite.DataType as the type its classes take. The litert AAR also contains org.tensorflow.lite.Interpreter, so leftover Interpreter code compiles (all but setUseNNAPI, which 2.x does not have) and the Stage 1 import check is what catches it. templates/LiteRtModel.kt compiles against the 2.3.0 and the 2.2.0 artifacts; start from it.

0. Automatic Discovery & Upfront Planning

Before writing code, the agent MUST inspect the project workspace and present the upfront planning interview to align migration parameters:

A. Automatic Workspace & Toolchain Discovery

The agent must automatically inspect the repository structure:

  1. Ecosystem & Build Engine:
    • Gradle Build System: Detected by build.gradle, build.gradle.kts, or settings.gradle. -> Enable Gradle & GitHub PR Workflow.
  2. Language & JNI Toolchain:
    • Native C++ / NDK: Detected if CMakeLists.txt, Android.mk, or *.cpp files exist. -> Enable C++ / JNI Migration Rules.
    • Pure Kotlin / Java: Default to JVM / Android SDK Migration Rules.

B. Upfront User Interview (Questions Asked Prior to Migration)

The agent must present the following review options to the user:

Before initiating the LiteRT Compiled Model Migration, please confirm your project preferences:

1. Model Workload & Domain:
   What type of data does this application process?
   - [A] Vision (Images / Video / Camera Feeds) -> Enables the camera-frame recipe: preprocess into a reused input buffer via writeFloat / writeInt8 (AHardwareBuffer interop is C++-only).
   - [B] Audio (Speech / Sound Classification) -> Enables the streaming FloatArray recipe (writeFloat per window).
   - [C] Text / NLP / GenAI -> Enables the token-id recipe (writeInt / writeLong into the model's input buffers).

2. Hardware Acceleration & Conditional INT8 Quantization:
   Do you want to enable NPU hardware acceleration via JIT on-device compilation?
   - [A] Yes (Recommended - replaces deprecated NNAPI; needs the vendor libraries of Upgrade 2.C on a supported SoC) [Default]
         * If the app uses a Float32 model: Would you like to generate an INT8 integer-quantized model via AI Edge Quantizer for peak NPU speed, or run the original Float32 model?
           -> Option A.1: Convert to INT8 (Generates model_int8.tflite with int8 weights and activations; needs a few representative inputs for calibration) [Default]
           -> Option A.2: Keep Float32 (Runs baseline float model directly on NPU)
   - [B] No (GPU and CPU acceleration only)

3. Encouraged Performance Upgrades:
   Should the agent upgrade the calling code to use LiteRT's advanced features?
   - [A] Yes (Move inference off the main thread and reuse buffers) [Default]
   - [B] No (Keep strict 1-to-1 synchronous baseline execution)

4. Automated Pull Request Provisioning:
   Should the agent automatically stage, commit, and create a GitHub PR when self-testing passes?
   - [A] Yes [Default] (Attaches verification test logs and before/after summary diff)
   - [B] No (Keep changes local in current working branch)

With no user to answer (CI, batch runs): take Q1 from the code, Q2 [A] only when the vendor libraries of Upgrade 2.C are already in the repo (otherwise [B]), Float32 unless calibration inputs exist, and the defaults of Q3 and Q4 with the PR opened as a draft; record the choices in the PR description. Today the runtime is the standalone com.google.ai.edge.litert:litert AAR: no LiteRT artifact for Google Play services is on Google Maven as of October 2026 (only the play-services-tflite-* Interpreter artifacts exist).

[!IMPORTANT] LiteRT Support (litert-support 2.3.0): an app on the TensorFlow Lite Support Library keeps it. The library now ships as LiteRT Support, with the same classes, methods and behavior; the migration is two steps of Step 1, swap the Gradle coordinates and rename the package org.tensorflow.lite.support to com.google.ai.edge.litert.support (its guide: https://github.com/google-ai-edge/LiteRT/blob/main/litert/support_lib/MIGRATION_GUIDE.md). ImageProcessor, ResizeOp, NormalizeOp, TensorImage, TensorLabel, FileUtil and MetadataExtractor stay as they are. The one class replaced is its Model, which runs on the Interpreter API: it becomes CompiledModel like Interpreter (Step 4), with templates/SupportBridge.kt between the Support tensors and the model's buffers. An app that never used the Support library does not add it; Bitmap.toModelInput in templates/LiteRtModel.kt is its preprocessing.


1. Phase 1: "Like for Like" Baseline Migration (1st Pass Success)

Phase 1 prioritizes functional equivalence, fast compilation, and immediate 1st pass self-test success.

Step 0: Capture the Golden Output

Before Step 1 changes any dependency, with the legacy runtime and preprocessing still in the project (no device attached: skip Step 0, stop after Stage 1, and open the PR as a draft that says so):

  • Define one fixed input: for a vision app a deterministic bitmap (fixedBitmap in templates/GoldenCaptureTest.kt, or a small image file in app/src/androidTest/assets/), otherwise a generated array; never a camera frame. A bitmap of the model's input size with rotation 0 leaves the resize and the rotation out of the gate: use another size (and aspect ratio), and the rotation the camera path passes, in the capture and in Stage 2 alike.
  • Declare the androidx.test dependencies and testInstrumentationRunner (Stage 2 needs them too), copy templates/GoldenCaptureTest.kt into app/src/androidTest/java/<package>/, make it call the app's existing inference entry point (the class the UI calls, with its own preprocessing) on the fixed input on the CPU (no GPU or NNAPI delegate, XNNPACK at its default); when that entry point returns only a thresholded or sorted list, first give the class a function returning the raw output and one returning the preprocessed input (preprocess(bitmap): Stage 2 calls it too), still on the legacy runtime. Then run the four commands in its header: they install both APKs, run the test on the device, and pull golden.txt into app/src/androidTest/assets/golden/output.txt. A raw tensor fed straight to the model is not a golden output: it leaves the preprocessing out of the gate.
  • Delete the capture test. Stage 2 compares against the file, so the test APK never needs org.tensorflow:tensorflow-lite.

Step 1: Clean & Modernize Dependencies

Inspect libs.versions.toml and build.gradle.kts:

  • Remove Legacy & Deprecated:
    • org.tensorflow:tensorflow-lite / tensorflow-lite-gpu / tensorflow-lite-select-tf-ops (Legacy Flex Delegate — see Deprecated API Remediation below), and every other org.tensorflow:tensorflow-lite* line, including ones other libraries pull in (./gradlew :app:dependencies lists them): the LiteRT artifacts carry the org.tensorflow.lite runtime classes and the model schema under their original names, and mixing old and new artifacts fails the build with duplicate classes. com.google.ai.edge.litert:litert-gpu 1.x goes too (the renamed TFLite GPU delegate, last 1.4.2; litert-gpu:2.3.0 is the GPU accelerator of the CompiledModel runtime and is added below), and com.google.ai.edge.litert:litert 1.x (the renamed Interpreter runtime, e.g. samples/litert_interpreter/* on 1.0.0) becomes litert:2.3.0 below.
  • Swap the Support library coordinates (apps on org.tensorflow.lite.support.*): org.tensorflow:tensorflow-lite-support or litert-support 1.x becomes implementation("com.google.ai.edge.litert:litert-support:2.3.0") (the Support API plus the LiteRT runtime: it depends on litert:2.3.0); tensorflow-lite-support-api becomes litert-support-api:2.3.0 (the API alone, the runtime being the litert line below); tensorflow-lite-metadata or litert-metadata 1.x becomes litert-metadata:2.3.0 (MetadataExtractor). Then rename the package in every source file and in R8/ProGuard rules (-keep class org.tensorflow.lite.support.**): grep -rl 'org\.tensorflow\.lite\.support' --include='*.java' --include='*.kt' app/src/ | xargs sed -i 's/org\.tensorflow\.lite\.support/com.google.ai.edge.litert.support/g' (GNU sed; on macOS, sed -i ''). org.tensorflow.lite.DataType, the type the Support classes take, keeps its name.
  • Add Modern LiteRT:
    • Standalone: implementation("com.google.ai.edge.litert:litert:2.3.0") and implementation("com.google.ai.edge.litert:litert-gpu:2.3.0") in Kotlin DSL (implementation 'com.google.ai.edge.litert:litert:2.3.0' and '…:litert-gpu:2.3.0' in Groovy). litert pulls in litert-api and sets minSdk 24. litert-support:2.3.0 depends on litert:2.3.0 and raises a litert:2.2.0 pin to 2.3.0 without a warning (Gradle resolves litert:2.2.0 -> 2.3.0), so pin 2.3.0 here. Since 2.3.0 the GPU accelerator (libLiteRtClGlAccelerator.so) ships in litert-gpu; in 2.2.0 it was inside litert. Without litert-gpu, CompiledModel.Options(Accelerator.GPU, Accelerator.CPU) creates and runs on the CPU with the log line GPU accelerator could not be loaded and registered, and Options(Accelerator.GPU) alone throws.
  • Toolchain the 2.3.0 artifacts need: litert-api:2.3.0 depends on androidx.lifecycle:lifecycle-runtime:2.10.0 (its POM), and its classes carry Kotlin 2.4 metadata (2.2.0: Kotlin 2.3). Set these before the first build on a project on AGP 8.3 / Kotlin 1.9 (e.g. samples/litert_interpreter/*), or each becomes a build error in turn: compileSdk 35 and AGP 8.6.0 (checkDebugAarMetadata), Gradle 8.7 (AGP 8.6.0), the Kotlin plugin 2.3 or newer (from 1.9 this is a plugin upgrade; freeCompilerArgs += "-Xskip-metadata-version-check" only lets a project already on 2.2 stay there, as samples/litert/image_segmentation/kotlin_cpu_gpu does; 2.2 otherwise stops at The binary version of its metadata is 2.4.0, expected version is 2.2.0, and 2.2 is enough for the 2.2.0 artifacts), and with Kotlin 2.x a Compose app needs the org.jetbrains.kotlin.plugin.compose plugin in place of composeOptions. On AGP 9 (built-in Kotlin) the Kotlin version is pinned with id("org.jetbrains.kotlin.android") version "2.3.21" apply false in the root plugins block.
  • Labels and metadata: FileUtil.loadLabels and MetadataExtractor work as before on the 2.3.0 artifacts. An app without the Support library reads the label file from assets: a .tflite with metadata is a zip, so extract it once (unzip -o model.tflite labels.txt -d app/src/main/assets/; unzip -l lists the name).
  • IDE Portability: Remove hardcoded org.gradle.java.home from gradle.properties and exclude local.properties.
  • Kotlin Compiler DSL: Use top-level kotlin { compilerOptions { ... } } outside android { ... }: kotlinOptions { jvmTarget = "1.8" } becomes kotlin { compilerOptions { jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8) } }.

Step 2: Deprecated API & Delegate Remediation

The agent must audit and replace all deprecated delegate APIs:

  1. NNAPI Delegate (NnApiDelegate, NnApiDelegate.Options, setUseNNAPI(true)):

    • Status: NNAPI is deprecated since Android 15, and the LiteRT 2.x Java/Kotlin API has no NNAPI entry point (Interpreter.Options has no setUseNNAPI).
    • Remediation: Remove org.tensorflow.lite.nnapi.NnApiDelegate imports. Replace with CompiledModel.Options(Accelerator.NPU) and Environment.create(context, BuiltinNpuAcceleratorProvider(context)) when BuiltinNpuAcceleratorProvider(context).isDeviceSupported() is true (NPU-only options compile as NPU + CPU, so unsupported ops fall back to the CPU). In 2.3.0 isDeviceSupported() is true on every Qualcomm, MediaTek or Samsung SoC from Android 12 and on Google Tensor from Android 16 (2.2.0 used a SoC list), and without the vendor libraries create still returns: the runtime logs Failed to apply compiler plugins: No compiler plugin found (2.2.0 adds Failed to initialize Dispatch API) and compiles for the CPU, so a cascade's reported accelerator reads NPU while the CPU runs. Implement an explicit NPU -> GPU -> CPU fallback cascade (try each CompiledModel.create, catch LiteRtException) to handle non-NPU hardware smoothly. Under Q2 [B], an NNAPI choice in the app's UI becomes the GPU option (Options(Accelerator.GPU, Accelerator.CPU)).
  2. Flex Delegate (FlexDelegate, org.tensorflow.lite.flex, select-tf-ops):

    • Status: Interpreter-only: the Flex delegate ships in the legacy tensorflow-lite-select-tf-ops artifact (68 MB for arm64 in 2.16.1) and in no LiteRT 2.x artifact, so a model with Select TF ops needs the re-export below.
    • Remediation:
      • Remove org.tensorflow:tensorflow-lite-select-tf-ops from build.gradle.kts.
      • Find Flex ops by FlatBuffer inspection (custom ops whose custom_code starts with Flex); check_gpu_compatibility in utilities/litert_gpu_toolkit (this repository) reports such a model as a compile failure, not op by op.
      • Replace Flex ops by re-exporting the model without them (LiteRT-Torch for PyTorch sources; the TensorFlow converter without SELECT_TF_OPS), or, if custom C++ math is required, implement a litert::CustomOpKernel (litert/cc/litert_custom_op_kernel.h, shipped in litert_cc_sdk.zip; guide: https://developers.google.com/edge/litert/next/custom_op_dispatcher).

Step 3: Native Build Toolchain (CMakeLists.txt / NDK)

For native C++ modules, use the C++ SDK the LiteRT docs describe (https://developers.google.com/edge/litert/next/android_cpp_sdk): download litert_cc_sdk.zip from the LiteRT GitHub release that matches the AAR version (https://github.com/google-ai-edge/LiteRT/releases/download/v2.2.0/litert_cc_sdk.zip; 2.3.0 has no GitHub release as of 2026-10-07, so a native module stays on the 2.2.0 AAR and zip until the matching zip exists, and an app with both a native module and the Support library waits for that zip, since litert-support:2.3.0 raises the runtime to 2.3.0), copy libLiteRt.so from the AAR's jni/arm64-v8a/ into its litert_cc_sdk/ directory (LITERT_CC_SDK below), and link the litert_cc_api target. The AAR ships no headers or prefab package, and liblitert_jni.so is the Kotlin binding, not a C++ target:

cmake
# Replace legacy tensorflowlite_jni with the LiteRT C++ SDK
add_subdirectory("${LITERT_CC_SDK}" "${LITERT_CC_SDK}/build")
include_directories("${LITERT_CC_SDK}")

target_link_libraries(your_native_lib
    litert_cc_api
    android
    log
)

Step 4: API & Lifecycle Refactoring (Rewrite Initialization & Dynamic Signatures)

[!IMPORTANT] Never Simple Swap: Do NOT merely perform a search-and-replace of the Interpreter class. Rewrite the model initialization logic to instantiate CompiledModel with an explicit hardware fallback cascade (NPU -> GPU -> CPU). When NPU is selected, prioritize NPU JIT compilation by instantiating an explicit Environment object (Environment.create(context, BuiltinNpuAcceleratorProvider(context))) and passing it to CompiledModel.create; on a supported device the provider itself sets Environment.Option.DispatchLibraryDir and Environment.Option.CompilerPluginLibraryDir to context.applicationInfo.nativeLibraryDir, and the context overload enables the compiler cache under context.cacheDir.

Legacy TFLite APIModern LiteRT V2 Drop-in Replacement
org.tensorflow.lite.Interpretercom.google.ai.edge.litert.CompiledModel
Interpreter(modelFile, options); LiteRT Support's Model.createModel(context, name, options)CompiledModel.create(context.assets, assetName, options, env) or create(filePath, options, env); no ByteBuffer overload (via NPU Environment & Fallback Cascade)
interpreter.run(input, output)compiledModel.run(inputBuffers, outputBuffers) (both List<TensorBuffer> from createInputBuffers() / createOutputBuffers())
interpreter.runForMultipleInputsOutputs(inputs, outputs); Support model.run(inputs, outputs)compiledModel.run(inputBuffers, outputBuffers)
interpreter.runSignature(inputs, outputs, key)compiledModel.run(inputMap, outputMap, key) (Map<String, TensorBuffer>)
Interpreter.Options().setNumThreads(n)options.cpuOptions = CompiledModel.CpuOptions(numThreads = n) on a fresh Options(Accelerator.CPU), not the shared Options.CPU
interpreter.resizeInput(...)none in 2.3.0 (resizeInputTensor is on LiteRT main after 2.3.0; the C++ API has ResizeInputTensor)
GpuDelegate() / NnApiDelegate()CompiledModel.Options(Accelerator.GPU, Accelerator.CPU) / CompiledModel.Options(Accelerator.NPU) (NPU-only compiles as NPU + CPU)
org.tensorflow.lite.flex.FlexDelegatenone: re-export the model without Select TF ops, or a C++ CustomOpKernel (Step 2)
interpreter.getInputTensor(0).shape()compiledModel.getInputTensorType(inputName).layout?.dimensions: by name only, no index or list call; args_0 / output_0 for LiteRT-Torch exports, the graph's tensor names otherwise (images / Softmax in the EfficientNet samples; print interpreter.getInputTensor(0).name() in the Step 0 capture test)
imageProcessor.process(TensorImage.fromBitmap(bitmap)) then interpreter.run(tensorImage.buffer, out)the same ImageProcessor, on com.google.ai.edge.litert.support.image; bridge.write(inputBuffers[0], tensorImage) (templates/SupportBridge.kt: writeFloat(tensorImage.tensorBuffer.floatArray) for a float image, writeInt8 for uint8, after a size check)
Support TensorBuffer.createFixedSize(shape, DataType.FLOAT32) as the output, then TensorLabel / TensorProcessorbridge.read(outputBuffers[0], shape, DataType.FLOAT32); TensorLabel / TensorProcessor unchanged
#include "tensorflow/lite/interpreter.h"#include "litert/cc/litert_compiled_model.h"
#include "tensorflow/lite/c/c_api.h"#include "litert/c/litert_compiled_model.h"

Reference implementation: templates/LiteRtModel.kt (needs androidx.core:core-ktx and org.jetbrains.kotlinx:kotlinx-coroutines-android; it already carries Upgrades 2.A and 2.B, and closes everything it created when a step after CompiledModel.create throws). Copy it next to the app's inference class, replace the package, and keep its shape:

  • LiteRtModel.create(context, assetName, wantNpu, wantGpu = true, numThreads = null) is a suspend factory that runs on the model's own serial dispatcher: it takes the process-wide Environment (created once, with BuiltinNpuAcceleratorProvider when the first caller wants the NPU and isDeviceSupported()), tries CompiledModel.create on NPU, then GPU + CPU, then CPU (wantGpu = false skips the GPU step for an app whose CPU setting means the CPU alone; numThreads is the legacy setNumThreads), catching LiteRtException, and creates the buffers once.
  • suspend fun run(FloatArray): FloatArray does writeFloat / run / readFloat on the same dispatcher; run(write, read) takes two lambdas over the input and output buffer for the Support bridge below; runSync(input) is the entry point for Java callers and for Q3 [B] (the synchronous baseline), from a background thread; close() closes the buffers, then the model (the shared Environment stays open: creating one loads the accelerator libraries, and one created and closed per model reloads them on every load), and is called after the last run has returned.
  • accelerator is the step that did not throw, not proof that every op runs there: NPU-only options compile as NPU + CPU, and a missing or failing compiler plugin is logged and skipped inside create; pass wantNpu = true only with the libraries of Upgrade 2.C in place.
  • Bitmap.toModelInput(size) in the same file is the preprocessing of an app without the Support library: androidx.core.graphics.scale and one loop writing (channel - mean) / std into a FloatArray, with the legacy NormalizeOp(mean, std) values (127.5 / 127.5 in the template; change them to the app's). An app that keeps LiteRT Support leaves its ImageProcessor as it is, deletes Bitmap.toModelInput, and copies templates/SupportBridge.kt too: SupportBridge(model, inputName, outputName), created once after create, reads the byte size of both tensors, and its write / read move a TensorImage or Support TensorBuffer into the model's buffers and back, and refuse one of another size or type (the Kotlin API itself accepts a shorter array, or uint8 bytes in a float input, without an error). Both libraries name a class TensorBuffer; the file imports the Support one as SupportTensorBuffer.
kotlin
val model = LiteRtModel.create(context, "model.tflite", wantNpu = false)
val scores = model.run(bitmap.toModelInput(224))
val bridge = SupportBridge(model.model, "images", "Softmax")
val image = imageProcessor.process(TensorImage.fromBitmap(bitmap))  // LiteRT Support, as before
val output = model.run({ bridge.write(it, image) }, { bridge.read(it, intArrayOf(1, 1000), DataType.FLOAT32) })
val scoresByLabel = TensorLabel(labels, output).mapWithFloatValue

Other one-to-one replacements: FileUtil.loadMappedFile(context, name) + Interpreter(buffer, options) becomes CompiledModel.create(context.assets, name, options, env) (loadMappedFile itself stays for MetadataExtractor); Interpreter.Options().addDelegate(GpuDelegate()) becomes CompiledModel.Options(Accelerator.GPU, Accelerator.CPU) (GPU alone makes create throw when an op is left undelegated, Some ops are not accelerated. Add kLiteRtHwAcceleratorCpu … from LiteRtCompiledModelT::Create in compiled_model.cc, and when the GPU cannot compile the graph at all, Failed to compile model; the legacy delegate left such ops on the CPU. GPU + CPU can still throw on a graph the GPU cannot compile, which is why the template keeps a CPU step after it); interpreter.run(inputByteBuffer, outputByteBuffer) becomes writeFloat / run / readFloat on the model's buffers (writeInt8 / readInt8 for int8 and uint8 tensors).

Step 5: Two-Stage Fast Verification Gate (Karpathy Self-Test)

To maximize execution speed:

  • Stage 1 (Refactoring Gate): Run fast incremental compile checks only (./gradlew compileDebugKotlin compileDebugAndroidTestKotlin or ./gradlew assembleDebug assembleDebugAndroidTest) to verify syntax in seconds. Done when both source sets compile and the only org.tensorflow.lite imports left are DataType and org.tensorflow.lite.schema.* (grep -rn 'import org.tensorflow.lite' app/src | grep -v -E 'lite.DataType|lite.schema' prints nothing: no Interpreter, no delegate, no org.tensorflow.lite.support).
  • Stage 2 (Final Verification Gate): Copy templates/MigrationValidationTest.kt into app/src/androidTest/java/<package>/ (model in app/src/main/assets/), build (./gradlew assembleDebug assembleDebugAndroidTest) and run it on a connected device or emulator (./gradlew connectedDebugAndroidTest); testDebugUnitTest runs only src/test.
    • The test runs the Step 0 input through the app's preprocessing (its ImageProcessor on LiteRT Support, exposed as a function such as preprocess(bitmap): TensorImage, or the function that replaced it, e.g. Bitmap.toModelInput) into the model's first input on the CPU (CompiledModel.Options(Accelerator.CPU)) and compares the first output with the golden file: max abs diff below 1e-3 when the file holds every output value (a tolerance for outputs of order 1; scale it for larger outputs), or the same top-1 index when it holds one (a last resort: a wrong normalization can keep the top-1).
    • Keep the test's own CPU model rather than calling LiteRtModel, which tries NPU and GPU first. Done when it passes on a device and the app's own entry point has run there once (the gate executes neither the cascade nor the app class); with no device attached, stop after Stage 1 and open the PR as a draft that says Stage 2 did not run.

2. Phase 2: Encouraged Performance Upgrades

Once Phase 1 compiles and passes self-testing, the agent applies high-value performance features:

Upgrade 2.A: Off-main-thread execution on one serial dispatcher

The Kotlin CompiledModel API has no runAsync. On the CPU, run() blocks until the outputs are ready; on the GPU it can return before the GPU finishes, and readFloat() blocks until it has. Keep the UI responsive by running create, run and read on one serial dispatcher from a coroutine, and reuse the buffers created once:

kotlin
private val modelDispatcher = Dispatchers.IO.limitedParallelism(1)
suspend fun infer(input: FloatArray): FloatArray = withContext(modelDispatcher) {
    inputBuffers[0].writeFloat(input)
    compiledModel.run(inputBuffers, outputBuffers)
    outputBuffers[0].readFloat()
}

Time run() and readFloat() together; timing run() alone under-reports GPU work. limitedParallelism(1) serializes the calls; it does not pin them to one thread.

Upgrade 2.B: Buffer reuse and zero-copy interop

In Kotlin, tensor buffers come from the model (createInputBuffers() / createOutputBuffers(), or createInputBuffer(name)), are filled with writeFloat / writeInt8, and are reused for every inference; there is no TensorBuffer.createFromAhwb in the Kotlin API. AHardwareBuffer zero-copy interop is part of the C++ API (litert::TensorBuffer::CreateFromAhwb in litert/cc/litert_tensor_buffer.h), so a camera pipeline that needs it runs the whole inference from C++ (Step 3): there is no supported way to hand a C++ buffer to Kotlin run().

kotlin
val inputBuffers = compiledModel.createInputBuffers()
val outputBuffers = compiledModel.createOutputBuffers()
compiledModel.run(inputBuffers, outputBuffers)
inputBuffers.forEach { it.close() }
outputBuffers.forEach { it.close() }
compiledModel.close()

Close the buffers, then the model, then the Environment if the app created one and no other model uses it (the model closes only an environment it created itself; templates/LiteRtModel.kt keeps one per process open).

Upgrade 2.C: NPU JIT Acceleration & Conditional INT8 Quantization

  1. Conditional INT8 Quantization: If NPU JIT is selected and the user opted in, run AI Edge Quantizer (aeq) to generate model_int8.tflite in assets/:
    python
    from ai_edge_quantizer import quantizer, recipe
    qt = quantizer.Quantizer("src/main/assets/model.tflite", recipe.static_wi8_ai8())
    calibration = qt.calibrate({"serving_default": representative_inputs})  # an iterable of {input_name: ndarray}
    qt.quantize(calibration).export_model("src/main/assets/model_int8.tflite")
    (ai-edge-quantizer 0.9.0 names; it pins ai-edge-litert 2.2.0 on the host, which says nothing about the app's runtime version. static_wi8_ai8 is the full-integer recipe that the accuracy-safe-quantization skill in this repo names for NPU targets; recipe.dynamic_wi8_afp32() needs no calibration but keeps float32 activations. Then point the app at model_int8.tflite and rerun the Stage 2 gate with a golden of the top-1 index only; the 1e-3 bound is for the like-for-like float model.)
  2. NPU JIT Runtime Bundling: Package vendor shared libraries in app/src/main/jniLibs/arm64-v8a/ (libLiteRtDispatch_Qualcomm.so, libLiteRtCompilerPlugin_Qualcomm.so, libQnnHtp.so, libQnnSystem.so, etc.); BuiltinNpuAcceleratorProvider reads them from context.applicationInfo.nativeLibraryDir. Compiled artifacts are cached under context.cacheDir by Environment.create(context, …) (enableCompilerCache defaults to true).
  3. Qualcomm FastRPC Permission: The litert-api AAR's manifest (pulled in by litert; in 2.2.0 both AARs carry it) already declares <uses-native-library android:name="libcdsprpc.so" android:required="false" /> (with the OpenCL and Google Tensor entries) and manifest merging brings it into the app; add it by hand only if merging is disabled.
  4. Environment & Fallback Cascade: Create the Environment with BuiltinNpuAcceleratorProvider (on a supported device it sets DispatchLibraryDir and CompilerPluginLibraryDir itself) and implement the Kotlin NPU -> GPU -> CPU cascade / C++ fail-fast pipeline.

3. Phase 3: Automated Pull Request Provisioning

Once self-testing succeeds:

  • GitHub Pull Request: Create a clean git commit, exclude local.properties and .gradle/, and run gh pr create with an attached before/after summary diff and test execution logs.

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Litert Compiled Model Migration AI skill do?

Rapidly migrate an Android application from legacy TensorFlow Lite (TFLite) to the modern LiteRT CompiledModel API (2.x) in Open Source GitHub repositories, keeping the TensorFlow Lite Support Library as LiteRT Support (litert-support 2.3.0). Covers buffer reuse, NPU JIT compilation, a reference implementation, and a two-stage verification gate (compile check, then an on-device parity test against a golden output).

Why use Litert Compiled Model Migration on TypingMind?

Because you install it once and use it with any model. Litert Compiled Model Migration is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Litert Compiled Model Migration in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/google-ai-edge/litert-samples/tree/main/skills/litert-compiled-model-migration. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Litert Compiled Model Migration?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Litert Compiled Model Migration?

As many as you like. As long as a model supports skills, you can use Litert Compiled Model Migration with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Litert Compiled Model Migration AI skill free?

Yes. It is published on GitHub by google-ai-edge under the Apache-2.0 license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇