char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Zijun YuOpenVINO: Qwen3.5, memory optimization, and test-recurrent-state-rollback (#26952)aee56b3ab

char/aliceai-t5-moe
9.4 KiB272 linesraw
1#include "model-cache.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
8#include <cerrno>
9#include <cstdio>
10#include <cstring>
11#include <fstream>
12#include <openvino/core/version.hpp>
13#include <string>
14#include <sys/stat.h>
15#include <sys/types.h>
16#include <vector>
17
18#if defined(_WIN32)
19#    include <direct.h>
20#endif
21
22namespace {
23
24// 64-bit FNV-1a, the mixing primitive for all fingerprints here.
25inline uint64_t fnv1a(uint64_t h, const void * data, size_t n) {
26    const uint8_t * p = static_cast<const uint8_t *>(data);
27    for (size_t i = 0; i < n; ++i) {
28        h ^= p[i];
29        h *= 0x100000001b3ull;
30    }
31    return h;
32}
33
34inline uint64_t fnv1a_u64(uint64_t h, uint64_t v) {
35    return fnv1a(h, &v, sizeof(v));
36}
37
38constexpr uint64_t FNV_OFFSET = 0xcbf29ce484222325ull;
39
40// Bytes sampled from each end of a weight tensor for the sampled hash. The whole
41// model is never hashed (that would cost seconds every run); instead we sample a
42// bounded window from the head and tail of each weight's bytes. The manifest
43// re-verify (same sample) guards the residual collision risk.
44constexpr size_t WEIGHT_SAMPLE_BYTES = 4096;
45
46// Is this src a model weight, mirroring create_weight_nodes()'s selection:
47// non-view tensor whose buffer is USAGE_WEIGHTS or whose type is quantized.
48bool is_weight_src(const ggml_tensor * src) {
49    if (src == nullptr || src->view_src != nullptr || src->buffer == nullptr) {
50        return false;
51    }
52    return src->buffer->usage == GGML_BACKEND_BUFFER_USAGE_WEIGHTS || ggml_is_quantized(src->type);
53}
54
55// Per-weight sampled fingerprint: identity (name/shape/type) + a bounded byte
56// sample. Returns FNV offset basis if data is unavailable (kept deterministic).
57uint64_t weight_fingerprint(const ggml_tensor * t) {
58    uint64_t h = FNV_OFFSET;
59    h = fnv1a(h, t->name, strlen(t->name));
60    for (int i = 0; i < GGML_MAX_DIMS; ++i) {
61        h = fnv1a_u64(h, static_cast<uint64_t>(t->ne[i]));
62    }
63    h = fnv1a_u64(h, static_cast<uint64_t>(t->type));
64    const size_t nbytes = ggml_nbytes(t);
65    h = fnv1a_u64(h, nbytes);
66    if (t->data != nullptr && nbytes > 0) {
67        const size_t head = nbytes < WEIGHT_SAMPLE_BYTES ? nbytes : WEIGHT_SAMPLE_BYTES;
68        h = fnv1a(h, t->data, head);
69        if (nbytes > WEIGHT_SAMPLE_BYTES) {
70            const size_t tail = nbytes < 2 * WEIGHT_SAMPLE_BYTES ? nbytes - WEIGHT_SAMPLE_BYTES : WEIGHT_SAMPLE_BYTES;
71            h = fnv1a(h, static_cast<const uint8_t *>(t->data) + (nbytes - tail), tail);
72        }
73    }
74    return h;
75}
76
77// Walk the cgraph and invoke fn(weight_tensor) for each distinct weight, in node
78// order. De-duplicates by tensor pointer so a weight used by several nodes is
79// fingerprinted once, deterministically.
80template <typename F>
81void for_each_weight(const ggml_cgraph * cgraph, F && fn) {
82    std::vector<const ggml_tensor *> seen;
83    for (int i = 0; i < cgraph->n_nodes; ++i) {
84        const ggml_tensor * node = cgraph->nodes[i];
85        for (int s = 0; s < GGML_MAX_SRC; ++s) {
86            const ggml_tensor * src = node->src[s];
87            if (!is_weight_src(src)) {
88                continue;
89            }
90            bool dup = false;
91            for (const auto * p : seen) {
92                if (p == src) {
93                    dup = true;
94                    break;
95                }
96            }
97            if (dup) {
98                continue;
99            }
100            seen.push_back(src);
101            fn(src);
102        }
103    }
104}
105
106std::string ov_version_string() {
107    const ov::Version v = ov::get_openvino_version();
108    return std::string(v.buildNumber ? v.buildNumber : "unknown");
109}
110
111std::string hex64(uint64_t v) {
112    char buf[17];
113    snprintf(buf, sizeof(buf), "%016llx", static_cast<unsigned long long>(v));
114    return std::string(buf);
115}
116
117// Portable mkdir for a single path component. Returns true if the directory
118// exists after the call (created now or already present).
119bool make_dir(const std::string & path) {
120#if defined(_WIN32)
121    int rc = _mkdir(path.c_str());
122#else
123    int rc = ::mkdir(path.c_str(), 0755);
124#endif
125    if (rc == 0 || errno == EEXIST) {
126        return true;
127    }
128    return false;
129}
130
131// Create `path` and any missing parents (like `mkdir -p`). Best-effort:
132// returns true only if the full directory exists afterwards.
133bool make_dirs(const std::string & path) {
134    if (path.empty()) {
135        return false;
136    }
137    std::string acc;
138    for (size_t i = 0; i < path.size(); ++i) {
139        const char c = path[i];
140        acc.push_back(c);
141        const bool sep = (c == '/'
142#if defined(_WIN32)
143                          || c == '\\'
144#endif
145        );
146        // Create each intermediate component (skip a leading "/" root).
147        if (sep && acc.size() > 1) {
148            std::string component = acc.substr(0, acc.size() - 1);
149            if (!make_dir(component)) {
150                return false;
151            }
152        }
153    }
154    return make_dir(path);
155}
156
157}  // namespace
158
159std::string ggml_openvino_model_cache_dir() {
160    const char * dir = ggml_openvino_getenv_str("GGML_OPENVINO_COMPILED_MODEL_CACHE_DIR");
161    if (!dir || strlen(dir) == 0) {
162        return std::string();
163    }
164    std::string path(dir);
165    // Create the cache directory (and parents) on first use so callers don't
166    // have to pre-create it; a missing dir would otherwise silently disable the
167    // cache (manifest/blob writes fail with no directory to write into).
168    if (!make_dirs(path)) {
169        GGML_LOG_WARN("ggml-openvino: could not create model cache dir '%s' (errno=%d); caching disabled\n",
170                      path.c_str(), errno);
171        return std::string();
172    }
173    return path;
174}
175
176uint64_t ggml_openvino_model_fingerprint(const ggml_cgraph * cgraph,
177                                         const std::string & device,
178                                         bool fa,
179                                         const int32_t * rope_params,
180                                         int rope_len,
181                                         uint64_t extra_cfg) {
182    uint64_t h = FNV_OFFSET;
183
184    // Topology: node count + each node's op and name (cheap, and distinguishes
185    // graphs that share weights but differ structurally).
186    h = fnv1a_u64(h, static_cast<uint64_t>(cgraph->n_nodes));
187    for (int i = 0; i < cgraph->n_nodes; ++i) {
188        const ggml_tensor * node = cgraph->nodes[i];
189        h = fnv1a_u64(h, static_cast<uint64_t>(node->op));
190        h = fnv1a(h, node->name, strlen(node->name));
191    }
192
193    // Weights: the model identity.
194    for_each_weight(cgraph, [&](const ggml_tensor * t) { h = fnv1a_u64(h, weight_fingerprint(t)); });
195
196    // Config that changes the produced blob.
197    h = fnv1a(h, device.data(), device.size());
198    h = fnv1a_u64(h, fa ? 1u : 0u);
199    if (rope_params && rope_len > 0) {
200        h = fnv1a(h, rope_params, sizeof(int32_t) * static_cast<size_t>(rope_len));
201    }
202    h = fnv1a_u64(h, extra_cfg);
203    const std::string ver = ov_version_string();
204    h = fnv1a(h, ver.data(), ver.size());
205
206    return h;
207}
208
209std::string ggml_openvino_model_cache_blob_path(const std::string & dir, uint64_t fingerprint) {
210    return dir + "/" + hex64(fingerprint) + ".blob";
211}
212
213std::string ggml_openvino_model_cache_manifest_path(const std::string & dir, uint64_t fingerprint) {
214    return dir + "/" + hex64(fingerprint) + ".manifest";
215}
216
217bool ggml_openvino_model_cache_write_manifest(const std::string & path,
218                                              const ggml_cgraph * cgraph,
219                                              uint64_t fingerprint) {
220    std::ofstream f(path, std::ios::trunc);
221    if (!f.is_open()) {
222        return false;
223    }
224    f << "fingerprint " << hex64(fingerprint) << "\n";
225    f << "ov_version " << ov_version_string() << "\n";
226    for_each_weight(cgraph, [&](const ggml_tensor * t) {
227        f << t->name << " " << t->ne[0] << " " << t->ne[1] << " " << t->ne[2] << " " << t->ne[3] << " "
228          << static_cast<int>(t->type) << " " << hex64(weight_fingerprint(t)) << "\n";
229    });
230    return f.good();
231}
232
233bool ggml_openvino_model_cache_verify_manifest(const std::string & path,
234                                               const ggml_cgraph * cgraph,
235                                               uint64_t fingerprint) {
236    std::ifstream f(path);
237    if (!f.is_open()) {
238        return false;
239    }
240    std::string tag, val;
241    // header: fingerprint
242    if (!(f >> tag >> val) || tag != "fingerprint" || val != hex64(fingerprint)) {
243        return false;
244    }
245    // header: ov_version
246    if (!(f >> tag >> val) || tag != "ov_version" || val != ov_version_string()) {
247        return false;
248    }
249
250    // Build the expected per-weight lines from the live cgraph, then require an
251    // exact match (same set, same order) against the manifest.
252    std::vector<std::string> expected;
253    for_each_weight(cgraph, [&](const ggml_tensor * t) {
254        expected.push_back(std::string(t->name) + " " + std::to_string(t->ne[0]) + " " + std::to_string(t->ne[1]) +
255                           " " + std::to_string(t->ne[2]) + " " + std::to_string(t->ne[3]) + " " +
256                           std::to_string(static_cast<int>(t->type)) + " " + hex64(weight_fingerprint(t)));
257    });
258
259    size_t idx = 0;
260    std::string line;
261    std::getline(f, line);  // consume rest of ov_version line
262    while (std::getline(f, line)) {
263        if (line.empty()) {
264            continue;
265        }
266        if (idx >= expected.size() || line != expected[idx]) {
267            return false;
268        }
269        ++idx;
270    }
271    return idx == expected.size();
272}