#include "ggml-openvino-extra.h"
#include "ggml-impl.h"
#include "ggml.h"
#include "model-cache.h"
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <openvino/runtime/intel_gpu/ocl/ocl.hpp>
#include <openvino/runtime/intel_npu/level_zero/level_zero.hpp>
#include <openvino/runtime/properties.hpp>
#include <mutex>
#include <optional>
ov::Core & ov_singleton_core() {
static ov::Core core;
return core;
}
static bool has_prefix(const std::string & s, const std::string & prefix) {
return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin());
}
static bool is_virtual_routing_device(const std::string & device_name) {
return has_prefix(device_name, "AUTO") || has_prefix(device_name, "MULTI") || has_prefix(device_name, "HETERO");
}
static std::vector<std::string> ov_enumerate_devices() {
std::vector<std::string> result;
for (const auto & device : ov_singleton_core().get_available_devices()) {
if (!is_virtual_routing_device(device)) {
result.push_back(device);
}
}
if (result.empty()) {
result.push_back("CPU");
}
std::sort(result.begin(), result.end());
result.erase(std::unique(result.begin(), result.end()), result.end());
return result;
}
std::string ggml_openvino_get_device_description(const std::string & device_name) {
std::string description = device_name;
try {
description = ov_singleton_core().get_property(device_name, ov::device::full_name);
} catch (...) {
return device_name;
}
if (has_prefix(device_name, "NPU")) {
try {
const std::string arch = ov_singleton_core().get_property(device_name, "DEVICE_ARCHITECTURE").as<std::string>();
if (!arch.empty()) {
description += " (NPU " + arch + ")";
}
} catch (...) {
}
}
return description;
}
static std::string resolve_openvino_device_name(const std::vector<std::string> & available_devices,
const char * requested) {
auto available = [&](const std::string & name) {
return std::find(available_devices.begin(), available_devices.end(), name) != available_devices.end();
};
if (requested == nullptr) {
return available("CPU") ? "CPU" : available_devices.front();
}
if (!available(requested)) {
std::string list;
for (const std::string & name : available_devices) {
list += "\n " + name + ": " + ggml_openvino_get_device_description(name);
}
GGML_ABORT("GGML OpenVINO Backend: GGML_OPENVINO_DEVICE=%s is not available. "
"Set it to one of the available OpenVINO devices:%s",
requested, list.c_str());
}
return requested;
}
void ggml_openvino_device_config::init() {
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
if (initialized) {
return;
}
initialized = true;
static constexpr const char * env_var_names[] = {
"GGML_OPENVINO_DEVICE",
"GGML_OPENVINO_CACHE_DIR",
"GGML_OPENVINO_SPILL_DIR",
"GGML_OPENVINO_DEBUG_NODE",
"GGML_OPENVINO_COMPILED_MODEL_CACHE_DIR",
"GGML_OPENVINO_COMPILED_MODEL_CACHE_ONLY",
"GGML_OPENVINO_NPU_COMPILE_CONFIG",
"GGML_OPENVINO_PREFILL_CHUNK_SIZE",
"GGML_OPENVINO_STATEFUL_EXECUTION",
"GGML_OPENVINO_PROFILING",
"GGML_OPENVINO_DUMP_CGRAPH",
"GGML_OPENVINO_DUMP_IR",
"GGML_OPENVINO_DEBUG_INPUT",
"GGML_OPENVINO_DEBUG_OUTPUT",
"GGML_OPENVINO_FORCE_STATIC",
"GGML_OPENVINO_PRINT_CGRAPH_TENSOR_ADDRESS",
"GGML_OPENVINO_ENABLE_CACHE",
"GGML_OPENVINO_DISABLE_CACHE",
"GGML_OPENVINO_DISABLE_KV_SLICE",
"GGML_OPENVINO_ENABLE_FALLBACK",
"GGML_OPENVINO_MANUAL_GQA_ATTN",
"GGML_OPENVINO_DISABLE_ELTWISE_RANK_ALIGN",
"GGML_OPENVINO_MOE_OP",
"GGML_OPENVINO_MEMORY_OPTIMIZE",
"GGML_OPENVINO_RELEASE_WEIGHTS",
"GGML_OPENVINO_REDUCE_COMPILE_MEM",
"GGML_OPENVINO_LOG_UNSUPPORTED_OPS",
"GGML_OPENVINO_LOG_SWA_LAYERS",
"GGML_OPENVINO_NATIVE_SOFTPLUS",
"GGML_OPENVINO_DISABLE_REMOTE_OUTPUTS",
"GGML_OPENVINO_REQUANT_KQUANT",
"GGML_OPENVINO_DISABLE_KV_STATE_RELAYOUT",
"GGML_OPENVINO_FULL_GRAPH_KEY",
};
for (const char * const & env_var : env_var_names) {
auto * env = getenv(env_var);
if (env) {
environment_variables[env_var] = env;
}
}
available_devices = ov_enumerate_devices();
device_name = resolve_openvino_device_name(available_devices, ggml_openvino_getenv_str("GGML_OPENVINO_DEVICE"));
is_npu = has_prefix(device_name, "NPU");
ggml_openvino_model_cache_init();
const char * cache_dir = ggml_openvino_getenv_str("GGML_OPENVINO_CACHE_DIR");
if (has_prefix(device_name, "NPU")) {
compile_config = {
{"NPU_COMPILER_DYNAMIC_QUANTIZATION", "YES" },
{"NPU_USE_NPUW", "YES" },
{"NPUW_DEVICES", "NPU" },
{"NPUW_FOLD", "YES" },
{"NPUW_WEIGHTS_BANK", "shared"},
{"NPUW_FUNCALL_FOR_ALL", "YES" },
{"NPUW_FUNCALL_ASYNC", "YES" },
{"NPUW_DQ", "YES" },
{"NPUW_DQ_FULL", "NO" },
};
if (cache_dir && strlen(cache_dir) > 0) {
compile_config["NPUW_CACHE_DIR"] = cache_dir;
compile_config.insert(ov::cache_mode(ov::CacheMode::OPTIMIZE_SIZE));
}
const char * compilation_mode_params =
ggml_openvino_getenv_str("GGML_OPENVINO_NPU_COMPILE_CONFIG");
if (compilation_mode_params && strlen(compilation_mode_params) > 0) {
compile_config["NPU_COMPILATION_MODE_PARAMS"] = compilation_mode_params;
}
} else if (cache_dir && strlen(cache_dir) > 0) {
compile_config.insert(ov::cache_dir(cache_dir));
compile_config.insert(ov::cache_mode(ov::CacheMode::OPTIMIZE_SIZE));
}
if (ggml_openvino_getenv_int("GGML_OPENVINO_PROFILING") >= 2) {
compile_config.insert(ov::enable_profiling(true));
}
if (has_prefix(device_name, "GPU")) {
cl_context cl_ctx;
try {
auto ov_ctx = ov_singleton_core().get_default_context(device_name).as<ov::intel_gpu::ocl::ClContext>();
cl_ctx = ov_ctx.get();
} catch (const std::exception & e) {
GGML_ABORT("ggml-openvino: failed to get the OpenCL context for %s: %s", device_name.c_str(), e.what());
}
cl_int err;
cl_device_id cl_device;
err = clGetContextInfo(cl_ctx, CL_CONTEXT_DEVICES, sizeof(cl_device), &cl_device, nullptr);
if (err != CL_SUCCESS) {
GGML_ABORT("ggml-openvino: failed to get the OpenCL device for %s: %d", device_name.c_str(), err);
}
cl_platform_id cl_platform;
err = clGetDeviceInfo(cl_device, CL_DEVICE_PLATFORM, sizeof(cl_platform), &cl_platform, nullptr);
if (err != CL_SUCCESS) {
GGML_ABORT("ggml-openvino: failed to get the OpenCL platform for %s: %d", device_name.c_str(), err);
}
cl_mem_fill_fn =
(clEnqueueMemFillINTEL_fn) clGetExtensionFunctionAddressForPlatform(cl_platform, "clEnqueueMemFillINTEL");
cl_mem_cpy_fn =
(clEnqueueMemcpyINTEL_fn) clGetExtensionFunctionAddressForPlatform(cl_platform, "clEnqueueMemcpyINTEL");
cl_ulong device_max_alloc = 0;
err = clGetDeviceInfo(cl_device, CL_DEVICE_MAX_MEM_ALLOC_SIZE, sizeof(device_max_alloc), &device_max_alloc,
nullptr);
if (err == CL_SUCCESS) {
max_alloc_size = device_max_alloc;
} else {
GGML_LOG_WARN("Failed to get OpenCL max allocation size: %d\n", err);
}
const cl_queue_properties profiling_properties[] = {
CL_QUEUE_PROPERTIES,
CL_QUEUE_PROFILING_ENABLE,
0,
};
const cl_queue_properties * queue_properties =
ggml_openvino_getenv_int("GGML_OPENVINO_PROFILING") >= 2 ? profiling_properties : nullptr;
cl_queue = clCreateCommandQueueWithProperties(cl_ctx, cl_device, queue_properties, &err);
if (err != CL_SUCCESS) {
GGML_ABORT("ggml-openvino: failed to create the OpenCL queue for %s: %d", device_name.c_str(), err);
}
remote_context = ov::intel_gpu::ocl::ClContext(ov_singleton_core(), cl_queue);
} else if (has_prefix(device_name, "NPU")) {
}
}
ggml_openvino_device_config::~ggml_openvino_device_config() {
if (cl_queue != nullptr) {
clReleaseCommandQueue(cl_queue);
cl_queue = nullptr;
}
}
ggml_openvino_device_config & ggml_openvino_get_device_config() {
static ggml_openvino_device_config config;
return config;
}
void ggml_openvino_init_device_config() {
ggml_openvino_get_device_config().init();
}
const std::string & ggml_openvino_get_device_name() {
return ggml_openvino_get_device_config().device_name;
}
std::vector<std::string> ggml_openvino_get_available_devices() {
auto & config = ggml_openvino_get_device_config();
config.init();
return config.available_devices;
}
const char * ggml_openvino_getenv_str(const char * var, const char * default_value) {
auto & env_map = ggml_openvino_get_device_config().environment_variables;
auto it = env_map.find(var);
return (it == env_map.end() || it->second.empty()) ? default_value : it->second.c_str();
}
int ggml_openvino_getenv_int(const char * var, int default_value) {
const char * v = ggml_openvino_getenv_str(var, nullptr);
return v ? std::atoi(v) : default_value;
}
bool ggml_openvino_reduce_compile_mem_enabled() {
const char * reduce_compile_mem = ggml_openvino_getenv_str("GGML_OPENVINO_REDUCE_COMPILE_MEM");
if (reduce_compile_mem != nullptr) {
return ggml_openvino_getenv_int("GGML_OPENVINO_REDUCE_COMPILE_MEM") != 0;
}
return ggml_openvino_getenv_int("GGML_OPENVINO_MEMORY_OPTIMIZE") != 0;
}
bool ggml_openvino_release_weights_enabled() {
const char * release_weights = ggml_openvino_getenv_str("GGML_OPENVINO_RELEASE_WEIGHTS");
if (release_weights != nullptr) {
return ggml_openvino_is_gpu() && ggml_openvino_getenv_int("GGML_OPENVINO_RELEASE_WEIGHTS") != 0;
}
return ggml_openvino_is_gpu() && ggml_openvino_getenv_int("GGML_OPENVINO_MEMORY_OPTIMIZE") != 0;
}
bool ggml_openvino_is_npu() {
return ggml_openvino_get_device_config().is_npu;
}
bool ggml_openvino_is_gpu() {
return has_prefix(ggml_openvino_get_device_name(), "GPU");
}
size_t ggml_openvino_max_alloc_size() {
return ggml_openvino_get_device_config().max_alloc_size;
}
std::optional<ov::RemoteContext> ggml_openvino_get_remote_context() {
return ggml_openvino_get_device_config().remote_context;
}
const ov::AnyMap & ggml_openvino_get_compile_config() {
return ggml_openvino_get_device_config().compile_config;
}
cl_command_queue ggml_openvino_get_cl_queue() {
return ggml_openvino_get_device_config().cl_queue;
}
clEnqueueMemFillINTEL_fn ggml_openvino_get_clEnqueueMemFillINTEL() {
return ggml_openvino_get_device_config().cl_mem_fill_fn;
}
clEnqueueMemcpyINTEL_fn ggml_openvino_get_clEnqueueMemcpyINTEL() {
return ggml_openvino_get_device_config().cl_mem_cpy_fn;
}
std::optional<ExtraQuantType> ggml_openvino_get_requant_type(const ggml_tensor * tensor, bool no_requant) {
if (no_requant) {
return std::nullopt;
}
if (strncmp(tensor->name, "token_embd.weight", 17) == 0) {
return ((ggml_openvino_is_npu() && tensor->type == GGML_TYPE_Q6_K) ? ExtraQuantType::F16 :
ExtraQuantType::Q8_0_C);
}
if (strncmp(tensor->name, "output.weight", 13) == 0) {
return ExtraQuantType::Q8_0_C;
}
if (ggml_openvino_is_npu()) {
return ExtraQuantType::Q4_0_128;
}
const char * rq = ggml_openvino_getenv_str("GGML_OPENVINO_REQUANT_KQUANT");
auto is_opt = [rq](const char * name) {
return rq && strcmp(rq, name) == 0;
};
const bool sym128 = is_opt("q4_sym128");
const bool sym128_all = is_opt("q4_sym128_all");
const bool asym64_all = is_opt("q4_asym64_all");
const bool asym64 = is_opt("q4_asym64");
if (tensor->type == GGML_TYPE_Q4_K) {
if (sym128_all) {
return ExtraQuantType::Q4_0_128;
}
if (asym64_all) {
return ExtraQuantType::Q4_1_64;
}
}
if (tensor->ne[2] > 1 && (tensor->type == GGML_TYPE_Q5_1 || tensor->type == GGML_TYPE_Q8_0)) {
if (sym128 || sym128_all) {
return ExtraQuantType::Q4_0_64;
}
if (asym64 || asym64_all) {
return ExtraQuantType::Q4_1_64;
}
if (ggml_openvino_is_gpu() && !is_opt("native")) {
return ExtraQuantType::Q4_0_64;
}
}
switch (tensor->type) {
case GGML_TYPE_Q6_K:
case GGML_TYPE_Q5_K:
if (sym128 || sym128_all) {
return ExtraQuantType::Q4_0_128;
}
if (asym64 || asym64_all) {
return ExtraQuantType::Q4_1_64;
}
if (is_opt("native")) {
return std::nullopt;
}
return ExtraQuantType::Q8_0_C;
default:
return std::nullopt;
}
}
ggml_openvino_extracted_layout ggml_openvino_get_extracted_layout(const ggml_tensor * tensor, bool use_bias) {
ggml_openvino_extracted_layout layout = {};
layout.is_symmetric = false;
if (!ggml_is_quantized(tensor->type)) {
return layout;
}
if (tensor->ne[3] != 1) {
return layout;
}
const bool for_gather_matmul = tensor->ne[2] > 1;
int64_t n_elements = ggml_nelements(tensor);
const size_t alignment = 64;
if (tensor->type == GGML_TYPE_MXFP4 && (tensor->ne[2] > 1 || tensor->ne[3] > 1)) {
layout.weights_per_block = 32;
layout.is_symmetric = true;
layout.weights_size = ggml_nbytes(tensor);
layout.weights_offset = 0;
layout.total_size = layout.weights_size;
return layout;
}
auto requant_type = ggml_openvino_get_requant_type(tensor, use_bias);
if (requant_type.has_value()) {
layout.is_requant = true;
layout.requant_type = requant_type;
if (requant_type.value() == ExtraQuantType::F16) {
layout.weights_size = n_elements * sizeof(uint16_t); layout.total_size = layout.weights_size;
layout.weights_offset = 0;
return layout;
}
switch (requant_type.value()) {
case ExtraQuantType::Q4_0_128:
layout.is_u4 = true;
layout.weights_per_block = 128;
layout.is_symmetric = true;
break;
case ExtraQuantType::Q4_1_64:
layout.is_u4 = true;
layout.weights_per_block = 64;
layout.is_symmetric = false;
break;
case ExtraQuantType::Q4_0_64:
layout.is_u4 = true;
layout.weights_per_block = 64;
layout.is_symmetric = true;
break;
case ExtraQuantType::Q4_0_C:
layout.is_u4 = true;
layout.weights_per_block = tensor->ne[0];
layout.is_symmetric = true;
break;
case ExtraQuantType::Q8_0_32:
layout.is_u4 = false;
layout.weights_per_block = 32;
layout.is_symmetric = true;
break;
case ExtraQuantType::Q8_0_C:
layout.is_u4 = false;
layout.weights_per_block = tensor->ne[0];
layout.is_symmetric = true;
break;
case ExtraQuantType::Q8_1_C:
layout.is_u4 = false;
layout.weights_per_block = tensor->ne[0];
break;
default:
GGML_ABORT("Code of re-quantizing to channel-wise is not updated");
}
if (layout.is_requant) {
layout.weights_size = layout.is_u4 ? (n_elements / 2) : n_elements;
int64_t n_blocks = n_elements / layout.weights_per_block;
layout.scales_size = n_blocks * sizeof(uint16_t);
if (layout.is_symmetric) {
layout.zp_size = 0;
} else {
layout.zp_size = layout.is_u4 ? ((n_blocks + 1) / 2) : n_blocks;
}
layout.weights_offset = 0;
layout.scales_offset = ((layout.weights_size + alignment - 1) / alignment) * alignment;
layout.zp_offset = layout.scales_offset + ((layout.scales_size + alignment - 1) / alignment) * alignment;
layout.total_size = layout.zp_offset + layout.zp_size;
layout.total_size = std::max(layout.total_size, ggml_nbytes(tensor));
return layout;
}
}
layout.is_u4 = false;
layout.weights_per_block = 32;
layout.is_symmetric = false;
switch (tensor->type) {
case GGML_TYPE_MXFP4:
case GGML_TYPE_Q4_0:
layout.is_u4 = true;
layout.is_symmetric = true;
break;
case GGML_TYPE_Q4_1:
case GGML_TYPE_Q4_K:
layout.is_u4 = true;
break;
case GGML_TYPE_Q8_0:
layout.is_symmetric = true;
break;
case GGML_TYPE_Q5_1:
break;
case GGML_TYPE_Q6_K:
layout.weights_per_block = 16;
layout.is_symmetric = true;
break;
case GGML_TYPE_Q5_K:
break;
default:
return layout;
}
layout.weights_size = layout.is_u4 ? (n_elements / 2) : n_elements;
int64_t n_blocks = n_elements / layout.weights_per_block;
layout.scales_size = n_blocks * (tensor->type == GGML_TYPE_MXFP4 ? sizeof(uint8_t) : sizeof(uint16_t));
if (layout.is_symmetric) {
layout.zp_size = 0;
} else if (use_bias || for_gather_matmul) {
layout.zp_size = n_blocks * sizeof(uint16_t);
} else {
layout.zp_size = layout.is_u4 ? ((n_blocks + 1) / 2) : n_blocks;
}
layout.weights_offset = 0;
layout.scales_offset = ((layout.weights_size + alignment - 1) / alignment) * alignment;
layout.zp_offset = layout.scales_offset + ((layout.scales_size + alignment - 1) / alignment) * alignment;
layout.total_size = layout.zp_offset + layout.zp_size;
layout.total_size = std::max(layout.total_size, ggml_nbytes(tensor));
return layout;
}
ggml_openvino_tensor_extra * ggml_openvino_create_tensor_extra(const ggml_tensor * tensor, bool is_remote) {
ov::Shape shape;
for (int i = GGML_MAX_DIMS - 1; i >= 0; --i) {
shape.push_back(static_cast<size_t>(tensor->ne[i]));
}
ov::element::Type element_type;
switch (tensor->type) {
case GGML_TYPE_F32:
element_type = ov::element::f32;
break;
case GGML_TYPE_F16:
element_type = ov::element::f16;
break;
case GGML_TYPE_BF16:
element_type = ov::element::bf16;
break;
case GGML_TYPE_I32:
element_type = ov::element::i32;
break;
case GGML_TYPE_I64:
element_type = ov::element::i64;
break;
default:
return nullptr;
}
auto remote_context = ggml_openvino_get_remote_context();
std::shared_ptr<ov::Tensor> ov_tensor;
if (is_remote) {
GGML_ASSERT(ggml_openvino_is_gpu());
auto gpu_context = remote_context->as<ov::intel_gpu::ocl::ClContext>();
auto usm_tensor = gpu_context.create_tensor(element_type, shape, tensor->data);
ov_tensor = std::make_shared<ov::intel_gpu::ocl::USMTensor>(std::move(usm_tensor));
} else {
ov_tensor = std::make_shared<ov::Tensor>(element_type, shape, tensor->data);
}
return new ggml_openvino_tensor_extra(ov_tensor);
}