Skip to content

Xuejun/enable npu opt v1 - #305

Open
zhaixuejun1993 wants to merge 23 commits into
ravi9:dev_backend_openvinofrom
zhaixuejun1993:xuejun/enable_npu_opt_v1
Open

Xuejun/enable npu opt v1#305
zhaixuejun1993 wants to merge 23 commits into
ravi9:dev_backend_openvinofrom
zhaixuejun1993:xuejun/enable_npu_opt_v1

Conversation

@zhaixuejun1993

Copy link
Copy Markdown
Collaborator

This pull request introduces several enhancements and configuration options to the OpenVINO integration in ggml, primarily focused on improving NPU (Neural Processing Unit) support, quantization flexibility, and memory efficiency. The changes expand environment variable controls, add new quantization pathways, and optimize certain operations for better performance on NPUs.

Key changes include:

Expanded NPU and Quantization Configuration

  • Added support for new environment variables to fine-tune NPU compilation and runtime behavior, including GGML_OPENVINO_NPU_COMPILER_TYPE, GGML_OPENVINO_NPUW_FUNCALL_FOR_ALL, GGML_OPENVINO_NPUW_UNFOLD_IREQS, GGML_OPENVINO_COMPILATION_NUM_THREADS, GGML_OPENVINO_NPU_CONFIG, GGML_OPENVINO_TOKEN_EMBD_I8, GGML_OPENVINO_TOKEN_EMBD_I4, GGML_OPENVINO_NPU_KEEP_Q4_0, GGML_OPENVINO_COMPILE_FROM_IR, and GGML_OPENVINO_KV_SCATTER_ELEMENTS. These allow more granular control over compilation strategies, quantization types, and memory/performance trade-offs. [1] [2] [3]

  • Enhanced quantization logic for embedding tables, allowing selection between F16, int8, or group-128 int4 representations based on new environment variables. This enables memory and performance optimizations, especially for tied embeddings.

Quantization and Memory Optimizations

  • Updated the quantize_q4_0 function and its usage to support streaming quantization with correct block offset handling, enabling efficient chunked quantization and reducing peak memory usage during compilation. [1] [2] [3] [4] [5] [6] [7]

  • Modified the get_rows operation to gather compressed (packed) embedding rows when possible, avoiding unnecessary materialization of large dequantized tables and improving efficiency for tied embeddings.

NPU-Specific Operation Improvements

  • Added an option to use ScatterElementsUpdate instead of ScatterUpdate for key-value cache updates on NPU, reducing unnecessary data copying and improving performance for single-row writes. [1] [2]

Bug Fixes and Code Cleanups

  • Fixed a bug in ROPE operation support checks, ensuring correct dimension comparisons for view operations.

  • Included missing header for extra OpenVINO helpers in set_rows.cpp.## Overview

Additional information

Requirements

mostafafaheem and others added 23 commits August 28, 2026 09:36
Static shapes:
- get_graph_input_shape() left the s_copy / s_copy-leaf inputs dynamic
  ([1,1,1,-1]) even in static mode, which propagated a dynamic slot dim through
  GET_ROWS into the conv/GDN state, the state reshapes and the GDN output.
- With -np 1 the s_copy defrag remainder gathers zero rows; short-circuit that
  CPY to the untouched cache instead of emitting a degenerate Slice/Concat, and
  skip binding its zero-byte ggml tensor as an output (the dynamic path already
  did the latter, the static path wrote the full cache over a 0-byte buffer).

Token-count independence:
- In static mode the compiled model's token count is the prefill chunk size or
  1, not the captured cgraph's. Offsets derived from the captured count were
  therefore wrong. Anchor the GDN state slice at the end of the packed
  [attn | state] output and drop the rs_src_begin runtime inputs, and make
  VIEWs over the GDN output / conv_input pass through so the consumer does the
  slicing.
- CONT could not identify its token axis when the graph was captured with a
  single token (every trailing dim has the same stride and size 1) and baked
  the captured shape into the prefill model.

Chunked prefill:
- The last chunk is padded with fabricated tokens. Attention masks them, but
  the recurrent path folded them into cache_r/cache_s permanently. Add a
  chunk_valid_len runtime input, use it to zero g and beta for padded steps
  (making the recurrence an exact identity) and to end the conv snapshot window
  at the last valid token, and disable the recurrent-cache reset after the
  first chunk so earlier chunks are not wiped.
