#include "model-cache.h"
#include "ggml-backend-impl.h"
#include "ggml-backend.h"
#include "ggml-impl.h"
#include "ggml-openvino-extra.h"
#include <cerrno>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <limits>
#include <sstream>
#include <openvino/core/version.hpp>
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <vector>
#if defined(_WIN32)
# define WIN32_LEAN_AND_MEAN
# ifndef NOMINMAX
# define NOMINMAX
# endif
# include <windows.h>
# include <psapi.h>
# include <direct.h>
# include <process.h>
#else
# include <unistd.h>
#endif
#ifdef __linux__
# include <sys/sysmacros.h>
#endif
namespace {
inline uint64_t fnv1a(uint64_t h, const void * data, size_t n) {
const uint8_t * p = static_cast<const uint8_t *>(data);
for (size_t i = 0; i < n; ++i) {
h ^= p[i];
h *= 0x100000001b3ull;
}
return h;
}
inline uint64_t fnv1a_u64(uint64_t h, uint64_t v) {
return fnv1a(h, &v, sizeof(v));
}
constexpr uint64_t FNV_OFFSET = 0xcbf29ce484222325ull;
constexpr size_t WEIGHT_SAMPLE_BYTES = 4096;
bool is_weight_src(const ggml_tensor * src) {
if (src == nullptr || src->view_src != nullptr || src->buffer == nullptr) {
return false;
}
return src->buffer->usage == GGML_BACKEND_BUFFER_USAGE_WEIGHTS || ggml_is_quantized(src->type);
}
uint64_t weight_fingerprint(const ggml_tensor * t) {
uint64_t h = FNV_OFFSET;
h = fnv1a(h, t->name, strlen(t->name));
for (int i = 0; i < GGML_MAX_DIMS; ++i) {
h = fnv1a_u64(h, static_cast<uint64_t>(t->ne[i]));
}
h = fnv1a_u64(h, static_cast<uint64_t>(t->type));
const size_t nbytes = ggml_nbytes(t);
h = fnv1a_u64(h, nbytes);
return fnv1a_u64(h, ggml_backend_openvino_weight_fingerprint(t));
}
template <typename F>
void for_each_weight(const ggml_cgraph * cgraph, F && fn) {
std::vector<const ggml_tensor *> seen;
for (int i = 0; i < cgraph->n_nodes; ++i) {
const ggml_tensor * node = cgraph->nodes[i];
for (int s = 0; s < GGML_MAX_SRC; ++s) {
const ggml_tensor * src = node->src[s];
if (!is_weight_src(src)) {
continue;
}
bool dup = false;
for (const auto * p : seen) {
if (p == src) {
dup = true;
break;
}
}
if (dup) {
continue;
}
seen.push_back(src);
fn(src);
}
}
}
std::string ov_version_string() {
const ov::Version v = ov::get_openvino_version();
return std::string(v.buildNumber ? v.buildNumber : "unknown");
}
std::string hex64(uint64_t v) {
char buf[17];
snprintf(buf, sizeof(buf), "%016llx", static_cast<unsigned long long>(v));
return std::string(buf);
}
bool make_dir(const std::string & path) {
#if defined(_WIN32)
int rc = _mkdir(path.c_str());
#else
int rc = ::mkdir(path.c_str(), 0755);
#endif
if (rc == 0 || errno == EEXIST) {
return true;
}
return false;
}
bool make_dirs(const std::string & path) {
if (path.empty()) {
return false;
}
std::string acc;
for (size_t i = 0; i < path.size(); ++i) {
const char c = path[i];
acc.push_back(c);
const bool sep = (c == '/'
#if defined(_WIN32)
|| c == '\\'
#endif
);
if (sep && acc.size() > 1) {
std::string component = acc.substr(0, acc.size() - 1);
if (!make_dir(component)) {
return false;
}
}
}
return make_dir(path);
}
}
bool ggml_openvino_model_cache_only() {
return ggml_openvino_getenv_int("GGML_OPENVINO_COMPILED_MODEL_CACHE_ONLY") != 0;
}
static const char * cache_settings[] = {
"GGML_OPENVINO_REQUANT_KQUANT",
"GGML_OPENVINO_NATIVE_SOFTPLUS",
"GGML_OPENVINO_DISABLE_KV_SLICE",
"GGML_OPENVINO_MANUAL_GQA_ATTN",
"GGML_OPENVINO_STATEFUL_EXECUTION",
"GGML_OPENVINO_DISABLE_KV_STATE_RELAYOUT",
"GGML_OPENVINO_DISABLE_REMOTE_OUTPUTS",
"GGML_OPENVINO_REDUCE_COMPILE_MEM",
"GGML_OPENVINO_MEMORY_OPTIMIZE",
"GGML_OPENVINO_PROFILING",
};
void ggml_openvino_model_cache_init() {
const bool cache_only = ggml_openvino_model_cache_only();
const std::string dir = ggml_openvino_model_cache_dir();
if (dir.empty()) {
if (cache_only) {
GGML_ABORT("ggml-openvino: cache-only mode requires GGML_OPENVINO_COMPILED_MODEL_CACHE_DIR");
}
return;
}
if (cache_only && (ggml_openvino_is_npu() || ggml_openvino_getenv_int("GGML_OPENVINO_FORCE_STATIC") ||
ggml_openvino_getenv_int("GGML_OPENVINO_DISABLE_CACHE") ||
ggml_openvino_getenv_int("GGML_OPENVINO_ENABLE_FALLBACK"))) {
GGML_ABORT("ggml-openvino: cache-only mode requires dynamic CPU/GPU execution with caching and without fallback");
}
#if !defined(__linux__) && !defined(_WIN32)
if (cache_only) {
GGML_ABORT("ggml-openvino: cache-only mmap identification requires Linux or Windows");
}
#endif
if (cache_only) {
auto & config = ggml_openvino_get_device_config();
config.environment_variables.erase("GGML_OPENVINO_SPILL_DIR");
config.environment_variables["GGML_OPENVINO_RELEASE_WEIGHTS"] = "0";
}
}
uint64_t ggml_openvino_source_fingerprint(const void * data, size_t size, std::vector<ggml_openvino_source_mapping> & mappings) {
const uintptr_t address = reinterpret_cast<uintptr_t>(data);
auto contains = [&](const ggml_openvino_source_mapping & m) {
return address >= m.begin && address < m.end && size <= m.end - address;
};
auto fingerprint = [&](const ggml_openvino_source_mapping & m) {
return fnv1a_u64(m.identity, m.offset + address - m.begin);
};
for (const auto & m : mappings) {
if (contains(m)) {
return fingerprint(m);
}
}
#ifdef __linux__
std::ifstream maps("/proc/self/maps");
std::string line;
while (std::getline(maps, line)) {
unsigned long long begin, end, offset, inode;
unsigned int dev_major, dev_minor;
char permissions[5];
int path_start = 0;
if (sscanf(line.c_str(), "%llx-%llx %4s %llx %x:%x %llu %n", &begin, &end, permissions,
&offset, &dev_major, &dev_minor, &inode, &path_start) != 7 || inode == 0) {
continue;
}
ggml_openvino_source_mapping m{uintptr_t(begin), uintptr_t(end), offset, FNV_OFFSET};
if (!contains(m)) {
continue;
}
struct stat st;
const std::string path = line.substr(path_start);
if (stat(path.c_str(), &st) != 0 || !S_ISREG(st.st_mode) || uint64_t(st.st_ino) != inode ||
major(st.st_dev) != dev_major || minor(st.st_dev) != dev_minor) {
break;
}
m.identity = fnv1a_u64(m.identity, st.st_dev);
m.identity = fnv1a_u64(m.identity, st.st_ino);
m.identity = fnv1a_u64(m.identity, st.st_size);
m.identity = fnv1a_u64(m.identity, st.st_mtim.tv_sec);
m.identity = fnv1a_u64(m.identity, st.st_mtim.tv_nsec);
m.identity = fnv1a_u64(m.identity, st.st_ctim.tv_sec);
m.identity = fnv1a_u64(m.identity, st.st_ctim.tv_nsec);
mappings.push_back(m);
return fingerprint(m);
}
#elif defined(_WIN32)
MEMORY_BASIC_INFORMATION memory;
if (VirtualQuery(data, &memory, sizeof(memory)) == sizeof(memory) && memory.Type == MEM_MAPPED) {
std::wstring name(MAX_PATH, L'\0');
DWORD length = 0;
while (name.size() <= 32768) {
length = GetMappedFileNameW(GetCurrentProcess(), const_cast<void *>(data), name.data(), static_cast<DWORD>(name.size()));
if (length == 0 || length < name.size() - 1) {
break;
}
name.resize(name.size() * 2);
}
if (length > 0 && length < name.size() - 1) {
name.resize(length);
const std::wstring path = L"\\\\?\\GLOBALROOT" + name;
HANDLE file = CreateFileW(path.c_str(), FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (file != INVALID_HANDLE_VALUE) {
BY_HANDLE_FILE_INFORMATION info;
FILE_BASIC_INFO basic;
const bool valid = GetFileInformationByHandle(file, &info) &&
GetFileInformationByHandleEx(file, FileBasicInfo, &basic, sizeof(basic));
CloseHandle(file);
if (valid) {
const uint64_t file_size = (uint64_t(info.nFileSizeHigh) << 32) | info.nFileSizeLow;
const uintptr_t begin = reinterpret_cast<uintptr_t>(memory.AllocationBase);
if (file_size <= std::numeric_limits<uintptr_t>::max() - begin) {
ggml_openvino_source_mapping m{begin, begin + static_cast<uintptr_t>(file_size), 0,
fnv1a(FNV_OFFSET, "win32", 5)};
if (contains(m)) {
m.identity = fnv1a_u64(m.identity, info.dwVolumeSerialNumber);
m.identity = fnv1a_u64(m.identity, (uint64_t(info.nFileIndexHigh) << 32) | info.nFileIndexLow);
m.identity = fnv1a_u64(m.identity, file_size);
m.identity = fnv1a_u64(m.identity, (uint64_t(info.ftLastWriteTime.dwHighDateTime) << 32) |
info.ftLastWriteTime.dwLowDateTime);
m.identity = fnv1a_u64(m.identity, static_cast<uint64_t>(basic.ChangeTime.QuadPart));
mappings.push_back(m);
return fingerprint(m);
}
}
}
}
}
}
#endif
if (ggml_openvino_model_cache_only()) {
GGML_ABORT("ggml-openvino: could not identify mapped GGUF weight; use --load-mode mmap");
}
uint64_t h = FNV_OFFSET;
const size_t head = std::min(size, WEIGHT_SAMPLE_BYTES);
h = fnv1a(h, data, head);
if (size > head) {
const size_t tail = std::min(size - head, WEIGHT_SAMPLE_BYTES);
h = fnv1a(h, static_cast<const uint8_t *>(data) + size - tail, tail);
}
return h;
}
std::string ggml_openvino_model_cache_dir() {
const char * dir = ggml_openvino_getenv_str("GGML_OPENVINO_COMPILED_MODEL_CACHE_DIR");
if (!dir || strlen(dir) == 0) {
return std::string();
}
std::string path(dir);
if (ggml_openvino_model_cache_only()) {
return path;
}
if (!make_dirs(path)) {
GGML_LOG_WARN("ggml-openvino: could not create model cache dir '%s' (errno=%d); caching disabled\n",
path.c_str(), errno);
return std::string();
}
return path;
}
std::string ggml_openvino_model_cache_temp_path(const std::string & path) {
#ifdef _WIN32
const int pid = _getpid();
#else
const int pid = getpid();
#endif
return path + ".tmp." + std::to_string(pid) + "." + std::to_string(ggml_time_us());
}
uint64_t ggml_openvino_model_fingerprint(const ggml_cgraph * cgraph,
const std::string & device,
bool fa,
const int32_t * rope_params,
int rope_len,
uint64_t extra_cfg,
const std::string & graph_signature) {
uint64_t h = FNV_OFFSET;
h = fnv1a_u64(h, 2);
h = fnv1a(h, graph_signature.data(), graph_signature.size());
for (const char * name : cache_settings) {
const char * value = ggml_openvino_getenv_str(name, "");
h = fnv1a(h, value, strlen(value) + 1);
}
if (const char * debug_nodes = ggml_openvino_getenv_str("GGML_OPENVINO_DEBUG_NODE")) {
h = fnv1a(h, "GGML_OPENVINO_DEBUG_NODE", sizeof("GGML_OPENVINO_DEBUG_NODE"));
h = fnv1a(h, debug_nodes, strlen(debug_nodes) + 1);
}
if (ggml_openvino_is_gpu() && ggml_openvino_getenv_int("GGML_OPENVINO_MOE_OP", 1) == 0) {
h = fnv1a(h, "GGML_OPENVINO_MOE_OP=0", sizeof("GGML_OPENVINO_MOE_OP=0"));
}
h = fnv1a_u64(h, static_cast<uint64_t>(cgraph->n_nodes));
for (int i = 0; i < cgraph->n_nodes; ++i) {
const ggml_tensor * node = cgraph->nodes[i];
h = fnv1a_u64(h, static_cast<uint64_t>(node->op));
h = fnv1a(h, node->name, strlen(node->name));
}
for_each_weight(cgraph, [&](const ggml_tensor * t) { h = fnv1a_u64(h, weight_fingerprint(t)); });
h = fnv1a(h, device.data(), device.size());
h = fnv1a_u64(h, fa ? 1u : 0u);
if (rope_params && rope_len > 0) {
h = fnv1a(h, rope_params, sizeof(int32_t) * static_cast<size_t>(rope_len));
}
h = fnv1a_u64(h, extra_cfg);
const std::string ver = ov_version_string();
h = fnv1a(h, ver.data(), ver.size());
return h;
}
std::string ggml_openvino_model_cache_blob_path(const std::string & dir, uint64_t fingerprint) {
return dir + "/" + hex64(fingerprint) + ".blob";
}
std::string ggml_openvino_model_cache_manifest_path(const std::string & dir, uint64_t fingerprint) {
return dir + "/" + hex64(fingerprint) + ".manifest";
}
bool ggml_openvino_model_cache_write_manifest(const std::string & path,
const ggml_cgraph * cgraph,
uint64_t fingerprint,
const std::vector<std::string> & inputs,
const std::vector<std::string> & outputs) {
std::ofstream f(path, std::ios::trunc);
if (!f.is_open()) {
return false;
}
f << "fingerprint " << hex64(fingerprint) << "\n";
f << "ov_version " << ov_version_string() << "\n";
for_each_weight(cgraph, [&](const ggml_tensor * t) {
f << t->name << " " << t->ne[0] << " " << t->ne[1] << " " << t->ne[2] << " " << t->ne[3] << " "
<< static_cast<int>(t->type) << " " << hex64(weight_fingerprint(t)) << "\n";
});
f << "ports\n";
for (const auto * names : { &inputs, &outputs }) {
f << names->size() << '\n';
for (const auto & name : *names) {
f << std::quoted(name) << '\n';
}
}
return f.good();
}
bool ggml_openvino_model_cache_verify_manifest(const std::string & path,
const ggml_cgraph * cgraph,
uint64_t fingerprint,
std::vector<std::string> & inputs,
std::vector<std::string> & outputs) {
std::ifstream f(path);
if (!f.is_open()) {
return false;
}
std::string tag;
std::string val;
if (!(f >> tag >> val) || tag != "fingerprint" || val != hex64(fingerprint)) {
return false;
}
if (!(f >> tag >> val) || tag != "ov_version" || val != ov_version_string()) {
return false;
}
std::vector<std::string> expected;
for_each_weight(cgraph, [&](const ggml_tensor * t) {
expected.push_back(std::string(t->name) + " " + std::to_string(t->ne[0]) + " " + std::to_string(t->ne[1]) +
" " + std::to_string(t->ne[2]) + " " + std::to_string(t->ne[3]) + " " +
std::to_string(static_cast<int>(t->type)) + " " + hex64(weight_fingerprint(t)));
});
size_t idx = 0;
std::string line;
std::getline(f, line); while (idx < expected.size() && std::getline(f, line)) {
if (line.empty()) {
continue;
}
if (idx >= expected.size() || line != expected[idx]) {
return false;
}
++idx;
}
if (idx != expected.size()) {
return false;
}
if (!std::getline(f, line)) {
return true;
}
if (line != "ports") {
return false;
}
for (auto * names : { &inputs, &outputs }) {
size_t count;
if (!(f >> count) || count > 100000) {
return false;
}
for (size_t i = 0; i < count; ++i) {
std::string name;
if (!(f >> std::quoted(name))) {
return false;
}
names->push_back(name);
}
}
f >> std::ws;
return f.eof();
}