mirror of
https://github.com/ikawrakow/ik_llama.cpp.git
synced 2026-08-12 22:29:39 +04:00
* common: fix coding mistakes (typos in identifiers, flags and log strings) Fix misspelled identifiers and user-facing strings across common, server and model loading: - allow_ruless -> allow_rules (misspelled identifier used in the allowlist CLI parsing and the server slot/context code) - get_formated_timings/get_formated_generation -> get_formatted_* - 'termionated' -> 'terminated' in the fit-margin assert message - 'defaulr' -> 'default' in the YAML dump - 'overriden' -> 'overridden' in tensor buffer type override logs - 'becausee' -> 'because' in the output-tensor split log - 'etected NaNs' -> 'detected NaNs' in the imatrix error message * common: fix comment typos across src, common, include and examples Fix misspelled words in code comments: - llama.h: 'typy' -> 'type', 'transfrom' -> 'transform', 'ecoder' -> 'encoder', 'indicies' -> 'indices', 'Intializes' -> 'Initializes' - common.h: 'embendings' -> 'embeddings', 'pr' -> 'or' in the fused-indexer-topk comment - chat.cpp: 'overridde' -> 'override' - ngram-map: 'occurences' -> 'occurrences', 'stastistics' -> 'statistics' - speculative.cpp: 'dont'/'inehit' -> 'don't'/'inherit' - llama-mmap.cpp: 'dont't' -> 'don't' - llama-model.h: 'hcurrently andle' -> 'currently handle' - build_gemma3/4.cpp: 'emdeddings' -> 'embeddings' - examples: 'quantizuation', 'logprobe', 'throught', 'retrun', 'swich', 'convinient', 'temporally' (-> 'temporary'), 'temproal', 'preceed' * common: remove duplicate definitions and duplicate help entries - clip-impl.h: drop the second, identical #define TN_FFN_GATE - common.cpp: remove the duplicate '-t, --threads N' help entry that was misplaced in the export-lora section (already listed in the general section) - common.cpp: merge the two 'embedding' help groups into a single group so the embedding options are listed together - llama.cpp: remove the redundant LLAMA_MAX_LAYERS define (llama-hparams.h already defines the same value and is included by llama.cpp) * common: fix remaining typos (accomodate, recommanded, occurences, occassionally) - accomodate -> accommodate in src/llama.cpp comment - recommanded -> recommended in quantize.cpp user-facing output - occurences -> occurrences in test-chat.cpp JSON string - occassionally -> occasionally in vendor/stb/stb_image_resize2.h comment Note: tokenizer.ggml.seperator_token_id kept as-is to match GGUF spec * common: remove duplicate help entries - remove the duplicate '--reasoning-budget N' help entry that was repeated in the main section (introduced ine0596bf614'Autoparser - complete refactoring of parser architecture (PR 1376)') - remove the second '--parallel-tool-calls' help entry that advertised the '-ptc' short flag, which belongs to '--print-token-count' (introduced ine0596bf614'Autoparser - complete refactoring of parser architecture (PR 1376)'); the '-ptc' alias was non-functional for '--parallel-tool-calls' because the parser only binds it to '--print-token-count' The canonical help entries are kept: - '--reasoning-budget N' is listed once - '--parallel-tool-calls' is listed once (without the conflicting '-ptc' alias) * common: remove duplicate LOG_ENABLE define - the '#undef LOG_ENABLE / #define LOG_ENABLE() // dummy stub' pair was repeated verbatim inside the LOG_DISABLE_LOGS section - remove the second occurrence (introduced ina2588b53e1'main : log file (PR 2748)') * llama-bench: align MLA and attention-max-batch flags with common tools llama-bench used '--mla-attn' and '--attn-max-batch' while the common CLI parsing (common/common.cpp) uses '--mla-use' and '--attention-max-batch' for the same features. This made the flags inconsistent across tools. - update the help text to advertise the canonical names '--mla-use' and '--attention-max-batch' - keep the old '--mla-attn' and '--attn-max-batch' spellings working as aliases so existing scripts are not broken The divergent names were introduced in3e536b95b0'Add optional MLA (PR 188)'. * fix typos in comments and user-facing strings - ngram-map.cpp: 'Do we haven a existing' -> 'Do we have an existing' (introduced in1cb7e1bf39'spec : add self speculative decoding, ngram and refactor (PR 1261)') - build_mamba.cpp: 'weigth' -> 'weight' (introduced in8befd92ea5'Refactor model compute graphs (PR 1651)') - gguf-split.cpp: 'one of splits have 0 tensors' -> 'one of the splits has 0 tensors' (introduced in75b580db0a'split: allow --split-max-size option (PR 6343)') - gguf-split.cpp: 'merged from %d split' -> 'merged from %d splits' (introduced in1b5523dc79'gguf-split: split and merge gguf per batch of tensors (PR 6135)') - convert-llama2c-to-ggml.cpp: missing opening quote in the help line, '(default %s\\')' -> '(default '%s\\')' (introduced inbb9ebb4394'Adding support for llama2.c models (PR 2559)') * harmonize British and American spelling to American English The codebase uses American English (e.g. --embd-normalize, --color), but a few strings/comments still used British spellings. Unify them: - 'normalisation' -> 'normalization' in common.h, common.cpp help text and code comment, and llama-build-context.cpp comment - 'colorise' -> 'colorize' in the --color help text (common.cpp) - 'behaviour' -> 'behavior' in a chat.cpp warning and a llama.cpp comment - also fix 'openai' -> 'OpenAI' capitalization in the embedding help text and common.h comment (embedding output format is OpenAI-style) * common: fix help text formatting inconsistencies - '-smf16'/'--split-mode-f16' and '-smf32'/'--split-mode-f32' help entries displayed hardcoded 'true'/'false' as the default value; show the actual state derived from params.reduce_type instead - '-no-mmad' help entry had 'fused_mmad?' without a space before the ternary operator - '--reasoning-tokens' help continuation lines used tab characters for indentation while the sibling '--reasoning-format' entry uses spaces; convert to consistent space indentation * common: revert smf16/smf32 help text default display change Revert the '-smf16'/'--split-mode-f16' and '-smf32'/'--split-mode-f32' help entries back to their original hardcoded 'true'/'false' default display. The change to derive the default from params.reduce_type was not desired; the split-mode options are legacy and the hardcoded defaults reflect their intended meaning. The other formatting fixes in the same area (fused_mmad ternary spacing and the reasoning-tokens tab-to-space indentation) are kept. * llama-bench: fix help text column alignment The --mla-use and --attention-max-batch help lines introduced by the flag alignment landed one column off from the sibling entries ((default: at column 51 instead of 50). Adjust the padding so all help lines align. * common: fix help text defaults for graph-reduce-type and log-format Mismatch 1: -grt, --graph-reduce-type help shows default "f32", but actual default (common.h:463) is "f16" and llama.cpp uses GGML_TYPE_F16. Mismatch 2: --log-format help shows default "json", but actual default (common.h:536 log_json=false) is text. * common: add -ptcall short flag for --parallel-tool-calls * typo
607 lines
21 KiB
C++
607 lines
21 KiB
C++
#include "llama.h"
|
|
#include "common.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <filesystem>
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <climits>
|
|
#include <stdexcept>
|
|
|
|
#if defined(_WIN32)
|
|
#include <windows.h>
|
|
#ifndef PATH_MAX
|
|
#define PATH_MAX MAX_PATH
|
|
#endif
|
|
#include <io.h>
|
|
#endif
|
|
|
|
enum split_operation : uint8_t {
|
|
OP_NONE,
|
|
OP_SPLIT,
|
|
OP_MERGE,
|
|
};
|
|
|
|
enum split_mode : uint8_t {
|
|
MODE_NONE,
|
|
MODE_TENSOR,
|
|
MODE_SIZE,
|
|
};
|
|
|
|
struct split_params {
|
|
split_operation operation = OP_NONE;
|
|
split_mode mode = MODE_NONE;
|
|
size_t n_bytes_split = 0;
|
|
int n_split_tensors = 128;
|
|
std::string input;
|
|
std::string output;
|
|
bool no_tensor_first_split = false;
|
|
bool dry_run = false;
|
|
};
|
|
|
|
static void split_print_usage(const char * executable) {
|
|
const split_params default_params;
|
|
printf("\n");
|
|
printf("usage: %s [options] GGUF_IN GGUF_OUT\n", executable);
|
|
printf("\n");
|
|
printf("Apply a GGUF operation on IN to OUT.");
|
|
printf("\n");
|
|
printf("options:\n");
|
|
printf(" -h, --help show this help message and exit\n");
|
|
printf(" --version show version and build info\n");
|
|
printf(" --split split GGUF to multiple GGUF (enabled by default)\n");
|
|
printf(" --merge merge multiple GGUF to a single GGUF\n");
|
|
printf(" --split-max-tensors max tensors in each split (default: %d)\n", default_params.n_split_tensors);
|
|
printf(" --split-max-size N(M|G) max size per split\n");
|
|
printf(" --no-tensor-first-split do not add tensors to the first split (disabled by default)\n");
|
|
printf(" --dry-run only print out a split plan and exit, without writing any new files\n");
|
|
printf("\n");
|
|
}
|
|
|
|
// return convert string, for example "128M" or "4G" to number of bytes
|
|
static size_t split_str_to_n_bytes(std::string str) {
|
|
size_t n_bytes = 0;
|
|
int n;
|
|
if (str.back() == 'M') {
|
|
sscanf(str.c_str(), "%d", &n);
|
|
n_bytes = (size_t)n * 1000 * 1000; // megabytes
|
|
} else if (str.back() == 'G') {
|
|
sscanf(str.c_str(), "%d", &n);
|
|
n_bytes = (size_t)n * 1000 * 1000 * 1000; // gigabytes
|
|
} else {
|
|
throw std::invalid_argument("error: supported units are M (megabytes) or G (gigabytes), but got: " + std::string(1, str.back()));
|
|
}
|
|
if (n <= 0) {
|
|
throw std::invalid_argument("error: size must be a positive value");
|
|
}
|
|
return n_bytes;
|
|
}
|
|
|
|
static void split_params_parse_ex(int argc, const char ** argv, split_params & params) {
|
|
std::string arg;
|
|
const std::string arg_prefix = "--";
|
|
bool invalid_param = false;
|
|
|
|
int arg_idx = 1;
|
|
for (; arg_idx < argc && strncmp(argv[arg_idx], "--", 2) == 0; arg_idx++) {
|
|
arg = argv[arg_idx];
|
|
if (arg.compare(0, arg_prefix.size(), arg_prefix) == 0) {
|
|
std::replace(arg.begin(), arg.end(), '_', '-');
|
|
}
|
|
|
|
bool arg_found = false;
|
|
if (arg == "-h" || arg == "--help") {
|
|
split_print_usage(argv[0]);
|
|
exit(0);
|
|
} else if (arg == "--version") {
|
|
fprintf(stderr, "version: %d (%s)\n", LLAMA_BUILD_NUMBER, LLAMA_COMMIT);
|
|
fprintf(stderr, "built with %s for %s\n", LLAMA_COMPILER, LLAMA_BUILD_TARGET);
|
|
exit(0);
|
|
} else if (arg == "--dry-run") {
|
|
arg_found = true;
|
|
params.dry_run = true;
|
|
} else if (arg == "--no-tensor-first-split") {
|
|
arg_found = true;
|
|
params.no_tensor_first_split = true;
|
|
} else if (arg == "--merge") {
|
|
arg_found = true;
|
|
if (params.operation != OP_NONE && params.operation != OP_MERGE) {
|
|
throw std::invalid_argument("error: either --split or --merge can be specified, but not both");
|
|
}
|
|
params.operation = OP_MERGE;
|
|
} else if (arg == "--split") {
|
|
arg_found = true;
|
|
if (params.operation != OP_NONE && params.operation != OP_SPLIT) {
|
|
throw std::invalid_argument("error: either --split or --merge can be specified, but not both");
|
|
}
|
|
params.operation = OP_SPLIT;
|
|
} else if (arg == "--split-max-tensors") {
|
|
if (++arg_idx >= argc) {
|
|
invalid_param = true;
|
|
break;
|
|
}
|
|
arg_found = true;
|
|
if (params.mode != MODE_NONE && params.mode != MODE_TENSOR) {
|
|
throw std::invalid_argument("error: either --split-max-tensors or --split-max-size can be specified, but not both");
|
|
}
|
|
params.mode = MODE_TENSOR;
|
|
params.n_split_tensors = atoi(argv[arg_idx]);
|
|
} else if (arg == "--split-max-size") {
|
|
if (++arg_idx >= argc) {
|
|
invalid_param = true;
|
|
break;
|
|
}
|
|
arg_found = true;
|
|
if (params.mode != MODE_NONE && params.mode != MODE_SIZE) {
|
|
throw std::invalid_argument("error: either --split-max-tensors or --split-max-size can be specified, but not both");
|
|
}
|
|
params.mode = MODE_SIZE;
|
|
params.n_bytes_split = split_str_to_n_bytes(argv[arg_idx]);
|
|
}
|
|
|
|
if (!arg_found) {
|
|
throw std::invalid_argument("error: unknown argument: " + arg);
|
|
}
|
|
}
|
|
|
|
// the operation is split if not specified
|
|
if (params.operation == OP_NONE) {
|
|
params.operation = OP_SPLIT;
|
|
}
|
|
// the split mode is by tensor if not specified
|
|
if (params.mode == MODE_NONE) {
|
|
params.mode = MODE_TENSOR;
|
|
}
|
|
|
|
if (invalid_param) {
|
|
throw std::invalid_argument("error: invalid parameter for argument: " + arg);
|
|
}
|
|
|
|
if (argc - arg_idx != 2) {
|
|
throw std::invalid_argument("error: bad arguments");
|
|
}
|
|
|
|
params.input = argv[arg_idx++];
|
|
params.output = argv[arg_idx++];
|
|
}
|
|
|
|
static bool split_params_parse(int argc, const char ** argv, split_params & params) {
|
|
bool result = true;
|
|
try {
|
|
split_params_parse_ex(argc, argv, params);
|
|
}
|
|
catch (const std::invalid_argument & ex) {
|
|
fprintf(stderr, "%s\n", ex.what());
|
|
split_print_usage(argv[0]);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void zeros(std::ofstream & file, size_t n) {
|
|
char zero = 0;
|
|
for (size_t i = 0; i < n; ++i) {
|
|
file.write(&zero, 1);
|
|
}
|
|
}
|
|
|
|
static void ensure_output_directory(const std::string & filepath) {
|
|
std::filesystem::path p(filepath);
|
|
if (p.has_parent_path()) {
|
|
std::error_code ec;
|
|
std::filesystem::create_directories(p.parent_path(), ec);
|
|
if (ec) {
|
|
fprintf(stderr, "Failed to create directory '%s': %s\n", p.parent_path().string().c_str(), ec.message().c_str());
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
|
|
struct split_strategy {
|
|
const split_params params;
|
|
std::ifstream & f_input;
|
|
struct gguf_context * ctx_gguf;
|
|
struct ggml_context * ctx_meta = NULL;
|
|
const int n_tensors;
|
|
|
|
// one ctx_out per one output file
|
|
std::vector<struct gguf_context *> ctx_outs;
|
|
|
|
// temporary buffer for reading in tensor data
|
|
std::vector<uint8_t> read_buf;
|
|
|
|
split_strategy(const split_params & params,
|
|
std::ifstream & f_input,
|
|
struct gguf_context * ctx_gguf,
|
|
struct ggml_context * ctx_meta) :
|
|
params(params),
|
|
f_input(f_input),
|
|
ctx_gguf(ctx_gguf),
|
|
ctx_meta(ctx_meta),
|
|
n_tensors(gguf_get_n_tensors(ctx_gguf)) {
|
|
|
|
// because we need to know list of tensors for each file in advance, we will build all the ctx_out for all output splits
|
|
int i_split = -1;
|
|
struct gguf_context * ctx_out = NULL;
|
|
auto new_ctx_out = [&](bool allow_no_tensors) {
|
|
i_split++;
|
|
if (ctx_out != NULL) {
|
|
if (gguf_get_n_tensors(ctx_out) == 0 && !allow_no_tensors) {
|
|
fprintf(stderr, "error: one of the splits has 0 tensors. Maybe size or tensors limit is too small\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
ctx_outs.push_back(ctx_out);
|
|
}
|
|
ctx_out = gguf_init_empty();
|
|
// Save all metadata in first split only
|
|
if (i_split == 0) {
|
|
gguf_set_kv(ctx_out, ctx_gguf);
|
|
}
|
|
gguf_set_val_u16(ctx_out, LLM_KV_SPLIT_NO, i_split);
|
|
gguf_set_val_u16(ctx_out, LLM_KV_SPLIT_COUNT, 0); // placeholder
|
|
gguf_set_val_i32(ctx_out, LLM_KV_SPLIT_TENSORS_COUNT, n_tensors);
|
|
};
|
|
|
|
// initialize ctx_out for the first split
|
|
new_ctx_out(false);
|
|
|
|
// skip first split if no_tensor_first_split is set
|
|
if (params.no_tensor_first_split) {
|
|
new_ctx_out(true);
|
|
}
|
|
|
|
// process tensors one by one
|
|
size_t curr_tensors_size = 0; // current size by counting only tensors size (without metadata)
|
|
for (int i = 0; i < n_tensors; ++i) {
|
|
struct ggml_tensor * t = ggml_get_tensor(ctx_meta, gguf_get_tensor_name(ctx_gguf, i));
|
|
// calculate the "imaginary" size = the current size + next tensor size
|
|
size_t n_bytes = GGML_PAD(ggml_nbytes(t), GGUF_DEFAULT_ALIGNMENT);
|
|
size_t next_tensors_size = curr_tensors_size + n_bytes;
|
|
if (should_split(i, next_tensors_size)) {
|
|
new_ctx_out(false);
|
|
curr_tensors_size = n_bytes;
|
|
} else {
|
|
curr_tensors_size = next_tensors_size;
|
|
}
|
|
gguf_add_tensor(ctx_out, t);
|
|
}
|
|
|
|
// push the last ctx_out
|
|
ctx_outs.push_back(ctx_out);
|
|
|
|
// set the correct n_split for all ctx_out
|
|
for (auto & ctx : ctx_outs) {
|
|
gguf_set_val_u16(ctx, LLM_KV_SPLIT_COUNT, ctx_outs.size());
|
|
}
|
|
}
|
|
|
|
~split_strategy() {
|
|
for (auto & ctx_out : ctx_outs) {
|
|
gguf_free(ctx_out);
|
|
}
|
|
}
|
|
|
|
bool should_split(int i_tensor, size_t next_size) {
|
|
if (params.mode == MODE_SIZE) {
|
|
// split by max size per file
|
|
return next_size > params.n_bytes_split;
|
|
} else if (params.mode == MODE_TENSOR) {
|
|
// split by number of tensors per file
|
|
return i_tensor > 0 && i_tensor < n_tensors && i_tensor % params.n_split_tensors == 0;
|
|
}
|
|
// should never happen
|
|
GGML_ABORT("invalid mode");
|
|
}
|
|
|
|
void print_info() {
|
|
printf("n_split: %ld\n", ctx_outs.size());
|
|
int i_split = 0;
|
|
for (auto & ctx_out : ctx_outs) {
|
|
// re-calculate the real gguf size for each split (= metadata size + total size of all tensors)
|
|
size_t total_size = gguf_get_meta_size(ctx_out);
|
|
for (int i = 0; i < gguf_get_n_tensors(ctx_out); ++i) {
|
|
struct ggml_tensor * t = ggml_get_tensor(ctx_meta, gguf_get_tensor_name(ctx_out, i));
|
|
total_size += ggml_nbytes(t);
|
|
}
|
|
total_size = total_size / 1000 / 1000; // convert to megabytes
|
|
printf("split %05d: n_tensors = %d, total_size = %ldM\n", i_split + 1, gguf_get_n_tensors(ctx_out), total_size);
|
|
i_split++;
|
|
}
|
|
}
|
|
|
|
void write() {
|
|
int i_split = 0;
|
|
int n_split = ctx_outs.size();
|
|
std::string output_prefix = params.output;
|
|
const std::string suffix = ".gguf";
|
|
if (output_prefix.size() >= suffix.size() &&
|
|
output_prefix.compare(output_prefix.size() - suffix.size(), suffix.size(), suffix) == 0) {
|
|
output_prefix.resize(output_prefix.size() - suffix.size());
|
|
}
|
|
for (auto & ctx_out : ctx_outs) {
|
|
// construct file path
|
|
char split_path[PATH_MAX] = {0};
|
|
llama_split_path(split_path, sizeof(split_path), output_prefix.c_str(), i_split, n_split);
|
|
|
|
ensure_output_directory(split_path);
|
|
|
|
// open the output file
|
|
printf("Writing file %s ... ", split_path);
|
|
fflush(stdout);
|
|
std::ofstream fout = std::ofstream(split_path, std::ios::binary);
|
|
fout.exceptions(std::ofstream::failbit); // fail fast on write errors
|
|
|
|
// write metadata
|
|
std::vector<uint8_t> data(gguf_get_meta_size(ctx_out));
|
|
gguf_get_meta_data(ctx_out, data.data());
|
|
fout.write((const char *)data.data(), data.size());
|
|
|
|
// write tensors
|
|
for (int i = 0; i < gguf_get_n_tensors(ctx_out); ++i) {
|
|
// read tensor meta and prepare buffer
|
|
const char * t_name = gguf_get_tensor_name(ctx_out, i);
|
|
struct ggml_tensor * t = ggml_get_tensor(ctx_meta, t_name);
|
|
auto n_bytes = ggml_nbytes(t);
|
|
read_buf.resize(n_bytes);
|
|
|
|
// calculate offset
|
|
auto i_tensor_in = gguf_find_tensor(ctx_gguf, t_name); // idx of tensor in the input file
|
|
auto offset = gguf_get_data_offset(ctx_gguf) + gguf_get_tensor_offset(ctx_gguf, i_tensor_in);
|
|
|
|
// copy tensor from input to output file
|
|
copy_file_to_file(f_input, fout, offset, n_bytes);
|
|
zeros(fout, GGML_PAD(n_bytes, GGUF_DEFAULT_ALIGNMENT) - n_bytes);
|
|
}
|
|
|
|
printf("done\n");
|
|
// close the file
|
|
fout.close();
|
|
i_split++;
|
|
}
|
|
}
|
|
|
|
void copy_file_to_file(std::ifstream & f_in, std::ofstream & f_out, const size_t in_offset, const size_t len) {
|
|
// TODO: detect OS and use copy_file_range() here for better performance
|
|
if (read_buf.size() < len) {
|
|
read_buf.resize(len);
|
|
}
|
|
f_in.seekg(in_offset);
|
|
f_in.read((char *)read_buf.data(), len);
|
|
f_out.write((const char *)read_buf.data(), len);
|
|
}
|
|
};
|
|
|
|
static void gguf_split(const split_params & split_params) {
|
|
struct ggml_context * ctx_meta = NULL;
|
|
|
|
struct gguf_init_params params = {
|
|
/*.no_alloc = */ true,
|
|
/*.ctx = */ &ctx_meta,
|
|
};
|
|
|
|
std::ifstream f_input(split_params.input.c_str(), std::ios::binary);
|
|
if (!f_input.is_open()) {
|
|
fprintf(stderr, "%s: failed to open input GGUF from %s\n", __func__, split_params.input.c_str());
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
auto * ctx_gguf = gguf_init_from_file(split_params.input.c_str(), params);
|
|
if (!ctx_gguf) {
|
|
fprintf(stderr, "%s: failed to load input GGUF from %s\n", __func__, split_params.input.c_str());
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// prepare the strategy
|
|
split_strategy strategy(split_params, f_input, ctx_gguf, ctx_meta);
|
|
int n_split = strategy.ctx_outs.size();
|
|
strategy.print_info();
|
|
|
|
if (!split_params.dry_run) {
|
|
// write all output splits
|
|
strategy.write();
|
|
}
|
|
|
|
// done, clean up
|
|
gguf_free(ctx_gguf);
|
|
f_input.close();
|
|
|
|
fprintf(stderr, "%s: %d gguf split written with a total of %d tensors.\n",
|
|
__func__, n_split, strategy.n_tensors);
|
|
}
|
|
|
|
static void gguf_merge(const split_params & split_params) {
|
|
fprintf(stderr, "%s: %s -> %s\n",
|
|
__func__, split_params.input.c_str(),
|
|
split_params.output.c_str());
|
|
int n_split = 1;
|
|
int total_tensors = 0;
|
|
|
|
ensure_output_directory(split_params.output);
|
|
|
|
// avoid overwriting existing output file
|
|
if (std::ifstream(split_params.output.c_str())) {
|
|
fprintf(stderr, "%s: output file %s already exists\n", __func__, split_params.output.c_str());
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
std::ofstream fout(split_params.output.c_str(), std::ios::binary);
|
|
fout.exceptions(std::ofstream::failbit); // fail fast on write errors
|
|
|
|
auto * ctx_out = gguf_init_empty();
|
|
|
|
std::vector<uint8_t> read_data;
|
|
std::vector<ggml_context *> ctx_metas;
|
|
std::vector<gguf_context *> ctx_ggufs;
|
|
|
|
char split_path[PATH_MAX] = {0};
|
|
strncpy(split_path, split_params.input.c_str(), sizeof(split_path) - 1);
|
|
char split_prefix[PATH_MAX] = {0};
|
|
|
|
// First pass to find KV and tensors metadata
|
|
for (int i_split = 0; i_split < n_split; i_split++) {
|
|
struct ggml_context * ctx_meta = NULL;
|
|
|
|
struct gguf_init_params params = {
|
|
/*.no_alloc = */ true,
|
|
/*.ctx = */ &ctx_meta,
|
|
};
|
|
|
|
if (i_split > 0) {
|
|
llama_split_path(split_path, sizeof(split_path), split_prefix, i_split, n_split);
|
|
}
|
|
fprintf(stderr, "%s: reading metadata %s ...", __func__, split_path);
|
|
|
|
auto * ctx_gguf = gguf_init_from_file(split_path, params);
|
|
if (!ctx_gguf) {
|
|
fprintf(stderr, "\n%s: failed to load input GGUF from %s\n", __func__, split_params.input.c_str());
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
ctx_ggufs.push_back(ctx_gguf);
|
|
ctx_metas.push_back(ctx_meta);
|
|
|
|
if (i_split == 0) {
|
|
auto key_n_split = gguf_find_key(ctx_gguf, LLM_KV_SPLIT_COUNT);
|
|
if (key_n_split < 0) {
|
|
fprintf(stderr,
|
|
"\n%s: input file does not contain %s metadata\n",
|
|
__func__,
|
|
LLM_KV_SPLIT_COUNT);
|
|
gguf_free(ctx_gguf);
|
|
ggml_free(ctx_meta);
|
|
gguf_free(ctx_out);
|
|
fout.close();
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
n_split = gguf_get_val_u16(ctx_gguf, key_n_split);
|
|
if (n_split < 1) {
|
|
fprintf(stderr,
|
|
"\n%s: input file does not contain a valid split count %d\n",
|
|
__func__,
|
|
n_split);
|
|
gguf_free(ctx_gguf);
|
|
ggml_free(ctx_meta);
|
|
gguf_free(ctx_out);
|
|
fout.close();
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// Verify the file naming and extract split_prefix
|
|
if (!llama_split_prefix(split_prefix, sizeof (split_prefix), split_path, i_split, n_split)) {
|
|
fprintf(stderr, "\n%s: unexpected input file name: %s"
|
|
" i_split=%d"
|
|
" n_split=%d\n", __func__,
|
|
split_path, i_split, n_split);
|
|
gguf_free(ctx_gguf);
|
|
ggml_free(ctx_meta);
|
|
gguf_free(ctx_out);
|
|
fout.close();
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// Do not trigger merge if we try to merge again the output
|
|
gguf_set_val_u16(ctx_gguf, LLM_KV_SPLIT_COUNT, 0);
|
|
|
|
// Set metadata from the first split
|
|
gguf_set_kv(ctx_out, ctx_gguf);
|
|
}
|
|
|
|
auto n_tensors = gguf_get_n_tensors(ctx_gguf);
|
|
for (int i_tensor = 0; i_tensor < n_tensors; i_tensor++) {
|
|
const char * t_name = gguf_get_tensor_name(ctx_gguf, i_tensor);
|
|
struct ggml_tensor * t = ggml_get_tensor(ctx_meta, t_name);
|
|
gguf_add_tensor(ctx_out, t);
|
|
}
|
|
total_tensors += n_tensors;
|
|
|
|
fprintf(stderr, "\033[3Ddone\n");
|
|
}
|
|
|
|
// placeholder for the meta data
|
|
{
|
|
auto meta_size = gguf_get_meta_size(ctx_out);
|
|
::zeros(fout, meta_size);
|
|
}
|
|
|
|
// Write tensors data
|
|
for (int i_split = 0; i_split < n_split; i_split++) {
|
|
llama_split_path(split_path, sizeof(split_path), split_prefix, i_split, n_split);
|
|
std::ifstream f_input(split_path, std::ios::binary);
|
|
if (!f_input.is_open()) {
|
|
fprintf(stderr, "%s: failed to open input GGUF from %s\n", __func__, split_path);
|
|
for (uint32_t i = 0; i < ctx_ggufs.size(); i++) {
|
|
gguf_free(ctx_ggufs[i]);
|
|
ggml_free(ctx_metas[i]);
|
|
}
|
|
gguf_free(ctx_out);
|
|
fout.close();
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
fprintf(stderr, "%s: writing tensors %s ...", __func__, split_path);
|
|
|
|
auto * ctx_gguf = ctx_ggufs[i_split];
|
|
auto * ctx_meta = ctx_metas[i_split];
|
|
|
|
auto n_tensors = gguf_get_n_tensors(ctx_gguf);
|
|
for (int i_tensor = 0; i_tensor < n_tensors; i_tensor++) {
|
|
const char * t_name = gguf_get_tensor_name(ctx_gguf, i_tensor);
|
|
struct ggml_tensor * t = ggml_get_tensor(ctx_meta, t_name);
|
|
|
|
auto n_bytes = ggml_nbytes(t);
|
|
|
|
if (read_data.size() < n_bytes) {
|
|
read_data.resize(n_bytes);
|
|
}
|
|
|
|
auto offset = gguf_get_data_offset(ctx_gguf) + gguf_get_tensor_offset(ctx_gguf, i_tensor);
|
|
f_input.seekg(offset);
|
|
f_input.read((char *)read_data.data(), n_bytes);
|
|
|
|
// write tensor data + padding
|
|
fout.write((const char *)read_data.data(), n_bytes);
|
|
zeros(fout, GGML_PAD(n_bytes, GGUF_DEFAULT_ALIGNMENT) - n_bytes);
|
|
}
|
|
|
|
gguf_free(ctx_gguf);
|
|
ggml_free(ctx_meta);
|
|
f_input.close();
|
|
fprintf(stderr, "\033[3Ddone\n");
|
|
}
|
|
|
|
{
|
|
// go back to beginning of file and write the updated metadata
|
|
fout.seekp(0);
|
|
std::vector<uint8_t> data(gguf_get_meta_size(ctx_out));
|
|
gguf_get_meta_data(ctx_out, data.data());
|
|
fout.write((const char *)data.data(), data.size());
|
|
|
|
fout.close();
|
|
gguf_free(ctx_out);
|
|
}
|
|
|
|
fprintf(stderr, "%s: %s merged from %d splits with %d tensors.\n",
|
|
__func__, split_params.output.c_str(), n_split, total_tensors);
|
|
}
|
|
|
|
int main(int argc, const char ** argv) {
|
|
split_params params;
|
|
split_params_parse(argc, argv, params);
|
|
|
|
switch (params.operation) {
|
|
case OP_SPLIT: gguf_split(params);
|
|
break;
|
|
case OP_MERGE: gguf_merge(params);
|
|
break;
|
|
default: split_print_usage(argv[0]);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
return 0;
|
|
}
|