Skip to content

Npu decode funcall gating#254

Draft
cavusmustafa wants to merge 1 commit into
ravi9:dev_backend_openvinofrom
cavusmustafa:npu-decode-funcall-gating
Draft

Npu decode funcall gating#254
cavusmustafa wants to merge 1 commit into
ravi9:dev_backend_openvinofrom
cavusmustafa:npu-decode-funcall-gating

Conversation

@cavusmustafa

Copy link
Copy Markdown
Collaborator

openvino: NPU decode fix for gemma3/4 — disable NPUW funcall for per-layer-embedding archs

On NPU, the decode (1-token) model wraps its non-repeated blocks as NPUW funcall
subgraphs. For gemma3/4-style architectures those blocks hold the per-layer input
embedding (inp_per_layer / per_layer_proj), so each pays a per-call function
prologue (tensor bind + closure unpack) on every decoded token. At batch-1 decode
this overhead dominates.

Fix: the decode model gets its own compile config, and for archs carrying a
per-layer input embedding we set NPUW_FUNCALL_FOR_ALL=NO for decode only.
Prefill is unchanged; other archs and CPU/GPU are unaffected. Arch detected via a
per_layer tensor in the graph (ModelParams::has_per_layer_embd).

Results (NPU, c=1024, decode steady-state):

Model Decode before Decode after Decode-model compile Output
gemma4 E2B Q4_K_M ~1.6 t/s ~13 t/s (~8x) ~62 s → ~167 s correct
Llama-3.2-1B Q4_K_M ~9.8 t/s ~9.8 t/s (no change) unchanged correct

Trade-off: decode-model compile time rises for gated archs (paid once per
process); prefill and CPU/GPU are unaffected.

Adds optional env overrides for experimentation:
GGML_OPENVINO_[DECODE_]NPUW_FOLD / _FUNCALL_FOR_ALL.

…layer-embedding archs

On NPU, the decode (1-token) model compiles the non-repeated blocks as NPUW
"funcall-everywhere" subgraphs. For gemma3/4-style architectures those blocks
carry the per-layer input embedding (inp_per_layer / per_layer_proj), so each
one pays a per-call function prologue (tensor bind + closure unpack) on every
decoded token. At batch-1 decode this per-call overhead dominates and makes
decode ~8x slower than it should be (gemma4 E2B Q4_K_M: ~1.6 -> ~13 t/s).

Fix: give the decode model its own compile config (compile_config_decode) and,
for archs that carry a per-layer input embedding, set NPUW_FUNCALL_FOR_ALL=NO
for the decode model only. Prefill is unchanged (funcalls there are amortized
over the whole prompt). The arch is detected via a per_layer tensor in the graph
(ModelParams::has_per_layer_embd); other archs and CPU/GPU are unaffected.
Verified no change on Llama-3.2-1B (no per-layer embedding).

Adds optional env overrides for experimentation:
  GGML_OPENVINO_NPUW_FOLD / _FUNCALL_FOR_ALL          (both prefill+decode)
  GGML_OPENVINO_DECODE_NPUW_FOLD / _FUNCALL_FOR_ALL   (decode only; precedence)
@cavusmustafa cavusmustafa force-pushed the npu-decode-funcall-gating branch from 74ad6de to 18d9d77 Compare July 15, 2026 23:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant