char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
0cae43063
char/aliceai-t5-moe
1#include "ggml-openvino.h" 2 3#include "ggml-backend-impl.h" 4#include "ggml-backend.h" 5#include "ggml-impl.h" 6#include "ggml-openvino-extra.h" 7#include "ggml-openvino/openvino/op_table.h" 8#include "ggml-openvino/utils.h" 9#include "ggml-quants.h" 10#include "ggml.h" 11 12#include <atomic> 13#include <cstdint> 14#include <cstdlib> 15#include <cstring> 16#include <memory> 17#include <mutex> 18#include <openvino/core/type/element_type.hpp> 19#include <openvino/openvino.hpp> 20#include <openvino/runtime/allocator.hpp> 21#include <openvino/runtime/intel_gpu/ocl/ocl.hpp> 22#include <openvino/runtime/intel_npu/level_zero/level_zero.hpp> 23#include <openvino/runtime/tensor.hpp> 24#include <set> 25#include <string> 26#include <vector> 27 28#if defined(_WIN32 ) 29# define WIN32_LEAN_AND_MEAN 30# ifndef NOMINMAX 31# define NOMINMAX 32# endif 33# include <windows.h> 34#else 35# include <sys/mman.h> 36# include <unistd.h> 37#endif 38 39// ===================================================== 40// OpenVINO Buffer Implementation using ov::Tensor 41// ===================================================== 42// 43// Design: This implementation uses a hybrid approach: 44// 1. For weight tensors: Store a pre-built ov::op::v0::Constant in tensor->extra 45// - This avoids the memcpy during graph construction 46// - For quantized weights, the constant is already converted to OpenVINO format 47// 2. For KV cache / compute tensors: Store an ov::Tensor in tensor->extra 48// - This can be directly passed to infer_request 49// - Future: can be changed to ov::RemoteTensor for GPU/NPU 50// 51// This design is similar to: 52// - CUDA split buffer: tensor->extra stores device pointers 53// - CPU repack buffer: tensor->extra stores tensor_traits with repacked data 54// ===================================================== 55 56// Buffer context that manages per-tensor allocations (no contiguous buffer for weights) 57struct ggml_backend_openvino_buffer_context { 58int device ; 59 std::string name ; 60size_t id ; 61 62// For non-weight buffers (KV cache, compute), we still use contiguous allocation 63void * data ; 64size_t size ; 65bool is_remote ; 66 67// Wrapping of the buffer 68 std::shared_ptr < ov::Tensor > ov_buffer ; 69 70// Track all extras for cleanup 71 std::map < ggml_tensor * ,ggml_openvino_extra_base *> tensor_extras ; 72 73// Used for re-allocation on device for kvcache 74void * data_prev ; 75 76ggml_backend_openvino_buffer_context (int device ,size_t size ,bool is_remote = false) : 77device (device ), 78name (std::string (GGML_OPENVINO_NAME )+ std::to_string (device )), 79id ([]() { 80static std::atomic < size_t > next_id {1 }; 81return next_id .fetch_add (1 ); 82 }()), 83data (nullptr ), 84size (size ), 85is_remote (is_remote ) { 86if (size == 0 ) { 87return ; 88 } 89 90const auto & device_name = ggml_openvino_get_device_name (); 91 92if (is_remote ) { 93GGML_ASSERT (device_name == "GPU" ); 94auto remote_context = ggml_openvino_get_remote_context (); 95auto gpu_context = remote_context -> as < ov::intel_gpu::ocl::ClContext > (); 96 ov::intel_gpu::ocl::USMTensor usm_tensor = 97gpu_context .create_usm_device_tensor (ov::element::u8 , ov::Shape {size }); 98data = usm_tensor .get (); 99ov_buffer = std::make_shared < ov::intel_gpu::ocl::USMTensor > (std::move (usm_tensor )); 100 }else { 101data = ggml_aligned_malloc (size ); 102GGML_ASSERT (data ); 103memset (data ,0 ,size ); 104ov_buffer = std::make_shared < ov::Tensor > (ov::element::u8 , ov::Shape {size },data ); 105 } 106 107if (data == nullptr ) { 108GGML_LOG_ERROR ("%s: failed to allocate %zu bytes\n" ,__func__ ,size ); 109return ; 110 } 111 112if (reinterpret_cast < uintptr_t > (data ) %TENSOR_ALIGNMENT != 0 ) { 113GGML_LOG_ERROR ("%s: %s buffer is not aligned to %d bytes\n" ,__func__ ,device_name .c_str (), 114TENSOR_ALIGNMENT ); 115GGML_ABORT ("fatal error" ); 116 } 117 } 118 119 ~ggml_backend_openvino_buffer_context () { 120// Clean up all tensor extras 121// GGML_LOG_DEBUG("Deleting OpenVINO buffer context #%zu for device %d, size %zu MB\n", id, device, 122// size / 1024 / 1024); 123for (auto & pair :tensor_extras ) { 124delete pair .second ; 125 } 126tensor_extras .clear (); 127if (!is_remote && data != nullptr ) { 128ggml_aligned_free (data ,size ); 129 } 130 } 131}; 132 133// Buffer type context (per-device) 134struct ggml_backend_openvino_buffer_type_context { 135int device ; 136 std::string name ; 137}; 138 139// ===================================================== 140// Host weight-buffer release (GGML_OPENVINO_RELEASE_WEIGHTS) 141// ===================================================== 142// The OpenVINO weight Constants are zero-copy views into the host buffers 143// allocated here (ggml_aligned_malloc, anonymous memory). On GPU the plugin 144// holds its own device copy after compile_model, so the host pages are dead 145// weight for inference and can be dropped to reclaim RSS (~weights size). 146// 147// We do NOT free the buffer (ggml owns its lifetime and tensors still point 148// into it); instead madvise(MADV_DONTNEED) drops the resident pages while 149// keeping the mapping valid. A later recompile would re-read these Constants 150// from now-zeroed memory and produce garbage, so once released we fail fast 151// if the cache-miss compile branch is reached again (see utils.cpp). 152namespace { 153struct ov_weight_buffer_registry { 154 std::mutex mutex ; 155// (data, size) of every non-remote weight buffer, for madvise. 156 std::vector < std::pair < void * ,size_t >> buffers ; 157bool released = false; 158}; 159 160ov_weight_buffer_registry & ov_weight_registry () { 161static ov_weight_buffer_registry reg ; 162return reg ; 163} 164}// namespace 165 166void ggml_openvino_register_weight_buffer (void * data ,size_t size ) { 167if (data == nullptr || size == 0 ) { 168return ; 169 } 170auto & reg = ov_weight_registry (); 171 std::lock_guard < std::mutex > lock (reg .mutex ); 172for (const auto & b :reg .buffers ) { 173if (b .first == data ) { 174return ;// already registered 175 } 176 } 177reg .buffers .emplace_back (data ,size ); 178} 179 180bool ggml_openvino_weight_buffers_released () { 181auto & reg = ov_weight_registry (); 182 std::lock_guard < std::mutex > lock (reg .mutex ); 183return reg .released ; 184} 185 186void ggml_openvino_release_weight_buffers () { 187auto & reg = ov_weight_registry (); 188 std::lock_guard < std::mutex > lock (reg .mutex ); 189if (reg .released ) { 190return ; 191 } 192size_t total = 0 ; 193#if !defined(_WIN32 ) 194for (const auto & b :reg .buffers ) { 195// Align down/up to page boundaries so madvise only drops whole pages 196// fully owned by this buffer. 197const long page = sysconf (_SC_PAGESIZE ); 198uintptr_t start = reinterpret_cast < uintptr_t > (b .first ); 199uintptr_t end = start + b .second ; 200uintptr_t astart = (start + page - 1 )& ~(uintptr_t ) (page - 1 ); 201uintptr_t aend = end & ~(uintptr_t ) (page - 1 ); 202if (aend > astart ) { 203if (madvise (reinterpret_cast < void *> (astart ),aend - astart ,MADV_DONTNEED )== 0 ) { 204total += aend - astart ; 205 } 206 } 207 } 208#endif 209reg .released = true; 210GGML_LOG_INFO ("%s: released %zu MB of host weight buffers (%zu buffers)\n" ,__func__ ,total /1024 /1024 , 211reg .buffers .size ()); 212} 213 214// Buffer interface functions 215static void ggml_backend_openvino_buffer_free_buffer (ggml_backend_buffer_t buffer ) { 216ggml_backend_openvino_buffer_context * ctx = (ggml_backend_openvino_buffer_context * )buffer -> context ; 217delete ctx ; 218} 219 220static void * ggml_backend_openvino_buffer_get_base (ggml_backend_buffer_t buffer ) { 221ggml_backend_openvino_buffer_context * ctx = (ggml_backend_openvino_buffer_context * )buffer -> context ; 222return ctx -> data ; 223} 224 225static bool is_stateful_enabled () { 226return ggml_openvino_getenv_int ("GGML_OPENVINO_STATEFUL_EXECUTION" )!= 0 ; 227} 228 229static enum ggml_status ggml_backend_openvino_buffer_init_tensor (ggml_backend_buffer_t buffer ,ggml_tensor * tensor ) { 230// GGML_LOG_DEBUG("%s: buffer usage=%d, tensor name=%s\n", __func__, buffer->usage, tensor->name); 231ggml_backend_openvino_buffer_context * ctx = (ggml_backend_openvino_buffer_context * )buffer -> context ; 232 233// Put kvcache on device memory for GPU (NPU memory is too small even for kvcache) 234if (strncmp (tensor -> name ,"cache_" ,6 )== 0 && !ctx -> is_remote && ggml_openvino_get_device_name ()== "GPU" && 235 !is_stateful_enabled ()) { 236GGML_ASSERT (ctx -> tensor_extras .empty ()); 237auto device = ctx -> device ; 238auto size = ctx -> size ; 239auto * data_prev = ctx -> data ; 240delete ctx ; 241ctx = new ggml_backend_openvino_buffer_context (device ,size , true); 242buffer -> context = ctx ; 243tensor -> data = (char * )ctx -> data + ((char * )tensor -> data - (char * )data_prev ); 244 } 245 246// Views share the extra from view_src 247if (tensor -> view_src != nullptr ) { 248GGML_ASSERT (tensor -> view_src -> buffer -> buft == buffer -> buft ); 249if (tensor -> view_src -> extra != nullptr ) { 250tensor -> extra = tensor -> view_src -> extra ; 251 } 252return GGML_STATUS_SUCCESS ; 253 } 254 255ctx = (ggml_backend_openvino_buffer_context * )buffer -> context ; 256 257if (tensor -> data != nullptr && !ggml_is_quantized (tensor -> type )) { 258ggml_openvino_tensor_extra * extra = ggml_openvino_create_tensor_extra (tensor ,ctx -> is_remote ); 259if (extra != nullptr ) { 260auto it = ctx -> tensor_extras .find (tensor ); 261if (it != ctx -> tensor_extras .end ()) { 262delete it -> second ; 263 } 264ctx -> tensor_extras [tensor ]= extra ; 265tensor -> extra = extra ; 266 } 267 } 268 269return GGML_STATUS_SUCCESS ; 270} 271 272static void ggml_backend_openvino_buffer_memset_tensor (ggml_backend_buffer_t buffer , 273ggml_tensor * tensor , 274uint8_t value , 275size_t offset , 276size_t size ) { 277// GGML_LOG_DEBUG("%s: buffer usage=%d, tensor name=%s\n", __func__, buffer->usage, tensor->name); 278GGML_ASSERT (tensor != nullptr && tensor -> data != nullptr ); 279ggml_backend_openvino_buffer_context * ctx = (ggml_backend_openvino_buffer_context * )buffer -> context ; 280 281if (ctx -> is_remote ) { 282// For remote (device) buffers, use OpenCL USM memfill 283cl_command_queue queue = ggml_openvino_get_cl_queue (); 284auto mem_fill_fn = ggml_openvino_get_clEnqueueMemFillINTEL (); 285if (queue != nullptr && mem_fill_fn != nullptr ) { 286uint8_t pattern = value ; 287cl_int err = mem_fill_fn (queue , (char * )tensor -> data + offset ,& pattern ,sizeof (pattern ),size ,0 ,nullptr , 288nullptr ); 289if (err != CL_SUCCESS ) { 290GGML_LOG_ERROR ("%s: clEnqueueMemFillINTEL failed with error %d\n" ,__func__ ,err ); 291 } 292clFinish (queue ); 293 }else { 294GGML_LOG_ERROR ("%s: no OpenCL queue or clEnqueueMemFillINTEL not available for GPU buffer\n" ,__func__ ); 295 } 296 }else { 297memset ((char * )tensor -> data + offset ,value ,size ); 298 } 299} 300 301static void ggml_backend_openvino_buffer_set_tensor (ggml_backend_buffer_t buffer , 302ggml_tensor * tensor , 303const void * data , 304size_t offset , 305size_t size ) { 306// GGML_LOG_DEBUG("%s: buffer usage=%d, tensor name=%s\n", __func__, buffer->usage, tensor->name); 307GGML_ASSERT (tensor != nullptr && tensor -> data != nullptr ); 308ggml_backend_openvino_buffer_context * ctx = (ggml_backend_openvino_buffer_context * )buffer -> context ; 309 310// Check if this is a weight buffer (usage is set BEFORE set_tensor is called, except in test-backend-ops) 311bool is_weight_buffer = (buffer -> usage == GGML_BACKEND_BUFFER_USAGE_WEIGHTS ); 312// Full tensor set: offset=0, full size, not a view 313bool is_full_tensor_set = (offset == 0 && size == ggml_nbytes (tensor )&& tensor -> view_src == nullptr ); 314// 2D tensor (typical weight shape), or a 3D quantized MoE expert weight (MUL_MAT_ID). Dense 3D 315// expert weights are handled later in create_weight_node instead. 316bool is_2d = (tensor -> ne [2 ]== 1 && tensor -> ne [3 ]== 1 ); 317bool is_supported_weight_shape = is_2d || (tensor -> ne [3 ]== 1 && ggml_is_quantized (tensor -> type )); 318 319if (is_weight_buffer && is_full_tensor_set && is_supported_weight_shape ) { 320try { 321auto result = process_weight_tensor (tensor ,data ,tensor -> data ); 322result .weight_node -> set_friendly_name (tensor -> name ); 323 324// const auto & layout = result.layout; 325ggml_openvino_extra_base * extra ; 326 327// Quantized path with extracted weight/scale/zp tensors 328if (result .is_quantized ()) { 329extra = new ggml_openvino_quantized_weight_extra (std::move (result .weights ), std::move (result .scales ), 330 std::move (result .zp ),result .weight_node ); 331 332// if (layout.is_requant) { 333// GGML_LOG_DEBUG("%s: requantized %s to %s (u%d, block_size=%ld)\n", __func__, tensor->name, 334// extra_quant_type_name(layout.requant_type.value()), layout.is_u4 ? 4 : 8, 335// layout.weights_per_block); 336// } else { 337// int64_t n_blocks = ggml_nelements(tensor) / layout.weights_per_block; 338// GGML_LOG_DEBUG("%s: extracted quantized weight node for %s (u%d, %zu weights, %ld blocks)\n", 339// __func__, tensor->name, layout.is_u4 ? 4 : 8, layout.weights_size, n_blocks); 340// } 341 }else { 342// F16/F32/BF16 weight or F16-requant 343extra = new ggml_openvino_weight_extra (std::move (result .weights ),result .weight_node ); 344 345// if (layout.total_size > 0) { 346// GGML_LOG_DEBUG("%s: requantized %s to F16\n", __func__, tensor->name); 347// } else { 348// GGML_LOG_DEBUG("%s: created shared-memory weight node for %s\n", __func__, tensor->name); 349// } 350 } 351 352ctx -> tensor_extras [tensor ]= extra ; 353tensor -> extra = extra ; 354 355// Register the host buffer so its pages can be dropped after the GPU 356// plugin has its own device copy (GGML_OPENVINO_RELEASE_WEIGHTS). 357if (!ctx -> is_remote ) { 358// Weights are set once at model load. Setting a weight after a release 359// means a second model is loading while the first's compiled graph is 360// pinned — that graph would be wrongly reused with this model's key. 361// Fail loud rather than return silently-wrong results. 362if (ggml_openvino_weight_buffers_released ()) { 363GGML_ABORT ( 364"ggml-openvino: loading a new model while GGML_OPENVINO_RELEASE_WEIGHTS pinned a previous " 365"model's compiled graph. This mode supports a single model per process; unset it for " 366"multi-model runs." ); 367 } 368ggml_openvino_register_weight_buffer (ctx -> data ,ctx -> size ); 369 } 370 371 }catch (const std::exception & e ) { 372GGML_LOG_ERROR ("%s: failed to process weight tensor for %s: %s\n" ,__func__ ,tensor -> name ,e .what ()); 373memcpy ((char * )tensor -> data + offset ,data ,size ); 374 } 375 }else { 376// Non-weight tensor (KV cache, activations, etc.) - copy data. test-backend-ops also goes here 377if (ctx -> is_remote ) { 378cl_command_queue queue = ggml_openvino_get_cl_queue (); 379auto mem_cpy_fn = ggml_openvino_get_clEnqueueMemcpyINTEL (); 380if (queue != nullptr && mem_cpy_fn != nullptr ) { 381cl_int err = 382mem_cpy_fn (queue ,CL_TRUE , (char * )tensor -> data + offset ,data ,size ,0 ,nullptr ,nullptr ); 383if (err != CL_SUCCESS ) { 384GGML_LOG_ERROR ("%s: clEnqueueMemcpyINTEL failed with error %d\n" ,__func__ ,err ); 385 } 386 }else { 387GGML_LOG_ERROR ("%s: no OpenCL queue or clEnqueueMemcpyINTEL not available for GPU buffer\n" ,__func__ ); 388 } 389 }else { 390memcpy ((char * )tensor -> data + offset ,data ,size ); 391 } 392 393ggml_openvino_tensor_extra * extra = ggml_openvino_create_tensor_extra (tensor ,ctx -> is_remote ); 394if (extra == nullptr ) { 395// GGML_LOG_ERROR("%s: failed to create tensor extra for %s\n", __func__, tensor->name); 396return ; 397 } 398 399auto it = ctx -> tensor_extras .find (tensor ); 400if (it != ctx -> tensor_extras .end ()) { 401delete it -> second ; 402 } 403ctx -> tensor_extras [tensor ]= extra ; 404tensor -> extra = extra ; 405 } 406} 407 408static void ggml_backend_openvino_buffer_get_tensor (ggml_backend_buffer_t buffer , 409const ggml_tensor * tensor , 410void * data , 411size_t offset , 412size_t size ) { 413// GGML_LOG_DEBUG("%s: buffer usage=%d, tensor name=%s\n", __func__, buffer->usage, tensor->name); 414GGML_ASSERT (tensor != nullptr && tensor -> data != nullptr ); 415ggml_backend_openvino_buffer_context * ctx = (ggml_backend_openvino_buffer_context * )buffer -> context ; 416 417if (ctx -> is_remote ) { 418// For remote (device) buffers, use OpenCL USM memcpy (device-to-host) 419cl_command_queue queue = ggml_openvino_get_cl_queue (); 420auto mem_cpy_fn = ggml_openvino_get_clEnqueueMemcpyINTEL (); 421if (queue != nullptr && mem_cpy_fn != nullptr ) { 422cl_int err = 423mem_cpy_fn (queue ,CL_TRUE ,data , (const char * )tensor -> data + offset ,size ,0 ,nullptr ,nullptr ); 424if (err != CL_SUCCESS ) { 425GGML_LOG_ERROR ("%s: clEnqueueMemcpyINTEL failed with error %d\n" ,__func__ ,err ); 426 } 427 }else { 428GGML_LOG_ERROR ("%s: no OpenCL queue or clEnqueueMemcpyINTEL not available for GPU buffer\n" ,__func__ ); 429 } 430 }else { 431memcpy (data , (const char * )tensor -> data + offset ,size ); 432 } 433} 434 435static bool ggml_backend_openvino_buffer_cpy_tensor (ggml_backend_buffer_t buffer , 436const ggml_tensor * src , 437ggml_tensor * dst ) { 438// GGML_LOG_DEBUG("%s: src tensor name=%s, dst tensor name=%s\n", __func__, src->name, dst->name); 439GGML_ASSERT (src != nullptr && dst != nullptr ); 440ggml_backend_openvino_buffer_context * ctx = (ggml_backend_openvino_buffer_context * )buffer -> context ; 441 442if (ctx -> is_remote ) { 443// For remote (device) buffers, use OpenCL USM memcpy 444cl_command_queue queue = ggml_openvino_get_cl_queue (); 445auto mem_cpy_fn = ggml_openvino_get_clEnqueueMemcpyINTEL (); 446if (queue == nullptr || mem_cpy_fn == nullptr ) { 447GGML_LOG_ERROR ("%s: no OpenCL queue or clEnqueueMemcpyINTEL not available for GPU buffer\n" ,__func__ ); 448return false; 449 } 450// Can copy from host to device 451if (ggml_backend_buffer_is_host (src -> buffer )) { 452cl_int err = mem_cpy_fn (queue ,CL_TRUE ,dst -> data ,src -> data ,ggml_nbytes (src ),0 ,nullptr ,nullptr ); 453if (err != CL_SUCCESS ) { 454GGML_LOG_ERROR ("%s: clEnqueueMemcpyINTEL (host-to-device) failed with error %d\n" ,__func__ ,err ); 455return false; 456 } 457return true; 458 } 459// Can also copy from device to device if both are OpenVINO remote buffers 460if (ggml_backend_buffer_is_openvino (src -> buffer )) { 461ggml_backend_openvino_buffer_context * src_ctx = 462 (ggml_backend_openvino_buffer_context * )src -> buffer -> context ; 463if (src_ctx -> is_remote ) { 464cl_int err = mem_cpy_fn (queue ,CL_TRUE ,dst -> data ,src -> data ,ggml_nbytes (src ),0 ,nullptr ,nullptr ); 465if (err != CL_SUCCESS ) { 466GGML_LOG_ERROR ("%s: clEnqueueMemcpyINTEL (device-to-device) failed with error %d\n" ,__func__ ,err ); 467return false; 468 } 469return true; 470 } 471 } 472return false; 473 } 474 475// Host buffer - can copy from any host buffer 476if (ggml_backend_buffer_is_host (src -> buffer )) { 477memcpy (dst -> data ,src -> data ,ggml_nbytes (src )); 478return true; 479 } 480return false; 481} 482 483static void ggml_backend_openvino_buffer_clear (ggml_backend_buffer_t buffer ,uint8_t value ) { 484ggml_backend_openvino_buffer_context * ctx = (ggml_backend_openvino_buffer_context * )buffer -> context ; 485GGML_ASSERT (ctx -> data != nullptr ); 486if (ctx -> is_remote ) { 487cl_command_queue queue = ggml_openvino_get_cl_queue (); 488auto mem_fill_fn = ggml_openvino_get_clEnqueueMemFillINTEL (); 489if (queue != nullptr && mem_fill_fn != nullptr ) { 490uint8_t pattern = value ; 491cl_int err = mem_fill_fn (queue ,ctx -> data ,& pattern ,sizeof (pattern ),ctx -> size ,0 ,nullptr ,nullptr ); 492if (err != CL_SUCCESS ) { 493GGML_LOG_WARN ("%s: clEnqueueMemFillINTEL failed with error %d\n" ,__func__ ,err ); 494 } 495clFinish (queue ); 496 }else { 497GGML_LOG_WARN ("%s: no OpenCL queue or clEnqueueMemFillINTEL not available for GPU buffer clear\n" , 498__func__ ); 499 } 500 }else { 501memset (ctx -> data ,value ,ctx -> size ); 502 } 503} 504 505static const ggml_backend_buffer_i ggml_backend_openvino_buffer_interface = { 506/* .free_buffer = */ ggml_backend_openvino_buffer_free_buffer , 507/* .get_base = */ ggml_backend_openvino_buffer_get_base , 508/* .init_tensor = */ ggml_backend_openvino_buffer_init_tensor , 509/* .memset_tensor = */ ggml_backend_openvino_buffer_memset_tensor , 510/* .set_tensor = */ ggml_backend_openvino_buffer_set_tensor , 511/* .get_tensor = */ ggml_backend_openvino_buffer_get_tensor , 512/* .set_tensor_2d = */ NULL , 513/* .get_tensor_2d = */ NULL , 514/* .cpy_tensor = */ ggml_backend_openvino_buffer_cpy_tensor , 515/* .clear = */ ggml_backend_openvino_buffer_clear , 516/* .reset = */ NULL , 517}; 518 519// Buffer type interface functions 520static const char * ggml_backend_openvino_buffer_type_get_name (ggml_backend_buffer_type_t buft ) { 521ggml_backend_openvino_buffer_type_context * ctx = (ggml_backend_openvino_buffer_type_context * )buft -> context ; 522return ctx -> name .c_str (); 523} 524 525static ggml_backend_buffer_t ggml_backend_openvino_buffer_type_alloc_buffer (ggml_backend_buffer_type_t buft , 526size_t size ) { 527ggml_backend_openvino_buffer_type_context * buft_ctx = (ggml_backend_openvino_buffer_type_context * )buft -> context ; 528 529// Create buffer context with contiguous memory allocation 530ggml_backend_openvino_buffer_context * ctx = new ggml_backend_openvino_buffer_context (buft_ctx -> device ,size ); 531 532if (ctx -> data == nullptr && size > 0 ) { 533GGML_LOG_ERROR ("%s: failed to allocate buffer of size %zu\n" ,__func__ ,size ); 534delete ctx ; 535return nullptr ; 536 } 537 538return ggml_backend_buffer_init (buft ,ggml_backend_openvino_buffer_interface ,ctx ,size ); 539} 540 541static size_t ggml_backend_openvino_buffer_type_get_alignment (ggml_backend_buffer_type_t buft ) { 542GGML_UNUSED (buft ); 543return TENSOR_ALIGNMENT ; 544} 545 546static size_t ggml_backend_openvino_buffer_type_get_max_size (ggml_backend_buffer_type_t buft ) { 547GGML_UNUSED (buft ); 548return SIZE_MAX ; 549} 550 551static size_t ggml_backend_openvino_buffer_type_get_alloc_size (ggml_backend_buffer_type_t buft , 552const ggml_tensor * tensor ) { 553GGML_UNUSED (buft ); 554 555// For quantized weight tensors, we need extra space for extracted data. 556if (ggml_is_quantized (tensor -> type )&& tensor -> ne [3 ]== 1 ) { 557ggml_openvino_extracted_layout layout = ggml_openvino_get_extracted_layout (tensor ); 558if (layout .total_size > 0 ) { 559// GGML_LOG_DEBUG("%s: tensor %s needs %zu bytes (original %zu, extracted: weights=%zu scales=%zu zp=%zu)\n", 560// __func__, tensor->name, layout.total_size, ggml_nbytes(tensor), layout.weights_size, 561// layout.scales_size, layout.zp_size); 562return layout .total_size ; 563 } 564 } 565 566return ggml_nbytes (tensor ); 567} 568 569static const ggml_backend_buffer_type_i ggml_backend_openvino_buffer_type_interface = { 570/* .get_name = */ ggml_backend_openvino_buffer_type_get_name , 571/* .alloc_buffer = */ ggml_backend_openvino_buffer_type_alloc_buffer , 572/* .get_alignment = */ ggml_backend_openvino_buffer_type_get_alignment , 573/* .get_max_size = */ ggml_backend_openvino_buffer_type_get_max_size , 574/* .get_alloc_size = */ ggml_backend_openvino_buffer_type_get_alloc_size , 575/* .is_host = */ nullptr , 576}; 577 578// Get buffer type for a specific device 579GGML_BACKEND_API ggml_backend_buffer_type_tggml_backend_openvino_buffer_type (int device ) { 580GGML_ASSERT (device >=0 && device < ggml_backend_openvino_get_device_count ()); 581 582static std::mutex mutex ; 583 std::lock_guard < std::mutex > lock (mutex ); 584 585static std::vector < ggml_backend_buffer_type > buffer_types ; 586static std::vector < ggml_backend_openvino_buffer_type_context > buffer_type_contexts ; 587 588if (buffer_types .empty ()) { 589int device_count = ggml_backend_openvino_get_device_count (); 590buffer_types .resize (device_count ); 591buffer_type_contexts .resize (device_count ); 592 593for (int i = 0 ;i < device_count ;i ++ ) { 594buffer_type_contexts [i ].device = i ; 595buffer_type_contexts [i ].name = std::string (GGML_OPENVINO_NAME )+ std::to_string (i ); 596 597buffer_types [i ]= ggml_backend_buffer_type { 598/* .iface = */ ggml_backend_openvino_buffer_type_interface , 599/* .device = */ ggml_backend_reg_dev_get (ggml_backend_openvino_reg (),i ), 600/* .context = */ & buffer_type_contexts [i ], 601 }; 602 } 603 } 604 605return & buffer_types [device ]; 606} 607 608// ===================================================== 609// OpenVINO Host Buffer Implementation 610// ===================================================== 611 612static const char * ggml_backend_openvino_host_buffer_type_get_name (ggml_backend_buffer_type_t buft ) { 613ggml_backend_openvino_buffer_type_context * ctx = (ggml_backend_openvino_buffer_type_context * )buft -> context ; 614static std::string name ; 615name = ctx -> name + "_HOST" ; 616return name .c_str (); 617} 618 619static bool ggml_backend_openvino_host_buffer_type_is_host (ggml_backend_buffer_type_t buft ) { 620GGML_UNUSED (buft ); 621return true; 622} 623 624static const ggml_backend_buffer_type_i ggml_backend_openvino_host_buffer_type_interface = { 625/* .get_name = */ ggml_backend_openvino_host_buffer_type_get_name , 626/* .alloc_buffer = */ ggml_backend_openvino_buffer_type_alloc_buffer , 627/* .get_alignment = */ ggml_backend_openvino_buffer_type_get_alignment , 628/* .get_max_size = */ ggml_backend_openvino_buffer_type_get_max_size , 629/* .get_alloc_size = */ ggml_backend_openvino_buffer_type_get_alloc_size , 630/* .is_host = */ ggml_backend_openvino_host_buffer_type_is_host , 631}; 632 633GGML_BACKEND_API ggml_backend_buffer_type_tggml_backend_openvino_host_buffer_type (int device ) { 634GGML_ASSERT (device >=0 && device < ggml_backend_openvino_get_device_count ()); 635 636static std::mutex mutex ; 637 std::lock_guard < std::mutex > lock (mutex ); 638 639static std::vector < ggml_backend_buffer_type > buffer_types ; 640static std::vector < ggml_backend_openvino_buffer_type_context > buffer_type_contexts ; 641 642if (buffer_types .empty ()) { 643int device_count = ggml_backend_openvino_get_device_count (); 644buffer_types .resize (device_count ); 645buffer_type_contexts .resize (device_count ); 646 647for (int i = 0 ;i < device_count ;i ++ ) { 648buffer_type_contexts [i ].device = i ; 649buffer_type_contexts [i ].name = std::string (GGML_OPENVINO_NAME )+ std::to_string (i ); 650 651buffer_types [i ]= ggml_backend_buffer_type { 652/* .iface = */ ggml_backend_openvino_host_buffer_type_interface , 653/* .device = */ ggml_backend_reg_dev_get (ggml_backend_openvino_reg (),i ), 654/* .context = */ & buffer_type_contexts [i ], 655 }; 656 } 657 } 658 659return & buffer_types [device ]; 660} 661 662bool ggml_backend_buffer_is_openvino (ggml_backend_buffer_t buffer ) { 663return buffer -> iface .free_buffer == ggml_backend_openvino_buffer_free_buffer ; 664} 665 666size_t ggml_backend_openvino_buffer_get_ctx_id (ggml_backend_buffer_t buffer ) { 667if (!ggml_backend_buffer_is_openvino (buffer )) { 668return 0 ; 669 } 670ggml_backend_openvino_buffer_context * ctx = (ggml_backend_openvino_buffer_context * )buffer -> context ; 671return ctx -> id ; 672} 673 674bool ggml_openvino_buffer_is_remote (const ggml_tensor * tensor ) { 675if (tensor == nullptr || tensor -> buffer == nullptr ) { 676return false; 677 } 678if (!ggml_backend_buffer_is_openvino (tensor -> buffer )) { 679return false; 680 } 681auto * ctx = static_cast < ggml_backend_openvino_buffer_context *> (tensor -> buffer -> context ); 682return ctx -> is_remote ; 683} 684 685void ggml_openvino_buffer_register_extra (ggml_tensor * tensor ,ggml_openvino_extra_base * extra ) { 686GGML_ASSERT (tensor != nullptr ); 687GGML_ASSERT (tensor -> buffer != nullptr ); 688GGML_ASSERT (ggml_backend_buffer_is_openvino (tensor -> buffer )); 689 690auto * ctx = static_cast < ggml_backend_openvino_buffer_context *> (tensor -> buffer -> context ); 691 692auto it = ctx -> tensor_extras .find (tensor ); 693if (it != ctx -> tensor_extras .end ()) { 694delete it -> second ; 695 } 696 697ctx -> tensor_extras [tensor ]= extra ; 698tensor -> extra = extra ; 699} 700 701bool ggml_backend_buft_is_openvino (ggml_backend_buffer_type_t buft ) { 702return buft -> iface .get_name == ggml_backend_openvino_buffer_type_get_name ; 703} 704 705bool ggml_backend_buft_is_openvino_host (ggml_backend_buffer_type_t buft ) { 706return buft -> iface .get_name == ggml_backend_openvino_host_buffer_type_get_name ; 707} 708 709static void ggml_backend_openvino_free (ggml_backend_t backend ) { 710ggml_backend_openvino_context * ctx = (ggml_backend_openvino_context * )backend -> context ; 711 712if (ctx -> runtime_context ) { 713auto r_ctx = std::static_pointer_cast < ov_runtime_context > (ctx -> runtime_context ); 714if (-- r_ctx -> backend_count == 0 ) { 715// If host weight buffers were released (GGML_OPENVINO_RELEASE_WEIGHTS), the 716// dropped pages can never be repopulated, so a recompile is impossible. Keep 717// the compiled-model cache alive across backend teardown so the next context 718// reuses it instead of recompiling against zeroed weights. 719if (!ggml_openvino_weight_buffers_released ()) { 720r_ctx -> clear_caches (); 721 } 722 } 723 } 724 725delete ctx ; 726delete backend ; 727} 728 729static const char * ggml_backend_openvino_get_name (ggml_backend_t backend ) { 730return GGML_OPENVINO_NAME ; 731GGML_UNUSED (backend ); 732} 733 734static enum ggml_status ggml_backend_openvino_graph_compute (ggml_backend_t backend ,ggml_cgraph * cgraph ) { 735return ov_graph_compute (cgraph ,backend ); 736GGML_UNUSED (backend ); 737} 738 739static const ggml_backend_i ggml_backend_openvino_interface = { 740/* .get_name = */ ggml_backend_openvino_get_name , 741/* .free = */ ggml_backend_openvino_free , 742/* .set_tensor_async = */ NULL , 743/* .get_tensor_async = */ NULL , 744/* .set_tensor_2d_async = */ NULL , 745/* .get_tensor_2d_async = */ NULL , 746/* .cpy_tensor_async = */ NULL , 747/* .synchronize = */ NULL , 748/* .graph_plan_create = */ NULL , 749/* .graph_plan_free = */ NULL , 750/* .graph_plan_update = */ NULL , 751/* .graph_plan_compute = */ NULL , 752/* .graph_compute = */ ggml_backend_openvino_graph_compute , 753/* .event_record = */ NULL , 754/* .event_wait = */ NULL , 755/* .graph_optimize = */ NULL , 756}; 757 758int ggml_backend_openvino_get_device_count () { 759return 1 ; 760} 761 762static ggml_guid_t ggml_backend_openvino_guid (void ) { 763static ggml_guid guid = {0x12 ,0xa8 ,0xae ,0xf4 ,0xc0 ,0x1e ,0x61 ,0x97 , 7640x8f ,0xeb ,0x33 ,0x04 ,0xa1 ,0x33 ,0x51 ,0x2d }; 765return & guid ; 766} 767 768static std::shared_ptr < ov_runtime_context > get_ov_runtime_context_ptr () { 769static std::shared_ptr < ov_runtime_context > r_ctx = [] { 770auto ctx = std::make_shared < ov_runtime_context > (); 771ctx -> device = ggml_openvino_get_device_name (); 772ctx -> stateful = is_stateful_enabled ()&& !ggml_openvino_is_npu (); 773return ctx ; 774 }(); 775return r_ctx ; 776} 777 778// backend API 779GGML_BACKEND_API ggml_backend_tggml_backend_openvino_init (int device ) { 780if (device < 0 || device >=ggml_backend_openvino_get_device_count ()) { 781GGML_LOG_ERROR ("%s: invalid device %d\n" ,__func__ ,device ); 782return nullptr ; 783 } 784 785ggml_backend_openvino_context * ctx = new ggml_backend_openvino_context ; 786if (ctx == nullptr ) { 787GGML_LOG_ERROR ("%s: failed to allocate context\n" ,__func__ ); 788return nullptr ; 789 } 790 791ctx -> runtime_context = get_ov_runtime_context_ptr (); 792if (ctx -> runtime_context == nullptr ) { 793GGML_LOG_ERROR ("%s: failed to allocate runtime context\n" ,__func__ ); 794delete ctx ; 795return nullptr ; 796 } 797 798 std::shared_ptr < ov_runtime_context > r_ctx = std::static_pointer_cast < ov_runtime_context > (ctx -> runtime_context ); 799r_ctx -> backend_count ++ ; 800 801ggml_backend_t openvino_backend = new ggml_backend { 802/* .guid = */ ggml_backend_openvino_guid (), 803/* .interface = */ ggml_backend_openvino_interface , 804/* .device = */ ggml_backend_reg_dev_get (ggml_backend_openvino_reg (),device ), 805/* .context = */ ctx , 806 }; 807 808return openvino_backend ; 809} 810 811GGML_BACKEND_API bool ggml_backend_is_openvino (ggml_backend_t backend ) { 812return backend != NULL && ggml_guid_matches (backend -> guid ,ggml_backend_openvino_guid ()); 813} 814 815struct ggml_backend_openvino_device_context { 816int device ; 817 std::string name ; 818 std::string description ; 819}; 820 821static const char * ggml_backend_openvino_device_get_name (ggml_backend_dev_t dev ) { 822ggml_backend_openvino_device_context * ctx = (ggml_backend_openvino_device_context * )dev -> context ; 823return ctx -> name .c_str (); 824} 825 826static const char * ggml_backend_openvino_device_get_description (ggml_backend_dev_t dev ) { 827ggml_backend_openvino_device_context * ctx = (ggml_backend_openvino_device_context * )dev -> context ; 828return ctx -> description .c_str (); 829} 830 831static void ggml_backend_openvino_device_get_memory (ggml_backend_dev_t dev ,size_t * free ,size_t * total ) { 832#ifdef _WIN32 833MEMORYSTATUSEX status ; 834status .dwLength = sizeof (status ); 835GlobalMemoryStatusEx (& status ); 836* total = status .ullTotalPhys ; 837* free = status .ullAvailPhys ; 838#else 839long pages = sysconf (_SC_PHYS_PAGES ); 840long page_size = sysconf (_SC_PAGE_SIZE ); 841* total = pages * page_size ; 842 843// "free" system memory is ill-defined, for practical purposes assume that all of it is free: 844* free = * total ; 845#endif // _WIN32 846 847GGML_UNUSED (dev ); 848} 849 850static enum ggml_backend_dev_type ggml_backend_openvino_device_get_type (ggml_backend_dev_t dev ) { 851GGML_UNUSED (dev ); 852return GGML_BACKEND_DEVICE_TYPE_GPU ; 853} 854 855static void ggml_backend_openvino_device_get_props (ggml_backend_dev_t dev ,ggml_backend_dev_props * props ) { 856props -> name = ggml_backend_openvino_device_get_name (dev ); 857props -> description = ggml_backend_openvino_device_get_description (dev ); 858props -> type = ggml_backend_openvino_device_get_type (dev ); 859ggml_backend_openvino_device_get_memory (dev ,& props -> memory_free ,& props -> memory_total ); 860 861props -> caps = { 862/* .async = */ false, 863/* .host_buffer = */ false, 864/* .buffer_from_host_ptr = */ false, 865/* .events = */ false, 866/* .mmap_support = */ true, 867 }; 868} 869 870static ggml_backend_t ggml_backend_openvino_device_init (ggml_backend_dev_t dev ,const char * params ) { 871GGML_UNUSED (params ); 872ggml_backend_openvino_device_context * ctx = (ggml_backend_openvino_device_context * )dev -> context ; 873return ggml_backend_openvino_init (ctx -> device ); 874} 875 876static ggml_backend_buffer_type_t ggml_backend_openvino_device_get_buffer_type (ggml_backend_dev_t dev ) { 877ggml_backend_openvino_device_context * ctx = (ggml_backend_openvino_device_context * )dev -> context ; 878return ggml_backend_openvino_buffer_type (ctx -> device ); 879} 880 881static ggml_backend_buffer_type_t ggml_backend_openvino_device_get_host_buffer_type (ggml_backend_dev_t dev ) { 882ggml_backend_openvino_device_context * ctx = (ggml_backend_openvino_device_context * )dev -> context ; 883return ggml_backend_openvino_host_buffer_type (ctx -> device ); 884} 885 886static bool has_view_op_input (const ggml_tensor * op ) { 887for (int i = 0 ;i < GGML_MAX_SRC ;i ++ ) { 888if (op -> src [i ]== nullptr ) { 889break ; 890 } 891if (op -> src [i ]-> op == GGML_OP_VIEW ) { 892return true; 893 } 894 } 895return false; 896} 897 898static bool has_non_contiguous_view_input (const ggml_tensor * op ) { 899for (int i = 0 ;i < GGML_MAX_SRC ;i ++ ) { 900if (op -> src [i ]== nullptr ) { 901break ; 902 } 903if (op -> src [i ]-> op == GGML_OP_VIEW && !ggml_is_contiguous (op -> src [i ])) { 904return true; 905 } 906 } 907return false; 908} 909 910static bool is_supported_flash_attn_pattern (const ggml_tensor * op ) { 911// Each Q/K/V input must follow one of: 912// PERMUTE -> VIEW -> base (view_src==nullptr) (llama KV-cache path) 913// PERMUTE -> RESHAPE -> base (view_src==nullptr) (whisper Q) 914// VIEW -> base (view_src==nullptr) (whisper K/V from kv_pad) 915for (int i = 0 ;i < 3 ;i ++ ) { 916const ggml_tensor * src = op -> src [i ]; 917if (src -> op == GGML_OP_PERMUTE ) { 918if (src -> src [0 ]== nullptr ) { 919return false; 920 } 921if (src -> src [0 ]-> op != GGML_OP_VIEW && src -> src [0 ]-> op != GGML_OP_RESHAPE ) { 922return false; 923 } 924if (src -> src [0 ]-> src [0 ]== nullptr || src -> src [0 ]-> src [0 ]-> view_src != nullptr ) { 925return false; 926 } 927 }else if (src -> op == GGML_OP_VIEW ) { 928if (src -> src [0 ]== nullptr || src -> src [0 ]-> view_src != nullptr ) { 929return false; 930 } 931 }else { 932return false; 933 } 934 } 935return true; 936} 937 938static bool is_gemma3n_flash_attn_pattern (const ggml_tensor * op ) { 939if (!is_supported_flash_attn_pattern (op )) { 940return false; 941 } 942 943const ggml_tensor * q_base = 944op -> src [0 ]!= nullptr && op -> src [0 ]-> src [0 ]!= nullptr ?op -> src [0 ]-> src [0 ]-> src [0 ] :nullptr ; 945const ggml_tensor * k_base = 946op -> src [1 ]!= nullptr && op -> src [1 ]-> src [0 ]!= nullptr ?op -> src [1 ]-> src [0 ]-> src [0 ] :nullptr ; 947const ggml_tensor * v_base = 948op -> src [2 ]!= nullptr && op -> src [2 ]-> src [0 ]!= nullptr ?op -> src [2 ]-> src [0 ]-> src [0 ] :nullptr ; 949 950if (q_base == nullptr || q_base -> op != GGML_OP_ROPE ) { 951return false; 952 } 953 954// gemma3n direct attention path (no KV cache): q=ROPE, k=ROPE, v=RMS_NORM 955// Only match this specific pattern to avoid falsely catching other models 956// (e.g. Gemma4) that also use scale=1.0 with KV-cache backed attention. 957const bool is_qkv_direct = 958k_base != nullptr && v_base != nullptr && k_base -> op == GGML_OP_ROPE && v_base -> op == GGML_OP_RMS_NORM ; 959 960return is_qkv_direct ; 961} 962 963static bool checked_mul_size (size_t a ,size_t b ,size_t & out ) { 964if (a == 0 || b == 0 ) { 965out = 0 ; 966return true; 967 } 968if (a > SIZE_MAX /b ) { 969return false; 970 } 971out = a * b ; 972return true; 973} 974 975static bool tensor_view_fits_src_buffer (const ggml_tensor * tensor ) { 976if (tensor -> view_src == nullptr ) { 977return true; 978 } 979 980const size_t src_nbytes = ggml_nbytes (tensor -> view_src ); 981if (tensor -> view_offs > src_nbytes ) { 982return false; 983 } 984 985const size_t tensor_nbytes = ggml_nbytes (tensor ); 986return tensor_nbytes <=src_nbytes - tensor -> view_offs ; 987} 988 989static bool cpy_output_view_is_supported (const ggml_tensor * op ) { 990if (op -> view_src == nullptr ) { 991return true; 992 } 993 994if (!tensor_view_fits_src_buffer (op )) { 995return false; 996 } 997 998return ggml_nbytes (op )== 0 || ggml_is_contiguous (op ); 999} 1000 1001static bool mul_mat_id_requires_large_tmp (const ggml_tensor * op ) { 1002const ggml_tensor * as = op -> src [0 ]; 1003const ggml_tensor * ids = op -> src [2 ]; 1004if (as == nullptr || ids == nullptr ) { 1005return true; 1006 } 1007 1008// The MXFP4 MUL_MAT_ID translation (translate_mul_mat_id_mxfp4_packed in mul_mat_id.cpp) 1009// materializes selected expert weights with shape [n_tokens, n_used, rows, k]. Skip cases that 1010// would create a very large temporary and let the scheduler fall back instead. Every other weight 1011// type goes through GatherMatmul, which never materializes this temporary. 1012size_t tmp_elems = 1 ; 1013if (!checked_mul_size (tmp_elems ,static_cast < size_t > (ids -> ne [1 ]),tmp_elems )|| 1014 !checked_mul_size (tmp_elems ,static_cast < size_t > (ids -> ne [0 ]),tmp_elems )|| 1015 !checked_mul_size (tmp_elems ,static_cast < size_t > (as -> ne [1 ]),tmp_elems )|| 1016 !checked_mul_size (tmp_elems ,static_cast < size_t > (as -> ne [0 ]),tmp_elems )) { 1017return true; 1018 } 1019 1020size_t tmp_bytes = 0 ; 1021if (!checked_mul_size (tmp_elems ,sizeof (float ),tmp_bytes )) { 1022return true; 1023 } 1024 1025static constexpr size_t mul_mat_id_tmp_limit = 1ULL <<30 ;// 1 GiB 1026return tmp_bytes > mul_mat_id_tmp_limit ; 1027} 1028 1029static bool tensor_name_starts_with (const ggml_tensor * tensor ,const char * prefix ) { 1030return tensor != nullptr && strncmp (tensor -> name ,prefix ,strlen (prefix ))== 0 ; 1031} 1032 1033static bool is_msa_block_mask_expansion (const ggml_tensor * op ) { 1034if (tensor_name_starts_with (op ,"msa_" )) { 1035return true; 1036 } 1037 1038const ggml_tensor * src = op -> src [0 ]; 1039while (src != nullptr && (src -> op == GGML_OP_RESHAPE || src -> op == GGML_OP_REPEAT )) { 1040if (tensor_name_starts_with (src ,"msa_block_mask" )) { 1041return true; 1042 } 1043src = src -> src [0 ]; 1044 } 1045 1046return tensor_name_starts_with (src ,"msa_block_mask" ); 1047} 1048 1049namespace { 1050struct ggml_openvino_op_support { 1051bool is_supported = true; 1052 std::string reason ; 1053 1054 operatorbool ()const { 1055return is_supported ; 1056 } 1057}; 1058}// namespace 1059 1060static ggml_openvino_op_support is_op_supported_case (const ggml_tensor * op ) { 1061if (is_msa_block_mask_expansion (op )) { 1062return {false,"MSA block mask expansion is not supported" }; 1063 } 1064 1065switch (op -> op ) { 1066case GGML_OP_CONCAT : { 1067if (op -> type == GGML_TYPE_I64 ) { 1068return {false,"CONCAT with I64 type is not supported" }; 1069 } 1070if (ggml_openvino_get_device_name ()== "GPU" && op -> type == GGML_TYPE_BF16 && has_view_op_input (op )) { 1071return {false,"CONCAT with BF16 type and VIEW input is not supported on GPU" }; 1072 } 1073break ; 1074 } 1075case GGML_OP_SET : { 1076const auto nb1 = static_cast < size_t > (op -> op_params [0 ]); 1077const auto nb2 = static_cast < size_t > (op -> op_params [1 ]); 1078const auto nb3 = static_cast < size_t > (op -> op_params [2 ]); 1079 1080// OpenVINO SET translation currently supports dst layouts that match src0 strides. 1081if (op -> src [0 ]== nullptr || nb1 != op -> src [0 ]-> nb [1 ]|| nb2 != op -> src [0 ]-> nb [2 ]|| nb3 != op -> src [0 ]-> nb [3 ]) { 1082return {false,"SET op with dst nb1=" + std::to_string (nb1 )+ ", nb2=" + std::to_string (nb2 )+ ", nb3=" + std::to_string (nb3 )+ 1083" that does not match src0 strides nb[1]=" + (op -> src [0 ]!= nullptr ? std::to_string (op -> src [0 ]-> nb [1 ]) :"null" )+ 1084", nb[2]=" + (op -> src [0 ]!= nullptr ? std::to_string (op -> src [0 ]-> nb [2 ]) :"null" )+ 1085", nb[3]=" + (op -> src [0 ]!= nullptr ? std::to_string (op -> src [0 ]-> nb [3 ]) :"null" )}; 1086 } 1087break ; 1088 } 1089case GGML_OP_GET_ROWS : 1090case GGML_OP_SET_ROWS : { 1091if (op -> ne [3 ]!= 1 ) { 1092return {false,"GET_ROWS/SET_ROWS with ne[3] != 1 (ne[3]=" + std::to_string (op -> ne [3 ])+ ") is not supported" }; 1093 } 1094if (op -> op == GGML_OP_GET_ROWS && ggml_is_quantized (op -> src [0 ]-> type )&& 1095op -> src [0 ]-> view_src != nullptr && op -> src [0 ]-> view_offs != 0 ) { 1096return {false,"GET_ROWS with a nonzero quantized src0 view offset is not supported" }; 1097 } 1098if (op -> op == GGML_OP_GET_ROWS && ggml_openvino_get_device_name ()== "GPU" && 1099op -> src [0 ]-> type == GGML_TYPE_BF16 ) { 1100return {false,"GET_ROWS with BF16 src0 is not supported on GPU" }; 1101 } 1102if (op -> ne [0 ]== 256 && (op -> src [0 ]-> type == GGML_TYPE_Q4_K || op -> src [0 ]-> type == GGML_TYPE_Q5_K || 1103op -> src [0 ]-> type == GGML_TYPE_Q4_1 || op -> src [0 ]-> type == GGML_TYPE_Q5_1 )) { 1104// These are all f16-arithmetic dequant rounding errors that intermittently exceed the 1105// tight 1e-7 NMSE threshold depending on the random test data (see ggml-quants.cpp 1106// make_int8_weights/make_int4_weights: dequant is done in f16, not f32, to keep the 1107// Convert/Subtract/Multiply chain fusable into GatherMatmulCompressed/FullyConnectedCompressed 1108// for the shared non-test code paths). 1109return {false,"GET_ROWS/SET_ROWS with ne[0] == 256 and type " + std::string (ggml_type_name (op -> src [0 ]-> type ))+ 1110" rejected due to f16-arithmetic dequant rounding errors that intermittently exceed 1e-7 NMSE threshold" }; 1111 } 1112break ; 1113 } 1114case GGML_OP_RESHAPE : { 1115if (strncmp (op -> name ,"ffn_norm_exps" ,sizeof ("ffn_norm_exps" )- 1 )== 0 ) { 1116return {false,"RESHAPE for ffn_norm_exps is not supported" }; 1117 } 1118break ; 1119 } 1120case GGML_OP_ADD : 1121case GGML_OP_MUL : 1122case GGML_OP_SUB : { 1123if (op -> src [1 ]-> op == GGML_OP_PERMUTE ) { 1124return {false,"ADD/MUL/SUB with PERMUTE src1 is not supported" }; 1125 } 1126for (int i = 0 ;i < 4 ;i ++ ) { 1127if (op -> src [0 ]-> ne [i ]!= op -> src [1 ]-> ne [i ]&& (op -> src [0 ]-> ne [i ]!= 1 && op -> src [1 ]-> ne [i ]!= 1 )) { 1128return {false,"ADD/MUL/SUB with incompatible broadcast shapes: src0->ne[" + std::to_string (i )+ "]=" + 1129 std::to_string (op -> src [0 ]-> ne [i ])+ ", src1->ne[" + std::to_string (i )+ "]=" + 1130 std::to_string (op -> src [1 ]-> ne [i ])}; 1131 } 1132 } 1133break ; 1134 } 1135case GGML_OP_ADD_ID : { 1136// Keep support aligned with the CPU backend implementation, which only handles f32 inputs/output and i32 ids. 1137if (op -> type != GGML_TYPE_F32 || op -> src [0 ]-> type != GGML_TYPE_F32 || op -> src [1 ]-> type != GGML_TYPE_F32 || 1138op -> src [2 ]-> type != GGML_TYPE_I32 ) { 1139return {false,"ADD_ID only supports F32 inputs/output and I32 ids" }; 1140 } 1141break ; 1142 } 1143case GGML_OP_DIV : { 1144// The GPU plugin can fuse broadcast DIV into the preceding FFN GEMM path 1145// and produce infs for per-channel scale vectors. Keep those DIVs on CPU 1146// until the fused GPU kernel is reliable. (falied case llama-arch-test mpt) 1147if (ggml_openvino_get_device_name ()== "GPU" && op -> src [1 ]-> ne [0 ]== op -> ne [0 ]&& 1148op -> src [1 ]-> ne [1 ]== 1 && op -> src [1 ]-> ne [2 ]== 1 && op -> src [1 ]-> ne [3 ]== 1 ) { 1149return {false,"DIV per-channel scale broadcast is not supported on GPU" }; 1150 } 1151break ; 1152 } 1153case GGML_OP_POOL_2D : { 1154const auto & name = ggml_openvino_get_device_name (); 1155if (name == "GPU" ) { 1156const int32_t * params = op -> op_params ; 1157const int k0 = params [1 ]; 1158const int k1 = params [2 ]; 1159const int p0 = params [5 ]; 1160const int p1 = params [6 ]; 1161if ((p0 > 0 || p1 > 0 )&& (k0 < 3 || k1 < 3 )) { 1162return {false,"POOL_2D with padding and kernel size < 3 is not supported on " + name }; 1163 } 1164 } 1165break ; 1166 } 1167case GGML_OP_SUM_ROWS : { 1168if (op -> src [0 ]-> op == GGML_OP_PERMUTE ) { 1169return {false,"SUM_ROWS with PERMUTE input is not supported" }; 1170 } 1171break ; 1172 } 1173case GGML_OP_FLASH_ATTN_EXT : { 1174float scale = 1.0f ; 1175float max_bias = 0.0f ; 1176float logit_softcap = 0.0f ; 1177const auto * op_params = op -> op_params ; 1178memcpy (& scale , (const float * )op_params + 0 ,sizeof (float )); 1179memcpy (& max_bias , (const float * )op_params + 1 ,sizeof (float )); 1180memcpy (& logit_softcap , (const float * )op_params + 2 ,sizeof (float )); 1181 1182// Keep gemma3n flash-attn pattern on CPU for GPU runs to avoid 1183// accuracy drift in the OpenVINO path. Restrict by scale=1.0 to avoid 1184// affecting non-gemma3n models such as Llama-3.2. 1185if (fabsf (scale - 1.0f )< 1e-6f && is_gemma3n_flash_attn_pattern (op )) { 1186return {false,"FLASH_ATTN_EXT gemma3n pattern on GPU is not supported" }; 1187 } 1188 1189if (op -> src [4 ]!= nullptr ) { 1190return {false,"FLASH_ATTN_EXT with sinks is not supported" }; 1191 } 1192if (!is_supported_flash_attn_pattern (op )) { 1193return {false,"FLASH_ATTN_EXT unsupported attention pattern" }; 1194 } 1195if (max_bias > 0 ) { 1196return {false,"FLASH_ATTN_EXT with max_bias > 0 (max_bias=" + std::to_string (max_bias )+ ") is not supported" }; 1197 } 1198if (logit_softcap != 0 ) { 1199return {false,"FLASH_ATTN_EXT with logit_softcap != 0 (logit_softcap=" + std::to_string (logit_softcap )+ ") is not supported" }; 1200 } 1201break ; 1202 } 1203case GGML_OP_PERMUTE : { 1204if (op -> type == GGML_TYPE_BF16 && ggml_openvino_get_device_name ()== "GPU" ) { 1205return {false,"PERMUTE with BF16 type is not supported on GPU" }; 1206 } 1207break ; 1208 } 1209case GGML_OP_CPY : { 1210if (op -> src [0 ]-> type == GGML_TYPE_BF16 || op -> src [1 ]-> type == GGML_TYPE_BF16 ) { 1211return {false,"CPY with BF16 src type is not supported" }; 1212 } 1213// CPY to a quantized destination (e.g. f32 -> q4_0) is numerically unstable with OpenVINO backend. 1214if (ggml_is_quantized (op -> type )) { 1215return {false,"CPY to quantized destination (e.g. f32 -> q4_0) is numerically unstable" }; 1216 } 1217if (ggml_nelements (op -> src [0 ])!= ggml_nelements (op -> src [1 ])) { 1218return {false,"CPY with mismatched element counts is not supported: src0=" + std::to_string (ggml_nelements (op -> src [0 ]))+ 1219" != src1=" + std::to_string (ggml_nelements (op -> src [1 ]))}; 1220 } 1221// op test case with non-contiguous src or dst 1222if ((op -> ne [0 ]== 3 && op -> ne [1 ]== 4 && op -> ne [2 ]== 3 && op -> ne [3 ]== 2 )|| 1223 (op -> ne [0 ]== 1 && op -> ne [1 ]== 4 && op -> ne [2 ]== 3 && op -> ne [3 ]== 2 )|| 1224 (op -> ne [0 ]== 2 && op -> ne [1 ]== 4 && op -> ne [2 ]== 3 && op -> ne [3 ]== 2 )) { 1225return {false,"CPY with non-contiguous shape [" + std::to_string (op -> ne [0 ])+ ", " + 1226 std::to_string (op -> ne [1 ])+ ", " + std::to_string (op -> ne [2 ])+ ", " + 1227 std::to_string (op -> ne [3 ])+ "] is not supported" }; 1228 } 1229if (!cpy_output_view_is_supported (op )) { 1230return {false,"CPY with non-contiguous output view is not supported" }; 1231 } 1232break ; 1233 } 1234case GGML_OP_MUL_MAT : { 1235if (ggml_openvino_get_device_name ()== "GPU" && op -> src [0 ]!= nullptr && op -> src [1 ]!= nullptr && 1236ggml_is_quantized (op -> src [0 ]-> type )&& strcmp (op -> src [0 ]-> name ,"a" )== 0 && 1237strcmp (op -> src [1 ]-> name ,"b" )== 0 && op -> src [0 ]-> ne [1 ]== 1 && op -> src [1 ]-> ne [1 ]== 64 && 1238op -> src [0 ]-> ne [0 ]== 256 && op -> src [1 ]-> ne [0 ]== 256 ) { 1239return {false,"MUL_MAT quantized benchmark test case on GPU is not supported" }; 1240 } 1241if (op -> src [0 ]-> ne [3 ]!= op -> src [1 ]-> ne [3 ]&& op -> src [0 ]-> ne [3 ]!= 1 && op -> src [1 ]-> ne [3 ]!= 1 ) { 1242return {false,"MUL_MAT with incompatible broadcast on ne[3]: src0->ne[3]=" + std::to_string (op -> src [0 ]-> ne [3 ])+ 1243", src1->ne[3]=" + std::to_string (op -> src [1 ]-> ne [3 ])}; 1244 } 1245if (op -> src [0 ]-> op == GGML_OP_VIEW && op -> src [1 ]-> op == GGML_OP_VIEW ) { 1246return {false,"MUL_MAT with both inputs as VIEW is not supported" }; 1247 } 1248break ; 1249 } 1250case GGML_OP_MUL_MAT_ID : { 1251// Single-expert (or empty) MUL_MAT_ID is a degenerate shape that stresses GatherMatmul edge 1252// cases and never occurs in real MoE; let it fall back to CPU. 1253if (op -> src [0 ]!= nullptr && op -> src [0 ]-> ne [2 ] <=1 ) { 1254return {false,"MUL_MAT_ID with single-expert or empty ne[2] <= 1 (ne[2]=" + 1255 std::to_string (op -> src [0 ]-> ne [2 ])+ ") is not supported" }; 1256 } 1257if (ggml_openvino_get_device_name ()== "GPU" && op -> src [0 ]!= nullptr && op -> src [0 ]-> type == GGML_TYPE_BF16 ) { 1258return {false,"MUL_MAT_ID with BF16 weights on GPU is not supported" }; 1259 } 1260// GPU MUL_MAT_ID uses a Gather+MatMul fallback because the GPU plugin rejects internal 1261// GatherMatmul for these test shapes. Skip cases that would materialize a large selected 1262// expert-weight temporary. 1263if (ggml_openvino_get_device_name ()== "GPU" && mul_mat_id_requires_large_tmp (op )) { 1264return {false,"MUL_MAT_ID requires large temporary on GPU" }; 1265 } 1266break ; 1267 } 1268case GGML_OP_ROPE : { 1269const int32_t * op_params = op -> op_params ; 1270const int n_dims = op_params [1 ]; 1271const int mode = op_params [2 ]; 1272if (op_params [15 ]!= 0 ) { 1273// FIXME: support ggml_rope_set_offset 1274return {false,"ggml_rope_set_offset is not supported" }; 1275 } 1276if (mode != GGML_ROPE_TYPE_NORMAL && mode != GGML_ROPE_TYPE_NEOX && mode != GGML_ROPE_TYPE_IMROPE ) { 1277return {false,"ROPE with mode " + std::to_string (mode )+ " is not supported" }; 1278 } 1279const int64_t head_dim = op -> src [0 ]-> ne [0 ]; 1280const int64_t rope_dims = n_dims == 0 ?head_dim :n_dims ; 1281if (rope_dims <=0 || rope_dims > head_dim || (rope_dims %2 )!= 0 ) { 1282return {false,"ROPE with n_dims=" + std::to_string (n_dims )+ ", head_dim=" + std::to_string (head_dim )+ " is not supported" }; 1283 } 1284if (op -> type != GGML_TYPE_F32 && op -> type != GGML_TYPE_F16 ) { 1285return {false,"ROPE with type " + std::string (ggml_type_name (op -> type ))+ " is not supported" }; 1286 } 1287if (op -> src [0 ]-> op == GGML_OP_VIEW ) { 1288const struct ggml_tensor * view = op -> src [0 ]; 1289const struct ggml_tensor * view_src = view -> view_src ; 1290if (view_src -> ne [1 ]!= view -> ne [1 ]|| view_src -> ne [2 ]!= view -> ne [2 ]|| view_src -> ne [3 ]!= view -> ne [3 ]) { 1291return {false,"ROPE with view_src->ne [" + std::to_string (view_src -> ne [1 ])+ ", " + 1292 std::to_string (view_src -> ne [2 ])+ ", " + std::to_string (view_src -> ne [3 ])+ 1293"] != view->ne [" + std::to_string (view -> ne [1 ])+ ", " + 1294 std::to_string (view -> ne [2 ])+ ", " + std::to_string (view -> ne [3 ])+ 1295"] is not supported" }; 1296 } 1297 } 1298if (mode == GGML_ROPE_TYPE_IMROPE && 1299 (op -> src [2 ]!= 0 || ((const float * )op_params )[6 ]!= 1 || ((const float * )op_params )[7 ]!= 0 || 1300 ((const float * )op_params )[8 ]!= 1 )) { 1301return {false,"IMROPE with freq_factors, freq_scale, ext_factor, and attn_factor is not supported" }; 1302 } 1303break ; 1304 } 1305case GGML_OP_TRANSPOSE : { 1306if (op -> type == GGML_TYPE_BF16 ) { 1307return {false,"TRANSPOSE with BF16 type is not supported" }; 1308 } 1309break ; 1310 } 1311case GGML_OP_REPEAT : { 1312if (ggml_openvino_get_device_name ()== "GPU" && op -> type == GGML_TYPE_BF16 ) { 1313return {false,"REPEAT with BF16 type is not supported on GPU" }; 1314 } 1315break ; 1316 } 1317case GGML_OP_GATED_DELTA_NET : { 1318// enable after https://github.com/openvinotoolkit/openvino/pull/35917 is included in OV release 1319// return true; 1320// if (ggml_openvino_get_device_name() == "GPU" && op->src[0]->ne[2] > 1) { 1321// // CVS-186471 1322// return true; 1323// } 1324if (op -> src [2 ]-> op == GGML_OP_PERMUTE ) { 1325return {false,"GATED_DELTA_NET with PERMUTE src2 is not supported" }; 1326 } 1327// kda (per-key-dimension gating) not supported by fused GatedDeltaNet op 1328if (op -> src [3 ]-> ne [0 ]!= 1 ) { 1329return {false,"GATED_DELTA_NET with kda (per-key-dimension gating) is not supported" }; 1330 } 1331// K > 1 (multiple state snapshots) not supported by fused op 1332if (((const int32_t * )op -> op_params )[0 ]> 1 ) { 1333return {false,"GATED_DELTA_NET with K > 1 (multiple state snapshots) is not supported" }; 1334 } 1335break ; 1336 } 1337case GGML_OP_SSM_CONV : { 1338// qwen3next is numerically unstable with OpenVINO SSM_CONV. 1339// Keep this op on CPU until the OpenVINO implementation is fixed. 1340// return true; 1341break ; 1342 } 1343case GGML_OP_VIEW : { 1344// Skip TOPK_MOE fused tests until it is fully supported. 1345// The argsort_top_k VIEW wrapping ARGSORT is named "selected_experts" in test_topk_moe. 1346if (strcmp (op -> name ,"selected_experts" )== 0 ) { 1347return {false,"VIEW for selected_experts (argsort_top_k) is not supported" }; 1348 } 1349break ; 1350 } 1351default : 1352break ; 1353 } 1354return {true,"" }; 1355} 1356 1357static ggml_openvino_op_support ggml_backend_openvino_device_supports_op_impl (ggml_backend_dev_t dev ,const ggml_tensor * op ) { 1358GGML_ASSERT (dev -> reg != nullptr ); 1359 1360static std::unordered_set < ggml_type > supported_types { 1361GGML_TYPE_F32 ,GGML_TYPE_F16 ,GGML_TYPE_BF16 ,GGML_TYPE_I64 ,GGML_TYPE_I32 ,GGML_TYPE_Q4_0 , 1362GGML_TYPE_Q4_1 ,GGML_TYPE_Q4_K ,GGML_TYPE_Q5_1 ,GGML_TYPE_Q5_K ,GGML_TYPE_Q8_0 ,GGML_TYPE_Q6_K , 1363GGML_TYPE_MXFP4 }; 1364 1365// derive supported op sets from the op_table map, keys in 1366// the map use the full macro name (e.g. "GGML_OP_ADD"), while 1367// the ggml_*_op_name() helpers return only the trailing part (e.g. "ADD"). 1368// each set is built once and cached. 1369static const auto build_supported_sets = [] { 1370const auto & table = ov::frontend::ggml::get_supported_ops (); 1371 std::unordered_set < ggml_op > ops ; 1372 std::unordered_set < ggml_unary_op > unary_ops ; 1373 std::unordered_set < ggml_glu_op > glu_ops ; 1374 1375// GGML_OP_NONE has no translator but is always safe to add to the supported set. 1376ops .insert (GGML_OP_NONE ); 1377 1378for (int i = 0 ;i < GGML_OP_COUNT ;++ i ) { 1379const std::string key = std::string ("GGML_OP_" )+ ggml_op_name (static_cast < ggml_op > (i )); 1380if (table .count (key )) { 1381ops .insert (static_cast < ggml_op > (i )); 1382 } 1383 } 1384for (int i = 0 ;i < GGML_UNARY_OP_COUNT ;++ i ) { 1385const std::string key = std::string ("GGML_UNARY_OP_" )+ ggml_unary_op_name (static_cast < ggml_unary_op > (i )); 1386if (table .count (key )) { 1387unary_ops .insert (static_cast < ggml_unary_op > (i )); 1388 } 1389 } 1390for (int i = 0 ;i < GGML_GLU_OP_COUNT ;++ i ) { 1391const std::string key = std::string ("GGML_GLU_OP_" )+ ggml_glu_op_name (static_cast < ggml_glu_op > (i )); 1392if (table .count (key )) { 1393glu_ops .insert (static_cast < ggml_glu_op > (i )); 1394 } 1395 } 1396return std::make_tuple (ops ,unary_ops ,glu_ops ); 1397 }; 1398static const auto supported_sets = build_supported_sets (); 1399static const auto & supported_ops = std::get < 0 > (supported_sets ); 1400static const auto & supported_unary_ops = std::get < 1 > (supported_sets ); 1401static const auto & supported_glu_ops = std::get < 2 > (supported_sets ); 1402 1403switch (op -> op ) { 1404case GGML_OP_UNARY : { 1405auto supported = supported_unary_ops .find (ggml_get_unary_op (op ))!= supported_unary_ops .end (); 1406if (!supported ) { 1407return {false,"unary op " + std::string (ggml_unary_op_name (ggml_get_unary_op (op )))+ " has no op translator" }; 1408 } 1409if (ggml_get_unary_op (op )== GGML_UNARY_OP_EXP && op -> type == GGML_TYPE_F32 ) { 1410return {false,"UNARY_EXP with F32 type is not supported" }; 1411 } 1412break ; 1413 } 1414case GGML_OP_GLU : { 1415auto supported = supported_glu_ops .find (ggml_get_glu_op (op ))!= supported_glu_ops .end (); 1416if (!supported ) { 1417return {false,"GLU op " + std::string (ggml_glu_op_name (ggml_get_glu_op (op )))+ " has no op translator" }; 1418 } 1419// if (has_view_op_input(op)) { 1420// return {false, "GLU op " + std::string(ggml_glu_op_name(ggml_get_glu_op(op))) + " with view input is not supported"}; 1421// } 1422if (op -> src [1 ]== nullptr && op -> src [0 ]-> ne [0 ] %2 != 0 ) { 1423// triggers bug in ov gpu 1424return {false,"GLU op with odd src0 ne[0] and null src1 is not supported" }; 1425 } 1426break ; 1427 } 1428default : { 1429auto supported = supported_ops .find (op -> op )!= supported_ops .end (); 1430if (!supported ) { 1431return {false,"op " + std::string (ggml_op_name (op -> op ))+ " has no op translator" }; 1432 } 1433static std::set < ggml_op > ops_not_support_view_input {}; 1434if (ops_not_support_view_input .find (op -> op )!= ops_not_support_view_input .end ()&& has_view_op_input (op )) { 1435return {false,"op " + std::string (ggml_op_name (op -> op ))+ " with VIEW input is not supported" }; 1436 } 1437 } 1438 } 1439 1440if (supported_types .find (op -> type )== supported_types .end ()) { 1441return {false,"tensor type " + std::string (ggml_type_name (op -> type ))+ " is not supported" }; 1442 } 1443for (int i = 0 ;i < GGML_MAX_SRC ;i ++ ) { 1444auto * src = op -> src [i ]; 1445if (src == nullptr ) { 1446break ; 1447 } 1448if (supported_types .find (src -> type )== supported_types .end ()) { 1449return {false,"src[" + std::to_string (i )+ "] type " + std::string (ggml_type_name (src -> type ))+ " is not supported" }; 1450 } 1451const bool is_supported_3d_moe_expert = 1452op -> op == GGML_OP_MUL_MAT_ID && i == 0 && (src -> type == GGML_TYPE_MXFP4 || src -> ne [3 ]== 1 ); 1453if (ggml_is_quantized (src -> type )&& src -> ne [2 ]!= 1 && !is_supported_3d_moe_expert ) { 1454return {false,"3D quantized tensor for src[" + std::to_string (i )+ "] is not supported" }; 1455 } 1456 } 1457 1458auto op_support_case = is_op_supported_case (op ); 1459if (!op_support_case .is_supported ) { 1460return op_support_case ; 1461 } 1462return {true,"" }; 1463} 1464 1465static bool ggml_backend_openvino_device_supports_op (ggml_backend_dev_t dev ,const ggml_tensor * op ) { 1466auto res = ggml_backend_openvino_device_supports_op_impl (dev ,op ); 1467if (!res .is_supported ) { 1468static const bool log_unsupported = ggml_openvino_getenv_int ("GGML_OPENVINO_LOG_UNSUPPORTED_OPS" )!= 0 ; 1469if (log_unsupported ) { 1470GGML_LOG_WARN ("OpenVINO op unsupported: op '%s' (%s), type %s: %s\n" , 1471op -> name ,ggml_op_name (op -> op ),ggml_type_name (op -> type ),res .reason .c_str ()); 1472 } 1473 } 1474return res .is_supported ; 1475} 1476 1477static bool ggml_backend_openvino_device_supports_buft (ggml_backend_dev_t dev ,ggml_backend_buffer_type_t buft ) { 1478return ggml_backend_buft_is_openvino (buft )|| ggml_backend_buft_is_host (buft ); 1479GGML_UNUSED (dev ); 1480} 1481 1482static const struct ggml_backend_device_i ggml_backend_openvino_device_interface = { 1483/* .get_name = */ ggml_backend_openvino_device_get_name , 1484/* .get_description = */ ggml_backend_openvino_device_get_description , 1485/* .get_memory = */ ggml_backend_openvino_device_get_memory , 1486/* .get_type = */ ggml_backend_openvino_device_get_type , 1487/* .get_props = */ ggml_backend_openvino_device_get_props , 1488/* .init_backend = */ ggml_backend_openvino_device_init , 1489/* .get_buffer_type = */ ggml_backend_openvino_device_get_buffer_type , 1490/* .get_host_buffer_type = */ ggml_backend_openvino_device_get_host_buffer_type , 1491/* .buffer_from_host_ptr = */ NULL , 1492/* .supports_op = */ ggml_backend_openvino_device_supports_op , 1493/* .supports_buft = */ ggml_backend_openvino_device_supports_buft , 1494/* .offload_op = */ NULL , 1495/* .event_new = */ NULL , 1496/* .event_free = */ NULL , 1497/* .event_synchronize = */ NULL , 1498}; 1499 1500struct ggml_backend_openvino_reg_context { 1501 std::vector < ggml_backend_dev_t > devices ; 1502}; 1503 1504static const char * ggml_backend_openvino_reg_get_name (ggml_backend_reg_t reg ) { 1505return GGML_OPENVINO_NAME ; 1506GGML_UNUSED (reg ); 1507} 1508 1509static size_t ggml_backend_openvino_reg_get_device_count (ggml_backend_reg_t reg ) { 1510GGML_UNUSED (reg ); 1511return (size_t )ggml_backend_openvino_get_device_count (); 1512} 1513 1514static ggml_backend_dev_t ggml_backend_openvino_reg_get_device (ggml_backend_reg_t reg ,size_t index ) { 1515ggml_backend_openvino_reg_context * ctx = (ggml_backend_openvino_reg_context * )reg -> context ; 1516GGML_ASSERT (index < ctx -> devices .size ()); 1517return ctx -> devices [index ]; 1518} 1519 1520static const struct ggml_backend_reg_i ggml_backend_openvino_reg_interface = { 1521/* .get_name = */ ggml_backend_openvino_reg_get_name , 1522/* .get_device_count = */ ggml_backend_openvino_reg_get_device_count , 1523/* .get_device = */ ggml_backend_openvino_reg_get_device , 1524/* .get_proc_address = */ NULL , 1525}; 1526 1527static void ggml_openvino_init () { 1528// Initialize device config singleton from env var 1529ggml_openvino_init_device_config (); 1530GGML_LOG_INFO ("OpenVINO: using device %s\n" ,ggml_openvino_get_device_name ().c_str ()); 1531} 1532 1533GGML_BACKEND_API ggml_backend_reg_tggml_backend_openvino_reg (void ) { 1534static ggml_backend_reg reg ; 1535 1536static bool initialized = false; 1537 { 1538static std::mutex mutex ; 1539 std::lock_guard < std::mutex > lock (mutex ); 1540if (!initialized ) { 1541ggml_openvino_init (); 1542 1543ggml_backend_openvino_reg_context * ctx = new ggml_backend_openvino_reg_context ; 1544 1545for (int i = 0 ;i < ggml_backend_openvino_get_device_count ();i ++ ) { 1546ggml_backend_openvino_device_context * dev_ctx = new ggml_backend_openvino_device_context ; 1547dev_ctx -> device = i ; 1548dev_ctx -> name = GGML_OPENVINO_NAME + std::to_string (i ); 1549 1550dev_ctx -> description = ov::get_openvino_version ().description ; 1551 1552ggml_backend_dev_t dev = 1553new ggml_backend_device {/* .interface = */ ggml_backend_openvino_device_interface , 1554/* .reg = */ & reg , 1555/* .context = */ dev_ctx }; 1556ctx -> devices .push_back (dev ); 1557 } 1558 1559reg = ggml_backend_reg {/* .api_version = */ GGML_BACKEND_API_VERSION , 1560/* .iface = */ ggml_backend_openvino_reg_interface , 1561/* .context = */ ctx }; 1562 } 1563 1564initialized = true; 1565 } 1566 1567return & reg ; 1568} 1569 1570GGML_BACKEND_DL_IMPL (ggml_backend_openvino_reg )