char-fork/llama.cpp

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

Georgi Gerganovci : add `[no release]` keyword + fix sanitizer builds (#23728)35a74c8fb

char/aliceai-t5-moe
22.7 KiB742 linesraw
1// GGUF binary parser adapted from the huggingface/gguf package.
2// Reference: https://github.com/huggingface/huggingface.js
3
4#include "gguf-model-data.h"
5
6#include "common.h"
7#include "ggml-cpp.h"
8#include "gguf.h"
9
10#include <algorithm>
11#include <cstdio>
12#include <cstring>
13#include <filesystem>
14#include <fstream>
15
16#include "http.h"
17#define JSON_ASSERT GGML_ASSERT
18#include <nlohmann/json.hpp>
19
20// Equivalent of RangeView
21struct gguf_buf_reader {
22    const char * data;
23    size_t       size;
24    size_t       pos;
25
26    gguf_buf_reader(const std::vector<char> & buf) : data(buf.data()), size(buf.size()), pos(0) {}
27
28    bool has_n_bytes(size_t n) const {
29        return pos + n <= size;
30    }
31
32    template <typename T>
33    bool read_val(T & out) {
34        if (!has_n_bytes(sizeof(T))) {
35            return false;
36        }
37        memcpy(&out, data + pos, sizeof(T));
38        pos += sizeof(T);
39        return true;
40    }
41
42    bool read_str(std::string & out) {
43        uint64_t len;
44        if (!read_val(len)) {
45            return false;
46        }
47        if (!has_n_bytes((size_t)len)) {
48            return false;
49        }
50        out.assign(data + pos, (size_t)len);
51        pos += (size_t)len;
52        return true;
53    }
54
55    bool skip(size_t n) {
56        if (!has_n_bytes(n)) {
57            return false;
58        }
59        pos += n;
60        return true;
61    }
62};
63
64static size_t gguf_val_type_size(int32_t vtype) {
65    switch (vtype) {
66        case GGUF_TYPE_UINT8:   return 1;
67        case GGUF_TYPE_INT8:    return 1;
68        case GGUF_TYPE_UINT16:  return 2;
69        case GGUF_TYPE_INT16:   return 2;
70        case GGUF_TYPE_UINT32:  return 4;
71        case GGUF_TYPE_INT32:   return 4;
72        case GGUF_TYPE_FLOAT32: return 4;
73        case GGUF_TYPE_BOOL:    return 1;
74        case GGUF_TYPE_UINT64:  return 8;
75        case GGUF_TYPE_INT64:   return 8;
76        case GGUF_TYPE_FLOAT64: return 8;
77        default:                return 0; // string/array handled separately
78    }
79}
80
81// Equivalent of readMetadataValue(), skips unused values rather than storing
82static bool gguf_skip_value(gguf_buf_reader & r, int32_t vtype) {
83    if (vtype == GGUF_TYPE_STRING) {
84        std::string tmp;
85        return r.read_str(tmp);
86    }
87    if (vtype == GGUF_TYPE_ARRAY) {
88        int32_t elem_type;
89        uint64_t count;
90        if (!r.read_val(elem_type)) {
91            return false;
92        }
93        if (!r.read_val(count)) {
94            return false;
95        }
96        if (elem_type == GGUF_TYPE_STRING) {
97            for (uint64_t i = 0; i < count; i++) {
98                std::string tmp;
99                if (!r.read_str(tmp)) {
100                    return false;
101                }
102            }
103            return true;
104        }
105        if (elem_type == GGUF_TYPE_ARRAY) {
106            // nested arrays - recurse
107            for (uint64_t i = 0; i < count; i++) {
108                if (!gguf_skip_value(r, GGUF_TYPE_ARRAY)) {
109                    return false;
110                }
111            }
112            return true;
113        }
114        size_t elem_sz = gguf_val_type_size(elem_type);
115        if (elem_sz == 0) {
116            return false;
117        }
118        return r.skip((size_t)count * elem_sz);
119    }
120    size_t sz = gguf_val_type_size(vtype);
121    if (sz == 0) {
122        return false;
123    }
124    return r.skip(sz);
125}
126
127static bool gguf_read_uint32_val(gguf_buf_reader & r, int32_t vtype, uint32_t & out) {
128    // Handle array-valued fields (e.g. per-layer head counts in hybrid models)
129    // by reading the first element as a representative value.
130    if (vtype == GGUF_TYPE_ARRAY) {
131        int32_t elem_type;
132        uint64_t count;
133        if (!r.read_val(elem_type)) {
134            return false;
135        }
136        if (!r.read_val(count)) {
137            return false;
138        }
139        if (count == 0) {
140            return false;
141        }
142        // Read first element, skip the rest
143        if (!gguf_read_uint32_val(r, elem_type, out)) {
144            return false;
145        }
146        for (uint64_t i = 1; i < count; i++) {
147            size_t sz = gguf_val_type_size(elem_type);
148            if (sz == 0) {
149                return false;
150            }
151            if (!r.skip(sz)) {
152                return false;
153            }
154        }
155        return true;
156    }
157    if (vtype == GGUF_TYPE_UINT8) {
158        uint8_t v;
159        if (!r.read_val(v)) {
160            return false;
161        }
162        out = v;
163        return true;
164    }
165    if (vtype == GGUF_TYPE_INT8) {
166        int8_t v;
167        if (!r.read_val(v)) {
168            return false;
169        }
170        out = (uint32_t)v;
171        return true;
172    }
173    if (vtype == GGUF_TYPE_UINT16) {
174        uint16_t v;
175        if (!r.read_val(v)) {
176            return false;
177        }
178        out = v;
179        return true;
180    }
181    if (vtype == GGUF_TYPE_INT16) {
182        int16_t v;
183        if (!r.read_val(v)) {
184            return false;
185        }
186        out = (uint32_t)v;
187        return true;
188    }
189    if (vtype == GGUF_TYPE_UINT32) {
190        uint32_t v;
191        if (!r.read_val(v)) {
192            return false;
193        }
194        out = v;
195        return true;
196    }
197    if (vtype == GGUF_TYPE_INT32) {
198        int32_t v;
199        if (!r.read_val(v)) {
200            return false;
201        }
202        out = (uint32_t)v;
203        return true;
204    }
205    if (vtype == GGUF_TYPE_UINT64) {
206        uint64_t v;
207        if (!r.read_val(v)) {
208            return false;
209        }
210        out = (uint32_t)v;
211        return true;
212    }
213    if (vtype == GGUF_TYPE_INT64) {
214        int64_t v;
215        if (!r.read_val(v)) {
216            return false;
217        }
218        out = (uint32_t)v;
219        return true;
220    }
221    return false;
222}
223
224// Follows the same header -> KV -> tensor parsing sequence as gguf() huggingface/gguf
225static std::optional<gguf_remote_model> gguf_parse_meta(const std::vector<char> & buf) {
226    gguf_buf_reader r(buf);
227
228    // Header: magic(4) + version(4) + tensor_count(8) + kv_count(8) = 24 bytes minimum
229    uint32_t magic_raw;
230    if (!r.read_val(magic_raw)) {
231        return std::nullopt;
232    }
233    if (memcmp(&magic_raw, "GGUF", 4) != 0) {
234        fprintf(stderr, "gguf_parse_meta: invalid magic\n");
235        return std::nullopt;
236    }
237
238    uint32_t version;
239    if (!r.read_val(version)) {
240        return std::nullopt;
241    }
242    if (version < 2 || version > 3) {
243        fprintf(stderr, "gguf_parse_meta: unsupported version %u\n", version);
244        return std::nullopt;
245    }
246
247    int64_t tensor_count_raw;
248    int64_t kv_count_raw;
249    if (!r.read_val(tensor_count_raw)) {
250        return std::nullopt;
251    }
252    if (!r.read_val(kv_count_raw)) {
253        return std::nullopt;
254    }
255
256    uint64_t tensor_count = (uint64_t)tensor_count_raw;
257    uint64_t kv_count     = (uint64_t)kv_count_raw;
258
259    gguf_remote_model model;
260
261    std::string arch_prefix;
262
263    // Parse KV pairs
264    for (uint64_t i = 0; i < kv_count; i++) {
265        std::string key;
266        if (!r.read_str(key)) {
267            return std::nullopt;
268        }
269
270        int32_t vtype;
271        if (!r.read_val(vtype)) {
272            return std::nullopt;
273        }
274
275        if (key == "general.architecture" && vtype == GGUF_TYPE_STRING) {
276            if (!r.read_str(model.architecture)) {
277                return std::nullopt;
278            }
279            arch_prefix = model.architecture + ".";
280            continue;
281        }
282
283        // Extract split.count for proper handling of split files
284        if (key == "split.count") {
285            uint32_t v;
286            if (!gguf_read_uint32_val(r, vtype, v)) {
287                return std::nullopt;
288            }
289            model.n_split = (uint16_t)v;
290            continue;
291        }
292
293        // Extract split.tensors.count so we can verify we have all tensors
294        if (key == "split.tensors.count") {
295            uint32_t v;
296            if (!gguf_read_uint32_val(r, vtype, v)) {
297                return std::nullopt;
298            }
299            model.n_split_tensors = v;
300            continue;
301        }
302
303        if (!arch_prefix.empty()) {
304            uint32_t * target = nullptr;
305
306            if      (key == arch_prefix + "embedding_length")         { target = &model.n_embd; }
307            else if (key == arch_prefix + "feed_forward_length")      { target = &model.n_ff; }
308            else if (key == arch_prefix + "block_count")              { target = &model.n_layer; }
309            else if (key == arch_prefix + "attention.head_count")     { target = &model.n_head; }
310            else if (key == arch_prefix + "attention.head_count_kv")  { target = &model.n_head_kv; }
311            else if (key == arch_prefix + "expert_count")             { target = &model.n_expert; }
312            else if (key == arch_prefix + "attention.key_length")     { target = &model.n_embd_head_k; }
313            else if (key == arch_prefix + "attention.value_length")   { target = &model.n_embd_head_v; }
314
315            if (target) {
316                if (!gguf_read_uint32_val(r, vtype, *target)) {
317                    return std::nullopt;
318                }
319                continue;
320            }
321        }
322
323        if (!gguf_skip_value(r, vtype)) {
324            return std::nullopt;
325        }
326    }
327
328    // Parse tensor info entries
329    model.tensors.reserve((size_t)tensor_count);
330    for (uint64_t i = 0; i < tensor_count; i++) {
331        gguf_remote_tensor t;
332
333        if (!r.read_str(t.name)) {
334            return std::nullopt;
335        }
336        if (!r.read_val(t.n_dims)) {
337            return std::nullopt;
338        }
339
340        if (t.n_dims > 4) {
341            fprintf(stderr, "gguf_parse_meta: tensor '%s' has %u dims (max 4)\n", t.name.c_str(), t.n_dims);
342            return std::nullopt;
343        }
344
345        for (uint32_t d = 0; d < t.n_dims; d++) {
346            if (!r.read_val(t.ne[d])) {
347                return std::nullopt;
348            }
349        }
350
351        int32_t type_raw;
352        if (!r.read_val(type_raw)) {
353            return std::nullopt;
354        }
355        t.type = (ggml_type)type_raw;
356
357        uint64_t offset;
358        if (!r.read_val(offset)) {
359            return std::nullopt;
360        }
361
362        // Infer n_vocab from token_embd.weight
363        if (t.name == "token_embd.weight") {
364            model.n_vocab = (uint32_t)t.ne[1];
365        }
366
367        model.tensors.push_back(std::move(t));
368    }
369
370    return model;
371}
372
373// cache handling for local download
374static std::string get_default_cache_dir() {
375    return fs_get_cache_directory() + "gguf-headers/";
376}
377
378static std::string sanitize_for_path(const std::string & s) {
379    std::string out = s;
380    for (char & c : out) {
381        if (c == '/' || c == '\\' || c == ':') {
382            c = '_';
383        }
384    }
385    return out;
386}
387
388static bool read_file(const std::string & path, std::vector<char> & out) {
389    std::ifstream f(path, std::ios::binary | std::ios::ate);
390    if (!f.good()) {
391        return false;
392    }
393    auto sz = f.tellg();
394    if (sz <= 0) {
395        return false;
396    }
397    out.resize((size_t)sz);
398    f.seekg(0);
399    f.read(out.data(), sz);
400    return f.good();
401}
402
403static bool write_file(const std::string & path, const std::vector<char> & data) {
404    std::ofstream f(path, std::ios::binary | std::ios::trunc);
405    if (!f.good()) {
406        return false;
407    }
408    f.write(data.data(), (std::streamsize)data.size());
409    return f.good();
410}
411
412// HuggingFace file auto-detection and HTTP download
413static std::pair<long, std::vector<char>> gguf_http_get(
414        const std::string & url,
415        const httplib::Headers & headers = {},
416        int timeout_sec = 60) {
417    try {
418        auto [cli, parts] = common_http_client(url);
419
420        if (timeout_sec > 0) {
421            cli.set_read_timeout(timeout_sec, 0);
422            cli.set_write_timeout(timeout_sec, 0);
423        }
424        cli.set_connection_timeout(30, 0);
425
426        std::vector<char> body;
427        auto res = cli.Get(parts.path, headers,
428            [&](const char * data, size_t len) {
429                body.insert(body.end(), data, data + len);
430                return true;
431            }, nullptr);
432
433        if (!res) {
434            fprintf(stderr, "gguf_fetch: HTTP request failed for %s (error %d)\n",
435                    url.c_str(), (int)res.error());
436            return {-1, {}};
437        }
438        return {res->status, std::move(body)};
439    } catch (const std::exception & e) {
440        fprintf(stderr, "gguf_fetch: HTTP error: %s\n", e.what());
441        return {-1, {}};
442    }
443}
444
445// Find the filename for given repo/quant.
446// For split models, returns the first shard (the one containing "00001-of-")
447// split_prefix is set to the portion before "-00001-of-XXXXX.gguf" when a split file is found
448static std::string detect_gguf_filename(const std::string & repo, const std::string & quant,
449                                        std::string & split_prefix) {
450    split_prefix.clear();
451    std::string api_url = "https://huggingface.co/api/models/" + repo;
452
453    auto [code, body] = gguf_http_get(api_url, {}, 30);
454    if (code != 200 || body.empty()) {
455        fprintf(stderr, "gguf_fetch: failed to query HF API for %s (HTTP %ld)\n", repo.c_str(), code);
456        return "";
457    }
458
459    nlohmann::json j;
460    try {
461        j = nlohmann::json::parse(body.begin(), body.end());
462    } catch (...) {
463        fprintf(stderr, "gguf_fetch: failed to parse HF API response\n");
464        return "";
465    }
466
467    if (!j.contains("siblings") || !j["siblings"].is_array()) {
468        fprintf(stderr, "gguf_fetch: unexpected HF API response format\n");
469        return "";
470    }
471
472    std::vector<std::string> matches;
473    std::string quant_upper = quant;
474    for (char & c : quant_upper) { c = (char)toupper(c); }
475
476    for (const auto & sibling : j["siblings"]) {
477        if (!sibling.contains("rfilename")) { continue; }
478        std::string fname = sibling["rfilename"].get<std::string>();
479        if (fname.size() < 5 || fname.substr(fname.size() - 5) != ".gguf") {
480            continue;
481        }
482
483        std::string fname_upper = fname;
484        for (char & c : fname_upper) { c = (char)toupper(c); }
485        if (fname_upper.find(quant_upper) != std::string::npos) {
486            matches.push_back(fname);
487        }
488    }
489
490    if (matches.empty()) {
491        fprintf(stderr, "gguf_fetch: no .gguf files matching '%s' in %s\n", quant.c_str(), repo.c_str());
492        return "";
493    }
494
495    std::sort(matches.begin(), matches.end());
496
497    // Prefer non-split, non-supplementary file
498    for (const auto & m : matches) {
499        if (m.find("-of-") == std::string::npos && m.find("mmproj") == std::string::npos) {
500            return m;
501        }
502    }
503
504    // Return the first shard (00001-of-) and extract the prefix
505    for (const auto & m : matches) {
506        auto pos = m.find("-00001-of-");
507        if (pos != std::string::npos) {
508            split_prefix = m.substr(0, pos);
509            return m;
510        }
511    }
512
513    return matches[0];
514}
515
516static std::optional<gguf_remote_model> fetch_and_parse(
517        const std::string & repo,
518        const std::string & filename,
519        const std::string & cache_path,
520        bool verbose) {
521    std::string url = "https://huggingface.co/" + repo + "/resolve/main/" + filename;
522
523    // Progressive download inspired by RangeView.fetchChunk()
524    // Start at 2MB, double each time, cap at 64MB
525    size_t chunk_size = 2 * 1024 * 1024;
526    const size_t max_chunk = 64 * 1024 * 1024;
527
528    while (chunk_size <= max_chunk) {
529        if (verbose) {
530            fprintf(stderr, "gguf_fetch: downloading %zu bytes from %s\n", chunk_size, filename.c_str());
531        }
532
533        char range_buf[64];
534        snprintf(range_buf, sizeof(range_buf), "bytes=0-%zu", chunk_size - 1);
535        httplib::Headers headers = {{"Range", range_buf}};
536
537        auto [code, body] = gguf_http_get(url, headers, 120);
538        if (code != 200 && code != 206) {
539            fprintf(stderr, "gguf_fetch: HTTP %ld fetching %s\n", code, url.c_str());
540            return std::nullopt;
541        }
542
543        if (body.empty()) {
544            fprintf(stderr, "gguf_fetch: empty response\n");
545            return std::nullopt;
546        }
547
548        auto result = gguf_parse_meta(body);
549        if (result.has_value()) {
550            write_file(cache_path, body);
551            return result;
552        }
553
554        if (code == 200) {
555            fprintf(stderr, "gguf_fetch: server returned full response but metadata parse failed\n");
556            return std::nullopt;
557        }
558
559        // Parse failed, try larger chunk
560        chunk_size *= 2;
561    }
562
563    fprintf(stderr, "gguf_fetch: metadata exceeds 64MB, giving up\n");
564    return std::nullopt;
565}
566
567static std::string get_cache_file_path(const std::string& cdir, const std::string& repo_part, const std::string& filename) {
568    std::string fname_part = sanitize_for_path(filename);
569    return cdir + "/" + repo_part + "--" + fname_part + ".partial";
570}
571
572// Try cache first, then fetch and parse a single GGUF shard.
573static std::optional<gguf_remote_model> fetch_or_cached(
574        const std::string & repo,
575        const std::string & filename,
576        const std::string & cdir,
577        const std::string & repo_part,
578        bool verbose) {
579    std::string cache_path = get_cache_file_path(cdir, repo_part, filename);
580
581    {
582        std::vector<char> cached;
583        if (std::filesystem::exists(cache_path) && read_file(cache_path, cached)) {
584            auto result = gguf_parse_meta(cached);
585            if (result.has_value()) {
586                if (verbose) {
587                    fprintf(stderr, "gguf_fetch: loaded from cache: %s\n", cache_path.c_str());
588                }
589                return result;
590            }
591        }
592    }
593
594    fs_create_directory_with_parents(cdir);
595    return fetch_and_parse(repo, filename, cache_path, verbose);
596}
597
598std::optional<gguf_remote_model> gguf_fetch_model_meta(
599        const std::string & repo,
600        const std::string & quant,
601        const std::string & cache_dir,
602        bool verbose) {
603    std::string cdir = cache_dir.empty() ? get_default_cache_dir() : cache_dir;
604    std::string repo_part = sanitize_for_path(repo);
605
606    std::string split_prefix;
607    std::string filename = detect_gguf_filename(repo, quant, split_prefix);
608    if (filename.empty()) {
609        return std::nullopt;
610    }
611
612    auto model_opt = fetch_or_cached(repo, filename, cdir, repo_part, verbose);
613    if (!model_opt.has_value()) {
614        fprintf(stderr, "gguf_fetch: failed to fetch %s\n", filename.c_str());
615        return std::nullopt;
616    }
617
618    auto & model = model_opt.value();
619
620    // If the model is split across multiple files we need to fetch the remaining shards metadata
621    if (model.n_split > 1) {
622        if (split_prefix.empty()) {
623            fprintf(stderr, "gguf_fetch: model reports %u splits but filename has no split pattern\n", model.n_split);
624            return std::nullopt;
625        }
626
627        if (verbose) {
628            fprintf(stderr, "gguf_fetch: split model with %u shards, fetching remaining %u...\n",
629                    model.n_split, model.n_split - 1);
630        }
631
632        for (int i = 2; i <= model.n_split; i++) {
633            char buf_num[32];
634            char buf_tot[32];
635            snprintf(buf_num, sizeof(buf_num), "%05d", i);
636            snprintf(buf_tot, sizeof(buf_tot), "%05d", (int)model.n_split);
637            std::string shard_name = split_prefix + "-" + buf_num + "-of-" + buf_tot + ".gguf";
638
639            auto shard = fetch_or_cached(repo, shard_name, cdir, repo_part, verbose);
640            if (!shard.has_value()) {
641                fprintf(stderr, "gguf_fetch: failed to fetch shard %d: %s\n", i, shard_name.c_str());
642                return std::nullopt;
643            }
644
645            model.tensors.insert(model.tensors.end(),
646                std::make_move_iterator(shard->tensors.begin()),
647                std::make_move_iterator(shard->tensors.end()));
648        }
649
650        if (model.n_split_tensors > 0 && model.tensors.size() != model.n_split_tensors) {
651            fprintf(stderr, "gguf_fetch: WARNING: expected %u tensors from split.tensors.count, got %zu\n",
652                    model.n_split_tensors, model.tensors.size());
653        }
654    }
655
656    return model_opt;
657}
658
659gguf_context_ptr gguf_fetch_gguf_ctx(
660        const std::string & repo,
661        const std::string & quant,
662        const std::string & cache_dir,
663        bool verbose) {
664    std::string cdir = cache_dir.empty() ? get_default_cache_dir() : cache_dir;
665    std::string repo_part = sanitize_for_path(repo);
666
667    std::string split_prefix;
668    std::string filename = detect_gguf_filename(repo, quant, split_prefix);
669
670    if (filename.empty()) {
671        return nullptr;
672    }
673
674    auto model_opt = fetch_or_cached(repo, filename, cdir, repo_part, verbose);
675    if (!model_opt.has_value()) {
676        fprintf(stderr, "gguf_fetch: failed to fetch %s\n", filename.c_str());
677        return nullptr;
678    }
679
680    auto & model = model_opt.value();
681
682    const std::string cache_path = get_cache_file_path(cdir, repo_part, filename);
683
684    ggml_context_ptr ggml_ctx_ptr;
685    ggml_context * ggml_ctx{};
686    gguf_init_params params{true, &ggml_ctx};
687    gguf_context_ptr ctx{gguf_init_from_file(cache_path.c_str(), params)};
688    ggml_ctx_ptr.reset(ggml_ctx);
689
690    if (ctx == nullptr) {
691        fprintf(stderr, "gguf_fetch: gguf_init_from_file failed\n");
692        return nullptr;
693    }
694
695    // If the model is split across multiple files we need to fetch the remaining shards metadata
696    if (model.n_split > 1) {
697        if (split_prefix.empty()) {
698            fprintf(stderr, "gguf_fetch: model reports %u splits but filename has no split pattern\n", model.n_split);
699            return nullptr;
700        }
701
702        if (verbose) {
703            fprintf(stderr, "gguf_fetch: split model with %u shards, fetching remaining %u...\n",
704                    model.n_split, model.n_split - 1);
705        }
706
707        for (int i = 2; i <= model.n_split; i++) {
708            char buf_num[32];
709            char buf_tot[32];
710            snprintf(buf_num, sizeof(buf_num), "%05d", i);
711            snprintf(buf_tot, sizeof(buf_tot), "%05d", (int)model.n_split);
712            std::string shard_name = split_prefix + "-" + buf_num + "-of-" + buf_tot + ".gguf";
713
714            auto shard = fetch_or_cached(repo, shard_name, cdir, repo_part, verbose);
715            if (!shard.has_value()) {
716                fprintf(stderr, "gguf_fetch: failed to fetch shard %d: %s\n", i, shard_name.c_str());
717                return nullptr;
718            }
719
720            // Load tensors from shard and add to main gguf_context
721            const std::string shard_path = get_cache_file_path(cdir, repo_part, shard_name);
722            ggml_context_ptr shard_ggml_ctx_ptr;
723            ggml_context * shard_ggml_ctx{};
724            gguf_init_params shard_params{true, &shard_ggml_ctx};
725            gguf_context_ptr shard_ctx{gguf_init_from_file(shard_path.c_str(), shard_params)};
726            shard_ggml_ctx_ptr.reset(shard_ggml_ctx);
727
728            if (shard_ctx == nullptr) {
729                fprintf(stderr, "gguf_fetch: shard gguf_init_from_file failed\n");
730                return nullptr;
731            }
732
733            for (ggml_tensor * t = ggml_get_first_tensor(shard_ggml_ctx); t; t = ggml_get_next_tensor(shard_ggml_ctx, t)) {
734                gguf_add_tensor(ctx.get(), t);
735            }
736        }
737
738        gguf_set_val_u16(ctx.get(), "split.count", 1);
739    }
740
741    return ctx;
742}