#include "engine/framework/assets/tensor_source.h" #include "engine/framework/assets/weight_adapter.h" #include "engine/framework/debug/trace.h" #include "engine/framework/io/filesystem.h" #include "engine/framework/io/json.h" #include "engine/framework/runtime/options.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { namespace json = engine::io::json; struct PackageSpecCandidate { engine::assets::GgufEmbeddedModelSpec spec; std::string origin; int priority = 0; }; const std::unordered_map & converter_model_specs() { static const std::unordered_map specs = { #include "gguf_converter_model_specs.inc" }; return specs; } std::string lower_ascii(std::string value) { std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); return value; } bool is_gguf_path(const std::filesystem::path & path) { return lower_ascii(path.extension().string()) == ".gguf"; } // A GGUF this tool wrote carries its conversion namespaces in the tensor names // ("/"), so re-converting one — requantising a published // bf16 package to q8_0, say — can recover them instead of asking the caller to // spell out namespaces the file already knows. std::set gguf_tensor_namespaces(const std::filesystem::path & path) { std::set namespaces; const auto source = engine::assets::open_tensor_source(path); for (const auto & tensor : source->tensors()) { const auto separator = tensor.name.find('/'); namespaces.insert(separator == std::string::npos ? std::string() : tensor.name.substr(0, separator)); } return namespaces; } std::set input_namespaces(const engine::assets::TensorSourceInput & input) { if (!input.tensor_prefix.empty()) return {input.tensor_prefix}; if (is_gguf_path(input.path)) { auto namespaces = gguf_tensor_namespaces(input.path); if (!namespaces.empty()) return namespaces; } return {std::string()}; } bool excluded_sidecar(const std::filesystem::path & path, const std::filesystem::path & output) { const std::string extension = lower_ascii(path.extension().string()); return extension == ".safetensors" || extension == ".gguf" || extension == ".bin" || extension == ".pt" || extension == ".pth" || std::filesystem::weakly_canonical(path) == std::filesystem::weakly_canonical(output) || std::filesystem::file_size(path) > 64u * 1024u * 1024u; } std::set planned_sidecar_destinations(const std::filesystem::path & root, const std::filesystem::path & output, const std::vector & explicit_sidecars, bool embed_sidecars) { std::set destinations; std::error_code error; for (std::filesystem::recursive_directory_iterator it(root, error), end; !error && it != end; it.increment(error)) { if (it->is_directory() && (it->path().filename() == ".cache" || it->path().filename() == ".git")) { it.disable_recursion_pending(); continue; } if (!it->is_regular_file() || (embed_sidecars && excluded_sidecar(it->path(), output)) || (!embed_sidecars && std::filesystem::weakly_canonical(it->path()) == std::filesystem::weakly_canonical(output))) continue; destinations.insert(std::filesystem::relative(it->path(), root).lexically_normal().generic_string()); } if (error) throw std::runtime_error("failed to enumerate model sidecars: " + error.message()); std::set explicit_destinations; for (const auto & sidecar : explicit_sidecars) { const auto destination = sidecar.destination.lexically_normal(); if (!engine::io::is_existing_file(sidecar.source_path)) { throw std::runtime_error("embedded sidecar source does not exist: " + sidecar.source_path.string()); } if (destination.empty() || destination.is_absolute() || *destination.begin() == "..") { throw std::runtime_error("invalid embedded sidecar destination: " + sidecar.destination.string()); } const auto normalized = destination.generic_string(); if (!explicit_destinations.insert(normalized).second) { throw std::runtime_error("duplicate embedded sidecar destination: " + destination.generic_string()); } destinations.insert(normalized); } return destinations; } PackageSpecCandidate parse_package_spec(const std::string & text, std::string origin, int priority) { const auto root = json::parse(text); PackageSpecCandidate candidate; candidate.spec.family = json::require_string(root, "family"); if (candidate.spec.family.empty()) throw std::runtime_error("model package spec family is empty: " + origin); const auto & sources = root.require("sources").as_array(); if (sources.empty()) throw std::runtime_error("model package spec has no sources: " + origin); bool has_gguf = false; for (const auto & source : sources) { if (json::require_string(source, "format") == "gguf") has_gguf = true; } if (!has_gguf) throw std::runtime_error("model package spec has no GGUF source: " + origin); candidate.spec.json = json::stringify(root); candidate.origin = std::move(origin); candidate.priority = priority; return candidate; } void add_candidate(std::vector & candidates, PackageSpecCandidate candidate) { for (auto & existing : candidates) { if (existing.spec.family == candidate.spec.family && existing.spec.json == candidate.spec.json) { if (candidate.priority < existing.priority) existing = std::move(candidate); return; } } candidates.push_back(std::move(candidate)); } void add_spec_file(std::vector & candidates, const std::filesystem::path & path, int priority) { if (!engine::io::is_existing_file(path)) return; add_candidate(candidates, parse_package_spec(engine::io::read_text_file(path), std::filesystem::weakly_canonical(path).string(), priority)); } void add_spec_path(std::vector & candidates, const std::filesystem::path & path, const std::optional & family, int priority) { if (engine::io::is_existing_file(path)) { add_spec_file(candidates, path, priority); return; } if (!engine::io::is_existing_directory(path)) { throw std::runtime_error("model package spec path does not exist: " + path.string()); } if (family.has_value()) { const auto file = path / (*family + ".json"); if (!engine::io::is_existing_file(file)) { throw std::runtime_error("model package spec not found: " + file.string()); } add_spec_file(candidates, file, priority); return; } for (const auto & entry : std::filesystem::directory_iterator(path)) { if (entry.is_regular_file() && lower_ascii(entry.path().extension().string()) == ".json") { try { add_spec_file(candidates, entry.path(), priority); } catch (...) { // A model_specs directory may contain unrelated JSON files. Only valid // package specs participate in automatic matching. } } } } void add_discovered_spec_directories(std::vector & candidates, const std::filesystem::path & start, const std::optional & family, int priority) { auto cursor = std::filesystem::weakly_canonical(start); while (!cursor.empty()) { const auto directory = cursor / "model_specs"; if (engine::io::is_existing_directory(directory)) { if (!family.has_value() || engine::io::is_existing_file(directory / (*family + ".json"))) { add_spec_path(candidates, directory, family, priority); } } const auto parent = cursor.parent_path(); if (parent == cursor || parent.empty()) break; cursor = parent; } } void add_converter_catalog(std::vector & candidates, const std::optional & family, int priority) { for (const auto & [catalog_family, text] : converter_model_specs()) { if (family.has_value() && *family != catalog_family) continue; add_candidate(candidates, parse_package_spec(std::string(text), "converter-catalog:" + catalog_family, priority)); } } const json::Value * embedded_spec_value(const json::Value & config) { if (const auto * value = config.find("audiocpp_model_spec")) return value; if (const auto * value = config.find("model_spec")) return value; if (const auto * audiocpp = config.find("audiocpp"); audiocpp != nullptr && audiocpp->is_object()) { if (const auto * value = audiocpp->find("model_spec")) return value; if (const auto * value = audiocpp->find("package_spec")) return value; } return nullptr; } std::optional infer_family_from_config(const json::Value & config) { if (const auto * value = config.find("audiocpp_family"); value != nullptr && value->is_string()) { return value->as_string(); } if (const auto * audiocpp = config.find("audiocpp"); audiocpp != nullptr && audiocpp->is_object()) { if (const auto * value = audiocpp->find("family"); value != nullptr && value->is_string()) { return value->as_string(); } } const auto * model_type_value = config.find("model_type"); if (model_type_value == nullptr || !model_type_value->is_string()) return std::nullopt; const std::string model_type = model_type_value->as_string(); if (model_type == "qwen3_asr") { const auto * thinker = config.find("thinker_config"); if (thinker != nullptr && thinker->is_object()) { const auto * thinker_type = thinker->find("model_type"); if (thinker_type != nullptr && thinker_type->is_string() && thinker_type->as_string() == "qwen3_forced_aligner") { return "qwen3_forced_aligner"; } } return "qwen3_asr"; } if (model_type == "nemotron3_5_asr") return "nemotron_asr"; if (model_type == "vibevoice") return "vibevoice_asr"; if (model_type == "higgs_audio_3") return "higgs_audio_stt"; if (model_type == "cohere_asr") return "hviske_asr"; if (model_type == "qwen3_tts") return "qwen3_tts"; return std::nullopt; } std::optional add_model_config_spec(std::vector & candidates, const std::filesystem::path & model_root) { const auto config_path = model_root / "config.json"; if (!engine::io::is_existing_file(config_path)) return std::nullopt; const auto config = json::parse_file(config_path); if (const auto * value = embedded_spec_value(config)) { if (value->is_object()) { add_candidate(candidates, parse_package_spec(json::stringify(*value), config_path.string() + "#model_spec", 1)); } else if (value->is_string()) { const std::string stored = value->as_string(); const size_t first_content = stored.find_first_not_of(" \t\r\n"); if (first_content == std::string::npos) { throw std::runtime_error("config.json model_spec string is empty"); } if (stored[first_content] == '{') { add_candidate(candidates, parse_package_spec(stored, config_path.string() + "#model_spec", 1)); } else { const auto stored_path = model_root / stored; if (!engine::io::is_existing_file(stored_path)) { throw std::runtime_error("config.json model_spec file does not exist: " + stored_path.string()); } add_spec_file(candidates, stored_path, 1); } } else { throw std::runtime_error("config.json model_spec must be an object, JSON " "string, or relative path"); } } return infer_family_from_config(config); } const json::Value & require_gguf_source(const json::Value & spec) { for (const auto & source : spec.require("sources").as_array()) { if (json::require_string(source, "format") == "gguf") return source; } throw std::runtime_error("model package spec has no GGUF source"); } std::string tensor_prefix(const json::Value & value) { if (value.is_string()) return {}; const auto * prefix = value.find("prefix"); return prefix == nullptr ? std::string() : prefix->as_string(); } std::optional required_destination(const json::Value & source, const std::string & reference) { const size_t separator = reference.find(':'); if (separator == std::string::npos || separator == 0) { throw std::runtime_error("invalid package resource reference: " + reference); } const std::string root_id = reference.substr(0, separator); const std::string relative = reference.substr(separator + 1); const auto & roots = source.require("roots").as_object(); const auto root = roots.find(root_id); if (root == roots.end()) throw std::runtime_error("unknown package root: " + root_id); if (root->second.as_string() == "$gguf") return std::nullopt; const auto destination = (std::filesystem::path(root->second.as_string()) / relative).lexically_normal(); if (destination.is_absolute() || destination.empty() || *destination.begin() == "..") { throw std::runtime_error("unsafe GGUF package resource destination: " + destination.string()); } return destination.generic_string(); } std::vector validate_candidate(const PackageSpecCandidate & candidate, const std::set & actual_prefixes, const std::set & sidecars, bool reconversion) { std::vector errors; try { const auto spec = json::parse(candidate.spec.json); const auto & source = require_gguf_source(spec); std::set expected_prefixes; for (const auto & [unused, value] : source.require("tensors").as_object()) { (void)unused; expected_prefixes.insert(tensor_prefix(value)); } // A namespace in `optional_tensors` is one the family's packages may or // may not ship — an ACE-Step XL DiT against a turbo-only package. It // cannot be required of every conversion, but a conversion that does // supply it is building exactly the package the spec describes, so it // is not unexpected either. std::set optional_prefixes; if (const auto * optional_tensors = source.find("optional_tensors"); optional_tensors != nullptr && !optional_tensors->is_null()) { for (const auto & [unused, value] : optional_tensors->as_object()) { (void)unused; optional_prefixes.insert(tensor_prefix(value)); } } // Requiring every namespace catches a fresh conversion that forgot an // input. Re-converting a finished package proves nothing of the sort: // its namespaces are already exactly what that package ships, and a // package legitimately built with `--exclude-prefix` (an ACE-Step XL // GGUF carries no turbo or base DiT) would otherwise be impossible to // re-quantise even though the runtime loads it happily. if (!reconversion) { for (const auto & prefix : expected_prefixes) { if (actual_prefixes.find(prefix) == actual_prefixes.end()) { errors.push_back("missing tensor namespace '" + (prefix.empty() ? std::string("") : prefix) + "'"); } } } for (const auto & prefix : actual_prefixes) { if (expected_prefixes.find(prefix) == expected_prefixes.end() && optional_prefixes.find(prefix) == optional_prefixes.end()) { errors.push_back("unexpected tensor namespace '" + (prefix.empty() ? std::string("") : prefix) + "'"); } } if (const auto * files = source.find("files")) { for (const auto & [id, value] : files->as_object()) { const auto destination = required_destination(source, value.as_string()); if (destination.has_value() && sidecars.find(*destination) == sidecars.end()) { errors.push_back("missing required sidecar '" + id + "' at " + *destination); } } } } catch (const std::exception & error) { errors.push_back(error.what()); } return errors; } struct SidecarPlan { std::filesystem::path root; // Carried across by name rather than rediscovered by walking `root`. A // directory walk applies excluded_sidecar(), which drops .bin, .pt, .pth and // anything over 64 MiB — right for a source tree full of weights, wrong for // files that were already chosen as a finished package's sidecars. Kokoro's // voices are voices/*.bin, so a re-encode silently produced a package with // none of its 54 voices while reporting embedded_sidecars=true. std::vector carried; // The package being re-emitted, if any. The end-of-run check reads its sidecar // list again from the file rather than trusting `carried`, so a bug in the // carrying is caught by the check instead of hidden by it. std::filesystem::path reused_from; }; // What the conversion embeds, and where it looks for anything else. The small // files (configs, tokenizers) that belong in the output: `--root` wins, and so // does an explicit `--sidecar` set. Otherwise a GGUF input's own embedded copies // are used — for a re-encode they are exactly the files that package ships, // where the directory the file happens to sit in is only a guess. Everything // else keeps using that directory, which is what every safetensors conversion // does. SidecarPlan plan_sidecars(const std::filesystem::path & requested, const std::vector & inputs, const std::vector & explicit_sidecars, bool embed_sidecars) { if (!requested.empty()) return {std::filesystem::weakly_canonical(requested), {}, {}}; const auto parent = std::filesystem::weakly_canonical(inputs.front().path.parent_path()); if (!embed_sidecars || !explicit_sidecars.empty()) return {parent, {}, {}}; for (const auto & input : inputs) { if (!is_gguf_path(input.path) || !engine::assets::gguf_has_embedded_sidecars(input.path)) continue; const auto materialized = engine::assets::materialize_gguf_sidecars(input.path); // ⚠ The names come from the GGUF, not from a walk of `materialized`. That // directory is a cache keyed by path+size+mtime and shared between runs, so // walking it would carry across anything that happened to be sitting in it. // The file is the authority on what the package contains. std::vector carried; for (const auto & name : engine::assets::gguf_embedded_sidecar_names(input.path)) { const auto destination = std::filesystem::path(name).lexically_normal(); const auto source = materialized / destination; if (!engine::io::is_existing_file(source)) { throw std::runtime_error("sidecar '" + name + "' embedded in " + input.path.string() + " did not materialise at " + source.string()); } carried.push_back({source, destination}); } std::cerr << "note: reusing the " << carried.size() << " sidecars embedded in " << input.path.string() << "; pass --root to override\n"; return {materialized, std::move(carried), input.path}; } return {parent, {}, {}}; } PackageSpecCandidate select_package_spec(const std::vector & inputs, const std::filesystem::path & model_root, const std::filesystem::path & output, const std::vector & explicit_sidecars, const std::optional & requested_spec, std::optional family, bool embed_sidecars) { // Every input is a GGUF this tool wrote for one family: the conversion is a // re-encode of a finished package rather than an assembly of a new one. const bool reconversion = !inputs.empty() && std::all_of(inputs.begin(), inputs.end(), [](const engine::assets::TensorSourceInput & input) { return is_gguf_path(input.path) && engine::assets::read_gguf_embedded_model_spec(input.path).has_value(); }); std::vector candidates; if (requested_spec.has_value()) { add_spec_path(candidates, *requested_spec, family, 0); } else { const size_t config_candidate_count = candidates.size(); // A GGUF input states its own family and package spec. Trusting that is // both more accurate than guessing from the catalog and safer: without // it, a single-namespace GGUF matches whichever unrelated family's spec // happens to accept one unnamed namespace, and the conversion is // silently stamped with that family. for (const auto & input : inputs) { if (!is_gguf_path(input.path)) continue; const auto embedded = engine::assets::read_gguf_embedded_model_spec(input.path); if (!embedded.has_value()) continue; add_candidate(candidates, parse_package_spec(embedded->json, "embedded:" + input.path.string(), 0)); if (!family.has_value()) family = embedded->family; } const auto config_family = add_model_config_spec(candidates, model_root); if (!family.has_value()) family = config_family; // An explicitly requested family that none of those specs describes still // falls through to the catalog, the way it did before they existed. const bool describes_requested_family = std::any_of(candidates.begin(), candidates.end(), [&family](const PackageSpecCandidate & candidate) { return !family.has_value() || candidate.spec.family == *family; }); if (candidates.size() == config_candidate_count || !describes_requested_family) { if (engine::io::is_existing_file(model_root / "model_spec.json")) { add_spec_file(candidates, model_root / "model_spec.json", 2); } if (engine::io::is_existing_directory(model_root / "model_specs")) { add_spec_path(candidates, model_root / "model_specs", family, 2); } add_discovered_spec_directories(candidates, std::filesystem::current_path(), family, 3); add_converter_catalog(candidates, family, 4); } } if (candidates.empty()) { throw std::runtime_error("no model package spec was found; pass --family/--model-spec, add " "model_spec to config.json, " "install model_specs/*.json, or use --allow-missing-model-spec for a " "non-runtime tensor archive"); } std::set prefixes; for (const auto & input : inputs) { for (const auto & prefix : input_namespaces(input)) { if (!prefixes.insert(prefix).second) { throw std::runtime_error("duplicate tensor namespace in conversion inputs: '" + (prefix.empty() ? std::string("") : prefix) + "'"); } } } const auto sidecars = planned_sidecar_destinations(model_root, output, explicit_sidecars, embed_sidecars); std::vector>> matches; std::vector>> rejected; std::optional selected_priority; for (const auto & candidate : candidates) { if (family.has_value() && candidate.spec.family != *family) continue; if (!selected_priority.has_value() || candidate.priority < *selected_priority) selected_priority = candidate.priority; } if (!selected_priority.has_value()) { throw std::runtime_error("no model package spec was found for family '" + (family.has_value() ? *family : std::string("")) + "'"); } for (const auto & candidate : candidates) { if ((family.has_value() && candidate.spec.family != *family) || candidate.priority != *selected_priority) continue; auto errors = validate_candidate(candidate, prefixes, sidecars, reconversion); if (errors.empty()) matches.push_back({candidate, {}}); else rejected.push_back({candidate.spec.family + " (" + candidate.origin + ")", std::move(errors)}); } if (matches.empty()) { std::ostringstream message; message << "no model package spec matches the conversion inputs"; if (family.has_value()) message << " for family '" << *family << "'"; for (const auto & [name, errors] : rejected) { message << "\n " << name << ':'; for (const auto & error : errors) message << "\n - " << error; } throw std::runtime_error(message.str()); } if (matches.size() != 1) { std::ostringstream message; message << "multiple model package specs match:"; for (const auto & match : matches) message << ' ' << match.first.spec.family; message << "; pass --family or --model-spec explicitly"; throw std::runtime_error(message.str()); } return matches.front().first; } void print_usage() { std::cout << "Usage: audiocpp_gguf --input [namespace=] [--input " "namespace= ...] " "--output --type " " " "[--family ] [--model-spec ] " "[--root ] [--sidecar =] " "[--bnb-nf4-type q8_0] [--exclude-prefix ] " "[--keep-type *=] " "[--fold-weight-norm *] " "[--weight-adapter ] [--adapter-strength ] " "[--adapter-merge-mode delta_f32|rankwise_f32|rounded_bf16_delta] " "[--log] " "[--overwrite] [--no-sidecars] " "[--allow-missing-model-spec]\n" << " audiocpp_gguf --inspect \n"; } } // namespace int main(int argc, char ** argv) { try { std::vector inputs; std::vector sidecars; std::filesystem::path output; std::filesystem::path inspect_path; std::filesystem::path sidecar_root; std::optional model_spec_path; std::optional family; std::string type; engine::assets::GgufConversionOptions conversion_options; std::filesystem::path adapter_path; engine::assets::WeightAdapterReadOptions adapter_options; adapter_options.require_scaling_metadata = true; auto adapter_mode = engine::assets::WeightMergeMode::DeltaF32; bool adapter_options_set = false; bool overwrite = false; bool embed_sidecars = true; bool allow_missing_model_spec = false; for (int i = 1; i < argc; ++i) { const std::string arg = argv[i]; if (arg == "--help" || arg == "-h") { print_usage(); return 0; } if (arg == "--log") { engine::debug::configure_logging({true}); continue; } if (arg == "--overwrite") { overwrite = true; continue; } if (arg == "--no-sidecars") { embed_sidecars = false; continue; } if (arg == "--allow-missing-model-spec") { allow_missing_model_spec = true; continue; } if ((arg == "--input" || arg == "--output" || arg == "--type" || arg == "--inspect" || arg == "--root" || arg == "--sidecar" || arg == "--family" || arg == "--model-spec" || arg == "--model-spec-override" || arg == "--bnb-nf4-type" || arg == "--exclude-prefix" || arg == "--keep-type" || arg == "--weight-adapter" || arg == "--adapter-strength" || arg == "--adapter-merge-mode" || arg == "--fold-weight-norm") && i + 1 < argc) { const std::string value = argv[++i]; if (arg == "--input") { const auto separator = value.find('='); if (separator == std::string::npos) inputs.push_back({value, ""}); else inputs.push_back({value.substr(separator + 1), value.substr(0, separator)}); } else if (arg == "--output") output = value; else if (arg == "--type") type = value; else if (arg == "--weight-adapter") adapter_path = std::filesystem::u8path(value); else if (arg == "--adapter-strength") { adapter_options.strength = *engine::runtime::parse_finite_float_option( {{"adapter_strength", value}}, {"adapter_strength"}); adapter_options_set = true; } else if (arg == "--adapter-merge-mode") { if (value == "delta_f32") adapter_mode = engine::assets::WeightMergeMode::DeltaF32; else if (value == "rankwise_f32") adapter_mode = engine::assets::WeightMergeMode::RankWiseF32; else if (value == "rounded_bf16_delta") adapter_mode = engine::assets::WeightMergeMode::RoundedBF16Delta; else throw std::runtime_error("unsupported adapter merge mode: " + value); adapter_options_set = true; } else if (arg == "--bnb-nf4-type") conversion_options.bnb_nf4_type = engine::assets::parse_tensor_storage_type(value); else if (arg == "--exclude-prefix") conversion_options.excluded_tensor_prefixes.push_back(value); else if (arg == "--fold-weight-norm") conversion_options.folded_weight_norm_patterns.push_back(value); else if (arg == "--inspect") inspect_path = value; else if (arg == "--root") sidecar_root = value; else if (arg == "--family") family = value; else if (arg == "--model-spec" || arg == "--model-spec-override") { model_spec_path = std::filesystem::path(value); } else if (arg == "--keep-type") { const auto separator = value.find('='); if (separator == std::string::npos || separator == 0 || separator + 1 == value.size()) { throw std::runtime_error("--keep-type requires *="); } conversion_options.type_overrides.push_back({ value.substr(0, separator), engine::assets::parse_tensor_storage_type(value.substr(separator + 1)), }); } else { const auto separator = value.find('='); if (separator == std::string::npos || separator == 0 || separator + 1 == value.size()) { throw std::runtime_error("--sidecar requires ="); } sidecars.push_back({value.substr(0, separator), value.substr(separator + 1)}); } continue; } throw std::runtime_error("unknown or incomplete argument: " + arg); } if (!inspect_path.empty()) { const auto source = engine::assets::open_tensor_source(inspect_path); const auto tensors = source->tensors(); size_t scalar_count = 0; std::set namespaces; for (const auto & tensor : tensors) { if (tensor.shape.empty()) ++scalar_count; const auto separator = tensor.name.find('/'); if (separator != std::string::npos) namespaces.insert(tensor.name.substr(0, separator)); } std::cout << "gguf=" << std::filesystem::weakly_canonical(inspect_path).string() << "\n"; std::cout << "tensors=" << tensors.size() << "\n"; std::cout << "rank0_scalars=" << scalar_count << "\n"; // The count, not just the boolean: a package that lost 54 of its 59 // sidecars still reports true, which is how that went unnoticed. const auto inspect_sidecars = engine::assets::gguf_embedded_sidecar_names(inspect_path); std::cout << "embedded_sidecars=" << (inspect_sidecars.empty() ? "false" : "true") << "\n"; std::cout << "embedded_sidecar_count=" << inspect_sidecars.size() << "\n"; const auto model_spec = engine::assets::read_gguf_embedded_model_spec(inspect_path); std::cout << "embedded_model_spec=" << (model_spec.has_value() ? "true" : "false") << "\n"; if (model_spec.has_value()) std::cout << "model_spec_family=" << model_spec->family << "\n"; for (const auto & value : namespaces) std::cout << "namespace=" << value << "\n"; return 0; } if (inputs.empty() || output.empty() || type.empty()) { print_usage(); return 2; } if (!embed_sidecars && !sidecars.empty()) { throw std::runtime_error("--sidecar cannot be combined with --no-sidecars"); } for (const auto & input : inputs) { if (!std::filesystem::is_regular_file(input.path)) { throw std::runtime_error("input tensor file does not exist: " + input.path.string()); } } if (std::filesystem::exists(output)) { if (!overwrite) { throw std::runtime_error("output already exists; pass --overwrite to replace it: " + output.string()); } } const auto storage_type = engine::assets::parse_tensor_storage_type(type); const auto sidecar_plan = plan_sidecars(sidecar_root, inputs, sidecars, embed_sidecars); const auto & resolved_sidecar_root = sidecar_plan.root; // Empty unless the input was a GGUF whose sidecars are being reused, which // plan_sidecars only does when the caller passed no --sidecar of its own, // so these cannot collide with an explicit destination. sidecars.insert(sidecars.end(), sidecar_plan.carried.begin(), sidecar_plan.carried.end()); std::optional embedded_model_spec; if (!allow_missing_model_spec) { embedded_model_spec = select_package_spec(inputs, resolved_sidecar_root, output, sidecars, model_spec_path, family, embed_sidecars).spec; } else { try { embedded_model_spec = select_package_spec(inputs, resolved_sidecar_root, output, sidecars, model_spec_path, family, embed_sidecars).spec; } catch (const std::exception & warning) { std::cerr << "warning: creating GGUF without model package spec: " << warning.what() << "\n"; } } if (conversion_options.bnb_nf4_type.has_value() && *conversion_options.bnb_nf4_type != engine::assets::TensorStorageType::Q8_0) { throw std::runtime_error("--bnb-nf4-type currently supports q8_0"); } if (adapter_path.empty() && adapter_options_set) { throw std::runtime_error("adapter strength/mode requires --weight-adapter"); } if (!adapter_path.empty()) { auto adapter = engine::assets::read_weight_adapter(adapter_path, adapter_options); if (adapter.updates.empty() && adapter.replacements.empty()) { throw std::runtime_error("weight adapter contains no updates or replacements"); } for (auto & entry : adapter.updates) { entry.second.merge_mode = adapter_mode; } conversion_options.weight_adapter = std::make_shared(std::move(adapter)); conversion_options.weight_adapter_merge_mode = adapter_mode; } engine::assets::convert_tensor_sources_to_gguf(inputs, output, storage_type, overwrite, embed_sidecars, resolved_sidecar_root, sidecars, embedded_model_spec, conversion_options); std::cout << "gguf=" << std::filesystem::weakly_canonical(output).string() << "\n"; std::cout << "weight_type=" << type << "\n"; std::cout << "tensor_sources=" << inputs.size() << "\n"; if (conversion_options.bnb_nf4_type.has_value()) std::cout << "bnb_nf4_type=q8_0\n"; for (const auto & prefix : conversion_options.excluded_tensor_prefixes) std::cout << "excluded_prefix=" << prefix << "\n"; std::cout << "type_overrides=" << conversion_options.type_overrides.size() << "\n"; std::cout << "folded_weight_norm_patterns=" << conversion_options.folded_weight_norm_patterns.size() << "\n"; // ⚠ READ THE FINISHED FILE BACK AND CHECK IT AGAINST WHAT WAS ASKED FOR. // This tool used to write a package missing 54 of its 59 sidecars, exit 0, // and report embedded_sidecars=true -- because "true" was read from the // output but was too coarse to notice, and the model-spec line was read // from the in-memory intent rather than from the file at all. A claim // about the output that is not read from the output is not a check. const auto written_sidecars = engine::assets::gguf_embedded_sidecar_names(output); // ⚠ The expected set is read from the INPUT package, not from the plan that // produced the output. An earlier version of this check compared against // planned_sidecar_destinations, which is built by the same filtered walk that // caused the bug -- so it agreed with a run that dropped 54 voices. A check // derived from the thing it is checking cannot fail. if (embed_sidecars && !sidecar_plan.reused_from.empty()) { const auto expected = engine::assets::gguf_embedded_sidecar_names(sidecar_plan.reused_from); const std::set expected_set(expected.begin(), expected.end()); const std::set written(written_sidecars.begin(), written_sidecars.end()); std::vector missing; std::set_difference(expected_set.begin(), expected_set.end(), written.begin(), written.end(), std::back_inserter(missing)); if (!missing.empty()) { std::ostringstream report; report << "re-encoding " << sidecar_plan.reused_from.string() << " kept " << written.size() << " of its " << expected_set.size() << " sidecars; missing:"; for (size_t i = 0; i < missing.size() && i < 8; ++i) report << "\n " << missing[i]; if (missing.size() > 8) report << "\n ... and " << (missing.size() - 8) << " more"; throw std::runtime_error(report.str()); } } // Read back, not reported from intent: a spec that failed to embed would // otherwise still print true. const auto written_spec = engine::assets::read_gguf_embedded_model_spec(output); if (embedded_model_spec.has_value() && !written_spec.has_value()) { throw std::runtime_error("model spec for family '" + embedded_model_spec->family + "' was not embedded in " + output.string()); } if (embedded_model_spec.has_value() && written_spec->family != embedded_model_spec->family) { throw std::runtime_error("embedded model spec family is '" + written_spec->family + "', expected '" + embedded_model_spec->family + "'"); } std::cout << "embedded_sidecars=" << (written_sidecars.empty() ? "false" : "true") << "\n"; std::cout << "embedded_sidecar_count=" << written_sidecars.size() << "\n"; std::cout << "embedded_model_spec=" << (written_spec.has_value() ? "true" : "false") << "\n"; if (written_spec.has_value()) { std::cout << "model_spec_family=" << written_spec->family << "\n"; } return 0; } catch (const std::exception & error) { std::cerr << "error: " << error.what() << "\n"; return 1; } }