On-device LLM tasks read long personalized prompts and emit a handful of tokens, so PREFILL — not decoding — eats 88–99% of latency, and the one mobile chip built for exactly that math, the NPU, sat unused because its static graphs, per-tensor-only quantization, and weak floating point clash with how LLMs work; llm.npu closes all three gaps with fixed-size chunk-sharing graphs, shadow execution of the 0.1–0.3% outlier channels on the CPU, and out-of-order subgraph scheduling — delivering 22.4× faster prefill, 30.7× energy savings, and the first 1,000+ tokens/sec prefill on a phone.
The argument in one glance
-
Problem
On-device LLMs are private — and unusably slow
Privacy pushes LLM inference onto phones (Apple Intelligence, Android AICore), and 1–10B-parameter models are now good enough. But the latency is not: one step of UI automation with Qwen1.5-1.8B takes 8.1 seconds — a 5-step task exceeds 40 seconds — and an automated email reply with Gemma-2B takes 26.7. The profiling twist: it's not token generation that hurts. Mobile tasks feed LONG prompts (UI view hierarchies of 500–827 tokens; email context of 1,168–1,835) and emit TINY outputs (3.5–7.9 tokens on average) — so prefill is 88.3–98.8% of latency on mobile CPUs, 54.2–91.7% on mobile GPUs.
-
What people did
Optimize decoding, or lean on mobile CPU/GPU engines
The research community had concentrated on the decode stage — activation sparsity, speculative decoding — while production engines (llama.cpp, MNN, TFLite, MLC-LLM) execute LLMs on mobile CPUs and GPUs, processors designed for app logic and rendering, with a fraction of a datacenter GPU's parallelism.
-
Why it fell short
The right silicon exists in every phone — and runs no LLMs
Modern SoCs ship Neural Processing Units — Qualcomm's Hexagon reaches 73 INT8 TOPS, beating mobile CPUs/GPUs by up to 18×/4× on CNNs at better energy — yet NO engine ran LLMs on them, for three structural reasons. NPUs compile static-shape graphs, but prompts vary in length: rebuilding the Gemma-2B graph takes ~11 seconds, and padding to max context burns the compute you came for. NPUs do per-TENSOR quantized MatMul only, but LLM activations have outliers that state-of-the-art quantization handles per-GROUP — faking that on the NPU splits every MatMul into sub-MatMuls plus float reductions, a 10.7× overhead. And quantization can't eliminate floating point (LayerNorm, attention), where NPUs are weak — naively offloading those stretches the critical path.
-
This paper's idea
Reconstruct the prompt, the tensors, and the schedule
llm.npu — the first LLM engine on commodity mobile NPUs — re-shapes the problem at three levels. Prompt level: variable-length prompts split into fixed 256-token CHUNKS running on pre-built graphs, with prompt-length-independent operators (FFN, linears) SHARED across chunks (4× memory saving). Tensor level: per-tensor INT8 MatMuls stay on the NPU while the 0.1–0.3% of channels holding outliers run as a compact float "shadow" MatMul on the CPU in parallel — with hot-channel caching and importance-based pruning eliminating most sync entirely. Block level: subgraphs are scheduled OUT OF ORDER across NPU and CPU/GPU, filling the 37% pipeline bubbles naive overlap leaves. Results: 22.4× average faster prefill, 30.7× energy savings, up to 32.8× end-to-end, <1% accuracy loss — and 1,000+ tokens/sec prefill on a phone, a first.
Why this problem is worth solving
The on-device LLM story is usually told as a privacy story —
your emails, screenshots, and messages never leave the phone —
but this paper’s contribution begins with a measurement that
inverts the field’s assumptions. Datacenter LLM serving is
dominated by the decode stage (autoregressive, memory-bound,
one token per pass), and this site’s datacenter papers optimize
accordingly. Mobile workloads flip the ratio: a UI-automation
agent ingests an entire screen’s view hierarchy to emit a
three-token action; an email auto-replier reads 1,800 tokens of
history to write two sentences. Long input, tiny output — so
the prompt-processing stage: all input tokens
are encoded in parallel, a compute-bound burst of matrix-matrix
work, in contrast to the memory-bound one-token-at-a-time decode
stage. On mobile tasks with long prompts and short answers,
prefill is nearly all of the latency
That reframing points at underused silicon. Every flagship SoC
ships an
neural processing unit — the mobile SoC’s ML
accelerator (Qualcomm Hexagon, Google Edge TPU), built for
quantized integer tensor math: up to 73 INT8 TOPS, far beyond
mobile CPU/GPU throughput, at better energy and without
contending with app logic or rendering. Designed in the CNN
era: static compiled graphs, per-tensor quantization, weak
floating point
What people did before, and where it broke
- Decode-side acceleration (activation sparsity like PowerInfer-v2, speculative decoding): real gains, wrong stage — on DroidTask-style workloads, decode is 1–12% of latency.
- CPU/GPU engines (llama.cpp, MNN, TFLite, MLC-LLM): mature, but mobile CPUs/GPUs are parallelism-starved by design; the 8.1-second UI step is llama.cpp on a flagship CPU doing its best.
- The NPU’s three refusals. (1) Static shapes: NPU
toolchains compile and heavily optimize a fixed-shape graph;
per-prompt-length rebuilds cost ~11 s (Gemma-2B) — longer than
the inference — and max-length padding wastes the TOPS.
(2) Per-tensor quantization only: LLM activations carry
; emulating per-group MatMul as sub-MatMuls plus float reductions costs up to 10.7×. (3) Floating point remains: LayerNorm, attention softmax, and dequantization stay FP even in aggressively quantized models, and NPUs handle FP poorly — naive CPU offload of those ops serializes the pipeline.
rare activation channels with magnitudes far beyond the rest — a transformer quirk that wrecks uniform INT8 quantization, because one scale factor stretched to cover outliers crushes everything else’s precision. State-of-the-art quantizers answer with per-GROUP scales, which mobile NPUs cannot execute natively
How llm.npu works
Three-level reconstruction: prompt, tensor, block
-
Chunk-sharing graphs (prompt level) new
Prompts split into fixed 256-token chunks executed on PRE-BUILT NPU subgraphs — no per-length rebuilds, minimal padding (only the last chunk). Causality makes this sound: chunk i's attention reads the KV cache of chunks ≤ i, so chunks process sequentially like a sliding prefill. Loading one graph per chunk would cost 2–4× the model's own memory, so llm.npu identifies prompt-length-INDEPENDENT operators (FFN, QKV/O linears — static ops) and shares one copy across all chunk graphs, keeping only attention (dynamic KV shapes) per-chunk: up to 75% (7.2 GB) memory saved at 1,024 tokens.
-
Shadow outlier execution (tensor level) new
The NPU keeps fast per-tensor INT8 MatMuls. By the associative law, x/s·w splits exactly into a clipped INT8 part (NPU) plus the overflow beyond ±127 (outliers) — extracted at runtime into a compact tensor (0.1–0.3% of channels) and multiplied in float on the CPU IN PARALLEL, then merged. Accuracy of per-group quantization, speed of per-tensor.
-
Making shadows nearly free new
Two observations kill the overheads. Outliers concentrate: under 3% of channels produce over 80% of outliers, so only those "hot" channels' float weights stay in RAM (rest fetched from disk on demand, overlapped with NPU work) — 34.3% memory saved. And most outliers don't matter: importance profiling (outlier magnitude relative to quantization scale) shows layers near the model's input and output are sensitive while the middle 85% are not — their outliers are simply pruned offline, eliminating CPU–NPU synchronization for most layers at negligible accuracy cost.
-
Out-of-order subgraph execution (block level) new
FP ops (LayerNorm, attention, shadow MatMuls) run on CPU/GPU; integer linears on NPU. In-order overlap leaves 37% bubbles on the critical path. But chunked execution creates many independent (chunk, subgraph) units with two clean dependency rules — attention needs the SAME layer's outputs from all earlier chunks; everything else needs only the previous subgraph of its own chunk — so any input-ready subgraph may run early. Optimal scheduling reduces to TSP (NP-hard, prompt-dependent), so an online heuristic picks, at each step, the CPU/GPU subgraph that most reduces NPU stalls — the NPU being the critical path. Worth 29.7% latency and 20.1% energy.
-
Implementation
Built on the MLLM framework and Qualcomm's QNN stack: 10K lines of C/C++ and assembly, supporting Qwen, Gemma, phi, LLaMA, and Mistral family models. Open source.
The idea
Static hardware, dynamic workload — so quantize the dynamism. The NPU’s compile-once model and the prompt’s variable length look irreconcilable until you notice that causal attention already processes text left-to-right: nothing about correctness requires the whole prompt in one graph. Chunking discretizes prompt length into a single reusable graph shape — the CNN-era constraint is satisfied by re-batching the language rather than re-engineering the hardware. The memory insight completes it: what varies per chunk is only attention’s KV geometry; the parameter-heavy operators (FFN, projections) are shape-identical across chunks and can be loaded once. The chunk size itself (256) is an empirical sweet spot — long enough to saturate the NPU’s parallelism, short enough to bound last-chunk padding.
Split the tensor where the hardware splits. Shadow outlier execution is an exact algebraic decomposition, not an approximation: clip-to-INT8 on the NPU plus the clipped-off remainder in float on the CPU, summed at the end. What makes it a systems idea is recognizing that the remainder is tiny (0.1–0.3% of channels), stable (hot channels persist across inputs), and mostly ignorable (importance profiling licenses pruning 85% of layers’ outliers outright) — so the accurate path costs almost nothing in time, memory, or synchronization. This is the same shape of insight as per-group quantization, but re-partitioned along the hardware boundary instead of the tensor boundary: NPUs get the uniform math they like, CPUs get the sparse exceptional math they’re fine at.
Chunking’s second dividend: an instruction window. Once execution is (chunk × subgraph) units with explicit dependencies, the classic ILP playbook applies — llm.npu’s out-of-order scheduler is a dataflow engine over transformer pieces, filling NPU stalls with whichever CPU-side subgraph unlocks the most downstream NPU work. The heuristic’s framing is the mature part: since the NPU is the critical path (its work is ~2× the CPU’s), the objective is not CPU utilization but NPU stall reduction — another instance of optimizing the end-to-end surface rather than a per-processor proxy.
A 1,024-token UI-automation prompt, through llm.npu
From screen dump to first action token on a Xiaomi 14
Chunk = 256 tokens on a pre-built NPU graph. Subgraph = a schedulable piece of a transformer block (linears on NPU; LayerNorm/attention/shadow on CPU). Hot channels = the <3% of activation channels producing >80% of outliers. Numbers from the paper's Xiaomi 14 evaluation.
Step 1 of 6
The prompt arrives; no graphs are built
The UI view hierarchy plus command tokenize to ~1,024 tokens — four 256-token chunks, mapped onto pre-built, pre-optimized NPU subgraphs. The 11-second per-length graph rebuild that made naive NPU offload absurd never happens; only the final chunk carries padding.
graph prep: offline, once — not per prompt shared static ops: one copy of FFN/linear weights across all chunks
Why it works
- Mobile tasks read much and write little → prefill dominates (88–99%) and prefill is compute-bound → the NPU’s integer TOPS are the right resource, and the whole problem reduces to feeding them despite CNN-era interface constraints.
- Causal attention processes tokens left-to-right anyway → fixed-size chunks on pre-built graphs preserve semantics exactly → the static-shape constraint is satisfied without rebuild latency or padding waste, and sharing length-independent operators keeps memory at ~1× model size.
- The quantization-hostile part of activations is tiny, spatially concentrated, and mostly unimportant → exact algebraic splitting sends 99.8% of the work to the NPU as uniform INT8 and 0.2% to the CPU as sparse float → per-group accuracy at per-tensor speed, with sync eliminated for 85% of layers by importance pruning.
- Chunking multiplies schedulable units with clean dependencies → out-of-order execution fills the 37% bubbles that in-order CPU/NPU overlap leaves → the heterogeneous pipeline behaves like a dataflow machine whose objective is NPU-stall minimization, the true critical path.
- Everything is post-training and engine-level → no model retraining, no hardware changes, five model families on commodity phones → the gap closed is purely systems impedance-matching, which is why it generalizes.
What it costs
- Vendor-stack coupling. Built on Qualcomm QNN and Hexagon; the techniques are principled but the 10K lines of C/C++/ assembly are per-ecosystem work, and other NPUs (Edge TPU, Apple ANE) have their own toolchains and quirks.
- Prefill-only. Decoding — memory-bound, GEMV-shaped — is untouched and stays on CPU/GPU; workloads with long outputs (chat generation) see proportionally less benefit than UI-automation-style read-heavy tasks.
- Offline profiling per model. Outlier importance and hot channels are profiled on large corpora offline; chunk length is tuned per device. Distribution-shifted inputs could in principle surface unprofiled outliers.
- Approximation at the margins. Pruning 85% of layers’ outliers and clipping to INT8 is validated to under-1% accuracy loss on the evaluated benchmarks — empirical, per-model assurance rather than a guarantee.
- Disk-in-the-loop. Cold outlier weights stream from storage; the overlap hides it in measurements, but it couples inference latency to flash behavior under memory pressure.
- Chunk-size padding. The last chunk pads to 256; very short prompts lose proportionally more, which is why the wins are quoted at long-prompt operating points.
The evaluation
Five mobile-sized models (Qwen1.5-1.8B, Gemma-2B, phi-2 2.7B, LLaMA-2-7B, Mistral-7B), four benchmark suites spanning the motivating applications (DroidTask UI automation, LongBench context-aware generation, Persona-Chat summarization), two commodity phones, five baselines covering industrial engines and research prototypes. Accuracy is held within 1% of FP16 throughout — the quantization tricks are validated, not assumed.
Where mobile prefill can run
| Property | Mobile CPU engines (llama.cpp, MNN) | Mobile GPU engines (TFLite, MLC-LLM) | llm.npu (NPU-centric) |
|---|---|---|---|
| Prefill share of latency (motivating tasks) | |||
| Why the NPU was unusable | — | — | |
| Prefill speed (1,024-token prompts) | Baseline | Varies; MLC-LLM strongest | |
| Energy | High — long CPU bursts at full clocks | Better | |
| Accuracy & end-to-end | FP16-class reference | Quantized variants vary |
Check yourself
Check your understanding
1. Datacenter LLM serving papers treat DECODE as the bottleneck and optimize batching, KV caches, and memory bandwidth. Predict why on-device inference inverts this — making PREFILL 88–99% of latency.
2. Mobile NPUs compile static-shape graphs, but prompts vary in length — and rebuilding Gemma-2B's graph takes ~11 seconds. How do chunk-sharing graphs resolve this, and what makes the approach EXACT rather than approximate?
3. LLM quantization needs per-GROUP scales because of activation outliers, but NPUs only execute per-TENSOR quantized MatMuls (emulation costs 10.7×). How does shadow outlier execution get both, and what two observations make it nearly free?
4. With linears on the NPU and float ops (LayerNorm, attention, shadows) on the CPU/GPU, naive in-order overlap still leaves 37% pipeline bubbles. Why does out-of-order execution fix this, and why is the scheduler's objective "NPU stall reduction" rather than overall utilization?
Sources
- arXiv preprint 2407.05858 (v2) — full text used for this explainer
- ACM DL: Fast On-device LLM Inference with NPUs — ASPLOS 2025
- mllm engine on GitHub