Skip to content

iree-android: stateful prefill/step contract with a reusable prompt-prefix KV snapshot (and an embeddings-input variant for Vulkan) #410

Description

@michalharakal

Motivation, with numbers

runtime-iree-android only offers the stateless redecode contract (step(IntArray) -> IntArray, whole sequence recomputed). On an arm32 Android device (32-bit process, 4× ARMv8, Mali) with FunctionGemma-270M FP32 and IREE 3.11.0:

SEQ CPU arm32 per step Vulkan per step (head sliced, embeddings gathered on the host)
64 10.4 s (6.6 s with the head sliced) 3.16 s
256 41.9 s 7.76 s
1024 77.3 s (sliced head) 29.9 s

A 15-token tool call against an 853-token catalog prompt therefore costs ≈ 19 min on the CPU and ≈ 7.5 min on the GPU. Prompt shortening does not fix it (short descriptions: −29 % tokens, −2/8 accuracy). With a KV contract that prefills the catalog once per process, snapshots the KV state, and per turn prefills only the utterance (≈ 36 tokens) plus ~15 decode steps, the same call projects to 8–29 s on the GPU. There is no other route to an interactive NLU on this class of device, and no arm64 firmware option (the product stays 32-bit).

Proposal

  1. Contract: prefill(ids) -> (lastId, kvHandle), snapshot(kvHandle) -> kvHandle', step(kvHandle, id) -> id, release(kvHandle); KV kept device-side (HAL buffers), never round-tripped through the JVM. The FunctionGemma export already emits gemma_prefill/gemma_with_past graphs (FunctionGemmaExportHarness.kt:258–383) — this is the runtime side of those.
  2. Head sliced to the target position in every graph (separate issue; 36 % of the step and the 32-bit memory cap).
  3. Embeddings-input variant (prefill(ids, embeddings)): IREE 3.11.0's SPIR-V backend cannot legalize the token-embedding gather (failed to legalize operation 'vector.step' on the gather dispatch). Gathering the 640-float rows on the host and passing tensor<1xSEQx640xf32> makes the same graph compile and run on Mali at 2.4–2.6× the CPU speed. The JNI currently accepts only IntArray.
  4. Error reporting through JNI (see the null-return issue).

I can provide the device, the measured graphs (MLIR with the sliced head and the host-gather input), the iree-benchmark-module scripts, and the golden sets for parity.

Context. Measured on 2026-09-03 while bringing FunctionGemma-270M up as an NLU cartridge on an arm32 Android device (Android 14, armeabi-v7a-only 32-bit process, 4× ARMv8 @ 2.0 GHz, Mali GPU) with SKaiNET 0.53.0 + SKaiNET-transformers 0.53.0 from Maven Central. Harness and raw result files: a local harness (can share on request).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions