← research

NPUW Pyramid JIT

Growing an NPU KV cache without paying for it up front · live Qwen2.5-0.5B FP16 measurement · Intel NPU 5 · OpenVINO 2026.3

An NPU fixes its context window when it compiles. Pick it large and you pay for capacity the session never uses — in compile time, in first-token latency, and in decode rate. Pick it small and the session dies at the ceiling. Real agent sessions make that choice impossible in advance: they start small, grow at a steady rate, and stop at an unpredictable point. Copy-on-growth removes the choice — start at the small window the session actually needs, and grow into a larger compiled artifact when the slope says you are about to cross.

1 · Why the NPU, when the same chip has an iGPU

The honest answer is that this is a Pareto choice, not a dominance one. Measured on this machine, Qwen2.5-7B INT8 decode at a right-sized 1024 window:

Decode engineEnergyLatency
Xe3 iGPU170 J6.7 s
NPU 5105 J12.2 s

The NPU costs 38% less energy and takes 1.8× longer. Prefill placement between the two is neutral. So the NPU is the right target whenever energy or thermal headroom is the binding constraint rather than latency — sustained background work, battery, or a device already using the iGPU for something else.

That last case is the non-energy argument, and it is the one shipping systems act on. The iGPU is also the display, compositor, video and game engine; the NPU is otherwise idle. Agent.xpu measures pure-iGPU prefill pinned at 100% utilization and reports a 32.5–37.1% reduction in iGPU utilization by moving work to the NPU. Microsoft gates Windows Studio Effects on an NPU with no iGPU fallback, on the stated rationale of “battery-friendly AI effects that reduce the burden on the device CPU and GPU.” That rationale is stated, not quantified — no vendor page in the surveyed set gives watts, joules or frame rates.

The reviewer's objection, in print — and why it is this work's opening. Every systems paper that ships a working decode path puts decode on the iGPU or CPU, not the NPU. Agent.xpu states the reason in a footnote: “NPU decode is omitted since per-iteration kernel compilation is infeasible under growing sequence length and varying batch size.” HeteroLLM’s scheduler “always chooses the GPU in decoding layers,” and separately notes that its NPU “does not support W4A16 for decoding.” On Snapdragon, Is Your NPU Ready for LLMs? measures NPU decode energy 2–4× higher than CPU or GPU. The named blocker is recompilation under a growing sequence — which is exactly what a progressive window with live state hand-off removes. The state of the art excludes NPU decode for a reason that is a toolchain limit, not a silicon one.

2 · Why the window is fixed at compile time

This is the deployment model of every edge NPU stack, not an OpenVINO quirk.

Intel: “Currently, only models with static shapes are supported on NPU.” The LLM window is MAX_PROMPT_LEN + MIN_RESPONSE_LEN, fixed when the pipeline is constructed.
Qualcomm: --context-length is an option to the export command, and that export “may take 2-3 hours.” The runtime genie_config.json must be edited to match the compiled binary.
Apple: “Use EnumeratedShapes for best performance” — at most 128 pre-declared shapes, and models with open-ended dynamic shapes fall off the Neural Engine at anything but their default shape.

3 · Why no single window is the right one

Across workloads, the right answer differs by task class. In Microsoft’s Azure production trace the conversation service has a median context of 1,020 tokens with 86% of requests under 2K, while the coding service sits at 1,469 with only 62.5% under 2K. Same service family, different correct window.

Within a single on-device agent session it is worse, because the requirement is not drawn once — it grows. From our own OSWorld run on this machine (Holo3, 93 episodes, history eviction disabled so the true demand is visible):

Per-episode contextTokens
first call3,254
episode peak, median18,623
episode peak, p95101,082
episode peak, max160,968

Provision for the observed maximum and the median episode uses 11.6% of it. Growth within one episode is 5.8× at the median and 49× at the worst.

But the rate is predictable even though the endpoint is not. Each step appends a median of 2,227 tokens — one screenshot plus one action — and not a single step in the run shrank the context. So the crossings are near-deterministic: 8K is first exceeded at step 4, 16K at step 7–8, 32K at step 14–15, while session length itself runs from 8 steps at the median to 100 at the tail. That gap is the whole opportunity: you cannot know where a session ends, but you always know when it is about to outgrow the artifact it is running on — several steps ahead of the fact.

4 · What an oversized window costs

AxisCostSource
Compile17 s at 1K → 154 s at 16K → 460 s at 32K this machine, Qwen2.5-1.5B INT8
First tokena short prompt pays the same TTFT as a full-length one Intel, documented
Decode rate20–24% slower as the compiled context grows 16 → 4096, at a fixed number of generated tokensSnapdragon, 1–1.5B
KV memoryonly 20.4% of pre-allocated KV holds real token state PagedAttention, SOSP’23

The decode-rate row is the one that surprises: on a static-shape NPU, decode cost tracks the compiled window rather than the tokens actually generated, so an oversized artifact is slower per token for the entire session. Our own arms show the same sign — the pyramid arms decode at 0.752–0.754 tok/s in the first 1K band against 0.740 for the model compiled at the full 16,384 context.

5 · Why existing mechanisms do not cover this

MechanismWhy it does not solve it on a static-shape NPU
PagedAttention / vLLMblock tables need runtime-mutable indirection; a compiled blob has fixed shapes and offsets, and the ceiling is a compile-time constant
vAttentionCUDA virtual-memory paging with a patched driver; no NPU equivalent, and it still reserves at the maximum context
Core ML EnumeratedShapesselects among fixed pre-declared buckets; never grows one, and carries no state across them
Qualcomm AR-N multi-graphbuckets enumerated at export; re-picking the window costs a 2–3 hour export
OpenVINO chunked prefillfeeds a long prompt in pieces within a still-bounded context; does not grow it
Llumnix-style migrationmoves requests between instances, not across differently-compiled artifacts on one device

No published mechanism copies live KV state from one compiled static artifact into a differently-compiled larger one on the same device.

What this page does not claim. The measured wins here are compile time, time to first token, and decode rate — not memory. Peak RSS in the three-arm sweep was higher for copy-on-grow (21.5 GiB) than for the shared-allocation pyramid (19.7 GiB), so the memory argument remains design intent that this implementation has not yet realised. The energy and trace numbers above are single-testbed and single-run: 93 agent episodes from one model on one benchmark, and no published source reports same-SoC NPU-vs-iGPU joules per decoded token on x86, so the 170 J / 105 J pair is our measurement, not a confirmed general result.