* OpenVINO backend: 1) enable gpt-oss moe on OV bk; 2) enable mxfp4 support * OpenVINO backend: disable TOPK_MOE op test * OpenVINO Backend: Add op FILL support * OpenVINO backend: enable set rows with multi dims * fix the name missmatch in setrow + view * OpenVINO backend: enable op GGML_UNARY_OP_SIGMOID * OpenVINO Backend: enable SQR & SQRT * OpenVINO backend: 1) ensure unique node names for OpenVINO; 2) add org_src to recorde the src ggml tensor for OpenVINO dynamic shape infer * OpenVINO backend: enable fallback for openVINO to CPU backend * OpenVINO backend: fix accurace issue in gemma3n arch test * fix mpt failed case * OpenVINO backend: clean nodeinfo * OpenVINO Backend: enable zero-size copy for view * add concat ssm_conv in compute_dynamic_dim enable qwen35 Fix after rebase remove logging * OpenVINO backend: disable EXP with FP32, which failed in op test. Root reason: the backend test initializes unary op inputs over a wide range, [-150, 150]. For FP32, exp(x) overflows around x ~= 88.7, so this test can randomly generate values right in or beyond the overflow region * OpenVINO backend: fix CPY op test failed issue * OpenVINO backend: fix GATED_DELTA_NET op test failed issue * handle in-place op, handle qwen35 dynamic clearing of cache in cgraph * handle qwen35 dynamic clearing of cache correctly * Enable qwen35 dense multi seq * Fix qwen35 9b gqa * Fix after rebase * Disable SOLVE_TRI * openvino: fix NEOX RoPE accuracy on GPU stateful (mixed-rank Multiply) In stateful mode the NEOX RoPE branch fed rank-3 data ([S, n_heads, head_size]) into the Multiply against the rank-4 cos/sin tables ([1, S, 1, n_dims/2]). That mixed-rank broadcast is miscomputed by the OpenVINO GPU plugin, corrupting the rotated Q/K and producing garbage output (e.g. Phi-3-mini). Lift the data to rank-4 before the split/ Multiply so the operands are equal-rank, matching what the TYPE_NORMAL branch already does. CPU and stateless paths are unaffected. Phi-3-mini-Q4_K_M, wiki.test perplexity, GPU stateful: before: PPL = 27120.43 after: PPL = 6.2263 (CPU reference: 6.2251) * OpenVINO backend: 1) remove the unique name in llama.cpp; 2) add new ov name in ov bk; 3) fix issue in arch test & op test with latest code update * OpenVINO Backenb: remove changes in llama.cpp * Doc change (use x64 Native Tools Command Prompt for VS) * Cleaner sentence Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * OpenVINO Backend: cache key upgrade includes all src name * OpenVINO Backend: enable llama arch test on ci * OpenVINO Backend: move parameter node creating from decoder into translate * OpenVINO Backend: create extra input ov node move from decoder to translate * fix for op regression due to is_model_splitted * openvino: fix CPY writeback for recurrent state rollback Detect the rollback conv/gdn state writeback CPY nodes structurally instead of by tensor name, since the rollback path in build_conv_state does not call cb() and left the nodes unnamed. Add per-node runtime offsets (rs_slot_begin_*, rs_src_begin_*) so the cached IR handles any kv head, sequence count and snapshot slot for both the conv state and the GDN state writeback. Assisted-by: GitHub Copilot * qwen35 moe * optimize MoE expert aggregation with ReduceSum * Skip GET_ROWS inaccurate test * openvino: fallback dynamic MUL_MAT_ID shapes * OpenVINO Backend: fix error in arch test model mpt * fix error caused by cpy in arch test model kimi-linear * OpenVINO Backend: fix error in arch test model minimax-m3 * openvino: fix GPU mul_mat_id op tests * ggml-openvino: add GGML_OPENVINO_RELEASE_WEIGHTS to reclaim host weight RSS on GPU The OpenVINO weight Constants are zero-copy views into host buffers allocated by the backend (ggml_aligned_malloc, anonymous memory). On GPU the plugin holds its own device copy after compile_model, so these host pages are dead weight for inference. For a 1B Q4_K_M model this leaves ~850 MB of host RSS resident that the GPU path never reads again. Add an opt-in GGML_OPENVINO_RELEASE_WEIGHTS mode that madvise(MADV_DONTNEED)s the registered host weight buffers once the model is compiled, dropping their resident pages while keeping the mappings valid (ggml still owns the lifetime; tensors still point in). Measured steady-state RSS drops from ~1555 MB to ~710 MB on Llama-3.2-1B-Q4_K_M (Arc iGPU) with unchanged throughput and correct output. The GPU backend uses a single dynamic-shape model for both prefill and decode, so a graph is compiled once and reused; the only event that forces a recompile is clear_caches() on backend teardown. The change therefore: - releases on the first cache-hit (model compiled, plugin has its copy); - pins the compiled-model cache across backend teardown so a later context reuses it instead of recompiling against the dropped pages; - fails loud (GGML_ABORT) on a cache-miss recompile or on a second model load, both of which would otherwise read zeroed weights or silently reuse the wrong compiled graph. Scope/limitations (all fail loud, never silently wrong): GPU only (the CPU plugin reads the host Constants at inference time), one model per process, and stable graph shapes. This reduces steady-state RSS, not the transient compile-time peak. All changes are confined to the OpenVINO backend. * ggml-openvino: stream weight requantization to cut the compile-time RSS peak requantize_to_buffers() dequantized the entire tensor to a temporary std::vector<float> of n_elements before requantizing. For token_embd.weight (128256 x 2048) that transient is ~1 GB (1B model) / ~2 GB (8B), and it is the single largest contributor to the OpenVINO compile-time memory peak -- it also fires twice for token_embd (once at load, once at graph build, because token_embd is loaded via a CPU/mmap buffer and not cached as an OV weight extra). Stream the dequant instead: process a fixed window of complete rows (CHUNK_ROWS=256) into a small scratch buffer and quantize/convert each chunk straight into the output buffers. The transient F32 footprint is now CHUNK_ROWS*ne0 floats regardless of tensor size. quantize_q8_0/q8_1 gain an optional block_offset arg (default 0) so a chunk writes its weights/scales/zp at the correct block. Streaming is applied to the Q8_0_C / Q8_1_C / F16 targets (the large requant cases); the u4 (Q4_0) path keeps the whole-array call because it packs two weights per byte with running zp ORs, and a fallback handles any future target whose block size does not divide a row. Measured peak RSS (cold compile, GPU): 1B 2868 -> 1809 MB (-1.06 GB); 8B 11618 -> 9608 MB (-2.0 GB). Output verified unchanged ("capital of France is Paris"); throughput unchanged. Unlike GGML_OPENVINO_RELEASE_WEIGHTS this reduces the transient peak, not just steady-state, and needs no env flag. All changes confined to the OpenVINO backend. * ggml-openvino: avoid redundant token_embd requantization at compile token_embd.weight is referenced twice in the graph path: as the GET_ROWS embedding (a CPU/mmap-buffer tensor) it was re-extracted/re-requantized on every weight-node build, and is_model_splitted() built a full (naive) set of weight nodes just to test name membership — each requant is a ~1-2 GB F32 dequant of the 262M-element embedding. Two changes: - Add collect_weight_names(): a name-only collector for topology checks. is_model_splitted() now uses it instead of create_weight_nodes(cgraph, true), so the splitted-check no longer triggers any weight extraction. - Memoize weight nodes built from non-OpenVINO buffers in a process-lifetime cache keyed by tensor->data. These tensors have no OV buffer context to own a cached extra, so without this they were rebuilt on every (re)compile; prefill and decode graphs now share one build (verified: 2nd graph hits the cache instead of re-requantizing). Peak RSS is unchanged (the streaming-requant commit already removed the F32 transient); this removes redundant compile-time work. Output verified unchanged ("capital of France is Paris"). Confined to the OpenVINO backend. * ggml-openvino: gate compile-memory optimizations behind GGML_OPENVINO_REDUCE_COMPILE_MEM The streaming requantization and the non-OpenVINO-buffer weight-node cache (plus the name-only is_model_splitted path that pairs with it) are now opt-in via GGML_OPENVINO_REDUCE_COMPILE_MEM. When unset, requantize_to_buffers() fully materializes the F32 buffer and weights are rebuilt per compile exactly as before; when set, the streaming path and the cross-compile weight cache are used. Default off keeps behavior identical to upstream unless explicitly enabled. Verified: flag off -> peak RSS 2800 MB (original), flag on -> 1810 MB; output "capital of France is Paris" in both modes. (GGML_OPENVINO_RELEASE_WEIGHTS, added earlier, remains a separate opt-in for the steady-state release.) * ggml-openvino: add frontend model cache (GGML_OPENVINO_MODEL_CACHE_DIR) The plugin-level ov::cache_dir caches the compiled blob keyed by the OV model, but producing that model still runs the full frontend every time: weight requantization (incl. the large token_embd F32 transient) and the ggml->OV graph conversion. This adds an opt-in frontend cache keyed off a fingerprint computed directly from the ggml cgraph, so a hit imports a previously exported CompiledModel and skips requant + convert + compile entirely. Key (model-cache.{h,cpp}) = 64-bit FNV-1a of: graph topology (n_nodes + per node op/name), a sampled per-weight fingerprint (name/shape/type + bounded head+tail byte sample), and blob-affecting config (device, flash-attn, rope params, REDUCE_COMPILE_MEM/stateful flags, OpenVINO version). A sidecar manifest stores every weight's fingerprint and is re-verified on load, so a sampled-hash collision cannot cause a wrong-model hit (verified: two different quantizations of the same model produce distinct cache entries). Flow (dynamic single-model path only; split models defer to ov::cache_dir): on a verified hit, core.import_model() restores the CompiledModel and a lightweight decoder is built with a names-only weight map (membership is all the decoder needs for I/O mapping; weights live in the imported model). On a miss, compile as usual then export the blob (atomic temp+rename, manifest written first). The frontend cache supersedes ov::cache_dir, so CACHE_DIR/ CACHE_MODE are stripped from the config used for the cached compile and the import — a blob compiled with cache_dir set cannot be re-imported. Measured 8B Q4_K_M (GPU): full requant+convert+compile 15.3s -> import 6.3s (~2.4x faster compile phase). Output verified unchanged on cold and warm, standalone and combined with REDUCE_COMPILE_MEM + RELEASE_WEIGHTS. Default off; confined to the OpenVINO backend. * ggml-openvino: harden frontend model cache correctness The frontend model cache imports a previously exported CompiledModel keyed by a fingerprint of the ggml graph, weights, and blob-affecting config. The original key covered device, stateful execution, REDUCE_COMPILE_MEM, RoPE params, OpenVINO version, topology, and sampled weights, but missed runtime/frontend toggles that can change the lowered graph or the I/O binding contract. That made it possible to reuse a blob produced under a different OpenVINO backend configuration. Add a small extra-config helper for the dynamic model-cache path and fold in the effective values of GGML_OPENVINO_DISABLE_KV_SLICE and GGML_OPENVINO_MANUAL_GQA_ATTN. MANUAL_GQA_ATTN is keyed by the behavior that actually takes effect: an explicit env value wins, otherwise GPU defaults to enabled and other devices default to disabled. This matches flash_attn_ext lowering and avoids unnecessary cache splits for equivalent configurations while separating genuinely different attention graphs. DISABLE_KV_SLICE is also included because it changes the KV-cache tensor shape/output binding strategy used around imported models. Even when weights and graph topology are identical, switching this flag should not inherit a CompiledModel cache entry created for a different binding mode. Also make cache artifact publication cleaner: write manifest.tmp and blob.tmp, publish the blob first, and publish the manifest last. Cache hits already require both blob and a verified manifest, so making the manifest the final visible artifact avoids leaving an apparently complete manifest for a failed or interrupted blob export. Temporary files are removed on the handled failure paths. While touching this path, fix the indentation of the non-imported compile branch so the cache miss flow is easier to review. Behavior is otherwise unchanged: verified hits still import, misses still create weights, convert, compile, export, and create the infer request normally. * ggml-openvino: add memory optimization umbrella switch Add GGML_OPENVINO_MEMORY_OPTIMIZE as a single opt-in switch for the OpenVINO backend memory-saving paths. The existing fine-grained GGML_OPENVINO_REDUCE_COMPILE_MEM and GGML_OPENVINO_RELEASE_WEIGHTS variables remain supported and explicitly override the umbrella switch when set, so users can still bisect or disable one side of the optimization independently. Centralize the policy in ggml_openvino_reduce_compile_mem_enabled() and ggml_openvino_release_weights_enabled(device). The umbrella switch enables compile-memory reductions everywhere REDUCE_COMPILE_MEM is used today: streaming requantization, non-OV weight-node caching, split-model weight-name collection, and the frontend model-cache fingerprint. On GPU it also enables host weight-buffer release unless GGML_OPENVINO_RELEASE_WEIGHTS is explicitly set. Keep host weight release GPU-only because it relies on the plugin holding its own device copy after compile_model. Update the fail-fast diagnostic and comments to mention GGML_OPENVINO_MEMORY_OPTIMIZE, so users who enable the umbrella switch get accurate guidance if a later cache-miss recompile would read released host weight pages. * ggml-openvino: rename compiled model cache env Rename the frontend export/import cache environment variable from GGML_OPENVINO_MODEL_CACHE_DIR to GGML_OPENVINO_COMPILED_MODEL_CACHE_DIR. The cache stores blobs produced by ov::CompiledModel::export_model() and restores them with core.import_model(), so the new name distinguishes it from GGML_OPENVINO_CACHE_DIR, which configures OpenVINO plugin-level ov::cache_dir. Update the registered env var, the cache-directory lookup, and comments around the frontend compiled-model cache. The old GGML_OPENVINO_MODEL_CACHE_DIR name is removed rather than kept as a fallback so there is a single spelling for the new option. * docs: document OpenVINO memory optimization env vars Add runtime configuration entries for the newly recognized OpenVINO environment variables. Document GGML_OPENVINO_COMPILED_MODEL_CACHE_DIR as the frontend compiled-model cache used to export and import compiled blobs for matching single-graph models. Document GGML_OPENVINO_MEMORY_OPTIMIZE as the umbrella switch, including how GGML_OPENVINO_REDUCE_COMPILE_MEM and the GPU-only GGML_OPENVINO_RELEASE_WEIGHTS override or inherit from it. * ggml-openvino: fix Qwen3VL crash and deepstack correctness bug 1. GGML_OP_PAD was missing from compute_node_dynamic_dims(), causing a crash on decode for models that pad the token embedding (n_embd -> n_embd_inp). PAD never reorders/merges dims, so it keeps the same dynamic dim index as its source. 2. process_view_input_new() chained VIEW inputs through src[0] (the immediate op-graph parent) using offsets treated as relative to that parent. But ggml_tensor::view_offs is always absolute from the true root allocation (ggml collapses VIEW-of-VIEW chains internally). For the per-layer deepstack view ("embd (view)", whose src[0] is "embd" - itself an already-narrowed, zero-offset VIEW of the padded root, with the SAME ggml shape as the deepstack view but a different absolute offset), this caused an out-of-bounds re-slice that silently fell back to returning the wrong (already-resolved sibling) tensor. In practice every deepstack ADD ended up adding the real base token embedding into the residual stream instead of zero, corrupting generation ("Hello my name is 1000000..." instead of coherent text). Fixed by detecting this pattern (same shape as the immediate src, different absolute offset) and re-slicing directly from the untouched root tensor using the innermost view's absolute offset. Also adds a GGML_OPENVINO_DEBUG_NODE=<name1>,<name2>,... env var that attaches extra debug Result nodes for arbitrary intermediate tensors, without binding them to any ggml buffer (avoiding the risk of reading a ggml buffer that has since been overwritten by a later in-place op). This was instrumental in diagnosing bug #2 above and is left in as a general-purpose debugging aid. * ggml-openvino: fix IMROPE inp_pos padding for NPU static shapes IMROPE's inp_pos tensor packs 4 stacked t/h/w/e position planes into ne[0] = 4*n_tokens instead of one value per token. On NPU's static-shape path, inp_pos was padded/shaped as if it held a single plane, which interleaved padding across the 4 planes and desynced later reshapes from the rest of the (chunk_size-wide) graph. - add GgmlOvDecoder::get_inp_pos_n_planes() to detect IMROPE's 4-plane layout - get_graph_input_shape(): size inp_pos as n_planes * chunk_size (prefill) or n_planes (decode) instead of assuming 1 value per token - get_ov_input_tensor_static_prefill(): pad each plane to chunk_size independently instead of one flat block - get_ov_input_tensor_static_decode(): copy n_planes contiguous values instead of asserting/copying a single scalar * disable test-llama-archs tests. * openvino: gate fallback with env var * Revert changes in test-llama-archs * Apply editor config * reject CPY with quantized destination as unsupported --------- Co-authored-by: Xuejun <Xuejun.Zhai@intel.com> Co-authored-by: Mustafa Cavus <mustafa.cavus@intel.com> Co-authored-by: virajwad <84867530+virajwad@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: suryasidd <surya.siddharth.pemmaraju@intel.com> Co-authored-by: Mustafa Cavus <mustafacavus@intel.com> Co-authored-by: Ravi Panchumarthy <ravi.panchumarthy@intel.com>
416 lines
18 KiB
C++
416 lines
18 KiB
C++
#pragma once
|
|
|
|
#include "ggml-backend-impl.h"
|
|
#include "ggml-backend.h"
|
|
#include "ggml.h"
|
|
#include "openvino/decoder.h"
|
|
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <openvino/core/partial_shape.hpp>
|
|
#include <optional>
|
|
#include <set>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
struct ModelParams {
|
|
int ctx = -1;
|
|
int ctx_per_seq = -1;
|
|
int ctx_per_seq_swa = -1;
|
|
int n_seq = 1;
|
|
int n_heads_kv = -1;
|
|
int head_size = -1;
|
|
int state_size = -1; // for SSM molels, eg qwen35
|
|
int32_t rope_params[15];
|
|
bool mixed_rope_params = false;
|
|
std::vector<int> swa_layers;
|
|
|
|
std::vector<std::string> kv_names;
|
|
size_t kv_buffer_ctx_id = 0;
|
|
|
|
bool same_rope_params(const ModelParams & other) const {
|
|
return mixed_rope_params == other.mixed_rope_params &&
|
|
memcmp(rope_params, other.rope_params, sizeof(int32_t) * 15) == 0;
|
|
}
|
|
|
|
bool can_reuse_dynamically(const ModelParams & other) const { return same_rope_params(other); }
|
|
|
|
bool can_reuse_statically(const ModelParams & other) const { return same_rope_params(other) && ctx == other.ctx; }
|
|
|
|
bool kv_buffer_changed(const ModelParams & other) const { return kv_buffer_ctx_id != other.kv_buffer_ctx_id; }
|
|
};
|
|
|
|
struct ComputeParams {
|
|
int n_seq_active = 1;
|
|
int seq_active_start = 0;
|
|
int attention_size = -1;
|
|
int attention_size_swa = -1;
|
|
int input_len = -1;
|
|
int token_len_per_seq = -1;
|
|
int past_kv_len = -1;
|
|
int output_len = 1;
|
|
|
|
int cache_rs_reset_idx = -1;
|
|
int cache_rs_reset_len = -1;
|
|
// SSM/DeltaNet models otionally clear cache_r and cache_s of certain slots in the cgraph
|
|
// 3: [ 18432, 4, 1, 1] RESHAPE cache_r_l0 (reshaped)
|
|
// [ 18432, 4, 1, 1] 0: NONE cache_r_l0
|
|
// 4: [ 18432, 1, 1, 1] VIEW cache_r_l0 (reshaped) (view)
|
|
// [ 18432, 4, 1, 1] 0: RESHAPE cache_r_l0 (reshaped)
|
|
// 5: [ 18432, 1, 1, 1] SCALE cache_r_l0 (reshaped) (view) (view)
|
|
// [ 18432, 1, 1, 1] 0: VIEW cache_r_l0 (reshaped) (view)
|
|
|
|
int s_copy_active_slot_len = -1;
|
|
// SSM/DeltaNet models otionally reorder slots of state cache, to make the active slots contiguous
|
|
// leaf_5 is the inp->s_copy in llama-graph.cpp, eg if there are 8 slots in total and slot 3 and 7
|
|
// are active in the current batch, leaf_5 will be [3, 7, 5, 6, 4]
|
|
// 6: [ 2, 1, 1, 1] VIEW (view)
|
|
// [ 2, 1, 1, 1] 0: NONE leaf_5
|
|
// 7: [ 18432, 2, 1, 1] GET_ROWS conv_states-0
|
|
// [ 18432, 4, 1, 1] 0: RESHAPE cache_r_l0 (reshaped)
|
|
// [ 2, 1, 1, 1] 1: VIEW (view)
|
|
// 8: [ 0, 1, 1, 1] VIEW (view)
|
|
// [ 2, 1, 1, 1] 0: NONE leaf_5
|
|
// 9: [ 18432, 0, 1, 1] GET_ROWS node_9
|
|
// [ 18432, 4, 1, 1] 0: RESHAPE cache_r_l0 (reshaped)
|
|
// [ 0, 1, 1, 1] 1: VIEW (view)
|
|
// 10: [ 18432, 0, 1, 1] VIEW cache_r_l0 (view)
|
|
// [ 18432, 4, 1, 1] 0: NONE cache_r_l0
|
|
// 11: [ 18432, 0, 1, 1] CPY cache_r_l0 (view) (copy of )
|
|
// [ 18432, 0, 1, 1] 0: GET_ROWS node_9
|
|
// [ 18432, 0, 1, 1] 1: VIEW cache_r_l0 (view)
|
|
|
|
struct RsWriteback {
|
|
int slot_begin = 0; // first cache slot written by the CPY
|
|
int src_begin = 0; // where the copied data starts in the source tensor (in rows of it)
|
|
};
|
|
|
|
std::map<std::string, RsWriteback> rs_writebacks;
|
|
// Offsets of the state cache writeback CPY nodes, keyed by node name. They change with the
|
|
// batch (kv head, active sequence count, token count) and, with rollback enabled
|
|
// (cparams.n_rs_seq > 0), the conv state is written back once per snapshot slot, each snapshot
|
|
// taking a different conv_input window. Passed to the cached model as runtime inputs.
|
|
};
|
|
|
|
class GgmlOvDecoder : public ov::frontend::ggml::GgmlDecoder {
|
|
public:
|
|
struct NodeInfo {
|
|
ggml_tensor * node;
|
|
std::string node_name;
|
|
std::string node_op_type;
|
|
std::map<std::string, ggml_tensor *> node_inputs;
|
|
std::map<std::string, std::vector<std::pair<std::string, ggml_tensor *>>> node_inputs_views;
|
|
std::vector<std::string> node_inputs_names;
|
|
int node_op_case = 0;
|
|
void * data_addr;
|
|
};
|
|
|
|
// Graph decoder
|
|
GgmlOvDecoder(ggml_cgraph * cgraph,
|
|
ModelParams & model_params,
|
|
ComputeParams & compute_params,
|
|
std::map<std::string, std::shared_ptr<ov::Node>> & model_weights,
|
|
bool is_static,
|
|
bool is_stateful = false,
|
|
bool model_is_splitted = false,
|
|
bool is_prefill = false,
|
|
int prefill_chunk_size = 256);
|
|
|
|
// Naive graph decoder
|
|
GgmlOvDecoder(ggml_cgraph * cgraph, std::map<std::string, std::shared_ptr<ov::Node>> & model_weights);
|
|
|
|
virtual ov::Any get_attribute(const std::string & name) const override {
|
|
return nullptr;
|
|
GGML_UNUSED(name);
|
|
}
|
|
|
|
virtual ov::PartialShape get_input_shape(int node_idx, const std::string & name) const override;
|
|
|
|
virtual std::vector<size_t> get_input_stride(int node_idx, const std::string & name) const override;
|
|
|
|
virtual size_t get_view_input_size(int node_idx, const std::string & name) const override;
|
|
|
|
virtual size_t get_view_input_offset(int node_idx, const std::string & name, size_t view_index) const override;
|
|
|
|
virtual size_t get_view_input_src_offset(int node_idx, const std::string & name, size_t view_index) const override;
|
|
|
|
virtual std::vector<size_t> get_view_input_stride(int node_idx,
|
|
const std::string & name,
|
|
size_t view_index) const override;
|
|
|
|
virtual std::vector<size_t> get_view_input_src_stride(int node_idx,
|
|
const std::string & name,
|
|
size_t view_index) const override;
|
|
|
|
virtual ov::Shape get_view_input_ggml_shape(int node_idx,
|
|
const std::string & name,
|
|
size_t view_index) const override;
|
|
|
|
virtual ov::Shape get_view_input_src_ggml_shape(int node_idx,
|
|
const std::string & name,
|
|
size_t view_index) const override;
|
|
|
|
virtual ov::PartialShape get_view_input_ov_shape(int node_idx,
|
|
const std::string & name,
|
|
size_t view_index) const override;
|
|
|
|
virtual ov::PartialShape get_view_input_src_ov_shape(int node_idx,
|
|
const std::string & name,
|
|
size_t view_index) const override;
|
|
|
|
virtual std::string get_view_input_name(int node_idx, const std::string & name, size_t view_index) const override;
|
|
|
|
virtual std::string get_view_input_src_name(int node_idx,
|
|
const std::string & name,
|
|
size_t view_index) const override;
|
|
|
|
virtual ov::element::Type get_input_type(int node_idx, const std::string & name) const override;
|
|
|
|
virtual size_t get_input_size() const override;
|
|
|
|
virtual size_t get_input_size(int node_idx) const override;
|
|
|
|
virtual void get_input_node(size_t input_port_idx,
|
|
std::string & producer_name,
|
|
std::string & producer_output_port_name,
|
|
size_t & producer_output_port_index) const override {
|
|
GGML_UNUSED(input_port_idx);
|
|
GGML_UNUSED(producer_name);
|
|
GGML_UNUSED(producer_output_port_name);
|
|
GGML_UNUSED(producer_output_port_index);
|
|
}
|
|
|
|
virtual std::vector<std::string> get_input_names(int node_idx) const override;
|
|
|
|
virtual ov::PartialShape get_output_shape(int node_idx) const override;
|
|
|
|
virtual ov::element::Type get_output_type(int node_idx) const override;
|
|
|
|
virtual std::vector<size_t> get_output_stride(int node_idx) const override;
|
|
|
|
virtual int32_t * get_input_op_params(int node_idx, const std::string & name) const override;
|
|
|
|
virtual int32_t * get_output_op_params(int node_idx) const override;
|
|
|
|
virtual size_t get_output_op_offset(int node_idx) const override;
|
|
|
|
virtual std::vector<std::string> get_output_names(int node_idx) const override;
|
|
|
|
virtual std::string get_inplace_op_src(int node_idx) const override;
|
|
|
|
virtual bool is_view_like_alias_of(int node_idx, const std::string & view_src_name) const override;
|
|
|
|
virtual const std::string & get_op_type() const override;
|
|
|
|
virtual const std::string & get_op_type(int node_idx) const override;
|
|
|
|
virtual const std::string & get_op_name() const override;
|
|
|
|
virtual const std::string & get_op_name(int node_idx) const override;
|
|
|
|
virtual int32_t get_op_dynamic_dim(int node_idx) const override;
|
|
|
|
virtual void visit_subgraph(
|
|
std::function<void(std::shared_ptr<GgmlDecoder>, int node_idx)> node_visitor) const override;
|
|
|
|
ggml_tensor * get_input_ggml_tensor(const std::string & name) const { return m_inputs.at(name); }
|
|
|
|
virtual int get_op_case(int node_idx) const override { return m_node_info_list[node_idx].node_op_case; }
|
|
|
|
virtual const std::map<std::string, ov::frontend::ggml::ModelInputInfo> & get_model_inputs() const override {
|
|
return m_model_inputs;
|
|
}
|
|
|
|
virtual const std::map<std::string, ov::frontend::ggml::ModelExtraInputInfo> & get_model_extra_inputs() const override {
|
|
return m_model_extra_inputs;
|
|
}
|
|
|
|
virtual const std::map<std::string, std::shared_ptr<ov::Node>> & get_model_weights() const override {
|
|
return m_model_weights;
|
|
}
|
|
|
|
virtual std::set<std::string> get_model_output_names() const override { return m_model_output_names; }
|
|
|
|
const std::map<std::string, ggml_tensor *> & get_model_outputs() const { return m_model_outputs; }
|
|
|
|
virtual int get_ctx_size() const { return m_model_params.ctx; }
|
|
|
|
virtual int get_ctx_per_seq() const { return m_model_params.ctx_per_seq; }
|
|
|
|
virtual int get_ctx_per_seq_swa() const { return m_model_params.ctx_per_seq_swa; }
|
|
|
|
virtual int get_n_seq() const { return m_model_params.n_seq; }
|
|
|
|
virtual int is_swa_layer(int layer) const override {
|
|
return std::find(m_model_params.swa_layers.begin(), m_model_params.swa_layers.end(), layer) !=
|
|
m_model_params.swa_layers.end();
|
|
}
|
|
|
|
int get_past_kv_len() const { return m_compute_params.past_kv_len; }
|
|
|
|
int get_input_len() const { return m_compute_params.input_len; }
|
|
|
|
virtual int32_t * get_rope_params() const override { return const_cast<int32_t *>(m_model_params.rope_params); }
|
|
|
|
virtual bool has_mixed_rope_params() const override { return m_model_params.mixed_rope_params; }
|
|
|
|
virtual int get_ssm_state_size() const override { return m_model_params.state_size; }
|
|
|
|
virtual std::map<std::string, std::string> get_kv_param_res_names() const override;
|
|
|
|
virtual bool is_static() const override { return m_is_static; }
|
|
|
|
virtual bool is_stateful() const override { return m_is_stateful; }
|
|
|
|
int get_static_n_tokens() const { return m_is_prefill ? m_prefill_chunk_size : 1; }
|
|
|
|
virtual bool is_splited_model() const override { return m_model_is_splitted; }
|
|
|
|
ov::PartialShape get_graph_input_shape(const ggml_tensor * op,
|
|
const ggml_tensor * input,
|
|
int dynamic_dim_index = -1) const;
|
|
|
|
static void dump_cgraph(const ggml_cgraph * cgraph, std::string & filename);
|
|
|
|
static std::shared_ptr<ov::Node> create_weight_node(ggml_tensor * tensor, bool naive = false);
|
|
|
|
static std::map<std::string, std::shared_ptr<ov::Node>> create_weight_nodes(ggml_cgraph * cgraph,
|
|
bool naive = false);
|
|
|
|
// Collect just the set of weight-tensor names referenced by the graph, without
|
|
// building (or requantizing) any OV weight nodes. Used by topology checks like
|
|
// is_model_splitted that only need name membership.
|
|
static std::set<std::string> collect_weight_names(ggml_cgraph * cgraph);
|
|
|
|
const ggml_tensor * get_tensor_used_op(const ggml_tensor * tensor) const;
|
|
|
|
const ggml_tensor * get_tensor_from_name(const std::string & name) const;
|
|
|
|
void clear_model_weights() { m_model_weights.clear(); }
|
|
|
|
static std::pair<ModelParams, ComputeParams> compute_llm_params(ggml_cgraph * cgraph, bool is_static);
|
|
|
|
ModelParams get_model_params() const { return m_model_params; }
|
|
|
|
ComputeParams get_compute_params() const { return m_compute_params; }
|
|
|
|
void set_model_params(const ModelParams & model_params) { m_model_params = model_params; }
|
|
|
|
void set_compute_params(const ComputeParams & compute_params) { m_compute_params = compute_params; }
|
|
|
|
bool m_is_static = false;
|
|
bool m_is_stateful = false;
|
|
bool m_is_prefill = false;
|
|
bool m_naive = false;
|
|
int m_prefill_chunk_size = 0;
|
|
bool m_model_is_splitted = false; // label the cgraph is splited or not
|
|
|
|
static ov::Shape get_shape(const ggml_tensor * tensor);
|
|
static std::vector<size_t> get_stride(const ggml_tensor * tensor);
|
|
static ov::element::Type get_ov_type(const ggml_tensor * tensor);
|
|
static std::string compute_op_type(const ggml_tensor * node);
|
|
void add_extra_inputs();
|
|
|
|
void update_io(ggml_cgraph * cgraph);
|
|
|
|
inline static bool is_inp_tok(const ggml_tensor * tensor, const ggml_tensor * op) {
|
|
return op->op == GGML_OP_GET_ROWS && tensor == op->src[1] && op->src[0]->op == GGML_OP_NONE;
|
|
}
|
|
|
|
inline static bool is_inp_pos(const ggml_tensor * tensor, const ggml_tensor * op) {
|
|
return op->op == GGML_OP_ROPE && tensor == op->src[1];
|
|
}
|
|
|
|
// IMROPE packs 4 stacked position planes (t/h/w/e) into inp_pos, each of length
|
|
// n_tokens; other modes carry a single position per token.
|
|
inline static int get_inp_pos_n_planes(const ggml_tensor * op) {
|
|
return op->op_params[2] == GGML_ROPE_TYPE_IMROPE ? 4 : 1;
|
|
}
|
|
|
|
inline static bool is_inp_emb(const ggml_tensor * tensor, const ggml_tensor * op) {
|
|
return tensor->op == GGML_OP_GET_ROWS && op->op == GGML_OP_RMS_NORM;
|
|
}
|
|
|
|
inline static bool is_inp_mask(const ggml_tensor * tensor, const ggml_tensor * op) {
|
|
return op->op == GGML_OP_CPY || (op->op == GGML_OP_FLASH_ATTN_EXT && tensor == op->src[3]) ||
|
|
(op->op == GGML_OP_SOFT_MAX && tensor == op->src[1]);
|
|
}
|
|
|
|
inline static bool is_rope_freqs_weight(const ggml_tensor * tensor, const ggml_tensor * op) {
|
|
return op->op == GGML_OP_ROPE && tensor == op->src[2];
|
|
}
|
|
|
|
// also returns true for cache_s and cache_r in SSM/DeltaNet models
|
|
inline static bool is_kvcache(const ggml_tensor * tensor, const ggml_tensor * op) {
|
|
if (tensor == nullptr) {
|
|
return false;
|
|
}
|
|
return (tensor->buffer != nullptr && tensor->buffer->usage == GGML_BACKEND_BUFFER_USAGE_ANY) ||
|
|
(op != nullptr && op->op == GGML_OP_SET_ROWS && op->src[2] == tensor);
|
|
}
|
|
|
|
inline static bool is_kv_idx(const ggml_tensor * tensor, const ggml_tensor * op) {
|
|
return op->op == GGML_OP_SET_ROWS && op->src[1] == tensor;
|
|
}
|
|
|
|
inline static bool is_output_idx(const ggml_tensor * tensor, const ggml_tensor * op) {
|
|
return op->op == GGML_OP_GET_ROWS && tensor == op->src[1] && op->src[0]->op != GGML_OP_NONE &&
|
|
op->src[1]->op == GGML_OP_NONE;
|
|
}
|
|
|
|
// the state permutation index input used in SSM/DeltaNet models (inp->s_copy in llama-graph.cpp)
|
|
inline static bool is_inp_s_copy(const ggml_tensor * tensor, const ggml_tensor * op) {
|
|
return op->op == GGML_OP_GET_ROWS && tensor == op->src[1] &&
|
|
op->src[0]->buffer->usage == GGML_BACKEND_BUFFER_USAGE_ANY;
|
|
}
|
|
|
|
std::string get_graph_input_ov_name(const ggml_tensor * tensor, const ggml_tensor * op) const {
|
|
if (is_inp_pos(tensor, op)) {
|
|
return "inp_pos";
|
|
}
|
|
if (is_inp_emb(tensor, op)) {
|
|
return "embd";
|
|
}
|
|
if (is_stateful() && is_inp_mask(tensor, op)) {
|
|
return std::string(tensor->name).find("swa") == std::string::npos ? "self_kq_mask" : "self_kq_mask_swa";
|
|
}
|
|
return tensor->name;
|
|
}
|
|
|
|
private:
|
|
void set_input_output();
|
|
int compute_op_case(const ggml_tensor * node) const;
|
|
bool node_is_used_as_src(const int node_idx);
|
|
void compute_model_inputs();
|
|
void compute_model_outputs();
|
|
|
|
// True if tensor is the inp->s_copy index leaf gathered by a recurrent state cache GET_ROWS
|
|
// (possibly through a VIEW), so it gets a dynamic [1,1,1,-1] graph-input shape.
|
|
bool is_s_copy_leaf(const ggml_tensor * tensor) const;
|
|
|
|
// Infer and propagate dynamic-dimension indices for all tensors in the GGML graph.
|
|
void compute_node_dynamic_dims();
|
|
|
|
void validate_cgraph() const;
|
|
|
|
ggml_cgraph * m_cgraph = nullptr;
|
|
std::map<std::string, ggml_tensor *> m_inputs;
|
|
|
|
std::map<std::string, ov::frontend::ggml::ModelInputInfo> m_model_inputs;
|
|
std::map<std::string, ov::frontend::ggml::ModelExtraInputInfo> m_model_extra_inputs;
|
|
std::map<std::string, std::shared_ptr<ov::Node>> m_model_weights;
|
|
std::map<std::string, ggml_tensor *> m_model_outputs;
|
|
std::set<std::string> m_model_output_names;
|
|
std::vector<NodeInfo> m_node_info_list;
|
|
std::map<ggml_tensor *, int> m_node_dynamic_dims;
|
|
|
|
ModelParams m_model_params;
|
|
ComputeParams m_compute_params;
|
|
};
|
|
|
|
void print_tensor_address_map(const ggml_cgraph * cgraph);
|
|
|
|
std::optional<int> extract_layer_from_name(const std::string & name);
|