- get_is_prefill() and the chunk loop bound read inp_pos->ne[0] directly, but
  IMROPE stacks 4 position planes, so every decode step was run through the
  padded prefill model and the loop ran extra out-of-bounds chunks.

cache_rs_reset_idx/len now stay runtime Parameters in static mode, since
can_reuse_statically() does not invalidate the cached model on ComputeParams
changes. Add GGML_OPENVINO_FORCE_STATIC to exercise the static path on CPU.
Add GGML_OPENVINO_NPU_COMPILE_CONFIG to the backend's cached environment so callers can configure the NPU compiler without using the generic property escape hatch.

When the value is non-empty, pass it to OpenVINO as NPU_COMPILATION_MODE_PARAMS. This enables settings such as optimization-level=3 for NPU compilation while preserving the existing behavior when the variable is unset and leaving CPU and GPU configuration unchanged.

Document the variable, its NPU-only scope, and the optimization-level=3 example in the OpenVINO backend runtime configuration table.
Groundwork that was sitting uncommitted in the tree, kept separate from the
tied-embedding change that follows:

- env passthrough for NPU_COMPILER_TYPE, NPUW_FUNCALL_FOR_ALL,
  NPUW_UNFOLD_IREQS and COMPILATION_NUM_THREADS, plus a generic
  GGML_OPENVINO_NPU_CONFIG KEY=VALUE escape hatch for bisecting plugin
  options without a rebuild
- GGML_OPENVINO_KV_SCATTER_ELEMENTS: ScatterElementsUpdate instead of
  ScatterUpdate for the single-row KV write, which is not in-place on NPU
- GGML_OPENVINO_REDUCE_COMPILE_MEM: stream requantization in chunks rather
  than materializing the whole tensor
- GGML_OPENVINO_RELEASE_WEIGHTS / GGML_OPENVINO_COMPILE_FROM_IR fail-fast
  guards for recompiles after host weight buffers are dropped
- GGML_OPENVINO_TOKEN_EMBD_I8 and GGML_OPENVINO_NPU_KEEP_Q4_0 opt-ins
For a tied embedding the token_embd table is also the lm_head weight, so one
OV constant feeds both a GET_ROWS at layer 0 and a MUL_MAT after the last
layer. Those two consumers land in different NPUW partitions, and a value
crossing a partition boundary has to be materialized, so gathering rows out
of the dequantized table forces the whole table into real memory. That is why
compressing the table used to lose: the dequantization traffic cost more than
the smaller weights saved.

gather_compressed_rows() walks the dequantization chain emitted by
make_int4_weights/make_int8_weights, gathers the requested rows out of the
leaf Constants, and rebuilds the chain on top of them. The row lookup then
reads packed rows and dequantizes only those, and the chain feeding the
lm_head matmul is left with a single consumer, so it stays compressed. Only
the node types those two builders emit are recognized; anything else falls
back to the plain Gather, and the rewrite is skipped for tables under 1M
elements and for chains with no low-precision Constant, which leaves the
current f16 default bit for bit unchanged.

GGML_OPENVINO_TOKEN_EMBD_I4 opts token_embd into group-128 int4, which is
only worth doing together with the above.

phi4-mini Q4_0 on Panther Lake, -p 512 -n 32 -b 512 -ub 512:

  arm                        pp512    tg32   device p50
  f16 (default)             344.34   10.70     89.71 ms
  int4 + packed gather      346.17   13.11     70.59 ms
  int4, gather not sunk     328.97    8.26    118.11 ms

Decode weight traffic goes from 2.90 GB to 1.99 GB per step, and the measured
19.1 ms saving matches the 21.5 ms that predicts at the 42.4 GB/s the device
reaches on an f16 matmul of this shape.

Accuracy of the int4 embedding is NOT established. llama-perplexity cannot
run on this backend (it asks for all-position logits, which the static NPU
graph has no shape for), and the one greedy sample that could be compared
diverged and read slightly worse. The flag stays opt-in for that reason.
Allow the Q/K ROPE subviews used by Phi-4 so the model remains a single OpenVINO graph, avoiding NPUW f16 interconnect output mismatches. Serialize prefill and decode static model compilation because concurrent NPUW compilation is unstable.
@ravi9
ravi9 force-pushed the dev_backend_openvino branch 2 times, most recently from fd9bc04 to 33237ab Compare September 3, 2026 21:10
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.

5 participants