Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
65db590
OpenVINO backend: 1) enable gpt-oss moe on OV bk; 2) enable mxfp4 sup…
zhaixuejun1993 Jun 23, 2026
44fcf81
OpenVINO backend: disable TOPK_MOE op test
zhaixuejun1993 Jun 24, 2026
c867f51
OpenVINO Backend: Add op FILL support
zhaixuejun1993 Jun 25, 2026
53ce035
OpenVINO backend: enable set rows with multi dims
zhaixuejun1993 Jun 25, 2026
c1d775f
fix the name missmatch in setrow + view
zhaixuejun1993 Jun 25, 2026
186db4f
OpenVINO backend: enable op GGML_UNARY_OP_SIGMOID
zhaixuejun1993 Jun 25, 2026
68da958
OpenVINO Backend: enable SQR & SQRT
zhaixuejun1993 Jun 25, 2026
7631aaf
OpenVINO backend: 1) ensure unique node names for OpenVINO; 2) add or…
zhaixuejun1993 May 27, 2026
0529e9e
OpenVINO backend: enable fallback for openVINO to CPU backend
zhaixuejun1993 May 27, 2026
bd532e4
OpenVINO backend: fix accurace issue in gemma3n arch test
zhaixuejun1993 Jun 30, 2026
026f1d0
fix mpt failed case
zhaixuejun1993 Jun 30, 2026
df1fbfd
OpenVINO backend: clean nodeinfo
zhaixuejun1993 Jul 3, 2026
b201559
OpenVINO Backend: enable zero-size copy for view
zhaixuejun1993 Jul 7, 2026
116c048
add concat ssm_conv in compute_dynamic_dim
wine99 May 21, 2026
98c9e93
OpenVINO backend: disable EXP with FP32, which failed in op test. Roo…
zhaixuejun1993 Jun 16, 2026
2736fa5
OpenVINO backend: fix CPY op test failed issue
zhaixuejun1993 Jun 16, 2026
63fdf3d
OpenVINO backend: fix GATED_DELTA_NET op test failed issue
zhaixuejun1993 Jun 16, 2026
f223472
handle in-place op, handle qwen35 dynamic clearing of cache in cgraph
wine99 Jun 18, 2026
98cbce0
handle qwen35 dynamic clearing of cache correctly
wine99 Jun 23, 2026
af70b49
Enable qwen35 dense multi seq
wine99 Jun 29, 2026
26f6a73
Fix qwen35 9b gqa
wine99 Jun 30, 2026
1be15a9
Fix after rebase
wine99 Jul 1, 2026
3ab8fce
Disable SOLVE_TRI
wine99 Jul 7, 2026
1192ab5
openvino: fix NEOX RoPE accuracy on GPU stateful (mixed-rank Multiply)
cavusmustafa Jul 7, 2026
0ab29cb
OpenVINO backend: 1) remove the unique name in llama.cpp; 2) add new …
zhaixuejun1993 Jul 8, 2026
f2088c5
OpenVINO Backenb: remove changes in llama.cpp
zhaixuejun1993 Jul 8, 2026
430d1db
Doc change (use x64 Native Tools Command Prompt for VS)
virajwad Jul 9, 2026
0f0047e
Cleaner sentence
virajwad Jul 9, 2026
7871c83
OpenVINO Backend: cache key upgrade includes all src name
zhaixuejun1993 Jul 9, 2026
7da52bc
OpenVINO Backend: enable llama arch test on ci
zhaixuejun1993 Jul 10, 2026
a5bd654
OpenVINO Backend: move parameter node creating from decoder into tran…
zhaixuejun1993 Jul 3, 2026
3d999cb
OpenVINO Backend: create extra input ov node move from decoder to tra…
zhaixuejun1993 Jul 3, 2026
9dd17d0
fix for op regression due to is_model_splitted
suryasidd Jul 17, 2026
e2e52ac
openvino: fix CPY writeback for recurrent state rollback
wine99 Jul 27, 2026
e2b116e
qwen35 moe
wine99 Jul 20, 2026
0303d27
optimize MoE expert aggregation with ReduceSum
wine99 Jul 27, 2026
36feef4
Skip GET_ROWS inaccurate test
wine99 Jul 30, 2026
2d6c8bf
openvino: fallback dynamic MUL_MAT_ID shapes
zhaixuejun1993 Jul 31, 2026
3885699
OpenVINO Backend: fix error in arch test model mpt
zhaixuejun1993 Jul 31, 2026
518e507
fix error caused by cpy in arch test model kimi-linear
zhaixuejun1993 Jul 31, 2026
3485ef3
OpenVINO Backend: fix error in arch test model minimax-m3
zhaixuejun1993 Jul 31, 2026
4a0e802
openvino: fix GPU mul_mat_id op tests
zhaixuejun1993 Aug 3, 2026
524f869
ggml-openvino: add GGML_OPENVINO_RELEASE_WEIGHTS to reclaim host weig…
Jun 25, 2026
ef9f854
ggml-openvino: stream weight requantization to cut the compile-time R…
Jun 30, 2026
868b022
ggml-openvino: avoid redundant token_embd requantization at compile
Jun 30, 2026
63e06ce
ggml-openvino: gate compile-memory optimizations behind GGML_OPENVINO…
Jun 30, 2026
747b4e9
ggml-openvino: add frontend model cache (GGML_OPENVINO_MODEL_CACHE_DIR)
Jun 30, 2026
4be0fd7
ggml-openvino: harden frontend model cache correctness
zhaixuejun1993 Jul 31, 2026
db15f2e
ggml-openvino: add memory optimization umbrella switch
zhaixuejun1993 Jul 31, 2026
a0108b0
ggml-openvino: rename compiled model cache env
zhaixuejun1993 Jul 31, 2026
30905fe
docs: document OpenVINO memory optimization env vars
zhaixuejun1993 Aug 4, 2026
12f3c78
ggml-openvino: fix Qwen3VL crash and deepstack correctness bug
wine99 Aug 3, 2026
6491854
ggml-openvino: fix IMROPE inp_pos padding for NPU static shapes
wine99 Aug 4, 2026
289ddb3
disable test-llama-archs tests.
ravi9 Aug 12, 2026
c66a9c9
openvino: gate fallback with env var
zhaixuejun1993 Aug 12, 2026
00457ce
Revert changes in test-llama-archs
wine99 Aug 12, 2026
2e0c1be
Apply editor config
wine99 Aug 12, 2026
ae02f5e
reject CPY with quantized destination as unsupported
ravi9 Aug 12, 2026
37b164f
Merge pull request #283 from ravi9/ravi9-patch-1
ravi9 Aug 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/backend/OPENVINO.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ cmake -B build/ReleaseOV -G Ninja -DCMAKE_BUILD_TYPE=Release -DGGML_OPENVINO=ON
cmake --build build/ReleaseOV --parallel
```

- **Windows:** Open a **Developer Command Prompt for VS 2022** (so the MSVC toolchain is on `PATH`), then run:
- **Windows:** Open **x64 Native Tools Command Prompt for VS** (so the MSVC toolchain is on `PATH`), then run:

```cmd
C:\Intel\openvino\setupvars.bat
Expand Down Expand Up @@ -710,11 +710,15 @@ Boolean flags follow a uniform convention: set to a **positive integer** (e.g. `
|-----------------------------------|-----------|------------|-------------------------------------------------------------------------------------------------------------|
| `GGML_OPENVINO_DEVICE` | String | `CPU` | Specify the target device (CPU, GPU, NPU). On systems with multiple GPUs, use `GPU.0` or `GPU.1` to explicitly target specific GPU. See [OpenVINO GPU Device](https://docs.openvino.ai/2026/openvino-workflow/running-inference/inference-devices-and-modes/gpu-device.html). When set to **NPU**, static compilation mode is enabled for optimal performance. |
| `GGML_OPENVINO_CACHE_DIR` | String | `not set` | Directory for OpenVINO model caching (recommended: `/tmp/ov_cache`). Enables model caching when set. **Not supported on NPU devices.** |
| `GGML_OPENVINO_COMPILED_MODEL_CACHE_DIR` | String | `not set` | Directory for the frontend compiled-model cache. When set, OpenVINO compiled models are exported as blobs and imported on later runs to skip weight requantization, graph conversion, and compilation for matching single-graph models. |
| `GGML_OPENVINO_PREFILL_CHUNK_SIZE`| Integer | `256` | Token chunk size for **NPU** prefill (NPU-only; ignored on CPU/GPU). Must be a positive integer; otherwise the default is used. |
| `GGML_OPENVINO_STATEFUL_EXECUTION`| Boolean | `0` | Enable stateful KV cache for better performance. Recommended on CPU, GPU. |
| `GGML_OPENVINO_DISABLE_CACHE` | Boolean | `0` | Disable the in-process compiled-model / decoder cache (cache is on by default). Set to `1` to disable. |
| `GGML_OPENVINO_DISABLE_KV_SLICE` | Boolean | `0` | Disable the KV-cache input-tensor slicing optimization (slicing is on by default on CPU/GPU). Set to `1` to disable. |
| `GGML_OPENVINO_MANUAL_GQA_ATTN` | Boolean | device-based | Tri-state. When **unset**, manual GQA attention is enabled by default on `GPU` and disabled on other devices. Set to a positive integer to force-enable, or `0` to force-disable. |
| `GGML_OPENVINO_MEMORY_OPTIMIZE` | Boolean | `0` | Umbrella switch for compile-time memory reductions. Enables `GGML_OPENVINO_REDUCE_COMPILE_MEM` and, on GPU, `GGML_OPENVINO_RELEASE_WEIGHTS` unless those fine-grained variables are explicitly set. |
| `GGML_OPENVINO_REDUCE_COMPILE_MEM`| Boolean | inherits from `GGML_OPENVINO_MEMORY_OPTIMIZE` | Reduce compile-time host memory use by streaming weight requantization and avoiding extra weight-node materialization where possible. Set explicitly to override the umbrella switch. |
| `GGML_OPENVINO_RELEASE_WEIGHTS` | Boolean | inherits from `GGML_OPENVINO_MEMORY_OPTIMIZE` on GPU | GPU-only. Release host weight buffers after the compiled model cache can reuse the device/plugin copy. Requires stable graph shapes; dynamic workloads that need recompilation should leave this disabled. |
| `GGML_OPENVINO_PROFILING` | Boolean | `0` | Enable execution-time profiling. |
| `GGML_OPENVINO_DUMP_CGRAPH` | Boolean | `0` | Dump the GGML compute graph to `cgraph_ov.txt`. |
| `GGML_OPENVINO_DUMP_IR` | Boolean | `0` | Serialize OpenVINO IR files with timestamps. |
Expand Down
496 changes: 423 additions & 73 deletions ggml/src/ggml-openvino/ggml-decoder.cpp

Large diffs are not rendered by default.

98 changes: 83 additions & 15 deletions ggml/src/ggml-openvino/ggml-decoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#include <memory>
#include <openvino/core/partial_shape.hpp>
#include <optional>
#include <set>
#include <string>
#include <vector>

struct ModelParams {
Expand All @@ -20,6 +22,7 @@ struct ModelParams {
int n_seq = 1;
int n_heads_kv = -1;
int head_size = -1;
int state_size = -1; // for SSM molels, eg qwen35
int32_t rope_params[15];
bool mixed_rope_params = false;
std::vector<int> swa_layers;
Expand Down Expand Up @@ -48,6 +51,47 @@ struct ComputeParams {
int token_len_per_seq = -1;
int past_kv_len = -1;
int output_len = 1;

int cache_rs_reset_idx = -1;
int cache_rs_reset_len = -1;
// SSM/DeltaNet models otionally clear cache_r and cache_s of certain slots in the cgraph
// 3: [ 18432, 4, 1, 1] RESHAPE cache_r_l0 (reshaped)
// [ 18432, 4, 1, 1] 0: NONE cache_r_l0
// 4: [ 18432, 1, 1, 1] VIEW cache_r_l0 (reshaped) (view)
// [ 18432, 4, 1, 1] 0: RESHAPE cache_r_l0 (reshaped)
// 5: [ 18432, 1, 1, 1] SCALE cache_r_l0 (reshaped) (view) (view)
// [ 18432, 1, 1, 1] 0: VIEW cache_r_l0 (reshaped) (view)

int s_copy_active_slot_len = -1;
// SSM/DeltaNet models otionally reorder slots of state cache, to make the active slots contiguous
// leaf_5 is the inp->s_copy in llama-graph.cpp, eg if there are 8 slots in total and slot 3 and 7
// are active in the current batch, leaf_5 will be [3, 7, 5, 6, 4]
// 6: [ 2, 1, 1, 1] VIEW (view)
// [ 2, 1, 1, 1] 0: NONE leaf_5
// 7: [ 18432, 2, 1, 1] GET_ROWS conv_states-0
// [ 18432, 4, 1, 1] 0: RESHAPE cache_r_l0 (reshaped)
// [ 2, 1, 1, 1] 1: VIEW (view)
// 8: [ 0, 1, 1, 1] VIEW (view)
// [ 2, 1, 1, 1] 0: NONE leaf_5
// 9: [ 18432, 0, 1, 1] GET_ROWS node_9
// [ 18432, 4, 1, 1] 0: RESHAPE cache_r_l0 (reshaped)
// [ 0, 1, 1, 1] 1: VIEW (view)
// 10: [ 18432, 0, 1, 1] VIEW cache_r_l0 (view)
// [ 18432, 4, 1, 1] 0: NONE cache_r_l0
// 11: [ 18432, 0, 1, 1] CPY cache_r_l0 (view) (copy of )
// [ 18432, 0, 1, 1] 0: GET_ROWS node_9
// [ 18432, 0, 1, 1] 1: VIEW cache_r_l0 (view)

struct RsWriteback {
int slot_begin = 0; // first cache slot written by the CPY
int src_begin = 0; // where the copied data starts in the source tensor (in rows of it)
};

std::map<std::string, RsWriteback> rs_writebacks;
// Offsets of the state cache writeback CPY nodes, keyed by node name. They change with the
// batch (kv head, active sequence count, token count) and, with rollback enabled
// (cparams.n_rs_seq > 0), the conv state is written back once per snapshot slot, each snapshot
// taking a different conv_input window. Passed to the cached model as runtime inputs.
};

class GgmlOvDecoder : public ov::frontend::ggml::GgmlDecoder {
Expand All @@ -59,8 +103,6 @@ class GgmlOvDecoder : public ov::frontend::ggml::GgmlDecoder {
std::map<std::string, ggml_tensor *> node_inputs;
std::map<std::string, std::vector<std::pair<std::string, ggml_tensor *>>> node_inputs_views;
std::vector<std::string> node_inputs_names;
ggml_tensor * node_output;
std::string node_output_name;
int node_op_case = 0;
void * data_addr;
};
Expand Down Expand Up @@ -156,6 +198,10 @@ class GgmlOvDecoder : public ov::frontend::ggml::GgmlDecoder {

virtual std::vector<std::string> get_output_names(int node_idx) const override;

virtual std::string get_inplace_op_src(int node_idx) const override;

virtual bool is_view_like_alias_of(int node_idx, const std::string & view_src_name) const override;

virtual const std::string & get_op_type() const override;

virtual const std::string & get_op_type(int node_idx) const override;
Expand All @@ -173,23 +219,19 @@ class GgmlOvDecoder : public ov::frontend::ggml::GgmlDecoder {

virtual int get_op_case(int node_idx) const override { return m_node_info_list[node_idx].node_op_case; }

virtual const std::map<std::string, std::shared_ptr<ov::Node>> & get_model_inputs() const override {
virtual const std::map<std::string, ov::frontend::ggml::ModelInputInfo> & get_model_inputs() const override {
return m_model_inputs;
}

virtual const std::map<std::string, std::shared_ptr<ov::Node>> & get_model_extra_inputs() const override {
virtual const std::map<std::string, ov::frontend::ggml::ModelExtraInputInfo> & get_model_extra_inputs() const override {
return m_model_extra_inputs;
}

virtual const std::map<std::string, std::shared_ptr<ov::Tensor>> & get_model_extra_input_values() const {
return m_model_extra_input_values;
}

virtual const std::map<std::string, std::shared_ptr<ov::Node>> & get_model_weights() const override {
return m_model_weights;
}

virtual std::vector<std::string> get_model_output_names() const override { return m_model_output_names; }
virtual std::set<std::string> get_model_output_names() const override { return m_model_output_names; }

const std::map<std::string, ggml_tensor *> & get_model_outputs() const { return m_model_outputs; }

Expand All @@ -214,6 +256,8 @@ class GgmlOvDecoder : public ov::frontend::ggml::GgmlDecoder {

virtual bool has_mixed_rope_params() const override { return m_model_params.mixed_rope_params; }

virtual int get_ssm_state_size() const override { return m_model_params.state_size; }

virtual std::map<std::string, std::string> get_kv_param_res_names() const override;

virtual bool is_static() const override { return m_is_static; }
Expand All @@ -235,6 +279,11 @@ class GgmlOvDecoder : public ov::frontend::ggml::GgmlDecoder {
static std::map<std::string, std::shared_ptr<ov::Node>> create_weight_nodes(ggml_cgraph * cgraph,
bool naive = false);

// Collect just the set of weight-tensor names referenced by the graph, without
// building (or requantizing) any OV weight nodes. Used by topology checks like
// is_model_splitted that only need name membership.
static std::set<std::string> collect_weight_names(ggml_cgraph * cgraph);

const ggml_tensor * get_tensor_used_op(const ggml_tensor * tensor) const;

const ggml_tensor * get_tensor_from_name(const std::string & name) const;
Expand Down Expand Up @@ -274,6 +323,12 @@ class GgmlOvDecoder : public ov::frontend::ggml::GgmlDecoder {
return op->op == GGML_OP_ROPE && tensor == op->src[1];
}

// IMROPE packs 4 stacked position planes (t/h/w/e) into inp_pos, each of length
// n_tokens; other modes carry a single position per token.
inline static int get_inp_pos_n_planes(const ggml_tensor * op) {
return op->op_params[2] == GGML_ROPE_TYPE_IMROPE ? 4 : 1;
}

inline static bool is_inp_emb(const ggml_tensor * tensor, const ggml_tensor * op) {
return tensor->op == GGML_OP_GET_ROWS && op->op == GGML_OP_RMS_NORM;
}
Expand All @@ -287,8 +342,12 @@ class GgmlOvDecoder : public ov::frontend::ggml::GgmlDecoder {
return op->op == GGML_OP_ROPE && tensor == op->src[2];
}

// also returns true for cache_s and cache_r in SSM/DeltaNet models
inline static bool is_kvcache(const ggml_tensor * tensor, const ggml_tensor * op) {
return tensor->buffer->usage == GGML_BACKEND_BUFFER_USAGE_ANY ||
if (tensor == nullptr) {
return false;
}
return (tensor->buffer != nullptr && tensor->buffer->usage == GGML_BACKEND_BUFFER_USAGE_ANY) ||
(op != nullptr && op->op == GGML_OP_SET_ROWS && op->src[2] == tensor);
}

Expand All @@ -301,7 +360,13 @@ class GgmlOvDecoder : public ov::frontend::ggml::GgmlDecoder {
op->src[1]->op == GGML_OP_NONE;
}

std::string get_graph_input_ov_name(const ggml_tensor * tensor, const ggml_tensor * op) {
// the state permutation index input used in SSM/DeltaNet models (inp->s_copy in llama-graph.cpp)
inline static bool is_inp_s_copy(const ggml_tensor * tensor, const ggml_tensor * op) {
return op->op == GGML_OP_GET_ROWS && tensor == op->src[1] &&
op->src[0]->buffer->usage == GGML_BACKEND_BUFFER_USAGE_ANY;
}

std::string get_graph_input_ov_name(const ggml_tensor * tensor, const ggml_tensor * op) const {
if (is_inp_pos(tensor, op)) {
return "inp_pos";
}
Expand All @@ -321,6 +386,10 @@ class GgmlOvDecoder : public ov::frontend::ggml::GgmlDecoder {
void compute_model_inputs();
void compute_model_outputs();

// True if tensor is the inp->s_copy index leaf gathered by a recurrent state cache GET_ROWS
// (possibly through a VIEW), so it gets a dynamic [1,1,1,-1] graph-input shape.
bool is_s_copy_leaf(const ggml_tensor * tensor) const;

// Infer and propagate dynamic-dimension indices for all tensors in the GGML graph.
void compute_node_dynamic_dims();

Expand All @@ -329,12 +398,11 @@ class GgmlOvDecoder : public ov::frontend::ggml::GgmlDecoder {
ggml_cgraph * m_cgraph = nullptr;
std::map<std::string, ggml_tensor *> m_inputs;

std::map<std::string, std::shared_ptr<ov::Node>> m_model_inputs;
std::map<std::string, std::shared_ptr<ov::Node>> m_model_extra_inputs;
std::map<std::string, std::shared_ptr<ov::Tensor>> m_model_extra_input_values;
std::map<std::string, ov::frontend::ggml::ModelInputInfo> m_model_inputs;
std::map<std::string, ov::frontend::ggml::ModelExtraInputInfo> m_model_extra_inputs;
std::map<std::string, std::shared_ptr<ov::Node>> m_model_weights;
std::map<std::string, ggml_tensor *> m_model_outputs;
std::vector<std::string> m_model_output_names;
std::set<std::string> m_model_output_names;
std::vector<NodeInfo> m_node_info_list;
std::map<ggml_tensor *, int> m_node_dynamic_dims;

Expand Down
59 changes: 54 additions & 5 deletions ggml/src/ggml-openvino/ggml-openvino-extra.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ void ggml_openvino_device_config::init() {
// String values (use ggml_openvino_getenv_str)
"GGML_OPENVINO_DEVICE",
"GGML_OPENVINO_CACHE_DIR",
"GGML_OPENVINO_DEBUG_NODE",
// Integer values (use ggml_openvino_getenv_int)
"GGML_OPENVINO_PREFILL_CHUNK_SIZE",
// Boolean toggles (treated as int flags via ggml_openvino_getenv_int)
Expand All @@ -44,7 +45,12 @@ void ggml_openvino_device_config::init() {
"GGML_OPENVINO_ENABLE_CACHE",
"GGML_OPENVINO_DISABLE_CACHE",
"GGML_OPENVINO_DISABLE_KV_SLICE",
"GGML_OPENVINO_ENABLE_FALLBACK",
"GGML_OPENVINO_MANUAL_GQA_ATTN",
"GGML_OPENVINO_MEMORY_OPTIMIZE",
"GGML_OPENVINO_RELEASE_WEIGHTS",
"GGML_OPENVINO_REDUCE_COMPILE_MEM",
"GGML_OPENVINO_COMPILED_MODEL_CACHE_DIR",
};

for (const char * const & env_var : env_var_names) {
Expand Down Expand Up @@ -168,6 +174,22 @@ int ggml_openvino_getenv_int(const char * var, int default_value) {
return v ? std::atoi(v) : default_value;
}

bool ggml_openvino_reduce_compile_mem_enabled() {
const char * reduce_compile_mem = ggml_openvino_getenv_str("GGML_OPENVINO_REDUCE_COMPILE_MEM");
if (reduce_compile_mem != nullptr) {
return ggml_openvino_getenv_int("GGML_OPENVINO_REDUCE_COMPILE_MEM") != 0;
}
return ggml_openvino_getenv_int("GGML_OPENVINO_MEMORY_OPTIMIZE") != 0;
}

bool ggml_openvino_release_weights_enabled(const std::string & device) {
const char * release_weights = ggml_openvino_getenv_str("GGML_OPENVINO_RELEASE_WEIGHTS");
if (release_weights != nullptr) {
return device == "GPU" && ggml_openvino_getenv_int("GGML_OPENVINO_RELEASE_WEIGHTS") != 0;
}
return device == "GPU" && ggml_openvino_getenv_int("GGML_OPENVINO_MEMORY_OPTIMIZE") != 0;
}

// Check if running on NPU
bool ggml_openvino_is_npu() {
return ggml_openvino_get_device_config().is_npu;
Expand Down Expand Up @@ -252,14 +274,31 @@ ggml_openvino_extracted_layout ggml_openvino_get_extracted_layout(const ggml_ten
return layout;
}

// Only handle 2D weight tensors
if (tensor->ne[2] != 1 || tensor->ne[3] != 1) {
// Most quantized weights use the existing 2D extraction path. 3D expert weights for
// MUL_MAT_ID (MoE) are also supported, either as MXFP4 (packed, dedicated branch below) or via the
// generic sizing math below, which is shape-agnostic (based on total element count). Only reject 4D.
if (tensor->ne[3] != 1) {
return layout;
}

// 3D MoE expert weights that are not requantized (see below) always use the exact f16
// zero-point extraction (see extract_quantized_weights), which needs a wider zp slot than
// the packed integer zero point -- must be kept in sync with that function so the buffer
// sizing here matches what process_weight_tensor actually writes.
const bool for_gather_matmul = tensor->ne[2] > 1;

int64_t n_elements = ggml_nelements(tensor);
const size_t alignment = 64; // Good for SIMD

if (tensor->type == GGML_TYPE_MXFP4 && (tensor->ne[2] > 1 || tensor->ne[3] > 1)) {
layout.weights_per_block = 32;
layout.is_symmetric = true;
layout.weights_size = ggml_nbytes(tensor);
layout.weights_offset = 0;
layout.total_size = layout.weights_size;
return layout;
}

// Check if requantization is needed (NPU-specific)
auto requant_type = ggml_openvino_get_requant_type(tensor, use_bias);
if (requant_type.has_value()) {
Expand Down Expand Up @@ -334,6 +373,11 @@ ggml_openvino_extracted_layout ggml_openvino_get_extracted_layout(const ggml_ten
layout.is_symmetric = false;

switch (tensor->type) {
case GGML_TYPE_MXFP4:
layout.is_u4 = true;
layout.is_symmetric = true;
break;

case GGML_TYPE_Q4_0:
layout.is_u4 = true;
layout.is_symmetric = true;
Expand Down Expand Up @@ -369,12 +413,17 @@ ggml_openvino_extracted_layout ggml_openvino_get_extracted_layout(const ggml_ten
// Weights: U4 = n_elements/2 bytes, U8 = n_elements bytes
layout.weights_size = layout.is_u4 ? (n_elements / 2) : n_elements;

// Scales: F16 per block
// Scales: F16 per block, except MXFP4 which stores one E8M0 byte per block.
int64_t n_blocks = n_elements / layout.weights_per_block;
layout.scales_size = n_blocks * sizeof(uint16_t); // F16 = 2 bytes
// For symmetric quantization, no zp needed (weights stored as signed)
layout.scales_size = n_blocks * (tensor->type == GGML_TYPE_MXFP4 ? sizeof(uint8_t) : sizeof(uint16_t));
// For symmetric quantization, no zp needed (weights stored as signed). Asymmetric
// for_gather_matmul (3D MoE expert) weights use an exact f16 zero point (see
// extract_quantized_weights/make_int8_weights/make_int4_weights), which needs one f16 per
// block instead of a packed u4/u8 integer zero point.
if (layout.is_symmetric) {
layout.zp_size = 0;
} else if (use_bias || for_gather_matmul) {
layout.zp_size = n_blocks * sizeof(uint16_t);
} else {
layout.zp_size = layout.is_u4 ? ((n_blocks + 1) / 2) : n_blocks;
}
Expand Down
Loading
Loading