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
70.1 KiB1496 linesraw
1#include "utils.h"
2
3#include "ggml-impl.h"
4#include "ggml-openvino-extra.h"
5#include "ggml-openvino/ggml-decoder.h"
6#include "ggml.h"
7#include "model-cache.h"
8#include "openvino/frontend.h"
9#include "openvino/input_model.h"
10
11#include <algorithm>
12#include <cassert>
13#include <cmath>
14#include <cstddef>
15#include <cstdint>
16#include <cstdlib>
17#include <cstring>
18#include <fstream>
19#include <functional>
20#include <future>
21#include <iomanip>
22#include <iostream>
23#include <memory>
24#include <openvino/core/any.hpp>
25#include <openvino/core/graph_util.hpp>
26#include <openvino/core/shape.hpp>
27#include <openvino/core/type/float16.hpp>
28#include <openvino/frontend/manager.hpp>
29#include <openvino/openvino.hpp>
30#include <openvino/runtime/compiled_model.hpp>
31#include <openvino/runtime/infer_request.hpp>
32#include <openvino/runtime/intel_gpu/ocl/ocl.hpp>
33#include <openvino/runtime/intel_npu/properties.hpp>
34#include <openvino/runtime/properties.hpp>
35#include <openvino/runtime/tensor.hpp>
36#include <optional>
37#include <string>
38#include <unordered_map>
39#include <vector>
40
41// Suppress  deprecation warning for ov::Tensor::data()
42#pragma GCC diagnostic push
43#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
44
45enum ggml_status ov_graph_compute(ggml_cgraph * cgraph, ggml_backend_t backend) {
46    ggml_backend_openvino_context * ctx = (ggml_backend_openvino_context *) backend->context;
47    try {
48        if (ggml_openvino_getenv_int("GGML_OPENVINO_DUMP_CGRAPH")) {
49            std::string filename = "cgraph_ov.txt";
50            GgmlOvDecoder::dump_cgraph(cgraph, filename);
51        }
52
53        const auto is_static = ggml_openvino_is_npu() || ggml_openvino_getenv_int("GGML_OPENVINO_FORCE_STATIC");
54
55        GGML_ASSERT(ctx->runtime_context != nullptr);
56        std::shared_ptr<ov_runtime_context> r_ctx = std::static_pointer_cast<ov_runtime_context>(ctx->runtime_context);
57
58        return is_static ? ov_graph_compute_static(cgraph, r_ctx) : ov_graph_compute_dynamic(cgraph, r_ctx);
59    } catch (const ov::Exception & e) {
60        GGML_LOG_ERROR("GGML OpenVINO backend ov::Exception: %s\n", e.what());
61        return GGML_STATUS_FAILED;
62    } catch (const std::exception & e) {
63        GGML_LOG_ERROR("GGML OpenVINO backend std::exception: %s\n", e.what());
64        return GGML_STATUS_FAILED;
65    } catch (...) {
66        GGML_LOG_ERROR("GGML OpenVINO backend unknown exception\n");
67        return GGML_STATUS_FAILED;
68    }
69}
70
71// For a KV cache input, return an ov::Tensor sized to n_kv (== attention_size
72// for that layer) instead of the fully-allocated ctx_per_seq. Pre-conditions:
73//   * non-static (CPU/GPU) backend, single sequence, seq_active_start == 0
74//   * ggml KV layout is a contiguous [1, 1, ctx_per_seq, n_heads_kv*head_size]
75//     so the first n_kv rows are the live prefix and shrinking the ctx axis
76//     gives a valid tensor over the same host storage
77//   * not an SWA layer (ring cache): once the window has wrapped the first
78//     n_kv rows no longer contain the live prefix
79// On any unmet pre-condition returns std::nullopt; the caller falls back to
80// the full-size tensor.
81static std::optional<ov::Tensor> try_make_kv_sliced_tensor(std::shared_ptr<GgmlOvDecoder> ggml_decoder,
82                                                           const std::string & name,
83                                                           const ggml_tensor * ggml_tensor) {
84    static const bool kv_slice_disabled = ggml_openvino_getenv_int("GGML_OPENVINO_DISABLE_KV_SLICE");
85    if (kv_slice_disabled) {
86        return std::nullopt;
87    }
88    if (ggml_decoder->is_static() || ggml_decoder->is_stateful()) {
89        return std::nullopt;
90    }
91    if (ggml_tensor->op != GGML_OP_NONE || ggml_tensor->view_src != nullptr) {
92        return std::nullopt;
93    }
94    const auto * op = ggml_decoder->get_tensor_used_op(ggml_tensor);
95    if (!GgmlOvDecoder::is_kvcache(ggml_tensor, op)) {
96        return std::nullopt;
97    }
98
99    const auto & compute_params = ggml_decoder->get_compute_params();
100    if (compute_params.n_seq_active != 1 || compute_params.seq_active_start != 0) {
101        return std::nullopt;
102    }
103
104    int layer;
105    if (auto layer_opt = extract_layer_from_name(name); layer_opt.has_value()) {
106        layer = layer_opt.value();
107    } else {
108        return std::nullopt;
109    }
110
111    const bool is_swa = ggml_decoder->is_swa_layer(layer);
112    if (is_swa) {
113        return std::nullopt;
114    }
115    const int ctx_per_seq = ggml_decoder->get_ctx_per_seq();
116    const int n_kv = compute_params.attention_size;
117    if (ctx_per_seq <= 0 || n_kv <= 0 || n_kv >= ctx_per_seq) {
118        return std::nullopt;
119    }
120
121    ov::Shape full_shape = ggml_decoder->get_shape(ggml_tensor);
122    if (full_shape.size() != 4 || full_shape[0] != 1 || full_shape[1] != 1 ||
123        static_cast<int>(full_shape[2]) != ctx_per_seq) {
124        return std::nullopt;
125    }
126
127    ov::Shape sliced_shape = full_shape;
128    sliced_shape[2] = static_cast<size_t>(n_kv);
129
130    // Disabling for now as gpu has bug with in-place ScatterUpdate with remote tensors, can re-enable once CVS-186519 is fixed
131    // if (ggml_openvino_buffer_is_remote(ggml_tensor)) {
132    //     auto remote_context = ggml_openvino_get_remote_context();
133    //     auto gpu_context = remote_context->as<ov::intel_gpu::ocl::ClContext>();
134    //     return gpu_context.create_tensor(ggml_decoder->get_ov_type(ggml_tensor), sliced_shape, ggml_tensor->data);
135    // }
136
137    return ov::Tensor(ggml_decoder->get_ov_type(ggml_tensor), sliced_shape, ggml_tensor->data);
138}
139
140static uint64_t ggml_openvino_model_cache_extra_cfg(const std::string & device, bool stateful) {
141    const char * manual_gqa_env = ggml_openvino_getenv_str("GGML_OPENVINO_MANUAL_GQA_ATTN");
142    const bool manual_gqa_enabled = manual_gqa_env != nullptr ?
143                                        ggml_openvino_getenv_int("GGML_OPENVINO_MANUAL_GQA_ATTN") > 0 :
144                                        device == "GPU";
145
146    uint64_t extra_cfg = 0;
147    extra_cfg = extra_cfg * 131 + (stateful ? 1u : 0u);
148    extra_cfg = extra_cfg * 131 + (ggml_openvino_reduce_compile_mem_enabled() ? 1u : 0u);
149    extra_cfg = extra_cfg * 131 + (ggml_openvino_getenv_int("GGML_OPENVINO_DISABLE_KV_SLICE") ? 1u : 0u);
150    extra_cfg = extra_cfg * 131 + (manual_gqa_enabled ? 1u : 0u);
151    return extra_cfg;
152}
153
154ov::Tensor create_ov_output_tensor(std::shared_ptr<GgmlOvDecoder> ggml_decoder,
155                                   std::shared_ptr<ov::InferRequest> infer_request,
156                                   int output_index,
157                                   const ggml_tensor * ggml_tensor) {
158    if (auto sliced = try_make_kv_sliced_tensor(ggml_decoder, std::string(ggml_tensor->name), ggml_tensor)) {
159        return *sliced;
160    }
161
162    // Disabling for now as gpu has bug with in-place ScatterUpdate with remote tensors, can re-enable once CVS-186519 is fixed
163    // if (ggml_tensor->extra != nullptr && !ggml_decoder->is_splited_model()) {
164    //     auto * extra_base = static_cast<ggml_openvino_extra_base *>(ggml_tensor->extra);
165    //     if (extra_base->type == ggml_openvino_extra_base::Type::TENSOR) {
166    //         auto * tensor_extra = static_cast<ggml_openvino_tensor_extra *>(extra_base);
167    //         return *tensor_extra->tensor;
168    //     }
169    // }
170
171    auto output_type = ggml_decoder->get_ov_type(ggml_tensor);
172    ov::Shape output_shape;
173    void * output_data = ggml_tensor->data;
174    if (ggml_decoder->is_static()) {
175        output_shape = infer_request->get_output_tensor(output_index).get_shape();
176    } else {
177        // For a CPY into a padded view_src (e.g. a padded KV cache buffer), the
178        // OV ScatterUpdate node outputs the full view_src shape, not the CPY node's
179        // own (smaller) shape.  Using the CPY shape here causes set_output_tensor to
180        // fail with a shape-incompatibility error.  Use view_src's shape and data
181        // pointer instead so the OV tensor matches the model output exactly.
182        if (ggml_tensor->op == GGML_OP_CPY && ggml_tensor->view_src != nullptr &&
183            ggml_nbytes(ggml_tensor) != ggml_nbytes(ggml_tensor->view_src)) {
184            output_shape = ggml_decoder->get_shape(ggml_tensor->view_src);
185            output_data = ggml_tensor->view_src->data;
186        } else {
187            output_shape = ggml_decoder->get_shape(ggml_tensor);
188        }
189    }
190    ov::Tensor output_tensor(output_type, output_shape, output_data);
191    return output_tensor;
192}
193
194enum ggml_status ov_graph_compute_dynamic(ggml_cgraph * cgraph, std::shared_ptr<ov_runtime_context> r_ctx) {
195    auto & core = ov_singleton_core();
196    const auto & config = ggml_openvino_get_compile_config();
197    const auto & device = r_ctx->device;
198    const auto & stateful = r_ctx->stateful;
199    static auto is_static = false;
200
201    static const bool cache_disabled = ggml_openvino_getenv_int("GGML_OPENVINO_DISABLE_CACHE");
202
203    // is_model_splitted is O(n_nodes^2) plus a create_weight_nodes scan and takes ~20 ms
204    // on a Llama-1B decode graph. It is called once per graph_compute invocation but the
205    // graph shape is identical across all decode steps, so memoize by graph_key: compute
206    // graph_key first (a few hundred us), and if the same key is already in decoder_cache
207    // we know the graph is not splitted (only not-splitted graphs get inserted there).
208    graph_key key(cgraph);
209    bool key_seen = false;
210    if (!cache_disabled) {
211        std::lock_guard<std::mutex> map_lock(r_ctx->ctx_mutex);
212        key_seen = r_ctx->decoder_cache.find(key) != r_ctx->decoder_cache.end();
213    }
214
215    bool model_is_splitted = key_seen ? false : is_model_splitted(cgraph);
216
217    if (is_naive(cgraph)) {
218        if (!model_is_splitted) {
219            return naive_compute(cgraph, core, device, config);
220        }
221    }
222
223    auto start_time = ggml_time_us();
224
225    std::shared_ptr<GgmlOvDecoder> ggml_decoder;
226    std::shared_ptr<ov::InferRequest> infer_request;
227    ModelParams m_params;
228    ComputeParams c_params;
229    std::tie(m_params, c_params) = GgmlOvDecoder::compute_llm_params(cgraph, is_static);
230
231    const bool cache_enabled = !model_is_splitted && !cache_disabled;
232    bool cache_hit = false;
233
234    int64_t decoder_end_time;
235    int64_t conversion_end_time;
236    int64_t compile_end_time;
237    int64_t infer_end_time;
238    int64_t ov_raw_infer_start;
239
240    {
241        std::shared_ptr<decoder_runtime_ctx> entry;
242        ModelParams old_m_params;
243
244        if (cache_enabled) {
245            std::lock_guard<std::mutex> map_lock(r_ctx->ctx_mutex);
246            auto it = r_ctx->decoder_cache.find(key);
247            cache_hit = it != r_ctx->decoder_cache.end();
248            if (cache_hit) {
249                entry = it->second;
250            } else {
251                r_ctx->clear_caches_locked();
252                auto mutex = std::make_shared<std::mutex>();
253                entry = std::make_shared<decoder_runtime_ctx>(mutex);
254                r_ctx->decoder_cache[key] = entry;
255            }
256        } else {
257            auto mutex = std::make_shared<std::mutex>();
258            entry = std::make_shared<decoder_runtime_ctx>(mutex);
259            cache_hit = false;
260        }
261
262        std::lock_guard<std::mutex> lock(*(entry->mutex));
263
264        if (cache_hit) {
265            ggml_decoder = entry->ptr;
266            old_m_params = ggml_decoder->get_model_params();
267            if (!ggml_decoder->is_splited_model()) {
268                cache_hit = old_m_params.can_reuse_dynamically(m_params);
269            }
270        }
271
272        std::vector<std::string> ov_input_names;
273        std::vector<std::string> ov_output_names;
274
275        if (cache_hit) {
276            std::map<std::string, std::shared_ptr<ov::Node>> model_weights;
277            ggml_decoder->set_compute_params(c_params);
278            ggml_decoder->set_model_params(m_params);
279            if (old_m_params.kv_buffer_changed(m_params)) {
280                ggml_decoder->update_io(cgraph);
281            }
282            ggml_decoder->add_extra_inputs();
283            {
284                std::lock_guard<std::mutex> map_lock(r_ctx->ctx_mutex);
285                infer_request = r_ctx->infer_request_cache.at(key);
286                ov_input_names = r_ctx->ov_input_names_cache.at(key);
287                ov_output_names = r_ctx->ov_output_names_cache.at(key);
288            }
289
290            if (stateful) {
291                const auto * inp_pos = get_inp_pos_tensor(cgraph);
292                int32_t * pos_data = (int32_t *) inp_pos->data;
293                auto pos_shape = ggml_decoder->get_shape(inp_pos);
294                if (pos_data[0] == 0) {
295                    infer_request->reset_state();
296                    r_ctx->stateful_kv_size = pos_shape[3];
297                } else if (r_ctx->stateful_kv_size == static_cast<size_t>(pos_data[0])) {
298                    r_ctx->stateful_kv_size += pos_shape[3];
299                } else {
300                    auto states = infer_request->query_state();
301                    for (auto state : states) {
302                        auto state_tensor = state.get_state();
303                        auto state_tensor_shape = state_tensor.get_shape();
304                        if (static_cast<uint32_t>(pos_data[0]) > r_ctx->stateful_kv_size) {
305                            std::string state_name;
306                            try {
307                                state_name = r_ctx->kv_state_input_name_map.at(state.get_name());
308                            } catch (...) {
309                                GGML_LOG_ERROR(
310                                    "GGML OpenVINO backend stateful inference failed: no input found for the state\n");
311                                return GGML_STATUS_FAILED;
312                            }
313                            auto kv_tensor = get_ov_input_tensor(ggml_decoder, state_name);
314                            kv_tensor.set_shape({state_tensor_shape[0], kv_tensor.get_shape()[2], state_tensor_shape[2],
315                                                 state_tensor_shape[3]});
316                            state_tensor = kv_tensor;
317                            state_tensor_shape = state_tensor.get_shape();
318                        }
319                        ov::Coordinate begin = {0, 0, 0, 0};
320                        ov::Coordinate end = {state_tensor_shape[0], static_cast<uint32_t>(pos_data[0]),
321                                              state_tensor_shape[2], state_tensor_shape[3]};
322                        ov::Tensor new_state_tensor(state_tensor, begin, end);
323                        state.set_state(new_state_tensor);
324                    }
325                    r_ctx->stateful_kv_size = pos_data[0] + pos_shape[3];
326                }
327            }
328
329            decoder_end_time = ggml_time_us();
330            conversion_end_time = decoder_end_time;
331            compile_end_time = decoder_end_time;
332        } else {
333            // Fail fast: a cache-miss recompile feeds weight data to compile_model, but
334            // GGML_OPENVINO_RELEASE_WEIGHTS (or GGML_OPENVINO_MEMORY_OPTIMIZE on GPU)
335            // may have already dropped the host weight pages
336            // (they would read as zeros). That mode requires stable graph shapes.
337            if (ggml_openvino_weight_buffers_released()) {
338                GGML_ABORT(
339                    "ggml-openvino: a new graph needs to be compiled but host weight buffers were already "
340                    "released via GGML_OPENVINO_RELEASE_WEIGHTS/GGML_OPENVINO_MEMORY_OPTIMIZE. This mode requires "
341                    "stable graph shapes; disable host weight release for dynamic workloads.");
342            }
343            if (cache_enabled) {
344                std::lock_guard<std::mutex> map_lock(r_ctx->ctx_mutex);
345                r_ctx->infer_request_cache.erase(key);
346            }
347
348            // Frontend-level compiled-model cache (GGML_OPENVINO_COMPILED_MODEL_CACHE_DIR): if this model
349            // was compiled before, import the saved blob and skip requant + convert +
350            // compile. Only the dynamic single-model path is cached (split models compile
351            // two graphs and are left to the plugin-level ov::cache_dir). The decoder is
352            // still needed for I/O mapping, but can be built without weight nodes since
353            // the weights are baked into the imported CompiledModel.
354            const std::string model_cache_dir = ggml_openvino_model_cache_dir();
355            uint64_t model_fp = 0;
356            std::string blob_path, manifest_path;
357            bool imported = false;
358            // When the frontend model cache is active it supersedes the plugin-level
359            // ov::cache_dir: a blob exported from a model compiled WITH cache_dir cannot
360            // be re-imported (import returns an uninitialized model). Strip cache_dir /
361            // cache_mode from the config used for the cached compile and the import.
362            ov::AnyMap mc_config = config;
363            if (!model_cache_dir.empty()) {
364                mc_config.erase("CACHE_DIR");
365                mc_config.erase("CACHE_MODE");
366            }
367            if (!model_cache_dir.empty() && !model_is_splitted) {
368                const uint64_t extra_cfg = ggml_openvino_model_cache_extra_cfg(device, stateful);
369                model_fp = ggml_openvino_model_fingerprint(cgraph, device, /*fa=*/true, m_params.rope_params,
370                                                           15, extra_cfg);
371                blob_path = ggml_openvino_model_cache_blob_path(model_cache_dir, model_fp);
372                manifest_path = ggml_openvino_model_cache_manifest_path(model_cache_dir, model_fp);
373
374                std::ifstream blob_in(blob_path, std::ios::binary);
375                bool blob_ok = blob_in.is_open();
376                bool manifest_ok = blob_ok && ggml_openvino_model_cache_verify_manifest(manifest_path, cgraph, model_fp);
377                if (blob_ok && manifest_ok) {
378                    int64_t import_start = ggml_time_us();
379                    try {
380                        ov::CompiledModel cm;
381                        auto remote_context = ggml_openvino_get_remote_context();
382                        if (remote_context.has_value()) {
383                            cm = core.import_model(blob_in, remote_context.value(), mc_config);
384                        } else {
385                            cm = core.import_model(blob_in, device, mc_config);
386                        }
387                        // Lightweight decoder: names-only weight map (membership is all the
388                        // decoder needs; weights live in the imported model).
389                        std::map<std::string, std::shared_ptr<ov::Node>> weight_names;
390                        for (const auto & n : GgmlOvDecoder::collect_weight_names(cgraph)) {
391                            weight_names[n] = nullptr;
392                        }
393                        ggml_decoder = std::make_shared<GgmlOvDecoder>(cgraph, m_params, c_params, weight_names,
394                                                                       is_static, stateful, model_is_splitted);
395                        infer_request = std::make_shared<ov::InferRequest>(cm.create_infer_request());
396                        entry->ptr = ggml_decoder;
397                        // Names must match the decoder's ggml-tensor keys. The non-cached
398                        // path keys off Parameter/Result *friendly names* (set by the
399                        // frontend); export_model preserves these, and each compiled-model
400                        // port's node is exactly that Parameter/Result. Use the port nodes
401                        // directly (NOT get_runtime_model(), whose graph differs and is
402                        // unsafe to deref this way).
403                        for (const auto & p : cm.inputs()) {
404                            ov_input_names.push_back(p.get_node()->get_friendly_name());
405                        }
406                        for (const auto & o : cm.outputs()) {
407                            ov_output_names.push_back(o.get_node()->get_friendly_name());
408                        }
409                        imported = true;
410                        if (ggml_openvino_getenv_int("GGML_OPENVINO_PROFILING")) {
411                            GGML_LOG_INFO("  - Model cache import time: %.3f ms \n",
412                                          (ggml_time_us() - import_start) / 1000.0);
413                        }
414                        GGML_LOG_INFO("ggml-openvino: model cache HIT %s\n", blob_path.c_str());
415                    } catch (const std::exception & e) {
416                        GGML_LOG_WARN("ggml-openvino: model cache import failed (%s), recompiling\n", e.what());
417                        imported = false;
418                    }
419                }
420            }
421
422            std::shared_ptr<ov::Model> model;
423            if (imported) {
424                decoder_end_time = conversion_end_time = compile_end_time = ggml_time_us();
425            } else {
426                auto model_weights = GgmlOvDecoder::create_weight_nodes(cgraph);
427
428                ggml_decoder = std::make_shared<GgmlOvDecoder>(cgraph, m_params, c_params, model_weights, is_static,
429                                                               stateful, model_is_splitted);
430                decoder_end_time = ggml_time_us();
431
432                auto input_model = std::make_shared<ov::frontend::ggml::InputModel>(ggml_decoder);
433                model = ov::frontend::ggml::FrontEnd::convert(input_model);
434                ggml_decoder->clear_model_weights();
435                conversion_end_time = ggml_time_us();
436
437                if (ggml_openvino_getenv_int("GGML_OPENVINO_DUMP_IR")) {
438                    char timestamped_filename[64];
439                    auto timestamp = (long long) ggml_time_us();
440                    snprintf(timestamped_filename, sizeof(timestamped_filename), "model_%lld.xml", timestamp);
441                    ov::serialize(model, timestamped_filename);
442                }
443
444                // Use the cache-stripped config when the frontend model cache is active, so
445                // the resulting CompiledModel can be exported and later re-imported.
446                const ov::AnyMap & compile_config = model_cache_dir.empty() ? config : mc_config;
447                ov::CompiledModel compiled_model;
448                auto remote_context = ggml_openvino_get_remote_context();
449                if (remote_context.has_value()) {
450                    compiled_model = core.compile_model(model, remote_context.value(), compile_config);
451                } else {
452                    compiled_model = core.compile_model(model, device, compile_config);
453                }
454                compile_end_time = ggml_time_us();
455
456                // Export to the frontend model cache for next time. Publish the blob first,
457                // then the manifest, so a cache hit only sees fully written artifacts.
458                if (!model_cache_dir.empty() && !model_is_splitted && model_fp != 0) {
459                    try {
460                        const std::string blob_tmp = blob_path + ".tmp";
461                        const std::string manifest_tmp = manifest_path + ".tmp";
462                        if (ggml_openvino_model_cache_write_manifest(manifest_tmp, cgraph, model_fp)) {
463                            std::ofstream blob_out(blob_tmp, std::ios::binary | std::ios::trunc);
464                            if (blob_out.is_open()) {
465                                compiled_model.export_model(blob_out);
466                                blob_out.close();
467                                if (blob_out.good()) {
468                                    if (std::rename(blob_tmp.c_str(), blob_path.c_str()) == 0 &&
469                                        std::rename(manifest_tmp.c_str(), manifest_path.c_str()) == 0) {
470                                        GGML_LOG_INFO("ggml-openvino: model cache WROTE %s\n", blob_path.c_str());
471                                    } else {
472                                        std::remove(blob_tmp.c_str());
473                                        std::remove(manifest_tmp.c_str());
474                                    }
475                                } else {
476                                    std::remove(blob_tmp.c_str());
477                                    std::remove(manifest_tmp.c_str());
478                                }
479                            } else {
480                                std::remove(manifest_tmp.c_str());
481                            }
482                        }
483                    } catch (const std::exception & e) {
484                        GGML_LOG_WARN("ggml-openvino: model cache export failed: %s\n", e.what());
485                    }
486                }
487
488                infer_request = std::make_shared<ov::InferRequest>(compiled_model.create_infer_request());
489                entry->ptr = ggml_decoder;
490
491                for (const auto & ov_param : model->get_parameters()) {
492                    ov_input_names.push_back(ov_param->get_friendly_name());
493                }
494                for (const auto & ov_output : model->get_results()) {
495                    ov_output_names.push_back(ov_output->get_friendly_name());
496                }
497            }  // end non-imported (compile) path
498
499            if (cache_enabled) {
500                std::lock_guard<std::mutex> map_lock(r_ctx->ctx_mutex);
501                r_ctx->infer_request_cache[key] = infer_request;
502                r_ctx->ov_input_names_cache[key] = ov_input_names;
503                r_ctx->ov_output_names_cache[key] = ov_output_names;
504            }
505
506            if (stateful && cache_enabled) {
507                const auto * inp_pos = get_inp_pos_tensor(cgraph);
508                auto pos_shape = ggml_decoder->get_shape(inp_pos);
509                r_ctx->stateful_kv_size = pos_shape[3];
510                const auto kv_param_res_names = ggml_decoder->get_kv_param_res_names();
511                for (const auto & pair : kv_param_res_names) {
512                    r_ctx->kv_state_input_name_map[pair.first + pair.second] = pair.first;
513                }
514            }
515        }
516
517        for (size_t i = 0; i < ov_input_names.size(); i++) {
518            auto param_name = ov_input_names[i];
519            auto input_tensor = get_ov_input_tensor(ggml_decoder, param_name);
520            infer_request->set_input_tensor(i, input_tensor);
521
522            if (ggml_openvino_getenv_int("GGML_OPENVINO_DEBUG_INPUT")) {
523                print_input_tensor_info(param_name, input_tensor);
524            }
525        }
526
527        for (size_t i = 0; i < ov_output_names.size(); i++) {
528            // Debug-only outputs added via GGML_OPENVINO_DEBUG_NODE (see
529            // translate_session.cpp) have no corresponding ggml tensor; leave
530            // them unbound so OpenVINO allocates its own tensor for them,
531            // rather than aliasing a ggml buffer that may be overwritten by a
532            // later in-place op before we get to read it.
533            const auto & model_outputs = ggml_decoder->get_model_outputs();
534            auto model_output_it = model_outputs.find(ov_output_names[i]);
535            if (model_output_it == model_outputs.end()) {
536                continue;
537            }
538            auto * ggml_tensor = model_output_it->second;
539            if (ggml_nbytes(ggml_tensor) == 0) {
540                continue;
541            }
542            auto output_tensor = create_ov_output_tensor(ggml_decoder, infer_request, i, ggml_tensor);
543            infer_request->set_output_tensor(i, output_tensor);
544        }
545
546        ov_raw_infer_start = ggml_time_us();
547        infer_request->infer();
548        infer_end_time = ggml_time_us();
549
550        if (ggml_openvino_getenv_int("GGML_OPENVINO_DEBUG_OUTPUT") ||
551            ggml_openvino_getenv_str("GGML_OPENVINO_DEBUG_NODE")) {
552            for (size_t i = 0; i < ov_output_names.size(); i++) {
553                const auto output_tensor = infer_request->get_output_tensor(i);
554                print_output_tensor_info(ov_output_names[i], output_tensor, output_tensor.data());
555            }
556        }
557
558        if (ggml_openvino_getenv_int("GGML_OPENVINO_PROFILING")) {
559            GGML_LOG_INFO("\nGGML OpenVINO Backend: \n");
560            GGML_LOG_INFO("  - Graph decoder time: %.3f ms \n", (decoder_end_time - start_time) / 1000.0);
561            if (!cache_hit) {
562                GGML_LOG_INFO("  - Graph conversion time: %.3f ms \n",
563                              (conversion_end_time - decoder_end_time) / 1000.0);
564                GGML_LOG_INFO("  - Graph compile time: %.3f ms \n", (compile_end_time - conversion_end_time) / 1000.0);
565            }
566            GGML_LOG_INFO("  - Graph inference time: %.3f ms \n", (infer_end_time - compile_end_time) / 1000.0);
567            GGML_LOG_INFO("  - OV raw infer time: %.3f ms \n", (infer_end_time - ov_raw_infer_start) / 1000.0);
568        }
569    }
570
571    // GGML_OPENVINO_RELEASE_WEIGHTS (or GGML_OPENVINO_MEMORY_OPTIMIZE on GPU): the plugin holds its own device copy of
572    // every weight after compile, so the host weight buffers can be dropped to reclaim
573    // RSS. The GPU backend uses a single dynamic-shape model for both prefill and decode,
574    // so once a graph is compiled it is reused for the whole session — the only thing
575    // that forces a recompile is clear_caches() on backend teardown. We therefore release
576    // on the first cache-hit (model compiled, plugin has its copy) and, crucially, pin the
577    // compiled-model cache so it survives backend teardown (see ggml_backend_openvino_free).
578    // Without the pin, a later test/context would recompile against the now-dropped pages.
579    // A genuinely new graph still fails fast at the cache-miss compile branch.
580    if (cache_hit && ggml_openvino_release_weights_enabled(device) &&
581        !ggml_openvino_weight_buffers_released()) {
582        ggml_openvino_release_weight_buffers();
583    }
584
585    return GGML_STATUS_SUCCESS;
586}
587
588enum ggml_status ov_graph_compute_static(ggml_cgraph * cgraph, std::shared_ptr<ov_runtime_context> r_ctx) {
589    auto & core = ov_singleton_core();
590
591    auto get_prefill_chunk_size = [] {
592        static const int chunk_size = []() {
593            int env_prefill_chunk_size = ggml_openvino_getenv_int("GGML_OPENVINO_PREFILL_CHUNK_SIZE");
594            return env_prefill_chunk_size > 0 ? env_prefill_chunk_size : 256;
595        }();
596        return chunk_size;
597    };
598
599    // Normally NPU, but honors GGML_OPENVINO_DEVICE so GGML_OPENVINO_FORCE_STATIC can run the
600    // static-shape path on CPU/GPU to isolate translation bugs from NPUW/NPU-driver issues.
601    static std::string device = ggml_openvino_get_device_name();
602    static auto is_static = true;
603    static auto stateful = false;
604
605    auto prefill_chunk_size = get_prefill_chunk_size();
606    const auto & config = ggml_openvino_get_compile_config();
607
608    if (is_naive(cgraph)) {
609        return naive_compute(cgraph, core, device, config);
610    }
611
612    auto start_time = ggml_time_us();
613
614    std::shared_ptr<GgmlOvDecoder> ggml_decoder;
615    std::shared_ptr<ov::InferRequest> infer_request;
616    ModelParams m_params;
617    ComputeParams c_params;
618    std::tie(m_params, c_params) = GgmlOvDecoder::compute_llm_params(cgraph, is_static);
619
620    const auto * inp_pos = get_inp_pos_tensor(cgraph);
621    const auto is_prefill = get_is_prefill(cgraph, inp_pos);
622    graph_key key(cgraph);
623    static const bool cache_enabled = !ggml_openvino_getenv_int("GGML_OPENVINO_DISABLE_CACHE");
624    bool cache_hit = false;
625
626    int64_t decoder_end_time;
627    int64_t conversion_end_time;
628    int64_t compile_end_time;
629    int64_t infer_end_time;
630    int64_t ov_raw_infer_start;
631    int64_t ov_raw_infer_total = 0;
632
633    std::shared_ptr<decoder_runtime_ctx> entry;
634    ModelParams old_m_params;
635
636    if (cache_enabled) {
637        std::lock_guard<std::mutex> map_lock(r_ctx->ctx_mutex);
638        auto it = r_ctx->decoder_cache.find(key);
639        cache_hit = it != r_ctx->decoder_cache.end();
640        if (cache_hit) {
641            entry = it->second;
642        } else {
643            r_ctx->clear_caches_locked();
644            auto mutex = std::make_shared<std::mutex>();
645            entry = std::make_shared<decoder_runtime_ctx>(mutex);
646            r_ctx->decoder_cache[key] = entry;
647        }
648    } else {
649        auto mutex = std::make_shared<std::mutex>();
650        entry = std::make_shared<decoder_runtime_ctx>(mutex);
651        cache_hit = false;
652    }
653
654    std::lock_guard<std::mutex> lock(*(entry->mutex));
655
656    if (cache_hit) {
657        ggml_decoder = entry->ptr;
658        old_m_params = ggml_decoder->get_model_params();
659        cache_hit = old_m_params.can_reuse_statically(m_params);
660    }
661
662    std::vector<std::string> ov_input_names_local;
663    std::vector<std::string> ov_output_names_local;
664
665    if (cache_hit) {
666        std::map<std::string, std::shared_ptr<ov::Node>> model_weights;
667        ggml_decoder->m_is_prefill = is_prefill;
668        ggml_decoder->set_model_params(m_params);
669        ggml_decoder->set_compute_params(c_params);
670        if (old_m_params.kv_buffer_changed(m_params)) {
671            ggml_decoder->update_io(cgraph);
672        }
673        ggml_decoder->add_extra_inputs();
674        {
675            std::lock_guard<std::mutex> map_lock(r_ctx->ctx_mutex);
676            infer_request =
677                is_prefill ? r_ctx->infer_request_cache_prefill.at(key) : r_ctx->infer_request_cache.at(key);
678            ov_input_names_local = r_ctx->ov_input_names_cache.at(key);
679            ov_output_names_local = r_ctx->ov_output_names_cache.at(key);
680        }
681
682        decoder_end_time = ggml_time_us();
683        conversion_end_time = decoder_end_time;
684        compile_end_time = decoder_end_time;
685    } else {
686        if (cache_enabled) {
687            std::lock_guard<std::mutex> map_lock(r_ctx->ctx_mutex);
688            r_ctx->infer_request_cache.erase(key);
689            r_ctx->infer_request_cache_prefill.erase(key);
690        }
691
692        std::shared_ptr<ov::Model> model;
693        auto model_weights = GgmlOvDecoder::create_weight_nodes(cgraph);
694
695        if (m_params.n_heads_kv == -1) {
696            // graph is not a LLM, e.g. context-shift graph
697            prefill_chunk_size = inp_pos->ne[0];
698        }
699        auto ggml_decoder_prefill = std::make_shared<GgmlOvDecoder>(
700            cgraph, m_params, c_params, model_weights, is_static, stateful, false, true, prefill_chunk_size);
701        auto ggml_decoder_decode = std::make_shared<GgmlOvDecoder>(cgraph, m_params, c_params, model_weights, is_static,
702                                                                   stateful, false, false, prefill_chunk_size);
703        decoder_end_time = ggml_time_us();
704
705        const bool dump_ir = ggml_openvino_getenv_int("GGML_OPENVINO_DUMP_IR");
706        const auto dump_ir_timestamp = static_cast<long long>(ggml_time_us());
707
708        auto build_static_model = [&core, &config, dump_ir, dump_ir_timestamp](
709                          std::shared_ptr<GgmlOvDecoder> decoder,
710                          const char * tag,
711                          std::shared_ptr<ov::Model> & model,
712                          ov::CompiledModel & compiled_model,
713                          std::shared_ptr<ov::InferRequest> & infer_request,
714                          int64_t & local_conversion_end_time,
715                          int64_t & local_compile_end_time) {
716            auto input_model = std::make_shared<ov::frontend::ggml::InputModel>(decoder);
717            model = ov::frontend::ggml::FrontEnd::convert(input_model);
718            decoder->clear_model_weights();
719            local_conversion_end_time = ggml_time_us();
720
721            if (dump_ir) {
722                char timestamped_filename[64];
723                snprintf(timestamped_filename, sizeof(timestamped_filename), "model_%s_%lld.xml", tag,
724                         dump_ir_timestamp);
725                ov::serialize(model, timestamped_filename);
726            }
727
728            compiled_model = core.compile_model(model, device, config);
729            infer_request = std::make_shared<ov::InferRequest>(compiled_model.create_infer_request());
730            local_compile_end_time = ggml_time_us();
731        };
732        std::shared_ptr<ov::Model> model_prefill;
733        std::shared_ptr<ov::Model> model_decode;
734        ov::CompiledModel compiled_model_prefill;
735        ov::CompiledModel compiled_model_decode;
736        std::shared_ptr<ov::InferRequest> infer_request_prefill;
737        std::shared_ptr<ov::InferRequest> infer_request_decode;
738        int64_t prefill_conversion_end_time;
739        int64_t decode_conversion_end_time;
740        int64_t prefill_compile_end_time;
741        int64_t decode_compile_end_time;
742        auto prefill_future = std::async(std::launch::async, build_static_model, ggml_decoder_prefill, "prefill",
743                                         std::ref(model_prefill), std::ref(compiled_model_prefill),
744                                         std::ref(infer_request_prefill), std::ref(prefill_conversion_end_time),
745                                         std::ref(prefill_compile_end_time));
746        auto decode_future = std::async(std::launch::async, build_static_model, ggml_decoder_decode, "decode",
747                                        std::ref(model_decode), std::ref(compiled_model_decode),
748                                        std::ref(infer_request_decode), std::ref(decode_conversion_end_time),
749                                        std::ref(decode_compile_end_time));
750        prefill_future.get();
751        decode_future.get();
752        conversion_end_time = std::max(prefill_conversion_end_time, decode_conversion_end_time);
753        compile_end_time = std::max(prefill_compile_end_time, decode_compile_end_time);
754
755        model = is_prefill ? model_prefill : model_decode;
756        ggml_decoder = is_prefill ? ggml_decoder_prefill : ggml_decoder_decode;
757        infer_request = is_prefill ? infer_request_prefill : infer_request_decode;
758        entry->ptr = ggml_decoder;
759
760        for (const auto & ov_param : model->get_parameters()) {
761            ov_input_names_local.push_back(ov_param->get_friendly_name());
762        }
763        for (const auto & ov_output : model->get_results()) {
764            ov_output_names_local.push_back(ov_output->get_friendly_name());
765        }
766
767        if (cache_enabled) {
768            std::lock_guard<std::mutex> map_lock(r_ctx->ctx_mutex);
769            r_ctx->infer_request_cache_prefill[key] = infer_request_prefill;
770            r_ctx->infer_request_cache[key] = infer_request_decode;
771            r_ctx->ov_input_names_cache[key] = ov_input_names_local;
772            r_ctx->ov_output_names_cache[key] = ov_output_names_local;
773        }
774    }
775
776    if (is_prefill) {
777        auto inp_len = get_inp_pos_n_tokens(cgraph, inp_pos);
778        for (int chunk_index = 0; chunk_index * prefill_chunk_size < inp_len; chunk_index++) {
779            for (size_t i = 0; i < ov_input_names_local.size(); i++) {
780                auto param_name = ov_input_names_local[i];
781                auto input_tensor = get_ov_input_tensor_static_prefill(ggml_decoder, param_name, chunk_index);
782                infer_request->set_input_tensor(i, input_tensor);
783
784                if (ggml_openvino_getenv_int("GGML_OPENVINO_DEBUG_INPUT")) {
785                    const auto input_tensor = infer_request->get_input_tensor(i);
786                    print_input_tensor_info(param_name, input_tensor);
787                }
788            }
789
790            for (size_t i = 0; i < ov_output_names_local.size(); i++) {
791                const auto & model_outputs = ggml_decoder->get_model_outputs();
792                auto model_output_it = model_outputs.find(ov_output_names_local[i]);
793                if (model_output_it == model_outputs.end()) {
794                    continue;
795                }
796                auto * ggml_tensor = model_output_it->second;
797                if (ggml_nbytes(ggml_tensor) == 0) {
798                    // Zero-row in-place writeback (e.g. the empty s_copy defrag remainder). The OV
799                    // Result is the full cache, so binding it over this 0-byte buffer overflows it.
800                    continue;
801                }
802                auto output_tensor = create_ov_output_tensor(ggml_decoder, infer_request, i, ggml_tensor);
803                infer_request->set_output_tensor(i, output_tensor);
804            }
805
806            ov_raw_infer_start = ggml_time_us();
807            infer_request->infer();
808            ov_raw_infer_total += ggml_time_us() - ov_raw_infer_start;
809
810            if (ggml_openvino_getenv_int("GGML_OPENVINO_DEBUG_OUTPUT") ||
811                ggml_openvino_getenv_str("GGML_OPENVINO_DEBUG_NODE")) {
812                for (size_t i = 0; i < ov_output_names_local.size(); i++) {
813                    const auto output_tensor = infer_request->get_output_tensor(i);
814                    print_output_tensor_info(ov_output_names_local[i], output_tensor, output_tensor.data());
815                }
816            }
817        }
818        infer_end_time = ggml_time_us();
819    } else {
820        for (size_t i = 0; i < ov_input_names_local.size(); i++) {
821            auto param_name = ov_input_names_local[i];
822            auto input_tensor = get_ov_input_tensor_static_decode(ggml_decoder, param_name);
823            infer_request->set_input_tensor(i, input_tensor);
824
825            if (ggml_openvino_getenv_int("GGML_OPENVINO_DEBUG_INPUT")) {
826                const auto input_tensor = infer_request->get_input_tensor(i);
827                print_input_tensor_info(param_name, input_tensor);
828            }
829        }
830
831        for (size_t i = 0; i < ov_output_names_local.size(); i++) {
832            const auto & model_outputs = ggml_decoder->get_model_outputs();
833            auto model_output_it = model_outputs.find(ov_output_names_local[i]);
834            if (model_output_it == model_outputs.end()) {
835                continue;
836            }
837            auto * ggml_tensor = model_output_it->second;
838            if (ggml_nbytes(ggml_tensor) == 0) {
839                continue;
840            }
841            auto output_tensor = create_ov_output_tensor(ggml_decoder, infer_request, i, ggml_tensor);
842            infer_request->set_output_tensor(i, output_tensor);
843        }
844
845        ov_raw_infer_start = ggml_time_us();
846        infer_request->infer();
847        infer_end_time = ggml_time_us();
848        ov_raw_infer_total = infer_end_time - ov_raw_infer_start;
849
850        if (ggml_openvino_getenv_int("GGML_OPENVINO_DEBUG_OUTPUT") ||
851            ggml_openvino_getenv_str("GGML_OPENVINO_DEBUG_NODE")) {
852            for (size_t i = 0; i < ov_output_names_local.size(); i++) {
853                const auto output_tensor = infer_request->get_output_tensor(i);
854                print_output_tensor_info(ov_output_names_local[i], output_tensor, output_tensor.data());
855            }
856        }
857    }
858
859    if (ggml_openvino_getenv_int("GGML_OPENVINO_PROFILING")) {
860        GGML_LOG_INFO("\nGGML OpenVINO Backend: \n");
861        GGML_LOG_INFO("  - Graph decoder time: %.3f ms \n", (decoder_end_time - start_time) / 1000.0);
862        if (!cache_hit) {
863            GGML_LOG_INFO("  - Graph conversion time: %.3f ms \n", (conversion_end_time - decoder_end_time) / 1000.0);
864            GGML_LOG_INFO("  - Graph compile time: %.3f ms \n", (compile_end_time - conversion_end_time) / 1000.0);
865        }
866        GGML_LOG_INFO("  - Graph inference time: %.3f ms \n", (infer_end_time - compile_end_time) / 1000.0);
867        GGML_LOG_INFO("  - OV raw infer time: %.3f ms \n", ov_raw_infer_total / 1000.0);
868    }
869
870    return GGML_STATUS_SUCCESS;
871}
872
873// Detect whether a cgraph is a split subgraph or not.
874// Step 1 compares each node's recorded use_count with actual fan-out references in node->src.
875// Step 2 verifies that node inputs come from model nodes/weights/leafs; external sources imply split.
876bool is_model_splitted(ggml_cgraph * cgraph) {
877    static const bool fallback_enabled = ggml_openvino_getenv_int("GGML_OPENVINO_ENABLE_FALLBACK") != 0;
878    if (!fallback_enabled) {
879        return false;
880    }
881
882    // Backend op tests execute each node through ggml_graph_view(), which preserves the original
883    // graph use_counts while exposing only one node. Treat those single-node views as regular
884    // naive graphs so intermediate ops do not look like split-model fragments.
885    if (cgraph->n_nodes <= 1 && cgraph->n_leafs == 0) {
886        return false;
887    }
888
889    // check the nodes of the model are used by the following nodes, through compare the node's use count and the count of nodes that use it as input. If does not match, return true, else return false.
890    for (int i = 0; i < cgraph->n_nodes; i++) {
891        ggml_tensor * node = cgraph->nodes[i];
892        int use_count = cgraph->use_counts[ggml_hash_find(&cgraph->visited_hash_set, node)];
893        // TODO: this is a workround for the tests case from llama.cpp, fix should from the root cause in the future.
894        if ((cgraph->n_nodes <= 1 && use_count == 0) ||
895            (cgraph->n_nodes <= 1 && node->op == GGML_OP_VIEW && use_count == 1 && node->src[0] != nullptr &&
896             node->src[0]->op == GGML_OP_NONE)) {
897            return false;
898        }
899        if (cgraph->n_nodes == 1 &&
900            (cgraph->nodes[0]->op == GGML_OP_TRANSPOSE || cgraph->nodes[0]->op == GGML_OP_PERMUTE)) {
901            return false;
902        }
903        int input_use_count = 0;
904        for (int j = 0; j < cgraph->n_nodes; j++) {
905            ggml_tensor * other_node = cgraph->nodes[j];
906            for (int k = 0; k < GGML_MAX_SRC; k++) {
907                if (other_node->src[k] == node) {
908                    input_use_count++;
909                }
910            }
911        }
912        if (use_count != input_use_count && node->op != GGML_OP_NONE) {
913            return true;
914        }
915    }
916    // if all nodes's src node's src is not come from the nodes in the model, we think the model is splitted. This is a complementary check for the above check, because for some special case like the output node is not used by any node, the use count and input use count are both 0, we can not determine whether the model is splitted or not just based on the first check.
917    // Only weight-name membership is needed below. With GGML_OPENVINO_REDUCE_COMPILE_MEM
918    // use the name-only collector (no weight extraction); otherwise keep the original
919    // behavior of building (naive) weight nodes and take their names.
920    std::set<std::string> model_weights;
921    if (ggml_openvino_reduce_compile_mem_enabled()) {
922        model_weights = GgmlOvDecoder::collect_weight_names(cgraph);
923    } else {
924        for (const auto & kv : GgmlOvDecoder::create_weight_nodes(cgraph, true)) {
925            model_weights.insert(kv.first);
926        }
927    }
928    std::set<ggml_tensor *> model_nodes(cgraph->nodes, cgraph->nodes + cgraph->n_nodes);
929    // leaf nodes
930    std::set<ggml_tensor *> model_leafs(cgraph->leafs, cgraph->leafs + cgraph->n_leafs);
931    for (int i = 0; i < cgraph->n_nodes; i++) {
932        ggml_tensor * node = cgraph->nodes[i];
933        for (int j = 0; j < GGML_MAX_SRC; j++) {
934            ggml_tensor * src = node->src[j];
935            // the src is also not the model weights, we think the model is splitted.
936            // the src is also not in model leafs, we think the model is splitted.
937            if (src != nullptr && model_nodes.find(src) == model_nodes.end() &&
938                model_weights.find(std::string(src->name)) == model_weights.end() && !model_leafs.empty() == false &&
939                model_leafs.find(src) == model_leafs.end()) {
940                if (GgmlOvDecoder::is_inp_tok(src, node)) {
941                    return false;
942                }
943                return true;
944            }
945        }
946    }
947    return false;
948}
949
950bool is_naive(ggml_cgraph * cgraph) {
951    constexpr int naive_graph_size_threshold = 20;
952    int count = 0;
953    for (int i = 0; i < cgraph->n_nodes; i++) {
954        if (cgraph->nodes[i]->op != GGML_OP_NONE) {
955            count++;
956        }
957    }
958    return count < naive_graph_size_threshold;
959}
960
961enum ggml_status naive_compute(ggml_cgraph * cgraph,
962                               ov::Core & core,
963                               const std::string & device,
964                               const ov::AnyMap & config) {
965    if (cgraph->n_nodes == 1 && (cgraph->nodes[0]->op == GGML_OP_NONE || cgraph->nodes[0]->op == GGML_OP_VIEW)) {
966        return GGML_STATUS_SUCCESS;
967    }
968
969    bool naive = true;
970    auto model_weights = GgmlOvDecoder::create_weight_nodes(cgraph, naive);
971    auto decoder = std::make_shared<GgmlOvDecoder>(cgraph, model_weights);
972    auto input_model = std::make_shared<ov::frontend::ggml::InputModel>(decoder);
973    auto model = ov::frontend::ggml::FrontEnd::convert(input_model, naive);
974    if (ggml_openvino_getenv_int("GGML_OPENVINO_DUMP_IR")) {
975        ov::serialize(model, "IR_naive.xml");
976    }
977
978    std::shared_ptr<ov::InferRequest> infer_request;
979    auto remote_context = ggml_openvino_get_remote_context();
980    if (cgraph->nodes[0]->op == GGML_OP_MUL_MAT) {
981        // TODO ACCURACY hint triggers a bug in GPU plugin/driver on Lunar Lake. Remove once CVS-182166 is resolved
982        core.set_property(device, ov::hint::execution_mode(ov::hint::ExecutionMode::PERFORMANCE));
983    } else {
984        core.set_property(device, ov::hint::execution_mode(ov::hint::ExecutionMode::ACCURACY));
985    }
986    if (remote_context.has_value()) {
987        infer_request = std::make_shared<ov::InferRequest>(
988            core.compile_model(model, remote_context.value(), config).create_infer_request());
989    } else {
990        infer_request =
991            std::make_shared<ov::InferRequest>(core.compile_model(model, device, config).create_infer_request());
992    }
993
994    auto ov_params = model->get_parameters();
995    for (size_t i = 0; i < ov_params.size(); i++) {
996        auto param_name = ov_params[i]->get_friendly_name();
997        auto input_tensor = get_ov_input_tensor(decoder, param_name);
998        infer_request->set_input_tensor(i, input_tensor);
999    }
1000
1001    // Use get_output_tensor + memcpy instead of set_output_tensor to avoid memory overwritten
1002    // when i/o buffer overlaps, e.g. the cgraph is a single PERMUTE
1003
1004    infer_request->infer();
1005
1006    auto ov_results = model->get_results();
1007    for (size_t i = 0; i < ov_results.size(); i++) {
1008        auto output_tensor = infer_request->get_output_tensor(i);
1009        const auto & model_outputs = decoder->get_model_outputs();
1010        auto model_output_it = model_outputs.find(ov_results[i]->get_friendly_name());
1011        if (model_output_it == model_outputs.end()) {
1012            // Debug-only output added via GGML_OPENVINO_DEBUG_NODE; nothing to copy into.
1013            if (ggml_openvino_getenv_int("GGML_OPENVINO_DEBUG_OUTPUT") ||
1014                ggml_openvino_getenv_str("GGML_OPENVINO_DEBUG_NODE")) {
1015                print_output_tensor_info(ov_results[i]->get_friendly_name(), output_tensor, output_tensor.data());
1016            }
1017            continue;
1018        }
1019        auto * ggml_tensor = model_output_it->second;
1020        std::memcpy(ggml_tensor->data, output_tensor.data(), output_tensor.get_byte_size());
1021    }
1022    return GGML_STATUS_SUCCESS;
1023}
1024
1025namespace {
1026template <typename T> void set_zero_diagonal(std::vector<T> & matrix, size_t rows, size_t cols, T zero_value = T{}) {
1027    for (size_t i = 0; i < rows; ++i) {
1028        size_t diag_col = std::min(i, cols - 1);
1029        matrix[i * cols + diag_col] = zero_value;
1030    }
1031}
1032
1033ov::Tensor make_contiguous_split_input_tensor(std::shared_ptr<GgmlOvDecoder> ggml_decoder,
1034                                              const struct ggml_tensor * ggml_tensor,
1035                                              const ov::Shape & input_shape) {
1036    const size_t element_size = ggml_type_size(ggml_tensor->type);
1037    const size_t block_size = ggml_blck_size(ggml_tensor->type);
1038
1039    GGML_ASSERT(block_size == 1 && "non-contiguous split inputs must be plain element types");
1040
1041    const struct ggml_tensor * source_tensor = ggml_tensor->view_src != nullptr ? ggml_tensor->view_src : ggml_tensor;
1042    const size_t source_offset = ggml_tensor->view_src != nullptr ? ggml_tensor->view_offs : 0;
1043
1044    std::vector<uint8_t> source_data(ggml_nbytes(source_tensor));
1045    ggml_backend_tensor_get(source_tensor, source_data.data(), 0, source_data.size());
1046
1047    ov::Tensor input_tensor(ggml_decoder->get_ov_type(ggml_tensor), input_shape);
1048    auto * dst = static_cast<uint8_t *>(input_tensor.data());
1049    size_t dst_offset = 0;
1050
1051    for (size_t i3 = 0; i3 < static_cast<size_t>(ggml_tensor->ne[3]); ++i3) {
1052        for (size_t i2 = 0; i2 < static_cast<size_t>(ggml_tensor->ne[2]); ++i2) {
1053            for (size_t i1 = 0; i1 < static_cast<size_t>(ggml_tensor->ne[1]); ++i1) {
1054                for (size_t i0 = 0; i0 < static_cast<size_t>(ggml_tensor->ne[0]); ++i0) {
1055                    const size_t src_offset = source_offset + i3 * ggml_tensor->nb[3] + i2 * ggml_tensor->nb[2] +
1056                                              i1 * ggml_tensor->nb[1] + i0 * ggml_tensor->nb[0];
1057                    std::memcpy(dst + dst_offset, source_data.data() + src_offset, element_size);
1058                    dst_offset += element_size;
1059                }
1060            }
1061        }
1062    }
1063
1064    return input_tensor;
1065}
1066
1067ov::Tensor convert_ggml_input_to_ov(std::shared_ptr<GgmlOvDecoder> ggml_decoder, const std::string & name) {
1068    const auto * ggml_tensor = ggml_decoder->get_input_ggml_tensor(name);
1069
1070    if (auto sliced = try_make_kv_sliced_tensor(ggml_decoder, name, ggml_tensor)) {
1071        return *sliced;
1072    }
1073
1074    if (ggml_tensor->extra != nullptr && !ggml_decoder->is_splited_model()) {
1075        auto * extra_base = static_cast<ggml_openvino_extra_base *>(ggml_tensor->extra);
1076        if (extra_base->type == ggml_openvino_extra_base::Type::TENSOR) {
1077            // GGML_LOG_DEBUG("Using ggml_tensor->extra as ov::Tensor for input: %s\n", name.c_str());
1078            auto * tensor_extra = static_cast<ggml_openvino_tensor_extra *>(extra_base);
1079            return *tensor_extra->tensor;
1080        }
1081    }
1082
1083    // GGML_LOG_DEBUG("Converting ggml tensor to ov::Tensor for input: %s\n", name.c_str());
1084    auto * input_data = ggml_tensor->data;
1085    ov::Shape input_shape;
1086    if (ggml_tensor->op == GGML_OP_VIEW && !ggml_decoder->is_splited_model()) {
1087        // This case is added to make test-backend-ops work
1088        input_shape = ggml_decoder->get_shape(ggml_tensor->view_src);
1089    } else {
1090        input_shape = ggml_decoder->get_shape(ggml_tensor);
1091    }
1092
1093    if (ggml_decoder->is_splited_model() && !ggml_is_contiguous(ggml_tensor)) {
1094        return make_contiguous_split_input_tensor(ggml_decoder, ggml_tensor, input_shape);
1095    }
1096
1097    auto input_tensor = ov::Tensor(ggml_decoder->get_ov_type(ggml_tensor), input_shape, input_data);
1098    return input_tensor;
1099}
1100}  // namespace
1101
1102ov::Tensor get_ov_input_tensor(std::shared_ptr<GgmlOvDecoder> ggml_decoder, const std::string & param_name) {
1103    ov::Tensor input_tensor;
1104    auto extra_input = ggml_decoder->get_model_extra_inputs().find(param_name);
1105    if (extra_input != ggml_decoder->get_model_extra_inputs().end()) {
1106        input_tensor = ov::Tensor(extra_input->second.type, extra_input->second.shape);
1107        *input_tensor.data<int64_t>() = extra_input->second.value;
1108    } else {
1109        input_tensor = convert_ggml_input_to_ov(ggml_decoder, param_name);
1110    }
1111    return input_tensor;
1112}
1113
1114ov::Tensor get_ov_input_tensor_static_decode(std::shared_ptr<GgmlOvDecoder> ggml_decoder,
1115                                             const std::string & param_name) {
1116    // NPU decoding stage
1117    if (ggml_decoder->get_model_extra_inputs().count(param_name)) {
1118        return get_ov_input_tensor(ggml_decoder, param_name);
1119    }
1120    const auto * ggml_tensor = ggml_decoder->get_input_ggml_tensor(param_name);
1121    const auto * op = ggml_decoder->get_tensor_used_op(ggml_tensor);
1122
1123    if (GgmlOvDecoder::is_inp_tok(ggml_tensor, op) || GgmlOvDecoder::is_inp_pos(ggml_tensor, op) ||
1124        GgmlOvDecoder::is_kv_idx(ggml_tensor, op)) {
1125        // IMROPE's inp_pos holds one value per t/h/w/e plane instead of a single position;
1126        // with a single decode token the planes are still contiguous, so a flat copy works.
1127        const int n_planes = GgmlOvDecoder::is_inp_pos(ggml_tensor, op) ? GgmlOvDecoder::get_inp_pos_n_planes(op) : 1;
1128        assert(ggml_tensor->ne[0] == n_planes);
1129        ov::Shape input_shape = {1, 1, 1, (size_t) n_planes};
1130        ov::Tensor input_tensor(ggml_decoder->get_ov_type(ggml_tensor), input_shape);
1131        std::memcpy(input_tensor.data(), ggml_tensor->data, n_planes * ggml_type_size(ggml_tensor->type));
1132        return input_tensor;
1133    }
1134
1135    if (GgmlOvDecoder::is_output_idx(ggml_tensor, op)) {
1136        ov::Shape input_shape = {1, 1, 1, 1};
1137        ov::Tensor input_tensor(ggml_decoder->get_ov_type(ggml_tensor), input_shape);
1138        int32_t inp_out_id = *((int32_t *) ggml_tensor->data);
1139        assert(ggml_tensor->ne[0] == 1);
1140        assert(inp_out_id == 0);
1141        *input_tensor.data<int32_t>() = inp_out_id;
1142        return input_tensor;
1143    }
1144
1145    if (GgmlOvDecoder::is_inp_mask(ggml_tensor, op)) {
1146        size_t context_size = ggml_decoder->get_ctx_size();
1147        if (ggml_tensor->type == GGML_TYPE_F16) {
1148            std::vector<ggml_fp16_t> padded_data =
1149                pad_input<ggml_fp16_t>(ggml_tensor, 1, context_size, GGML_FP32_TO_FP16(-INFINITY));
1150            ov::Tensor input_tensor(ov::element::f16, ov::Shape{1, 1, 1, context_size});
1151            std::memcpy(input_tensor.data(), padded_data.data(), padded_data.size() * sizeof(ggml_fp16_t));
1152            return input_tensor;
1153        }
1154
1155        std::vector<float> padded_data = pad_input<float>(ggml_tensor, 1, context_size, -INFINITY);
1156        ov::Tensor input_tensor(ov::element::f32, ov::Shape{1, 1, 1, context_size});
1157        auto * data_ptr = input_tensor.data<float>();
1158        std::copy(padded_data.begin(), padded_data.begin() + context_size, data_ptr);
1159        return input_tensor;
1160    }
1161
1162    return get_ov_input_tensor(ggml_decoder, param_name);
1163}
1164
1165ov::Tensor get_ov_input_tensor_static_prefill(std::shared_ptr<GgmlOvDecoder> ggml_decoder,
1166                                              const std::string & param_name,
1167                                              int chunk_index) {
1168    // NPU prompt processing stage
1169    const size_t input_len = ggml_decoder->get_input_len();
1170    const size_t chunk_size = ggml_decoder->m_prefill_chunk_size;
1171    const size_t chunk_valid_size = std::min(chunk_size, input_len - chunk_index * chunk_size);
1172    const size_t chunk_pad_size = chunk_size - chunk_valid_size;
1173
1174    if (param_name == "chunk_valid_len") {
1175        ov::Tensor input_tensor(ov::element::i64, ov::Shape{1});
1176        *input_tensor.data<int64_t>() = (int64_t) chunk_valid_size;
1177        return input_tensor;
1178    }
1179    if (chunk_index > 0 && param_name == "cache_rs_reset_len") {
1180        // The recurrent-state clear belongs to the start of the sequence. Re-applying it on every
1181        // chunk would wipe the state accumulated by the preceding chunks, so disable it (a zero
1182        // length makes scale.cpp's keep-mask select every slot) after the first chunk.
1183        ov::Tensor input_tensor(ov::element::i64, ov::Shape{1});
1184        *input_tensor.data<int64_t>() = 0;
1185        return input_tensor;
1186    }
1187    if (ggml_decoder->get_model_extra_inputs().count(param_name)) {
1188        return get_ov_input_tensor(ggml_decoder, param_name);
1189    }
1190    const auto * ggml_tensor = ggml_decoder->get_input_ggml_tensor(param_name);
1191    const auto * op = ggml_decoder->get_tensor_used_op(ggml_tensor);
1192
1193    if (GgmlOvDecoder::is_inp_pos(ggml_tensor, op) && GgmlOvDecoder::get_inp_pos_n_planes(op) > 1) {
1194        // IMROPE: inp_pos stacks n_planes (t/h/w/e) position planes, each of length
1195        // input_len; pad every plane independently so they stay aligned to chunk_size.
1196        const int n_planes = GgmlOvDecoder::get_inp_pos_n_planes(op);
1197        const size_t element_size = ggml_type_size(ggml_tensor->type);
1198        ov::Shape input_shape = {1, 1, 1, (size_t) n_planes * chunk_size};
1199        ov::Tensor input_tensor(ggml_decoder->get_ov_type(ggml_tensor), input_shape);
1200        for (int p = 0; p < n_planes; p++) {
1201            const char * src =
1202                (const char *) ggml_tensor->data + (p * input_len + chunk_index * chunk_size) * element_size;
1203            char * dst = (char *) input_tensor.data() + p * chunk_size * element_size;
1204            std::memcpy(dst, src, chunk_valid_size * element_size);
1205            if (chunk_pad_size > 0) {
1206                if (ggml_tensor->type == GGML_TYPE_I32) {
1207                    int32_t last_value = *((const int32_t *) src + chunk_valid_size - 1);
1208                    int32_t * out = (int32_t *) dst;
1209                    std::fill(out + chunk_valid_size, out + chunk_size, last_value + 1);
1210                } else if (ggml_tensor->type == GGML_TYPE_I64) {
1211                    int64_t last_value = *((const int64_t *) src + chunk_valid_size - 1);
1212                    int64_t * out = (int64_t *) dst;
1213                    std::fill(out + chunk_valid_size, out + chunk_size, last_value + 1);
1214                } else {
1215                    throw std::runtime_error("Unexpected tensor type for " + param_name);
1216                }
1217            }
1218        }
1219        return input_tensor;
1220    }
1221
1222    if (GgmlOvDecoder::is_inp_tok(ggml_tensor, op) || GgmlOvDecoder::is_inp_pos(ggml_tensor, op) ||
1223        GgmlOvDecoder::is_kv_idx(ggml_tensor, op)) {
1224        ov::Shape input_shape = {1, 1, 1, chunk_size};
1225        ov::Tensor input_tensor(ggml_decoder->get_ov_type(ggml_tensor), input_shape);
1226        // copy the chunk_index-th chunk from ggml_tensor
1227        size_t element_size = ggml_type_size(ggml_tensor->type);
1228        void * input_data = (char *) ggml_tensor->data + chunk_index * chunk_size * element_size;
1229        std::memcpy(input_tensor.data(), input_data, chunk_valid_size * element_size);
1230        // pad the rest with last_value + 1, so that kv's of padded positions are inserted
1231        // to the next row after the valids row in the kvcache
1232        if (chunk_pad_size > 0) {
1233            if (ggml_tensor->type == GGML_TYPE_I32) {
1234                int32_t last_value =
1235                    *((int32_t *) ggml_tensor->data + (chunk_index * chunk_size + chunk_valid_size - 1));
1236                int32_t * output_data = input_tensor.data<int32_t>();
1237                std::fill(output_data + chunk_valid_size, output_data + chunk_size, last_value + 1);
1238            } else if (ggml_tensor->type == GGML_TYPE_I64) {
1239                int64_t last_value =
1240                    *((int64_t *) ggml_tensor->data + (chunk_index * chunk_size + chunk_valid_size - 1));
1241                int64_t * output_data = input_tensor.data<int64_t>();
1242                std::fill(output_data + chunk_valid_size, output_data + chunk_size, last_value + 1);
1243            } else {
1244                throw std::runtime_error("Unexpected tensor type for " + param_name);
1245            }
1246        }
1247        return input_tensor;
1248    }
1249
1250    if (GgmlOvDecoder::is_output_idx(ggml_tensor, op)) {
1251        size_t output_len = ggml_decoder->get_compute_params().output_len;
1252        ov::Shape input_shape = {1, 1, 1, output_len};
1253        ov::Tensor input_tensor(ggml_decoder->get_ov_type(ggml_tensor), input_shape);
1254        if (ggml_tensor->ne[0] == 0) {
1255            *input_tensor.data<int32_t>() = 0;
1256        } else {
1257            auto * data_addr = input_tensor.data<int32_t>();
1258            for (size_t i = 0; i < output_len; i++) {
1259                data_addr[i] = ((int32_t *) ggml_tensor->data)[i] % chunk_size;
1260            }
1261        }
1262        return input_tensor;
1263    }
1264
1265    if (GgmlOvDecoder::is_inp_mask(ggml_tensor, op)) {
1266        size_t cols = ggml_tensor->ne[0];
1267        size_t rows = ggml_tensor->ne[1];
1268        size_t chunk_valid_rows = std::min(chunk_size, rows - chunk_index * chunk_size);
1269        size_t context_size = ggml_decoder->get_ctx_size();
1270        if (ggml_tensor->type == GGML_TYPE_F16) {
1271            const auto * ggml_data =
1272                static_cast<const ggml_fp16_t *>(ggml_tensor->data) + chunk_index * chunk_size * cols;
1273            std::vector<ggml_fp16_t> padded_data = pad_input<ggml_fp16_t>(ggml_data, chunk_valid_rows, cols, chunk_size,
1274                                                                          context_size, GGML_FP32_TO_FP16(-INFINITY));
1275            set_zero_diagonal(padded_data, chunk_size, context_size, GGML_FP32_TO_FP16(0.0f));
1276            ov::Tensor input_tensor(ov::element::f16, ov::Shape{1, 1, chunk_size, context_size});
1277            std::memcpy(input_tensor.data(), padded_data.data(), padded_data.size() * sizeof(ggml_fp16_t));
1278            return input_tensor;
1279        }
1280
1281        const auto * ggml_data = static_cast<const float *>(ggml_tensor->data) + chunk_index * chunk_size * cols;
1282        std::vector<float> padded_data =
1283            pad_input<float>(ggml_data, chunk_valid_rows, cols, chunk_size, context_size, -INFINITY);
1284        set_zero_diagonal(padded_data, chunk_size, context_size);
1285        ov::Tensor input_tensor(ov::element::f32, ov::Shape{1, 1, chunk_size, context_size});
1286        auto * data_ptr = input_tensor.data<float>();
1287        std::copy(padded_data.begin(), padded_data.begin() + chunk_size * context_size, data_ptr);
1288        return input_tensor;
1289    }
1290
1291    return get_ov_input_tensor(ggml_decoder, param_name);
1292}
1293
1294size_t checksum(const void * data, size_t size) {
1295    const uint8_t * bytes = static_cast<const uint8_t *>(data);
1296    size_t sum = 0;
1297    for (size_t i = 0; i < size; ++i) {
1298        sum += (uint8_t) i;
1299        sum += bytes[i];
1300    }
1301    return sum;
1302}
1303
1304bool save_ggml_tensor_data_to_txt(const ggml_tensor * tensor, const std::string & file_path) {
1305    if (tensor == nullptr || tensor->data == nullptr) {
1306        return false;
1307    }
1308
1309    std::ofstream out(file_path);
1310    if (!out.is_open()) {
1311        return false;
1312    }
1313
1314    const size_t n = ggml_nelements(tensor);
1315    out << "name: " << tensor->name << ", type: " << ggml_type_name(tensor->type) << ", shape: [" << tensor->ne[0]
1316        << ", " << tensor->ne[1] << ", " << tensor->ne[2] << ", " << tensor->ne[3] << "]" << ", elements: " << n
1317        << ", data:" << '\n';
1318
1319    switch (tensor->type) {
1320    case GGML_TYPE_F32: {
1321        const auto * data = static_cast<const float *>(tensor->data);
1322        for (size_t i = 0; i < n; ++i) {
1323            out << data[i] << '\n';
1324        }
1325        break;
1326    }
1327    case GGML_TYPE_F16: {
1328        const auto * data = static_cast<const ggml_fp16_t *>(tensor->data);
1329        for (size_t i = 0; i < n; ++i) {
1330            out << ggml_fp16_to_fp32(data[i]) << '\n';
1331        }
1332        break;
1333    }
1334    case GGML_TYPE_BF16: {
1335        const auto * data = static_cast<const ggml_bf16_t *>(tensor->data);
1336        for (size_t i = 0; i < n; ++i) {
1337            out << ggml_bf16_to_fp32(data[i]) << '\n';
1338        }
1339        break;
1340    }
1341    case GGML_TYPE_I32: {
1342        const auto * data = static_cast<const int32_t *>(tensor->data);
1343        for (size_t i = 0; i < n; ++i) {
1344            out << data[i] << '\n';
1345        }
1346        break;
1347    }
1348    case GGML_TYPE_I64: {
1349        const auto * data = static_cast<const int64_t *>(tensor->data);
1350        for (size_t i = 0; i < n; ++i) {
1351            out << data[i] << '\n';
1352        }
1353        break;
1354    }
1355    default:
1356        out << "unsupported tensor type for text dump" << '\n';
1357        return false;
1358    }
1359
1360    return true;
1361}
1362
1363void print_input_tensor_info(const std::string & name, const ov::Tensor & tensor) {
1364    std::cout << "Input name: " << name << ", Input shape: " << tensor.get_shape() << ", Address: " << tensor.data()
1365              << std::endl;
1366    switch (tensor.get_element_type()) {
1367    case ov::element::f32: {
1368        if (name.find("self_kq_mask") == std::string::npos && name.find("KQ_mask") == std::string::npos) {
1369            std::cout << *(tensor.data<float>()) << std::endl;
1370        } else {
1371            size_t rows = tensor.get_shape()[2];
1372            size_t cols = tensor.get_shape()[3];
1373            auto * data = tensor.data<float>();
1374            for (size_t i = 0; i < rows; ++i) {
1375                for (size_t j = 0; j < cols; ++j) {
1376                    float val = data[i * cols + j];
1377                    if (std::isinf(val) && val < 0) {
1378                        std::cout << std::setw(5) << "-inf";
1379                    } else {
1380                        std::cout << std::setw(5) << val;
1381                    }
1382                }
1383                std::cout << std::endl;
1384            }
1385        }
1386
1387        break;
1388    }
1389    case ov::element::f16:
1390        std::cout << *(tensor.data<ov::float16>()) << std::endl;
1391        break;
1392    case ov::element::i32:
1393        for (size_t i = 0; i < tensor.get_size(); ++i) {
1394            std::cout << tensor.data<int32_t>()[i] << " ";
1395        }
1396        std::cout << std::endl;
1397        break;
1398    case ov::element::i64:
1399        for (size_t i = 0; i < tensor.get_size(); ++i) {
1400            std::cout << tensor.data<int64_t>()[i] << " ";
1401        }
1402        std::cout << std::endl;
1403        break;
1404    default:
1405        break;
1406    }
1407}
1408
1409void print_output_tensor_info(const std::string & name, const ov::Tensor & tensor, const void * output_dst) {
1410    std::cout << "Output name: " << name << ", Output shape: " << tensor.get_shape() << ", Address: " << output_dst
1411              << std::endl;
1412
1413    auto print_float_stats = [](const std::string & type_name, size_t size, auto get_value) {
1414        if (size == 0) {
1415            return;
1416        }
1417
1418        float first = get_value(0);
1419        float min = first;
1420        float max = first;
1421        double sum = first;
1422
1423        for (size_t i = 1; i < size; ++i) {
1424            float v = get_value(i);
1425            if (v < min) {
1426                min = v;
1427            }
1428            if (v > max) {
1429                max = v;
1430            }
1431            sum += v;
1432        }
1433        double mean = sum / size;
1434
1435        std::cout << std::right << std::setw(6) << type_name << std::right << std::setw(12) << "First" << std::setw(12)
1436                  << "Min" << std::setw(12) << "Max" << std::setw(12) << "Mean" << std::endl;
1437        std::cout << std::right << std::setw(6) << "" << std::right << std::setw(12) << first << std::setw(12) << min
1438                  << std::setw(12) << max << std::setw(12) << mean << std::endl;
1439    };
1440
1441    switch (tensor.get_element_type()) {
1442    case ov::element::f32: {
1443        const float * data = tensor.data<float>();
1444        size_t size = tensor.get_size();
1445        print_float_stats("[f32]", size, [data](size_t i) { return data[i]; });
1446        break;
1447    }
1448    case ov::element::f16: {
1449        const ov::float16 * data = tensor.data<ov::float16>();
1450        size_t size = tensor.get_size();
1451        print_float_stats("[f16]", size, [data](size_t i) { return static_cast<float>(data[i]); });
1452        break;
1453    }
1454    default:
1455        break;
1456    }
1457}
1458
1459const ggml_tensor * get_inp_pos_tensor(ggml_cgraph * cgraph) {
1460    for (int i = 0; i < cgraph->n_nodes; ++i) {
1461        auto * op = cgraph->nodes[i];
1462        for (int j = 0; j < GGML_MAX_SRC; ++j) {
1463            auto * src = op->src[j];
1464            if (src == nullptr) {
1465                break;
1466            }
1467            if (GgmlOvDecoder::is_inp_pos(src, op)) {
1468                return src;
1469            }
1470        }
1471    }
1472    GGML_LOG_ERROR("get_inp_pos_tensor: inp_pos not found in cgraph");
1473    throw std::runtime_error("get_inp_pos_tensor: inp_pos not found in cgraph");
1474}
1475
1476int64_t get_inp_pos_n_tokens(ggml_cgraph * cgraph, const ggml_tensor * inp_pos) {
1477    // IMROPE stacks n_planes (t/h/w/e) position planes into inp_pos, so ne[0] is
1478    // n_planes * n_tokens. Callers that need a token count must divide the planes out.
1479    int n_planes = 1;
1480    for (int i = 0; i < cgraph->n_nodes; ++i) {
1481        auto * op = cgraph->nodes[i];
1482        for (int j = 0; j < GGML_MAX_SRC; ++j) {
1483            if (op->src[j] == inp_pos) {
1484                n_planes = GgmlOvDecoder::get_inp_pos_n_planes(op);
1485                break;
1486            }
1487        }
1488    }
1489    return inp_pos->ne[0] / n_planes;
1490}
1491
1492bool get_is_prefill(ggml_cgraph * cgraph, const ggml_tensor * inp_pos) {
1493    return get_inp_pos_n_tokens(cgraph, inp_pos) > 1;
1494}
1495
1496#pragma GCC diagnostic pop