char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Zijun YuOpenVINO: Update OV to 2026.3.1, whisper.cpp support, Qwen3.5 on NPU, and new ops (#27843)511f9c137

char/aliceai-t5-moe
88.4 KiB2057 linesraw
1#include "ggml-decoder.h"
2
3#include "ggml-impl.h"
4#include "ggml-openvino-extra.h"
5#include "ggml-openvino.h"
6#include "ggml-quants.h"
7#include "ggml.h"
8#include "utils.h"
9
10#include <algorithm>
11#include <cassert>
12#include <cstddef>
13#include <cstdint>
14#include <cstdlib>
15#include <fstream>
16#include <iomanip>
17#include <map>
18#include <memory>
19#include <mutex>
20#include <openvino/core/dimension.hpp>
21#include <openvino/core/except.hpp>
22#include <openvino/core/node.hpp>
23#include <openvino/core/partial_shape.hpp>
24#include <openvino/core/type/bfloat16.hpp>
25#include <openvino/core/type/element_type.hpp>
26#include <openvino/core/type/float16.hpp>
27#include <openvino/op/constant.hpp>
28#include <openvino/op/convert.hpp>
29#include <openvino/runtime/tensor.hpp>
30#include <ostream>
31#include <set>
32#include <stdexcept>
33#include <string>
34#include <cstring>
35#include <unordered_map>
36#include <vector>
37
38GgmlOvDecoder::GgmlOvDecoder(ggml_cgraph * cgraph,
39                             ModelParams & model_params,
40                             ComputeParams & compute_params,
41                             std::map<std::string, std::shared_ptr<ov::Node>> & model_weights,
42                             bool is_static,
43                             bool is_stateful,
44                             bool model_is_splitted,
45                             bool is_prefill,
46                             int prefill_chunk_size) :
47    m_is_static(is_static),
48    m_is_stateful(is_stateful),
49    m_is_prefill(is_prefill),
50    m_naive(false),
51    m_prefill_chunk_size(prefill_chunk_size),
52    m_model_is_splitted(model_is_splitted),
53    m_cgraph(cgraph),
54    m_model_weights(model_weights),
55    m_model_params(model_params),
56    m_compute_params(compute_params) {
57    static bool printed_address_map = false;
58    if (!printed_address_map) {
59        if (ggml_openvino_getenv_int("GGML_OPENVINO_PRINT_CGRAPH_TENSOR_ADDRESS")) {
60            printed_address_map = true;
61            print_tensor_address_map(cgraph);
62        }
63    }
64
65    validate_cgraph();
66
67    set_input_output();
68    compute_node_dynamic_dims();
69    compute_model_inputs();
70    compute_model_outputs();
71
72    for (int node_n = 0; node_n < cgraph->n_nodes; node_n++) {
73        m_node_info_list[node_n].node_op_case = compute_op_case(m_node_info_list[node_n].node);
74        m_node_info_list[node_n].node_op_type = compute_op_type(m_node_info_list[node_n].node);
75    }
76
77    add_extra_inputs();
78}
79
80void GgmlOvDecoder::update_io(ggml_cgraph * cgraph) {
81    m_cgraph = cgraph;
82    m_model_inputs.clear();
83    m_model_outputs.clear();
84    m_node_info_list.clear();
85    set_input_output();
86    compute_model_inputs();
87    compute_model_outputs();
88}
89
90GgmlOvDecoder::GgmlOvDecoder(ggml_cgraph * cgraph, std::map<std::string, std::shared_ptr<ov::Node>> & model_weights) {
91    m_cgraph = cgraph;
92    m_model_weights = model_weights;
93    m_naive = true;
94    set_input_output();
95    compute_model_inputs();
96    compute_model_outputs();
97    for (int node_n = 0; node_n < cgraph->n_nodes; node_n++) {
98        m_node_info_list[node_n].node_op_case = compute_op_case(m_node_info_list[node_n].node);
99        m_node_info_list[node_n].node_op_type = compute_op_type(m_node_info_list[node_n].node);
100    }
101}
102
103namespace {
104bool is_inplace_op(const ggml_tensor * node) {
105    return node->op == GGML_OP_SET_ROWS || node->op == GGML_OP_CPY || (node->op == GGML_OP_SCALE && node->view_src);
106}
107
108bool is_same_shape(const ggml_tensor * a, const ggml_tensor * b) {
109    for (int i = 0; i < GGML_MAX_DIMS; i++) {
110        if (a->ne[i] != b->ne[i]) {
111            return false;
112        }
113    }
114    return true;
115}
116
117bool is_conv_states_all_tensor(const ggml_tensor * tensor) {
118    return tensor != nullptr && strncmp(tensor->name, "conv_states_all", strlen("conv_states_all")) == 0;
119}
120
121// CPY writing the tail of conv_input (the concat of the previous conv state and the new tokens)
122// back into a slot block of the recurrent state cache. Detected structurally because the rollback
123// variant (cparams.n_rs_seq > 0) emits one such CPY per snapshot slot without naming them.
124bool is_conv_state_writeback(const ggml_tensor * node) {
125    return node->op == GGML_OP_CPY && node->view_src != nullptr && GgmlOvDecoder::is_kvcache(node->view_src, nullptr) &&
126           node->src[0] != nullptr && node->src[0]->op == GGML_OP_VIEW && node->src[0]->src[0] != nullptr &&
127           node->src[0]->src[0]->op == GGML_OP_CONCAT && node->src[1] != nullptr && node->src[1]->op == GGML_OP_VIEW &&
128           node->src[1]->view_src == node->view_src;
129}
130
131// MoE expert aggregation (build_moe_ffn in llama-graph.cpp): each expert plane is
132// `ggml_view_2d(experts, n_embd, n_tokens, experts->nb[2], i*experts->nb[1])` and the planes
133// are summed with a chain of ADDs: moe_out = ((view_0 + view_1) + view_2) + ... + view_{n-1}.
134// Detected structurally by walking the ADD chain and checking every leaf is a same-shape,
135// same-stride VIEW of one common base tensor, indexed by a distinct expert-plane offset, and
136// that the chain covers every plane of that base (leaf count == base->ne[1]). Only the
137// outermost ADD of the chain satisfies this (inner ADDs see fewer leaves than base->ne[1]).
138bool is_moe_expert_sum_add(const ggml_tensor * node) {
139    std::vector<const ggml_tensor *> leaves;
140    const ggml_tensor * cur = node;
141    while (cur->op == GGML_OP_ADD) {
142        if (cur->src[0] == nullptr || cur->src[1] == nullptr) {
143            return false;
144        }
145        leaves.push_back(cur->src[1]);
146        cur = cur->src[0];
147    }
148    leaves.push_back(cur);
149
150    const ggml_tensor * base = nullptr;
151    std::set<int64_t> plane_indices;
152    for (const ggml_tensor * leaf : leaves) {
153        if (leaf->op != GGML_OP_VIEW || leaf->src[0] == nullptr) {
154            return false;
155        }
156        const ggml_tensor * leaf_base = leaf->src[0];
157        if (base == nullptr) {
158            base = leaf_base;
159        } else if (leaf_base != base) {
160            return false;
161        }
162        if (leaf->ne[0] != base->ne[0] || leaf->ne[1] != base->ne[2] || leaf->ne[2] != 1 || leaf->ne[3] != 1 ||
163            leaf->nb[1] != base->nb[2]) {
164            return false;
165        }
166        if (base->nb[1] == 0 || leaf->view_offs % base->nb[1] != 0) {
167            return false;
168        }
169        int64_t plane = static_cast<int64_t>(leaf->view_offs / base->nb[1]);
170        if (plane < 0 || plane >= base->ne[1] || !plane_indices.insert(plane).second) {
171            return false;
172        }
173    }
174
175    return base != nullptr && base->ne[1] > 1 && plane_indices.size() == static_cast<size_t>(base->ne[1]);
176}
177}  // namespace
178
179static std::string get_tensor_ov_name(const ggml_cgraph * cgraph, const ggml_tensor * tensor) {
180    if (tensor == nullptr) {
181        return "";
182    }
183    const size_t hash_pos = ggml_hash_find(&cgraph->visited_hash_set, tensor);
184    if (((tensor->flags & GGML_TENSOR_FLAG_COMPUTE) || GgmlOvDecoder::is_kvcache(tensor, nullptr)) &&
185        hash_pos != GGML_HASHSET_FULL && ggml_bitset_get(cgraph->visited_hash_set.used, hash_pos)) {
186        return std::string(tensor->name) + "#" + std::to_string(hash_pos);
187    }
188    return tensor->name;
189}
190
191static std::string get_tensor_graph_input_ov_name(const GgmlOvDecoder * decoder,
192                                                  const ggml_cgraph * cgraph,
193                                                  const ggml_tensor * tensor,
194                                                  const ggml_tensor * op) {
195    if (GgmlOvDecoder::is_inp_pos(tensor, op)) {
196        return "inp_pos";
197    }
198    if (GgmlOvDecoder::is_inp_emb(tensor, op)) {
199        return "embd";
200    }
201    if (decoder->is_stateful() && GgmlOvDecoder::is_inp_mask(tensor, op)) {
202        return std::string(tensor->name).find("swa") == std::string::npos ? "self_kq_mask" : "self_kq_mask_swa";
203    }
204    return get_tensor_ov_name(cgraph, tensor);
205}
206
207void GgmlOvDecoder::set_input_output() {
208    for (int node_n = 0; node_n < m_cgraph->n_nodes; node_n++) {
209        auto * node = m_cgraph->nodes[node_n];
210
211        NodeInfo current_node_info;
212        auto node_name = get_tensor_ov_name(m_cgraph, node);
213
214        current_node_info.node = node;
215        current_node_info.node_name = node_name;
216        current_node_info.node_op_case = 0;
217        current_node_info.data_addr = node->data;
218
219        for (int i = 0; i < GGML_MAX_SRC; i++) {
220            auto * src = node->src[i];
221            if (src == nullptr) {
222                continue;
223            }
224            auto src_name = get_tensor_ov_name(m_cgraph, src);
225            if (src->flags & GGML_TENSOR_FLAG_INPUT) {
226                src_name = get_tensor_graph_input_ov_name(this, m_cgraph, src, node);
227            }
228            current_node_info.node_inputs[src_name] = src;
229            current_node_info.node_inputs_names.push_back(src_name);
230
231            if (src->op == GGML_OP_VIEW) {
232                // Traverse upward through nested VIEW operations
233                std::remove_reference_t<decltype(current_node_info.node_inputs_views[src_name])> view_chain;
234                auto current = src;
235
236                while (current != nullptr) {
237                    auto current_name = get_tensor_ov_name(m_cgraph, current);
238                    if (current->flags & GGML_TENSOR_FLAG_INPUT) {
239                        current_name = get_tensor_graph_input_ov_name(this, m_cgraph, current, node);
240                    }
241                    view_chain.emplace_back(current_name, current);
242                    // If current src is also a VIEW, continue traversing
243                    if (current->src[0] != nullptr && current->src[0]->op == GGML_OP_VIEW) {
244                        current = current->src[0];
245                    } else {
246                        break;
247                    }
248                }
249
250                // Assign all collected view inputs to node_inputs_views
251                current_node_info.node_inputs_views[src_name] = view_chain;
252            }
253        }
254
255        m_node_info_list.push_back(current_node_info);
256    }
257}
258
259int GgmlOvDecoder::compute_op_case(const ggml_tensor * node) const {
260    int op_case = 0;
261    switch (node->op) {
262    case GGML_OP_RESHAPE: {
263        auto name = std::string(node->name);
264        auto * src = node->src[0];
265        if (src->op == GGML_OP_RESHAPE && src->src[0]->ne[0] == node->ne[0] && src->src[0]->ne[1] == node->ne[1]) {
266            op_case = 4;
267        } else if (node->ne[0] * node->ne[1] == src->ne[0]) {
268            op_case = 1;
269        } else if (src->ne[0] * src->ne[1] == node->ne[0]) {
270            op_case = 2;
271            if (src->ne[2] * src->ne[3] == node->ne[1]) {
272                op_case = 5;
273            }
274        } else if (src->ne[0] * src->ne[1] * src->ne[2] == node->ne[1]) {
275            op_case = 3;
276        } else if (name.find("linear_attn_qkv_mixed") == 0 || name.find("alpha") == 0) {
277            op_case = 6;
278        } else if (name.find("linear_attn_out") == 0) {
279            op_case = 7;
280        } else if (name.find("state_predelta") == 0) {
281            op_case = 8;
282        }
283        break;
284    }
285    case GGML_OP_PERMUTE: {
286        if (node->src[0]->op != GGML_OP_VIEW) {
287            op_case = 1;
288        } else if (node->src[0]->src[0]->op == GGML_OP_NONE) {
289            // kv cache tensor
290            std::string src_name(node->view_src->name);
291            int layer = extract_layer_from_name(src_name).value();
292            if (ggml_is_contiguous(node->src[0])) {
293                // -  19: [    64,     8,   256,     1] VIEW            cache_k_l0 (view)             [ 2,   128,  1024, 1048576]
294                //         [   512,  1024,     1,     1]      0: NONE     cache_k_l0                    [ 2,  1024, 1048576, 1048576]
295                // -  20: [    64,   256,     8,     1] PERMUTE         cache_k_l0 (view) (permuted)  [ 2,  1024,   128, 1048576]
296                //         [    64,     8,   256,     1]      0: VIEW     cache_k_l0 (view)             [ 2,   128,  1024, 1048576]
297                if (!is_swa_layer(layer)) {
298                    op_case = 3;
299                } else {
300                    op_case = 4;
301                }
302            } else {
303                // special case of cache v when `-fa off`
304                // -  17: [   256,     8,    64,     1] VIEW            cache_v_l0 (view)             [ 2, 131072,  2048, 1048576]
305                //         [   512,  1024,     1,     1]      0: NONE     cache_v_l0                   [ 2,  1024, 1048576, 1048576]
306                // -  18: [   256,    64,     8,     1] PERMUTE         cache_v_l0 (view) (permuted)  [ 2,  2048, 131072, 1048576]
307                //         [   256,     8,    64,     1]      0: VIEW     cache_v_l0 (view)            [ 2, 131072,  2048, 1048576]
308                if (!is_swa_layer(layer)) {
309                    op_case = 5;
310                } else {
311                    op_case = 6;
312                }
313            }
314        } else {
315            // rope'ed query tensor
316            op_case = 2;
317        }
318        break;
319    }
320    case GGML_OP_MUL_MAT: {
321        if (node->src[0]->op == GGML_OP_VIEW && node->src[1]->op == GGML_OP_VIEW) {
322            op_case = 3;
323        } else if (node->src[1]->op == GGML_OP_SOFT_MAX) {
324            // In the case of `-fa off`, softmax is used, v_trans=true, the dynamic dim is ne[0] for cache_v
325            op_case = 2;
326        }
327        break;
328    }
329    case GGML_OP_GET_ROWS: {
330        if (node->src[1]->op == GGML_OP_VIEW) {
331            // GET_ROWS gathering recurrent state cache rows via the inp->s_copy index list:
332            // src[0] is a reshape of cache_r/cache_s, src[1] is a view of the s_copy leaf.
333            // op_case 3: main view (active sequences, view offset 0)
334            // op_case 4: extra view (defrag remainder, nonzero view offset)
335            if (node->src[0]->op == GGML_OP_RESHAPE && node->src[0]->src[0] != nullptr &&
336                is_kvcache(node->src[0]->src[0], nullptr)) {
337                op_case = node->src[1]->view_offs == 0 ? 1 : 2;
338            }
339        }
340        break;
341    }
342    case GGML_OP_ROPE: {
343        const int mode = node->op_params[2];
344        switch (mode) {
345        case GGML_ROPE_TYPE_NEOX: {
346            op_case = 1;
347            break;
348        }
349        case GGML_ROPE_TYPE_IMROPE: {
350            op_case = 2;
351            break;
352        }
353        default:
354            op_case = 0;
355            break;
356        }
357        break;
358    }
359    case GGML_OP_VIEW: {
360        if (m_is_static && node->src[0] != nullptr &&
361            (node->src[0]->op == GGML_OP_GATED_DELTA_NET || node->src[0]->op == GGML_OP_CONCAT)) {
362            // VIEW slicing a GATED_DELTA_NET combined [attn|state] output, or the conv_input
363            // CONCAT. The consuming CPY/RMS_NORM op recovers the true window at runtime via
364            // ssm_state_size / the fixed conv kernel width, so this VIEW must stay an identity
365            // pass-through of the full source here too (it already is on the dynamic path);
366            // otherwise the generic static-mode Slice below would bake in the *captured*
367            // cgraph's token count, which is wrong once the compiled static model runs with a
368            // different token count (prefill chunk size or 1).
369            op_case = 1;
370            break;
371        }
372        if (node->src[0]->op == GGML_OP_VIEW) {
373            auto * src = node->src[0];
374            if (ggml_nelements(node) != ggml_nelements(src)) {
375                // throw std::runtime_error("Unsupported VIEW case");
376            }
377            op_case = 0;
378            if (m_model_is_splitted && m_model_inputs.find(get_tensor_ov_name(m_cgraph, src)) != m_model_inputs.end()) {
379                op_case = 0;
380            }
381        }
382        {
383            auto * src = node->src[0];
384            if (ggml_nelements(node) != ggml_nelements(src)) {
385                // Case 4: select one slice on src dim1 (via view offset), keep src dim2 as output dim1.
386                // Typical pattern:
387                //   src: ne=[N, M, K, 1], nb=[b0, b1, b2, b3]
388                //   dst: ne=[N, K, 1, 1], nb=[b0, b2, b3, b3]
389                if (node->ne[0] == src->ne[0] && node->ne[1] == src->ne[2] && node->ne[2] == 1 &&
390                    node->nb[0] == src->nb[0] && node->nb[1] == src->nb[2] && src->ne[1] > 1) {
391                    op_case = 0;
392                    break;
393                }
394
395                // General case 3: shape differs from source (one or more dims) and is handled as VIEW slicing.
396                int diff_count = 0;
397                for (int i = 0; i < GGML_MAX_DIMS; i++) {
398                    if (node->ne[i] != src->ne[i]) {
399                        diff_count++;
400                    }
401                    // if node ne[i] > src ne[i], case = 0
402                    if (node->ne[i] > src->ne[i]) {
403                        return 0;
404                    }
405                }
406                if (diff_count >= 1) {
407                    op_case = 0;
408                }
409            }
410        }
411        break;
412    }
413    case GGML_OP_RMS_NORM: {
414        if (node->src[0]->op == GGML_OP_VIEW) {
415            if (is_same_shape(node->src[0]->src[0], node->src[0])) {
416                op_case = 1;
417            } else if (node->src[0]->src[0]->op == GGML_OP_GATED_DELTA_NET) {
418                op_case = 2;
419            }
420        }
421        break;
422    }
423    case GGML_OP_POOL_2D: {
424        const ggml_op_pool pool_mode = static_cast<ggml_op_pool>(node->op_params[0]);
425        switch (pool_mode) {
426        case GGML_OP_POOL_MAX: {
427            op_case = 1;
428            break;
429        }
430        case GGML_OP_POOL_AVG: {
431            op_case = 2;
432            break;
433        }
434        default:
435            op_case = 0;
436            break;
437        }
438        break;
439    }
440    case GGML_OP_CPY: {
441        if (node->src[0]->op == GGML_OP_VIEW) {
442            if (node->src[0]->src[0]->op == GGML_OP_GATED_DELTA_NET) {
443                op_case = 1;
444            } else if (is_conv_state_writeback(node)) {
445                op_case = 2;
446                break;
447            } else if (is_conv_states_all_tensor(node->view_src) && node->src[1] != nullptr &&
448                       node->src[1]->op == GGML_OP_VIEW && node->src[1]->view_src == node->view_src) {
449                op_case = 4;
450                break;
451            }
452        } else if (node->src[0]->op == GGML_OP_GET_ROWS && node->src[1] != nullptr &&
453                   node->src[1]->op == GGML_OP_VIEW && node->src[1]->view_src != nullptr &&
454                   is_kvcache(node->src[1]->view_src, nullptr)) {
455            // s_copy defrag remainder writeback: gathered extra state rows copied back into the cache
456            op_case = 3;
457        } else if (node->src[1] != nullptr && node->src[1]->op == GGML_OP_VIEW && node->src[1]->view_src != nullptr) {
458            // op_case 5: KV write for decoder self-attention (dynamic write offset)
459            // op_case 6: KV write for encoder self-attn or cross-attn (static offset)
460            const ggml_tensor * kv_buf = node->src[1]->view_src;
461            if (kv_buf->ne[1] == 1 && kv_buf->ne[2] == 1 && kv_buf->ne[3] == 1) {
462                op_case = 6;
463                // Forward-scan the graph for a FLASH_ATTN_EXT that reads from
464                // the same buffer. Having a mask (src[3] != nullptr) implies
465                // decoder self-attention and the write offset is dynamic.
466                for (int i = 0; i < m_cgraph->n_nodes; i++) {
467                    const ggml_tensor * n = m_cgraph->nodes[i];
468                    if (n->op != GGML_OP_FLASH_ATTN_EXT) {
469                        continue;
470                    }
471                    // K (src[1]) and V (src[2]) are 3-D views whose view_src is
472                    // the flat KV buffer we are writing to.
473                    if ((n->src[1] != nullptr && n->src[1]->view_src == kv_buf) ||
474                        (n->src[2] != nullptr && n->src[2]->view_src == kv_buf)) {
475                        if (n->src[3] != nullptr) {
476                            op_case = 5;  // decoder self-attention: mask present
477                        }
478                        break;
479                    }
480                }
481            }
482        }
483        break;
484    }
485    case GGML_OP_ADD: {
486        if (is_moe_expert_sum_add(node)) {
487            // Outermost ADD of a MoE expert-plane sum chain: translated as a single
488            // ReduceSum over the base tensor instead of N-1 chained Adds over N Slices.
489            op_case = 1;
490        }
491        break;
492    }
493    case GGML_OP_SCALE: {
494        if (node->view_src && node->buffer->usage == GGML_BACKEND_BUFFER_USAGE_ANY) {
495            op_case = 1;
496        }
497        break;
498    }
499    case GGML_OP_L2_NORM: {
500        if (std::string(node->name).find("predelta") != std::string::npos) {
501            op_case = 1;
502        }
503        break;
504    }
505    case GGML_OP_FLASH_ATTN_EXT: {
506        if (node->src[1] != nullptr && node->src[1]->op == GGML_OP_VIEW && node->src[1]->view_src != nullptr) {
507            const ggml_tensor * kv_buf = node->src[1]->view_src;
508            if (kv_buf->ne[1] == 1 && kv_buf->ne[2] == 1 && kv_buf->ne[3] == 1) {
509                op_case = (node->src[3] != nullptr) ? 1 : 2;
510            }
511        }
512        break;
513    }
514    default:
515        break;
516    }
517    return op_case;
518}
519
520std::optional<int> extract_layer_from_name(const std::string & name) {
521    size_t pos1 = name.find("_l");
522    if (pos1 == std::string::npos) {
523        return std::nullopt;
524    }
525    pos1 += 2;
526    size_t pos2 = name.find(' ', pos1);
527    if (pos2 == std::string::npos) {
528        pos2 = name.length();
529    }
530    std::string layer_str = name.substr(pos1, pos2 - pos1);
531    int layer = std::stoi(layer_str);
532    return layer;
533}
534
535std::pair<ModelParams, ComputeParams> GgmlOvDecoder::compute_llm_params(ggml_cgraph * cgraph, bool is_static) {
536    ModelParams model_params;
537    ComputeParams compute_params;
538    auto get_attention_pattern_case = [](const ggml_tensor * node) -> int {
539        if (node == nullptr) {
540            return -1;
541        }
542
543        switch (node->op) {
544        case GGML_OP_FLASH_ATTN_EXT:
545            if (node->src[0] == nullptr || node->src[1] == nullptr) {
546                return -1;
547            }
548            switch (node->src[1]->op) {
549            case GGML_OP_PERMUTE:
550                // case 0: src[1] is PERMUTE of a cache VIEW, mask required
551                if (node->src[3] != nullptr && node->src[1]->src[0] != nullptr &&
552                    node->src[1]->src[0]->op == GGML_OP_VIEW) {
553                    return 0;
554                }
555                break;
556            case GGML_OP_CPY:
557                // case 1: src[1] is CPY of a PERMUTE(VIEW), mask required
558                if (node->src[3] != nullptr && node->src[1]->src[0] != nullptr &&
559                    node->src[1]->src[0]->op == GGML_OP_PERMUTE && node->src[1]->src[0]->src[0] != nullptr &&
560                    node->src[1]->src[0]->src[0]->op == GGML_OP_VIEW) {
561                    return 1;
562                }
563                break;
564            case GGML_OP_VIEW:
565                // cases 4/5/6: whisper - K is a direct non-contiguous VIEW_3D of a KV cache
566                if (node->src[1]->view_src != nullptr) {
567                    if (node->src[3] != nullptr) {
568                        return 4;  // decoder self-attention
569                    } else {
570                        return 5;  // cross-attention or encoder self-attention
571                    };
572                }
573                break;
574            default:
575                break;
576            }
577            break;
578        case GGML_OP_SOFT_MAX:
579            // case 2: node op is SOFT_MAX, src 0 not null & op is MUL_MAT & the src 0 of MUL_MAT is PERMUTE & the permuted tensor src is the view of cache k
580            if (node->src[0] != nullptr && node->src[1] != nullptr && node->src[0]->op == GGML_OP_MUL_MAT &&
581                node->src[0]->src[0] != nullptr && node->src[0]->src[1] != nullptr &&
582                node->src[0]->src[0]->op == GGML_OP_PERMUTE && node->src[0]->src[0]->src[0] != nullptr &&
583                node->src[0]->src[0]->src[0]->op == GGML_OP_VIEW) {
584                return 2;
585            }
586            // case 3: node op is SOFT_MAX, src 0 not null & op is ADD & the src 0 of ADD is MUL_MAT & the src 0 of MUL_MAT is PERMUTE
587            if (node->src[0]->op == GGML_OP_ADD && node->src[0]->src[0] != nullptr &&
588                node->src[0]->src[0]->op == GGML_OP_MUL_MAT && node->src[0]->src[0]->src[0] != nullptr &&
589                node->src[0]->src[0]->src[0]->op == GGML_OP_PERMUTE) {
590                return 3;
591            }
592            break;
593        default:
594            break;
595        }
596
597        return -1;
598    };
599
600    bool rope_seen = false;
601    for (int i = 0; i < cgraph->n_nodes; i++) {
602        auto * node = cgraph->nodes[i];
603        std::string name = std::string(node->name);
604        const int attention_pattern_case = get_attention_pattern_case(node);
605        if (attention_pattern_case != -1) {
606            ggml_tensor * cache_k_permute = nullptr;
607            ggml_tensor * mask = nullptr;
608
609            switch (attention_pattern_case) {
610            case 0:
611                cache_k_permute = node->src[1];
612                mask = node->src[3];
613                break;
614            case 1:
615                cache_k_permute = node->src[1]->src[0];
616                mask = node->src[3];
617                break;
618            case 2:
619                cache_k_permute = node->src[0]->src[0];
620                mask = node->src[1];
621                break;
622            case 3:
623                cache_k_permute = node->src[0]->src[0]->src[0];
624                mask = node->src[1];
625                break;
626            case 4:
627            case 5: {
628                // whisper: K is a direct VIEW_3D of the KV buffer, no PERMUTE node
629                auto * cache_k_view = node->src[1];  // VIEW_3D of kv_self.k or kv_cross.k`
630                compute_params.token_len_per_seq = node->src[0]->ne[1];
631                if (attention_pattern_case == 4) {
632                    compute_params.attention_size = cache_k_view->ne[1];
633                } else {
634                    compute_params.attention_size_static = cache_k_view->ne[1];
635                }
636                continue;
637            }
638            default:
639                break;
640            }
641
642            assert(cache_k_permute != nullptr);
643
644            model_params.head_size = cache_k_permute->ne[0];
645            model_params.n_heads_kv = cache_k_permute->ne[2];
646            compute_params.input_len = node->src[0]->ne[1];
647            compute_params.token_len_per_seq = node->src[0]->ne[1];
648
649            auto * cache_k_view = cache_k_permute->src[0];
650            if (cache_k_view->op != GGML_OP_VIEW || mask == nullptr) {
651                continue;
652            }
653
654            ggml_tensor * cache_k = cache_k_view->src[0];
655            int layer = extract_layer_from_name(cache_k->name).value();
656
657            std::string mask_name(mask->name);
658
659            model_params.kv_buffer_ctx_id = ggml_backend_openvino_buffer_get_ctx_id(cache_k->buffer);
660            if (mask_name.find("swa") != std::string::npos) {
661                model_params.swa_layers.push_back(layer);
662                model_params.ctx_per_seq_swa = cache_k->ne[1];
663            } else {
664                model_params.ctx_per_seq = cache_k->ne[1];
665                model_params.n_seq = cache_k->ne[2];
666            }
667
668            compute_params.n_seq_active = mask->ne[3];
669            auto seq_size = cache_k->ne[0] * cache_k->ne[1] * ggml_type_size(cache_k->type);
670            size_t offset;
671            memcpy(&offset, cache_k_view->op_params, sizeof(size_t));
672            compute_params.seq_active_start = offset / seq_size;
673
674            if (mask_name.find("swa") != std::string::npos) {
675                compute_params.attention_size_swa = mask->ne[0];
676            } else {
677                compute_params.attention_size = mask->ne[0];
678            }
679            if (is_static) {
680                compute_params.attention_size = model_params.ctx_per_seq;
681                compute_params.attention_size_swa = model_params.ctx_per_seq_swa;
682                compute_params.token_len_per_seq = 1;
683            }
684        }
685
686        if (node->op == GGML_OP_MUL_MAT && node->src[0]->op == GGML_OP_PERMUTE &&
687            node->src[0]->src[0]->op == GGML_OP_VIEW && is_kvcache(node->src[0]->view_src, node->view_src)) {
688            if (node->src[1]->op == GGML_OP_PERMUTE && node->src[1]->src[0]->op == GGML_OP_VIEW &&
689                node->src[1]->src[0]->src[0]->op == GGML_OP_ROPE) {
690                compute_params.attention_size = node->ne[0];
691            }
692        }
693
694        // if the node op is TRANSPOSE and its input is PERMUTE and the source of the PERMUTE is VIEW, then get the attention size with the TRANSPOSE node ne[0] (in case no GGML_OP_FLASH_ATTN_EXT)
695        if (node->op == GGML_OP_TRANSPOSE && node->src[0]->op == GGML_OP_PERMUTE &&
696            node->src[0]->src[0]->op == GGML_OP_VIEW) {
697            compute_params.attention_size = node->ne[0];
698            if (is_static) {
699                compute_params.attention_size = model_params.ctx_per_seq;
700            }
701        }
702        if (node->op == GGML_OP_ROPE) {
703            if (compute_params.token_len_per_seq == -1 && node->src[1] != nullptr) {
704                compute_params.token_len_per_seq = ggml_nelements(node->src[1]);
705            }
706
707            // When multiple ROPE ops in the graph disagree on op_params (e.g. gemma4's
708            // mixed SWA/non-SWA layers with different n_dims or freq_base), we cannot
709            // share a single precomputed rope_sin/rope_cos. Track divergence so the
710            // translator falls back to per-op make_sin_cos in that case.
711            static_assert(sizeof(model_params.rope_params) == sizeof(int32_t) * 15, "rope_params size");
712            if (!rope_seen) {
713                memcpy(model_params.rope_params, node->op_params, sizeof(int32_t) * 15);
714                rope_seen = true;
715            } else if (memcmp(model_params.rope_params, node->op_params, sizeof(int32_t) * 15) != 0) {
716                model_params.mixed_rope_params = true;
717            }
718        }
719        if (node->op == GGML_OP_GATED_DELTA_NET) {
720            model_params.state_size = node->src[0]->ne[0];
721        }
722        if (node->op == GGML_OP_SCALE && node->view_src != nullptr && is_kvcache(node->view_src, nullptr)) {
723            compute_params.cache_rs_reset_len = ggml_nelements(node) / node->view_src->ne[0];
724            compute_params.cache_rs_reset_idx = node->src[0]->view_offs / node->view_src->ne[0];
725        }
726        // Capture the destination slot block of every recurrent state cache writeback, plus the
727        // conv_input window the conv state writeback copies. The active sequences occupy a
728        // contiguous slot block [begin, begin + n_seqs) of the cache; the block and the window move
729        // with the batch, so they are fed to the cached model as runtime inputs.
730        if (node->op == GGML_OP_CPY && node->view_src != nullptr && is_kvcache(node->view_src, nullptr) &&
731            node->src[1] != nullptr && node->src[1]->op == GGML_OP_VIEW && node->src[1]->view_src == node->view_src) {
732            const bool is_conv = is_conv_state_writeback(node);
733            const bool is_gdn = node->src[0]->op == GGML_OP_VIEW && node->src[0]->src[0] != nullptr &&
734                                node->src[0]->src[0]->op == GGML_OP_GATED_DELTA_NET;
735            const bool is_extra = node->src[0]->op == GGML_OP_GET_ROWS;
736
737            const ggml_tensor * dest_view = node->src[1];
738            const ggml_tensor * cache = node->view_src;
739            const size_t row_bytes = cache->ne[0] * ggml_type_size(cache->type);
740            if (row_bytes > 0 && (is_conv || is_gdn || is_extra)) {
741                ComputeParams::RsWriteback writeback;
742                writeback.slot_begin = (int) (dest_view->view_offs / row_bytes);
743                if (is_conv) {
744                    writeback.src_begin = (int) (node->src[0]->view_offs / node->src[0]->view_src->nb[0]);
745                } else if (is_gdn) {
746                    writeback.src_begin = (int) (node->src[0]->view_offs / node->src[0]->view_src->nb[1]);
747                }
748                compute_params.rs_writebacks[get_tensor_ov_name(cgraph, node)] = writeback;
749            }
750            if (is_conv || is_gdn) {
751                compute_params.s_copy_active_slot_len = (int) dest_view->ne[1];
752            }
753        }
754    }
755    auto * output_tensor = cgraph->nodes[cgraph->n_nodes - 1];
756    compute_params.output_len = output_tensor->ne[1];
757    // for NPU, output_len is always 1 except for llama-perplexity
758    if (is_static && compute_params.output_len == 0) {
759        compute_params.output_len = 1;
760    }
761    model_params.ctx = model_params.ctx_per_seq * model_params.n_seq;
762    return {model_params, compute_params};
763}
764
765void GgmlOvDecoder::validate_cgraph() const {
766    if (m_model_params.n_seq > 1 && m_is_static == true) {
767        throw std::runtime_error("n_seq > 1 is not supported on NPU. Try setting -np 1.");
768    }
769}
770
771ov::PartialShape GgmlOvDecoder::get_graph_input_shape(const ggml_tensor * op,
772                                                      const ggml_tensor * input,
773                                                      int dynamic_dim_index) const {
774    if (m_naive) {
775        return input != nullptr ? ov::PartialShape{get_shape(input)} : ov::PartialShape{get_shape(op)};
776    }
777    auto name = std::string(input->name);
778    ov::PartialShape input_shape;
779
780    if (is_inp_tok(input, op) || is_inp_pos(input, op)) {
781        // tokens or positions
782        int len = m_is_static ? (m_is_prefill ? m_prefill_chunk_size : 1) : -1;
783        if (m_is_static && is_inp_pos(input, op)) {
784            // IMROPE stacks n_planes (t/h/w/e) position planes back to back
785            len *= get_inp_pos_n_planes(op);
786        }
787        input_shape = ov::PartialShape{1, 1, 1, len};
788
789    } else if (is_output_idx(input, op)) {
790        // output index
791        input_shape = ov::PartialShape{1, 1, 1, m_is_static ? m_compute_params.output_len : -1};
792
793    } else if (is_inp_mask(input, op)) {
794        // mask
795        if (m_is_static) {
796            input_shape = ov::PartialShape{1, 1, m_is_prefill ? m_prefill_chunk_size : 1, m_model_params.ctx};
797        } else if (m_is_stateful) {
798            input_shape = ov::PartialShape{1, 1, -1, -1};
799        } else {
800            input_shape = ov::PartialShape{-1, 1, -1, -1};
801        }
802
803    } else if (is_kvcache(input, op)) {
804        // kvcache
805        input_shape = ov::PartialShape{get_shape(input)};
806        // Whisper.cpp uses a fixed size 1D KV buffer [N, 1, 1, 1] (GGML) or [1, 1, 1, N] (OV).
807        // the token fill level is handled by token_len_per_seq + dynamic mask input.
808        // skip dynamic dim and stateful reshape for this layout.
809        const bool is_flat_kv = (input->ne[1] == 1 && input->ne[2] == 1 && input->ne[3] == 1);
810        if (!m_is_static && !is_flat_kv) {
811            // do not fix ctx size to make llama-bench work across test params
812            input_shape[2] = -1;
813        }
814        if (is_stateful() && !is_flat_kv) {
815            // Convert stateless KV cache layout [1, 1, seq, n_heads_kv * head_size]
816            // to stateful layout [1, seq, n_heads_kv, head_size].
817            assert(input_shape.size() == 4 && input_shape[0] == 1 && input_shape[1] == 1 &&
818                   input_shape[2].is_dynamic() &&
819                   input_shape[3] == (m_model_params.n_heads_kv * m_model_params.head_size));
820            input_shape = {input_shape[0], ov::Dimension::dynamic(), m_model_params.n_heads_kv,
821                           m_model_params.head_size};
822        }
823
824    } else if (is_kv_idx(input, op)) {
825        // kv update index
826        int len = m_is_static ? (m_is_prefill ? m_prefill_chunk_size : 1) : -1;
827        input_shape = ov::PartialShape{1, 1, 1, len};
828
829    } else if (is_inp_s_copy(input, op) || is_s_copy_leaf(input)) {
830        // On NPU the total slot count (n_seq_max) is fixed at translation time, so the s_copy
831        // index list has a static length; on CPU/GPU it may change across compiles (defrag).
832        input_shape = m_is_static ? ov::PartialShape{get_shape(input)} : ov::PartialShape{1, 1, 1, -1};
833
834    } else {
835        input_shape = ov::PartialShape{get_shape(input)};
836    }
837    if (dynamic_dim_index != -1 && m_model_is_splitted) {
838        input_shape[3 - dynamic_dim_index] = -1;
839    }
840    if (op->op == GGML_OP_SOFT_MAX && op->src[1] != nullptr && op->src[1]->op == GGML_OP_NONE &&
841        op->src[1]->flags & GGML_TENSOR_FLAG_INPUT && op->src[1] == input) {
842        // for softmax input mask, the shape is [1, 1, seq_active, seq_active], where seq_active is determined by the input active sequence length instead of the kv cache sequence length
843        input_shape[2] = -1;
844        input_shape[3] = -1;
845    }
846    return input_shape;
847}
848
849bool GgmlOvDecoder::is_s_copy_leaf(const ggml_tensor * tensor) const {
850    if (tensor == nullptr || tensor->op != GGML_OP_NONE || m_cgraph == nullptr) {
851        return false;
852    }
853    for (int i = 0; i < m_cgraph->n_nodes; i++) {
854        const ggml_tensor * node = m_cgraph->nodes[i];
855        if (node->op != GGML_OP_GET_ROWS || node->src[0] == nullptr || node->src[1] == nullptr) {
856            continue;
857        }
858        // The index list may reach the s_copy leaf through one or more VIEWs.
859        const ggml_tensor * idx = node->src[1];
860        while (idx != nullptr && idx->op == GGML_OP_VIEW) {
861            idx = idx->src[0];
862        }
863        if (idx != tensor) {
864            continue;
865        }
866        // The gathered data must be a recurrent state cache (cache_r/cache_s).
867        const ggml_tensor * data = node->src[0];
868        while (data != nullptr && (data->op == GGML_OP_VIEW || data->op == GGML_OP_RESHAPE)) {
869            data = data->src[0];
870        }
871        if (data != nullptr && is_kvcache(data, nullptr)) {
872            return true;
873        }
874    }
875    return false;
876}
877
878void GgmlOvDecoder::add_extra_inputs() {
879    // Extra inputs:
880    // 1. `attention_size`, used in FLASH_ATTN where the shape of the matmul's are 256 aligned,
881    //     see llama_kv_cache_unified::get_n_kv and llama_kv_cache_unified::get_padding.
882    // 2. `n_seq_active` and `seq_active_start`, used in FLASH_ATTN_EXT to indicate the active sequences in the batch
883
884    auto create_1d_input = [this](const std::string & name, int64_t value, bool force_parameter = false) {
885        m_model_extra_inputs[name] = {ov::element::i64, ov::Shape{1}, value, force_parameter || !m_is_static};
886    };
887
888    if (m_compute_params.attention_size != -1) {
889        create_1d_input("attention_size", m_compute_params.attention_size);
890    }
891    if (m_compute_params.attention_size_static != -1) {
892        create_1d_input("attention_size_static", m_compute_params.attention_size_static);
893    }
894    if (m_compute_params.attention_size_swa != -1) {
895        create_1d_input("attention_size_swa", m_compute_params.attention_size_swa);
896    }
897    create_1d_input("n_seq_active", m_compute_params.n_seq_active);
898    create_1d_input("seq_active_start", m_compute_params.seq_active_start);
899    create_1d_input("seq_active_end", m_compute_params.seq_active_start + m_compute_params.n_seq_active);
900    if (m_compute_params.token_len_per_seq != -1) {
901        create_1d_input("token_len_per_seq", m_compute_params.token_len_per_seq);
902    }
903    // create_1d_input("token_len", m_compute_params.token_len_per_seq * m_compute_params.n_seq_active);
904
905    if (m_compute_params.cache_rs_reset_idx != -1) {
906        // Whether/which cache slot to reset varies per compute call (e.g. a new sequence starting
907        // vs. continued decoding). can_reuse_statically() does not invalidate the cached static
908        // model on ComputeParams changes, so these must stay runtime Parameters even when static
909        // (scale.cpp op_case 1 only uses them in value comparisons, never as Slice bounds, so this
910        // does not reintroduce dynamic shapes).
911        create_1d_input("cache_rs_reset_idx", m_compute_params.cache_rs_reset_idx, /*force_parameter=*/true);
912        create_1d_input("cache_rs_reset_len", m_compute_params.cache_rs_reset_len, /*force_parameter=*/true);
913    }
914
915    if (m_compute_params.s_copy_active_slot_len != -1) {
916        create_1d_input("s_copy_active_slot_len", m_compute_params.s_copy_active_slot_len);
917        if (m_is_static) {
918            // Number of real tokens in the current prefill chunk. The last chunk is padded with
919            // fabricated token ids; attention masks them out, but the recurrent (GDN/conv) path
920            // would otherwise fold them into cache_r/cache_s permanently. Varies per chunk, so it
921            // must stay a runtime Parameter; it is only compared against a Range or used as Gather
922            // indices, so it does not make any shape dynamic.
923            create_1d_input("chunk_valid_len", get_static_n_tokens(), /*force_parameter=*/true);
924        }
925    }
926
927    for (const auto & [node_name, writeback] : m_compute_params.rs_writebacks) {
928        create_1d_input("rs_slot_begin_" + node_name, writeback.slot_begin);
929        if (!m_is_static) {
930            create_1d_input("rs_src_begin_" + node_name, writeback.src_begin);
931        }
932    }
933}
934
935bool GgmlOvDecoder::node_is_used_as_src(const int node_idx) {
936    ggml_tensor * node = m_cgraph->nodes[node_idx];
937    for (int i = node_idx; i < m_cgraph->n_nodes; i++) {
938        ggml_tensor * other_node = m_cgraph->nodes[i];
939        for (int j = 0; j < GGML_MAX_SRC; j++) {
940            if (other_node->src[j] == node) {
941                return true;
942            }
943        }
944    }
945    return false;
946}
947
948void GgmlOvDecoder::compute_model_inputs() {
949    m_model_inputs.clear();
950    m_inputs.clear();
951    for (int i = 0; i < m_cgraph->n_nodes; i++) {
952        ggml_tensor * node = m_cgraph->nodes[i];
953        // the node op is NONE means this node maybe as input of later nodes, we should add it to model inputs for this node.
954        if (node->op == GGML_OP_NONE && node_is_used_as_src(i)) {
955            std::string node_name = get_tensor_ov_name(m_cgraph, node);
956            if (m_model_weights.find(node_name) == m_model_weights.end()) {
957                m_inputs[node_name] = node;
958                m_model_inputs[node_name] = {get_ov_type(node),
959                                             get_graph_input_shape(node, nullptr, m_node_dynamic_dims[node])};
960            }
961            continue;
962        }
963        for (int i = 0; i < GGML_MAX_SRC; i++) {
964            auto * src = node->src[i];
965            if (src == nullptr) {
966                continue;
967            }
968            std::string src_name = get_tensor_ov_name(m_cgraph, src);
969            if (src->flags & GGML_TENSOR_FLAG_INPUT) {
970                src_name = get_tensor_graph_input_ov_name(this, m_cgraph, src, node);
971            }
972            if (m_model_weights.find(src_name) != m_model_weights.end()) {
973                continue;
974            }
975
976            bool is_intermediate_node = false;
977            for (const auto & node_info : m_node_info_list) {
978                if (node_info.node == src) {
979                    is_intermediate_node = true;
980                    break;
981                }
982            }
983            if (is_intermediate_node) {
984                continue;
985            }
986            if (m_model_inputs.find(src_name) != m_model_inputs.end()) {
987                continue;
988            }
989
990            m_inputs[src_name] = src;
991
992            ggml_backend_buffer * buffer = src->buffer;
993            // GGML_BACKEND_BUFFER_USAGE_ANY are kv caches
994            if (buffer->usage == GGML_BACKEND_BUFFER_USAGE_ANY) {
995                if (auto it = std::find(m_model_params.kv_names.begin(), m_model_params.kv_names.end(), src_name);
996                    it == m_model_params.kv_names.end()) {
997                    m_model_params.kv_names.push_back(src_name);
998                }
999            }
1000            // Resolve nested VIEW nodes by following src[0] until the first non-VIEW tensor.
1001            while (src->op == GGML_OP_VIEW && src->src[0] != nullptr) {
1002                src = src->src[0];
1003                src_name = get_tensor_ov_name(m_cgraph, src);
1004            }
1005            m_inputs[src_name] = src;
1006            m_model_inputs[src_name] = {get_ov_type(src),
1007                                        get_graph_input_shape(node, src, m_node_dynamic_dims[src])};
1008        }
1009    }
1010}
1011
1012void GgmlOvDecoder::compute_model_outputs() {
1013    m_model_outputs.clear();
1014    m_model_output_names.clear();
1015    for (int node_n = 0; node_n < m_cgraph->n_nodes; node_n++) {
1016        auto * cur_node = m_cgraph->nodes[node_n];
1017        // if the node op is NONE means this node is not used at all, we can skip it directly without adding to model outputs.
1018        if (cur_node->op == GGML_OP_NONE || cur_node->op == GGML_OP_VIEW || cur_node->op == GGML_OP_RESHAPE) {
1019            continue;
1020        }
1021        auto cur_node_use_count = m_cgraph->use_counts[ggml_hash_find(&m_cgraph->visited_hash_set, cur_node)];
1022        if (cur_node_use_count == 0) {
1023            // The output of in-place ops is the view_src tensor, which is updated in place. We should use the view_src name as the output name to make sure it can be correctly matched with the later ops that use the view_src.
1024            if (cur_node != nullptr && ::is_inplace_op(cur_node) && ggml_nbytes(cur_node) > 0) {
1025                cur_node = cur_node->view_src;
1026            }
1027        } else {
1028            int input_use_count = 0;
1029            for (int i = 0; i < m_cgraph->n_nodes; i++) {
1030                ggml_tensor * node = m_cgraph->nodes[i];
1031                for (int j = 0; j < GGML_MAX_SRC; j++) {
1032                    if (node->src[j] != NULL && node->src[j] == cur_node) {
1033                        input_use_count++;
1034                    }
1035                }
1036            }
1037            if (input_use_count == cur_node_use_count) {
1038                cur_node = nullptr;
1039            }
1040        }
1041        if (cur_node != nullptr) {
1042            std::string cur_node_name = get_tensor_ov_name(m_cgraph, cur_node);
1043            m_model_outputs[cur_node_name] = cur_node;
1044            m_model_output_names.insert(cur_node_name);
1045        }
1046    }
1047}
1048
1049const ggml_tensor * GgmlOvDecoder::get_tensor_used_op(const ggml_tensor * tensor) const {
1050    if (tensor == nullptr) {
1051        return nullptr;
1052    }
1053    for (int i = 0; i < m_cgraph->n_nodes; i++) {
1054        const auto * node = m_cgraph->nodes[i];
1055        for (int j = 0; j < GGML_MAX_SRC; j++) {
1056            if (node->src[j] == tensor) {
1057                return node;
1058            }
1059        }
1060    }
1061    return nullptr;
1062}
1063
1064const ggml_tensor * GgmlOvDecoder::get_tensor_from_name(const std::string & name) const {
1065    for (int i = 0; i < m_cgraph->n_nodes; i++) {
1066        const auto * node = m_cgraph->nodes[i];
1067        for (int j = 0; j < GGML_MAX_SRC; j++) {
1068            const auto * src = node->src[j];
1069            if (src == nullptr) {
1070                break;
1071            }
1072            if (get_tensor_ov_name(m_cgraph, src) == name) {
1073                return src;
1074            }
1075        }
1076    }
1077    return nullptr;
1078}
1079
1080std::map<std::string, std::string> GgmlOvDecoder::get_kv_param_res_names() const {
1081    std::map<std::string, std::string> kv_param_res_names;
1082    for (const auto & name : m_model_params.kv_names) {
1083        kv_param_res_names[name] = name;
1084    }
1085    return kv_param_res_names;
1086}
1087
1088// MUL_MAT_ID's src[0] is the [k, m, n_expert] expert-weight tensor. It is always a constant per-expert
1089// weight table -- never a computed activation -- regardless of whether the backend happened to mark its
1090// buffer as GGML_BACKEND_BUFFER_USAGE_WEIGHTS (test-backend-ops, for example, never sets that usage
1091// flag, unlike real inference). Without this, non-quantized (F16/F32/BF16) expert weights would fall
1092// through the check below as "not a weight", get decoded as a Parameter/activation instead of a
1093// Constant, and crash GatherMatmul's "only constant weights are supported" check.
1094static bool is_mul_mat_id_expert_weight(const ggml_tensor * node, int src_index) {
1095    return node->op == GGML_OP_MUL_MAT_ID && src_index == 0;
1096}
1097
1098std::map<std::string, std::shared_ptr<ov::Node>> GgmlOvDecoder::create_weight_nodes(ggml_cgraph * cgraph, bool naive) {
1099    std::map<std::string, std::shared_ptr<ov::Node>> model_weights;
1100    auto * nodes = cgraph->nodes;
1101    auto n_nodes = cgraph->n_nodes;
1102    for (int node_i = 0; node_i < n_nodes; node_i++) {
1103        auto * node = nodes[node_i];
1104        for (int i = 0; i < GGML_MAX_SRC; i++) {
1105            auto * src = node->src[i];
1106            if (src == nullptr) {
1107                continue;
1108            }
1109
1110            std::string src_name = get_tensor_ov_name(cgraph, src);
1111            if (is_rope_freqs_weight(src, node)) {
1112                src_name = "rope_freqs.weight";
1113            }
1114            if (!src->view_src) {
1115                ggml_backend_buffer * buffer = src->buffer;
1116                if (buffer->usage == GGML_BACKEND_BUFFER_USAGE_WEIGHTS || ggml_is_quantized(src->type) ||
1117                    is_mul_mat_id_expert_weight(node, i)) {
1118                    if (model_weights.find(src_name) == model_weights.end()) {
1119                        auto weight_node = create_weight_node(src, naive);
1120                        weight_node->set_friendly_name(src_name);
1121                        model_weights[src_name] = weight_node;
1122                    }
1123                }
1124            }
1125        }
1126    }
1127    return model_weights;
1128}
1129
1130// Process-lifetime cache for weight nodes built from NON-OpenVINO buffers (e.g. the
1131// token_embd.weight copy that lives in a CPU/mmap buffer and feeds GET_ROWS). Such
1132// tensors have no OV buffer context to own a cached extra, so without this they are
1133// re-extracted/re-requantized on every (re)compile — for token_embd that is a ~1-2 GB
1134// F32 dequant each time. Keyed by tensor->data, which is stable for the process and
1135// uniquely identifies the immutable weight bytes. OV-buffer weights keep using the
1136// per-tensor extra cache and never reach here.
1137static std::mutex g_nonov_weight_cache_mutex;
1138static std::unordered_map<const void *, std::shared_ptr<ov::Node>> g_nonov_weight_cache;
1139
1140std::set<std::string> GgmlOvDecoder::collect_weight_names(ggml_cgraph * cgraph) {
1141    // Mirrors the name-selection logic of create_weight_nodes() but builds no nodes,
1142    // so topology checks don't trigger weight extraction/requantization.
1143    std::set<std::string> names;
1144    for (int node_i = 0; node_i < cgraph->n_nodes; node_i++) {
1145        auto * node = cgraph->nodes[node_i];
1146        for (int i = 0; i < GGML_MAX_SRC; i++) {
1147            auto * src = node->src[i];
1148            if (src == nullptr) {
1149                continue;
1150            }
1151            std::string src_name(src->name);
1152            if (is_rope_freqs_weight(src, node)) {
1153                src_name = "rope_freqs.weight";
1154            }
1155            if (!src->view_src) {
1156                ggml_backend_buffer * buffer = src->buffer;
1157                if (buffer->usage == GGML_BACKEND_BUFFER_USAGE_WEIGHTS || ggml_is_quantized(src->type)) {
1158                    names.insert(src_name);
1159                }
1160            }
1161        }
1162    }
1163    return names;
1164}
1165
1166std::shared_ptr<ov::Node> GgmlOvDecoder::create_weight_node(ggml_tensor * tensor, bool naive) {
1167    const bool is_ov_buffer = ggml_backend_buffer_is_openvino(tensor->buffer);
1168
1169    // Check if we have a pre-built constant from the OpenVINO backend buffer
1170    // This is set during ggml_backend_openvino_buffer_set_tensor
1171    if (tensor->extra) {
1172        OPENVINO_ASSERT(is_ov_buffer, "Unsupported weight tensor: " + std::string(tensor->name) +
1173                                          " Possibly this is a cpu backend repacked quantized weights");
1174        // Cast to our extra base type and check the type
1175        auto * extra_base = static_cast<ggml_openvino_extra_base *>(tensor->extra);
1176
1177        if (extra_base->type == ggml_openvino_extra_base::Type::WEIGHT) {
1178            // F16/F32/BF16 weight with shared-memory constant
1179            auto * weight_extra = static_cast<ggml_openvino_weight_extra *>(tensor->extra);
1180            if (weight_extra->weight_node) {
1181                // GGML_LOG_DEBUG("%s: using pre-built weight node for %s\n", __func__, tensor->name);
1182                return weight_extra->weight_node;
1183            }
1184        } else if (extra_base->type == ggml_openvino_extra_base::Type::QUANTIZED_WEIGHT) {
1185            // Quantized weight with pre-extracted data
1186            auto * quant_extra = static_cast<ggml_openvino_quantized_weight_extra *>(tensor->extra);
1187            if (quant_extra->weight_node) {
1188                // GGML_LOG_DEBUG("%s: using pre-extracted quantized weight node for %s\n", __func__, tensor->name);
1189                return quant_extra->weight_node;
1190            }
1191        }
1192    }
1193
1194    // MUL_MAT_ID expert weights are 3D GGML tensors [k, m, n_expert].
1195    // Keep the full reversed 4D shape when materializing non-quantized constants,
1196    // otherwise the expert dimension is collapsed and later Gather/MatMul logic
1197    // only sees a single expert slice.
1198    if (!ggml_is_quantized(tensor->type) && (tensor->ne[2] > 1 || tensor->ne[3] > 1)) {
1199        auto weight_tensor = ov::Tensor(get_ov_type(tensor), get_shape(tensor), tensor->data);
1200        auto weight_node = std::make_shared<ov::op::v0::Constant>(weight_tensor);
1201        weight_node->set_friendly_name(tensor->name);
1202        return weight_node;
1203    }
1204
1205    // Non-OV-buffer weights (CPU/mmap, e.g. the GET_ROWS token_embd copy) have no buffer
1206    // context to cache an extra in, so memoize them here keyed by their (stable) data
1207    // pointer to avoid re-extracting on every recompile. Opt-in via
1208    // GGML_OPENVINO_REDUCE_COMPILE_MEM or GGML_OPENVINO_MEMORY_OPTIMIZE. Skip
1209    // for `naive` (test/naive path) since use_bias changes the produced node.
1210    const bool cacheable_nonov = ggml_openvino_reduce_compile_mem_enabled() && !is_ov_buffer &&
1211                                 !naive && tensor->data != nullptr;
1212    if (cacheable_nonov) {
1213        std::lock_guard<std::mutex> lock(g_nonov_weight_cache_mutex);
1214        auto it = g_nonov_weight_cache.find(tensor->data);
1215        if (it != g_nonov_weight_cache.end()) {
1216            return it->second;
1217        }
1218    }
1219
1220    // There are three cases where we need to create a new weight node:
1221    // 1. weights are in openvino_host_buffer. Weight loading to host buffer will not trigger backend_buffer_set_tensor
1222    // 2. weights are in cpu/cpu_mapped buffer. On token_embd.weight goes to case 1 or 2, depending on whether mmap or direct_io is used
1223    // 3. test-backend-ops. buffers in test-backend-ops does not set USAGE_WEIGHT so backend_buffer_set_tensor will not create weight node
1224
1225    // GGML_LOG_DEBUG("%s: creating new weight node for %s\n", __func__, tensor->name);
1226    static const std::set<ggml_type> weight_types = {GGML_TYPE_F32,  GGML_TYPE_F16,  GGML_TYPE_BF16, GGML_TYPE_Q8_0,
1227                                                     GGML_TYPE_Q4_0, GGML_TYPE_Q4_1, GGML_TYPE_Q5_1, GGML_TYPE_Q4_K,
1228                                                     GGML_TYPE_Q5_K, GGML_TYPE_Q6_K, GGML_TYPE_MXFP4};
1229    if (weight_types.find(tensor->type) == weight_types.end()) {
1230        throw std::runtime_error("Unexpected weight tensor type: " + std::string(tensor->name) + " with type " +
1231                                 ggml_type_name(tensor->type));
1232    }
1233
1234    OvWeight ov_weight;
1235    if (ggml_is_quantized(tensor->type)) {
1236        auto use_bias = naive;
1237        if (is_ov_buffer) {
1238            // For quantized weights, copy raw data to a temp buffer first because
1239            // process_weight_tensor reads from data and writes extracted results
1240            // (weights/scales/zp) to output_base_ptr — they would overlap if both
1241            // point to tensor->data.
1242            size_t raw_size = ggml_nbytes(tensor);
1243            std::vector<uint8_t> tmp(raw_size);
1244            memcpy(tmp.data(), tensor->data, raw_size);
1245            ov_weight = process_weight_tensor(tensor, tmp.data(), tensor->data, use_bias);
1246        } else {
1247            ov_weight = process_weight_tensor(tensor, tensor->data, nullptr, use_bias);
1248        }
1249    } else {
1250        // For non-quantized weights (F16/F32/BF16), data is already in tensor->data.
1251        // process_weight_tensor will create an ov::Tensor wrapping tensor->data directly.
1252        ov_weight = process_weight_tensor(tensor, tensor->data, tensor->data);
1253    }
1254
1255    ov_weight.weight_node->set_friendly_name(tensor->name);
1256    if (!is_ov_buffer) {
1257        if (cacheable_nonov) {
1258            std::lock_guard<std::mutex> lock(g_nonov_weight_cache_mutex);
1259            // Another thread may have inserted concurrently; keep the first.
1260            auto [it, inserted] = g_nonov_weight_cache.emplace(tensor->data, ov_weight.weight_node);
1261            return it->second;
1262        }
1263        return ov_weight.weight_node;
1264    }
1265
1266    ggml_openvino_extra_base * extra;
1267    if (ov_weight.is_quantized()) {
1268        extra = new ggml_openvino_quantized_weight_extra(std::move(ov_weight.weights), std::move(ov_weight.scales),
1269                                                         std::move(ov_weight.zp), ov_weight.weight_node);
1270    } else {
1271        extra = new ggml_openvino_weight_extra(std::move(ov_weight.weights), ov_weight.weight_node);
1272    }
1273    ggml_openvino_buffer_register_extra(tensor, extra);
1274
1275    return ov_weight.weight_node;
1276}
1277
1278void GgmlOvDecoder::dump_cgraph(const ggml_cgraph * cgraph, std::string & filename) {
1279    std::ofstream file(filename);
1280    if (!file.is_open()) {
1281        std::cerr << "Failed to open file" << std::endl;
1282        return;
1283    }
1284
1285    file << "=== GRAPH ===\n";
1286
1287    // clang-format off
1288    file << "n_nodes = " << cgraph->n_nodes << "\n";
1289    file << " " << std::setw(3) << "nodes"
1290                <<  std::setw(15) << "shape"
1291                << std::setw(20) << "op"
1292                << std::setw(20) << "name"
1293                << std::setw(3) << "    "
1294                << std::setw(62) << "stride"
1295                << std::setw(20) << "buffer_type"
1296                << "\n";
1297    for (int i = 0; i < cgraph->n_nodes; i++) {
1298        ggml_tensor * node = cgraph->nodes[i];
1299
1300        // Get buffer type name
1301        const char * buf_name = "none";
1302        ggml_backend_buffer_t buf = node->view_src ? node->view_src->buffer : node->buffer;
1303        if (buf) {
1304            buf_name = ggml_backend_buffer_name(buf);
1305        }
1306
1307        file << " - " << std::setw(3) << i << ": [ "
1308             << std::setw(5) << node->ne[0] << ", "
1309             << std::setw(5) << node->ne[1] << ", "
1310             << std::setw(5) << node->ne[2] << ", "
1311             << std::setw(5) << node->ne[3] << "] "
1312             << std::left << std::setw(20) << ggml_op_name(node->op) << std::right << " "
1313             << std::left << std::setw(45) << node->name << std::right
1314             << std::setw(2) << "[ "
1315             << std::setw(0) << node->nb[0] << ", "
1316             << std::setw(5) << node->nb[1] << ", "
1317             << std::setw(5) << node->nb[2] << ", "
1318             << std::setw(5) << node->nb[3] << "] "
1319             << std::right << std::setw(15) << buf_name << std::right
1320             << "\n";
1321
1322        for (int i = 0; i < GGML_MAX_SRC; i++) {
1323            if (auto* src = node->src[i]) {
1324                // Get buffer type name for source
1325                const char * src_buf_name = "none";
1326                ggml_backend_buffer_t src_buf = src->view_src ? src->view_src->buffer : src->buffer;
1327                if (src_buf) {
1328                    src_buf_name = ggml_backend_buffer_name(src_buf);
1329                }
1330
1331                file << std::setw(10) << " [ "
1332                << std::setw(5) << src->ne[0] << ", "
1333                << std::setw(5) << src->ne[1] << ", "
1334                << std::setw(5) << src->ne[2] << ", "
1335                << std::setw(5) << src->ne[3] << "] "
1336                << std::setw(12)
1337                << i << ": " << std::left << std::setw(12) << ggml_op_name(src->op) << std::right;
1338                file << std::left << std::setw(30) << src->name << std::right
1339                << std::setw(16) << "[ "
1340                << std::setw(0) << src->nb[0] << ", "
1341                << std::setw(5) << src->nb[1] << ", "
1342                << std::setw(5) << src->nb[2] << ", "
1343                << std::setw(5) << src->nb[3] << "] "
1344                << std::right << std::setw(15) << src_buf_name << std::right
1345                << "\n";
1346            }
1347        }
1348    }
1349
1350    file << "n_leafs = " << cgraph->n_leafs << "\n";
1351    for (int i = 0; i < cgraph->n_leafs; i++) {
1352        ggml_tensor * node = cgraph->leafs[i];
1353
1354        // Get buffer type name for leaf
1355        const char * leaf_buf_name = "none";
1356        ggml_backend_buffer_t leaf_buf = node->view_src ? node->view_src->buffer : node->buffer;
1357        if (leaf_buf) {
1358            leaf_buf_name = ggml_backend_buffer_name(leaf_buf);
1359        }
1360
1361        file << " - " << std::setw(3) << i << ": [ "
1362             << std::setw(5) << node->ne[0] << ", "
1363             << std::setw(5) << node->ne[1] << "] "
1364             << std::setw(8) << ggml_op_name(node->op) << " "
1365             << std::setw(16) << ggml_get_name(node)
1366             << std::setw(20) << leaf_buf_name << "\n";
1367    }
1368    // clang-format on
1369    file << "========================================\n";
1370
1371    file.close();
1372}
1373
1374void print_tensor_address_map(const ggml_cgraph * cgraph) {
1375    std::map<void *, std::vector<std::string>> address_map;
1376    for (int node_n = 0; node_n < cgraph->n_nodes; node_n++) {
1377        auto * node = cgraph->nodes[node_n];
1378        if (node->data) {
1379            auto it = address_map.find(node->data);
1380            if (it == address_map.end()) {
1381                address_map[node->data] = std::vector<std::string>();
1382            }
1383            address_map[node->data].push_back(node->name);
1384        }
1385    }
1386    for (const auto & pair : address_map) {
1387        std::cout << "Address: " << pair.first << std::endl;
1388        for (const auto & name : pair.second) {
1389            std::cout << name << " ; ";
1390        }
1391        std::cout << std::endl << std::endl;
1392    }
1393}
1394
1395ov::Shape GgmlOvDecoder::get_shape(const ggml_tensor * tensor) {
1396    std::vector<size_t> shape;
1397    for (int i = GGML_MAX_DIMS - 1; i >= 0; --i) {
1398        shape.push_back(static_cast<size_t>(tensor->ne[i]));
1399    }
1400    return shape;
1401}
1402
1403std::vector<size_t> GgmlOvDecoder::get_stride(const ggml_tensor * tensor) {
1404    std::vector<size_t> stride;
1405    for (int i = GGML_MAX_DIMS - 1; i >= 0; --i) {
1406        stride.push_back(static_cast<size_t>(tensor->nb[i]));
1407    }
1408    return stride;
1409}
1410
1411ov::element::Type GgmlOvDecoder::get_ov_type(const ggml_tensor * tensor) {
1412    switch (tensor->type) {
1413    case GGML_TYPE_F64:
1414        return ov::element::f64;
1415    case GGML_TYPE_F32:
1416        return ov::element::f32;
1417    case GGML_TYPE_F16:
1418        return ov::element::f16;
1419    case GGML_TYPE_BF16:
1420        return ov::element::bf16;
1421    case GGML_TYPE_I8:
1422        return ov::element::i8;
1423    case GGML_TYPE_I16:
1424        return ov::element::i16;
1425    case GGML_TYPE_I32:
1426        return ov::element::i32;
1427    case GGML_TYPE_I64:
1428        return ov::element::i64;
1429    default:
1430        return ov::element::dynamic;
1431    }
1432}
1433
1434ov::PartialShape GgmlOvDecoder::get_input_shape(int node_idx, const std::string & name) const {
1435    return ov::PartialShape(get_shape(m_node_info_list[node_idx].node_inputs.at(name)));
1436}
1437
1438std::vector<size_t> GgmlOvDecoder::get_input_stride(int node_idx, const std::string & name) const {
1439    return get_stride(m_node_info_list[node_idx].node_inputs.at(name));
1440}
1441
1442size_t GgmlOvDecoder::get_view_input_size(int node_idx, const std::string & name) const {
1443    auto it = m_node_info_list[node_idx].node_inputs_views.find(name);
1444    if (it != m_node_info_list[node_idx].node_inputs_views.end()) {
1445        return it->second.size();
1446    }
1447    return 0;
1448}
1449
1450size_t GgmlOvDecoder::get_view_input_offset(int node_idx, const std::string & name, size_t view_index) const {
1451    auto it = m_node_info_list[node_idx].node_inputs_views.find(name);
1452    if (it != m_node_info_list[node_idx].node_inputs_views.end()) {
1453        if (view_index < it->second.size()) {
1454            return it->second[view_index].second->view_offs;
1455        }
1456    }
1457    return 0;
1458}
1459
1460size_t GgmlOvDecoder::get_view_input_src_offset(int node_idx, const std::string & name, size_t view_index) const {
1461    auto it = m_node_info_list[node_idx].node_inputs_views.find(name);
1462    if (it != m_node_info_list[node_idx].node_inputs_views.end()) {
1463        if (view_index < it->second.size()) {
1464            auto * view_tensor = it->second[view_index].second;
1465            if (view_tensor && view_tensor->src[0]) {
1466                return view_tensor->src[0]->view_offs;
1467            }
1468        }
1469    }
1470    return 0;
1471}
1472
1473std::vector<size_t> GgmlOvDecoder::get_view_input_stride(int node_idx,
1474                                                         const std::string & name,
1475                                                         size_t view_index) const {
1476    auto it = m_node_info_list[node_idx].node_inputs_views.find(name);
1477    if (it != m_node_info_list[node_idx].node_inputs_views.end()) {
1478        if (view_index < it->second.size()) {
1479            return get_stride(it->second[view_index].second);
1480        }
1481    }
1482    return {};
1483}
1484
1485std::vector<size_t> GgmlOvDecoder::get_view_input_src_stride(int node_idx,
1486                                                             const std::string & name,
1487                                                             size_t view_index) const {
1488    auto it = m_node_info_list[node_idx].node_inputs_views.find(name);
1489    if (it != m_node_info_list[node_idx].node_inputs_views.end()) {
1490        if (view_index < it->second.size()) {
1491            auto * view_tensor = it->second[view_index].second;
1492            if (view_tensor && view_tensor->src[0]) {
1493                return get_stride(view_tensor->src[0]);
1494            }
1495        }
1496    }
1497    return {};
1498}
1499
1500ov::Shape GgmlOvDecoder::get_view_input_ggml_shape(int node_idx, const std::string & name, size_t view_index) const {
1501    auto it = m_node_info_list[node_idx].node_inputs_views.find(name);
1502    if (it != m_node_info_list[node_idx].node_inputs_views.end()) {
1503        if (view_index < it->second.size()) {
1504            return get_shape(it->second[view_index].second);
1505        }
1506    }
1507    return {};
1508}
1509
1510ov::Shape GgmlOvDecoder::get_view_input_src_ggml_shape(int node_idx,
1511                                                       const std::string & name,
1512                                                       size_t view_index) const {
1513    auto it = m_node_info_list[node_idx].node_inputs_views.find(name);
1514    if (it != m_node_info_list[node_idx].node_inputs_views.end()) {
1515        if (view_index < it->second.size()) {
1516            auto * view_tensor = it->second[view_index].second;
1517            if (view_tensor && view_tensor->src[0]) {
1518                return get_shape(view_tensor->src[0]);
1519            }
1520        }
1521    }
1522    return {};
1523}
1524
1525ov::PartialShape GgmlOvDecoder::get_view_input_ov_shape(int node_idx,
1526                                                        const std::string & name,
1527                                                        size_t view_index) const {
1528    auto it = m_node_info_list[node_idx].node_inputs_views.find(name);
1529    if (it != m_node_info_list[node_idx].node_inputs_views.end()) {
1530        if (view_index < it->second.size()) {
1531            auto * tensor = it->second[view_index].second;
1532            ov::PartialShape shape = ov::PartialShape{get_shape(tensor)};
1533
1534            // Check if this tensor has a dynamic dimension
1535            auto dynamic_it = m_node_dynamic_dims.find(tensor);
1536            if (dynamic_it != m_node_dynamic_dims.end() && dynamic_it->second != -1) {
1537                int dynamic_dim_index = dynamic_it->second;
1538                // GGML uses reverse indexing, so convert to OpenVINO indexing
1539                shape[3 - dynamic_dim_index] = m_is_static ? get_static_n_tokens() : -1;
1540            }
1541
1542            return shape;
1543        }
1544    }
1545    return {};
1546}
1547
1548ov::PartialShape GgmlOvDecoder::get_view_input_src_ov_shape(int node_idx,
1549                                                            const std::string & name,
1550                                                            size_t view_index) const {
1551    auto it = m_node_info_list[node_idx].node_inputs_views.find(name);
1552    if (it != m_node_info_list[node_idx].node_inputs_views.end()) {
1553        if (view_index < it->second.size()) {
1554            auto * view_tensor = it->second[view_index].second;
1555            if (view_tensor && view_tensor->src[0]) {
1556                auto * src_tensor = view_tensor->src[0];
1557                ov::PartialShape shape = ov::PartialShape{get_shape(src_tensor)};
1558
1559                // Check if this tensor has a dynamic dimension
1560                auto dynamic_it = m_node_dynamic_dims.find(src_tensor);
1561                if (dynamic_it != m_node_dynamic_dims.end() && dynamic_it->second != -1) {
1562                    int dynamic_dim_index = dynamic_it->second;
1563                    // GGML uses reverse indexing, so convert to OpenVINO indexing
1564                    shape[3 - dynamic_dim_index] = m_is_static ? get_static_n_tokens() : -1;
1565                }
1566
1567                return shape;
1568            }
1569        }
1570    }
1571    return {};
1572}
1573
1574std::string GgmlOvDecoder::get_view_input_name(int node_idx, const std::string & name, size_t view_index) const {
1575    auto it = m_node_info_list[node_idx].node_inputs_views.find(name);
1576    if (it != m_node_info_list[node_idx].node_inputs_views.end()) {
1577        if (view_index < it->second.size()) {
1578            return it->second[view_index].first;
1579        }
1580    }
1581    return "";
1582}
1583
1584std::string GgmlOvDecoder::get_view_input_src_name(int node_idx, const std::string & name, size_t view_index) const {
1585    auto it = m_node_info_list[node_idx].node_inputs_views.find(name);
1586    if (it != m_node_info_list[node_idx].node_inputs_views.end()) {
1587        if (view_index < it->second.size()) {
1588            auto * view_tensor = it->second[view_index].second;
1589            if (view_tensor && view_tensor->src[0]) {
1590                return get_tensor_ov_name(m_cgraph, view_tensor->src[0]);
1591            }
1592        }
1593    }
1594    return "";
1595}
1596
1597ov::element::Type GgmlOvDecoder::get_input_type(int node_idx, const std::string & name) const {
1598    return get_ov_type(m_node_info_list[node_idx].node_inputs.at(name));
1599}
1600
1601size_t GgmlOvDecoder::get_input_size() const {
1602    return m_model_inputs.size();
1603}
1604
1605size_t GgmlOvDecoder::get_input_size(int node_idx) const {
1606    return m_node_info_list[node_idx].node_inputs_names.size();
1607}
1608
1609std::vector<std::string> GgmlOvDecoder::get_input_names(int node_idx) const {
1610    return m_node_info_list[node_idx].node_inputs_names;
1611}
1612
1613ov::PartialShape GgmlOvDecoder::get_output_shape(int node_idx) const {
1614    auto * ggml_tensor = m_node_info_list[node_idx].node;
1615    return ov::PartialShape(get_shape(ggml_tensor));
1616}
1617
1618ov::element::Type GgmlOvDecoder::get_output_type(const int node_idx) const {
1619    return get_ov_type(m_node_info_list[node_idx].node);
1620}
1621
1622std::vector<size_t> GgmlOvDecoder::get_output_stride(int node_idx) const {
1623    auto * ggml_tensor = m_node_info_list[node_idx].node;
1624    return get_stride(ggml_tensor);
1625}
1626
1627std::vector<std::string> GgmlOvDecoder::get_output_names(int node_idx) const {
1628    return {m_node_info_list[node_idx].node_name};
1629}
1630
1631std::string GgmlOvDecoder::get_inplace_op_src(int node_idx) const {
1632    auto * node = m_node_info_list[node_idx].node;
1633    if (!::is_inplace_op(node) || node->view_src == nullptr || ggml_nbytes(node) == 0) {
1634        return "";
1635    }
1636    const int op_case = m_node_info_list[node_idx].node_op_case;
1637    if (node->op == GGML_OP_CPY && (op_case == 1 || op_case == 2 || op_case == 3) &&
1638        m_compute_params.s_copy_active_slot_len == -1) {
1639        return "";
1640    }
1641    return get_tensor_ov_name(m_cgraph, node->view_src);
1642}
1643
1644bool GgmlOvDecoder::is_view_like_alias_of(int node_idx, const std::string & view_src_name) const {
1645    auto * node = m_node_info_list[node_idx].node;
1646    if (node->view_src == nullptr || get_tensor_ov_name(m_cgraph, node->view_src) != view_src_name) {
1647        return false;
1648    }
1649    return node->op == GGML_OP_RESHAPE || node->op == GGML_OP_VIEW;
1650}
1651
1652const std::string & GgmlOvDecoder::get_op_name() const {
1653    static const std::string unknown_name = "UNKNOWN_OP_NAME";
1654    return unknown_name;
1655}
1656
1657int32_t GgmlOvDecoder::get_op_dynamic_dim(int node_idx) const {
1658    auto it = m_node_dynamic_dims.find(m_node_info_list[node_idx].node);
1659    if (it == m_node_dynamic_dims.end()) {
1660        return -1;
1661    }
1662    return it->second;
1663}
1664
1665const std::string & GgmlOvDecoder::get_op_name(int node_idx) const {
1666    return m_node_info_list[node_idx].node_name;
1667}
1668
1669int32_t * GgmlOvDecoder::get_input_op_params(int node_idx, const std::string & name) const {
1670    return m_node_info_list[node_idx].node_inputs.at(name)->op_params;
1671}
1672
1673int32_t * GgmlOvDecoder::get_output_op_params(int node_idx) const {
1674    return m_node_info_list[node_idx].node->op_params;
1675}
1676
1677size_t GgmlOvDecoder::get_output_op_offset(int node_idx) const {
1678    return m_node_info_list[node_idx].node->view_offs;
1679}
1680
1681void GgmlOvDecoder::visit_subgraph(std::function<void(std::shared_ptr<GgmlDecoder>, int node_idx)> node_visitor) const {
1682    for (int node_idx = 0; node_idx < m_cgraph->n_nodes; node_idx++) {
1683        if (m_cgraph->nodes[node_idx]->op == GGML_OP_NONE) {
1684            continue;
1685        }
1686        node_visitor(std::make_shared<GgmlOvDecoder>(*this), node_idx);
1687    }
1688}
1689
1690std::string GgmlOvDecoder::compute_op_type(const ggml_tensor * node) {
1691    switch (node->op) {
1692    case GGML_OP_UNARY:
1693        return std::string("GGML_UNARY_OP_") + ggml_unary_op_name(ggml_get_unary_op(node));
1694    case GGML_OP_GLU:
1695        return std::string("GGML_GLU_OP_") + ggml_glu_op_name(ggml_get_glu_op(node));
1696    default:
1697        return std::string("GGML_OP_") + ggml_op_name(node->op);
1698    }
1699}
1700
1701const std::string & GgmlOvDecoder::get_op_type(int node_idx) const {
1702    return m_node_info_list[node_idx].node_op_type;
1703}
1704
1705const std::string & GgmlOvDecoder::get_op_type() const {
1706    static const std::string unknown_op = "UNKNOWN_GGML_OP";
1707    return unknown_op;
1708}
1709
1710void GgmlOvDecoder::compute_node_dynamic_dims() {
1711    auto visit_node = [&](auto && self, ggml_tensor * node) -> void {
1712        if (!node) {
1713            return;
1714        }
1715
1716        if (node->op == GGML_OP_CPY) {
1717            m_node_dynamic_dims[node] = -1;
1718        }
1719
1720        if (m_node_dynamic_dims.count(node)) {
1721            return;
1722        }
1723        for (int i = 0; i < GGML_MAX_SRC; i++) {
1724            ggml_tensor * src = node->src[i];
1725            if (src == nullptr) {
1726                continue;
1727            }
1728            struct ggml_tensor * root_src = nullptr;
1729            // if (src->org_src) {
1730            //     root_src = src->org_src;
1731            // }
1732            if (root_src) {
1733                if (is_inp_tok(root_src, node) || is_inp_pos(root_src, node) || is_output_idx(root_src, node)) {
1734                    m_node_dynamic_dims[root_src] = 0;
1735                    m_node_dynamic_dims[src] = m_node_dynamic_dims[root_src];
1736                    continue;
1737                }
1738                self(self, root_src);
1739                m_node_dynamic_dims[src] = m_node_dynamic_dims[root_src];
1740            } else {
1741                if (is_inp_tok(src, node) || is_inp_pos(src, node) || is_output_idx(src, node)) {
1742                    m_node_dynamic_dims[src] = 0;
1743                    continue;
1744                }
1745                if (node->op == GGML_OP_VIEW && src->op == GGML_OP_NONE && !is_stateful() && !m_model_is_splitted) {
1746                    m_node_dynamic_dims[src] = 1;
1747                    continue;
1748                }
1749                self(self, src);
1750            }
1751        }
1752        switch (node->op) {
1753        case GGML_OP_NONE:
1754            m_node_dynamic_dims[node] = -1;
1755            break;
1756        case GGML_OP_GET_ROWS:
1757            m_node_dynamic_dims[node] = -1;
1758            if (m_node_dynamic_dims[node->src[1]] != -1) {
1759                auto dynamic_dim_idx = m_node_dynamic_dims[node->src[1]];
1760                if (dynamic_dim_idx == 0) {
1761                    m_node_dynamic_dims[node] = 1;
1762                } else {
1763                    auto dynamic_dim_stride = node->src[1]->nb[dynamic_dim_idx] / ggml_type_size(node->src[1]->type) *
1764                                              ggml_type_size(node->src[0]->type);
1765                    for (int i = 0; i < GGML_MAX_DIMS; i++) {
1766                        if (dynamic_dim_stride == node->src[0]->nb[i]) {
1767                            m_node_dynamic_dims[node] = i;
1768                            break;
1769                        }
1770                    }
1771                }
1772                // OPENVINO_ASSERT(dynamic_dim_value == node->ne[m_node_dynamic_dims[node]],
1773                //                 "Dynamic dim value mismatch for node: " + std::string(node->name) +
1774                //                     " and its src[1]: " + std::string(node->src[1]->name));
1775            }
1776            break;
1777        case GGML_OP_MUL:
1778        case GGML_OP_MUL_MAT:
1779            m_node_dynamic_dims[node] = -1;
1780            if (m_node_dynamic_dims[node->src[0]] != -1) {
1781                m_node_dynamic_dims[node] = m_node_dynamic_dims[node->src[0]];
1782            }
1783            if (m_node_dynamic_dims[node->src[1]] != -1) {
1784                m_node_dynamic_dims[node] = m_node_dynamic_dims[node->src[1]];
1785            }
1786            break;
1787        case GGML_OP_PERMUTE:
1788            m_node_dynamic_dims[node] = -1;
1789            if (m_node_dynamic_dims[node->src[0]] != -1) {
1790                auto dynamic_dim_idx = m_node_dynamic_dims[node->src[0]];
1791                // auto dynamic_dim_value = node->src[0]->ne[dynamic_dim_idx];
1792                for (int i = 0; i < GGML_MAX_DIMS; i++) {
1793                    if (node->op_params[i] == dynamic_dim_idx) {
1794                        m_node_dynamic_dims[node] = i;
1795                        break;
1796                    }
1797                }
1798                // OPENVINO_ASSERT(dynamic_dim_value == node->ne[m_node_dynamic_dims[node]],
1799                //                 "Dynamic dim value mismatch for node: " + std::string(node->name) +
1800                //                     " and its src[0]: " + std::string(node->src[0]->name));
1801            }
1802            break;
1803        case GGML_OP_VIEW: {
1804            // Use stride-based matching: the stride of a VIEW dimension directly
1805            // encodes which source dimension it indexes into, so it uniquely
1806            // identifies the dynamic dim even when two dims share the same size.
1807            m_node_dynamic_dims[node] = -1;
1808            if (m_node_dynamic_dims[node->src[0]] != -1) {
1809                if (node->src[0]->op == GGML_OP_NONE) {
1810                    m_node_dynamic_dims[node] = m_node_dynamic_dims[node->src[0]];
1811                    break;
1812                }
1813                auto dynamic_dim_idx = m_node_dynamic_dims[node->src[0]];
1814                auto dynamic_dim_value = node->src[0]->ne[dynamic_dim_idx];
1815                auto dynamic_dim_stride =
1816                    node->src[0]->nb[dynamic_dim_idx] / ggml_type_size(node->src[0]->type) * ggml_type_size(node->type);
1817                for (int i = 0; i < GGML_MAX_DIMS; i++) {
1818                    if (node->nb[i] == dynamic_dim_stride) {
1819                        m_node_dynamic_dims[node] = i;
1820                        break;
1821                    }
1822                }
1823                if (m_node_dynamic_dims[node] != -1 && dynamic_dim_value != node->ne[m_node_dynamic_dims[node]]) {
1824                    m_node_dynamic_dims[node] = -1;
1825                    GGML_LOG_WARN("ggml-openvino: dynamic dim value mismatch for VIEW node '%s', src[0]: '%s'\n",
1826                                  node->name, node->src[0]->name);
1827                }
1828            }
1829            break;
1830        }
1831        case GGML_OP_TRANSPOSE:
1832        case GGML_OP_RESHAPE: {
1833            if (is_same_shape(node->src[0], node)) {
1834                m_node_dynamic_dims[node] = m_node_dynamic_dims[node->src[0]];
1835                break;
1836            }
1837            // RESHAPE requires src[0] to be contiguous, so both src and result
1838            // have standard compact strides: nb[i] = type_size * prod(ne[0..i-1]).
1839            // Match src->nb[dynamic_dim] against result->nb[i] to find the output
1840            // dimension whose flat-memory boundary aligns with the source dynamic
1841            // boundary. This is unambiguous (result strides are strictly monotone)
1842            // and handles merged-lower-dim cases that ne-value matching misses.
1843            m_node_dynamic_dims[node] = -1;
1844            if (m_node_dynamic_dims[node->src[0]] != -1) {
1845                auto dynamic_dim_idx = m_node_dynamic_dims[node->src[0]];
1846                auto dynamic_dim_stride = node->src[0]->nb[dynamic_dim_idx];
1847                for (int i = 0; i < GGML_MAX_DIMS; i++) {
1848                    if (node->nb[i] == dynamic_dim_stride && node->ne[i] == node->src[0]->ne[dynamic_dim_idx]) {
1849                        m_node_dynamic_dims[node] = i;
1850                        break;
1851                    }
1852                }
1853                if (m_node_dynamic_dims[node] == -1) {
1854                    GGML_LOG_WARN("ggml-openvino: cannot determine dynamic dim for RESHAPE node '%s'\n", node->name);
1855                }
1856            }
1857            break;
1858        }
1859        case GGML_OP_FLASH_ATTN_EXT: {
1860            // Output shape is hard-coded in ggml_flash_attn_ext as:
1861            //   ne = { v->ne[0], q->ne[2], q->ne[1], q->ne[3] }
1862            // i.e. output dim 0 <- v dim 0 (head_size, static)
1863            //      output dim 1 <- q dim 2 (n_heads,   static)
1864            //      output dim 2 <- q dim 1 (n_tokens,  potentially dynamic)
1865            //      output dim 3 <- q dim 3 (batch,     static)
1866            // Using the fixed q-dim -> output-dim mapping table.
1867            // q is src[0]; the mapping from q's dynamic dim to the output dim is:
1868            //   q dim 1 -> output dim 2
1869            //   q dim 2 -> output dim 1
1870            //   q dim 3 -> output dim 3
1871            //   q dim 0 -> output dim 0  (head_size axis, unlikely to be dynamic)
1872            constexpr int q_to_out[GGML_MAX_DIMS] = {0, 2, 1, 3};
1873            m_node_dynamic_dims[node] = -1;
1874            if (m_node_dynamic_dims[node->src[0]] != -1) {
1875                auto q_dynamic_dim = m_node_dynamic_dims[node->src[0]];
1876                m_node_dynamic_dims[node] = q_to_out[q_dynamic_dim];
1877            }
1878            break;
1879        }
1880        case GGML_OP_CONT:
1881            m_node_dynamic_dims[node] = -1;
1882            if (m_node_dynamic_dims[node->src[0]] != -1) {
1883                auto dynamic_dim_idx = m_node_dynamic_dims[node->src[0]];
1884                if (ggml_are_same_shape(node, node->src[0])) {
1885                    m_node_dynamic_dims[node] = dynamic_dim_idx;
1886                } else {
1887                    size_t src_logical_nb[GGML_MAX_DIMS];
1888                    src_logical_nb[0] = ggml_type_size(node->src[0]->type);
1889                    src_logical_nb[1] = src_logical_nb[0] * (node->src[0]->ne[0] / ggml_blck_size(node->src[0]->type));
1890                    for (int i = 2; i < GGML_MAX_DIMS; i++) {
1891                        src_logical_nb[i] = src_logical_nb[i - 1] * node->src[0]->ne[i - 1];
1892                    }
1893
1894                    auto dynamic_dim_stride = src_logical_nb[dynamic_dim_idx] / ggml_type_size(node->src[0]->type) *
1895                                              ggml_type_size(node->type);
1896                    int matched_dim_count = 0;
1897                    int first_matched_dim = -1;
1898                    for (int i = 0; i < GGML_MAX_DIMS; i++) {
1899                        if (node->nb[i] == dynamic_dim_stride && node->ne[i] == node->src[0]->ne[dynamic_dim_idx]) {
1900                            if (first_matched_dim == -1) {
1901                                first_matched_dim = i;
1902                            }
1903                            m_node_dynamic_dims[node] = i;
1904                            matched_dim_count++;
1905                        }
1906                    }
1907                    if (matched_dim_count > 1 && node->src[0]->ne[dynamic_dim_idx] == 1) {
1908                        // Single-token capture: every trailing dim is size 1 with the same stride, so
1909                        // the match is ambiguous. The lowest index is the real axis; the rest are
1910                        // ggml's size-1 padding. Bailing out here would bake the captured token count
1911                        // into the static prefill model, which then runs with a different one.
1912                        m_node_dynamic_dims[node] = first_matched_dim;
1913                    } else if (matched_dim_count != 1) {
1914                        m_node_dynamic_dims[node] = -1;
1915                        GGML_LOG_WARN("ggml-openvino: cannot determine dynamic dim for CONT node '%s', src[0]: '%s'\n",
1916                                      node->name, node->src[0]->name);
1917                    }
1918                }
1919            }
1920            break;
1921        case GGML_OP_CONCAT:
1922            for (int i = 0; i < GGML_MAX_DIMS; i++) {
1923                if (node->src[0]->ne[i] != node->ne[i]) {
1924                    m_node_dynamic_dims[node] = i;
1925                    break;
1926                }
1927            }
1928            break;
1929        case GGML_OP_SSM_CONV:
1930        case GGML_OP_GATED_DELTA_NET:
1931            m_node_dynamic_dims[node] = 1;
1932            break;
1933        case GGML_OP_RMS_NORM:
1934        case GGML_OP_L2_NORM:
1935        case GGML_OP_NORM:
1936        case GGML_OP_ADD:
1937        case GGML_OP_SUB:
1938        case GGML_OP_GLU:
1939        case GGML_OP_ROPE:
1940        case GGML_OP_SCALE:
1941        case GGML_OP_SOFT_MAX:
1942        case GGML_OP_ARGSORT:
1943        case GGML_OP_ADD_ID:
1944        case GGML_OP_UNARY:
1945        case GGML_OP_CUMSUM:
1946        case GGML_OP_FILL:
1947        case GGML_OP_SET:
1948        case GGML_OP_DIAG:
1949        case GGML_OP_TRI:
1950        case GGML_OP_REPEAT:
1951        // Shape-preserving elementwise ops: the dynamic dim is unchanged from src[0].
1952        // DIV/CLAMP are used in the MoE routing-weight normalization
1953        // (sum_rows -> clamp -> div). If they are left untracked here the dynamic
1954        // (token) dim is lost there, the captured prefill token count gets baked into
1955        // the downstream reshapes, and every decoder layer after layer 0 turns static
1956        // (which then triggers the GPU in-place-concat KV-cache corruption).
1957        case GGML_OP_DIV:
1958        case GGML_OP_CLAMP:
1959        case GGML_OP_PAD:
1960            m_node_dynamic_dims[node] = m_node_dynamic_dims[node->src[0]];
1961            break;
1962        case GGML_OP_SUM_ROWS:
1963            // SUM_ROWS reduces ggml axis 0 to size 1 and preserves all other axes, so the
1964            // dynamic dim is preserved unless it was axis 0 (then it is summed away).
1965            m_node_dynamic_dims[node] =
1966                (m_node_dynamic_dims[node->src[0]] == 0) ? -1 : m_node_dynamic_dims[node->src[0]];
1967            break;
1968        case GGML_OP_MUL_MAT_ID:
1969        case GGML_OP_SOLVE_TRI:
1970            m_node_dynamic_dims[node] = m_node_dynamic_dims[node->src[1]];
1971            break;
1972        case GGML_OP_CPY:
1973        case GGML_OP_SET_ROWS:
1974            m_node_dynamic_dims[node] = -1;
1975            break;
1976        case GGML_OP_IM2COL: {
1977            m_node_dynamic_dims[node] = -1;
1978            if (m_node_dynamic_dims[node->src[1]] != -1) {
1979                const bool is_2D = node->op_params[6] == 1;
1980                const int src_dyn = m_node_dynamic_dims[node->src[1]];
1981                if (is_2D) {
1982                    if (src_dyn == 0) {
1983                        m_node_dynamic_dims[node] = 1;  // IW -> OW
1984                    } else if (src_dyn == 1) {
1985                        m_node_dynamic_dims[node] = 2;  // IH -> OH
1986                    } else if (src_dyn == 3) {
1987                        m_node_dynamic_dims[node] = 3;  // N  -> N
1988                    }
1989                } else {
1990                    if (src_dyn == 0) {
1991                        m_node_dynamic_dims[node] = 1;  // IW -> OW
1992                    } else if (src_dyn == 2) {
1993                        m_node_dynamic_dims[node] = 2;  // N  -> N  (1D: b->ne[2] is the batch/channel dim)
1994                    }
1995                }
1996                if (m_node_dynamic_dims[node] != -1) {
1997                    OPENVINO_ASSERT(node->src[1]->ne[src_dyn] == node->ne[m_node_dynamic_dims[node]],
1998                                    "Dynamic dim value mismatch for IM2COL node: " + std::string(node->name) +
1999                                        " and its src[1]: " + std::string(node->src[1]->name));
2000                }
2001            }
2002            break;
2003        }
2004        default:
2005            GGML_LOG_DEBUG("ggml-openvino: compute_node_dynamic_dims: unhandled op %s for node '%s'\n",
2006                           ggml_op_name(node->op), node->name);
2007            break;
2008        }
2009    };
2010
2011    for (int i = 0; i < m_cgraph->n_nodes; i++) {
2012        ggml_tensor * node = m_cgraph->nodes[i];
2013        visit_node(visit_node, node);
2014    }
2015
2016    // print the nodes in m_cgraph name & shape with the dynamic dim (the dynamic dim is the dimension with -1 in m_node_dynamic_dims) for debugging
2017    if (0) {
2018        for (int i = 0; i < m_cgraph->n_nodes; i++) {
2019            ggml_tensor * node = m_cgraph->nodes[i];
2020            int dynamic_dim = m_node_dynamic_dims[node];
2021            std::cout << "[" << i << "] " << "node_name: " << node->name << " op: " << ggml_op_name(node->op)
2022                      << " shape: [";
2023            for (int j = 0; j < 4; j++) {
2024                if (j == dynamic_dim) {
2025                    std::cout << "*";
2026                } else {
2027                    std::cout << node->ne[j];
2028                }
2029                if (j < 3) {
2030                    std::cout << ", ";
2031                }
2032            }
2033            std::cout << "]" << std::endl;
2034            // print the src name & shape with the dynamic dim for debugging
2035            for (int j = 0; j < GGML_MAX_SRC; j++) {
2036                ggml_tensor * src = node->src[j];
2037                if (src == nullptr) {
2038                    continue;
2039                }
2040                int src_dynamic_dim = m_node_dynamic_dims[src];
2041                std::cout << "    [" << j << "] src_name: " << src->name << " [";
2042                for (int k = 0; k < 4; k++) {
2043                    if (k == src_dynamic_dim) {
2044                        std::cout << "*";
2045                    } else {
2046                        std::cout << src->ne[k];
2047                    }
2048                    if (k < 3) {
2049                        std::cout << ", ";
2050                    }
2051                }
2052                std::cout << "]" << std::endl;
2053            }
2054            std::cout << std::endl;
2055        }
2056    }
2057}