voice_audio_multi/tests/unittests/test_gguf_tensor_source.cpp
Fabio c36510c21e
Some checks failed
Linux Build / Linux cpu (push) Has been cancelled
Linux Build / Linux cpu-clang (push) Has been cancelled
Linux Build / Linux vulkan (push) Has been cancelled
mac-build / macOS CPU (push) Has been cancelled
Nix Build / Nix (cpu) (push) Has been cancelled
Nix Build / Nix (vulkan) (push) Has been cancelled
windows-build / Windows CPU (push) Has been cancelled
Initial import: audio.cpp with Orange Pi documentation
2026-11-04 19:05:22 +01:00

760 lines
38 KiB
C++

#include "engine/framework/model_spec/package.h"
#include "engine/framework/assets/tensor_source.h"
#include "engine/framework/assets/weight_adapter.h"
#include "engine/framework/assets/resource_bundle.h"
#include "engine/framework/io/filesystem.h"
#include "engine/framework/io/safetensors.h"
#include "engine/framework/core/execution_context.h"
#include "engine/framework/core/backend_weight_store.h"
#include "engine/framework/modules/packed_linear_weights.h"
#include "gguf.h"
#include "test_assert.h"
#include <cstring>
#include <algorithm>
#include <atomic>
#include <filesystem>
#include <fstream>
#include <future>
#include <iostream>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>
namespace {
template <typename T>
std::vector<unsigned char> bytes_for(const std::vector<T> & values) {
std::vector<unsigned char> bytes(values.size() * sizeof(T));
std::memcpy(bytes.data(), values.data(), bytes.size());
return bytes;
}
void write_rank0_safetensors_with_null_metadata(
const std::filesystem::path & path,
std::string_view name,
int64_t value) {
std::string header = "{\"__metadata__\":null,\"" + std::string(name) +
"\":{\"dtype\":\"I64\",\"shape\":[],\"data_offsets\":[0,8]}}";
while (header.size() % 8 != 0) header.push_back(' ');
const uint64_t header_size = static_cast<uint64_t>(header.size());
std::ofstream output(path, std::ios::binary);
output.write(reinterpret_cast<const char *>(&header_size), sizeof(header_size));
output.write(header.data(), static_cast<std::streamsize>(header.size()));
output.write(reinterpret_cast<const char *>(&value), sizeof(value));
}
void test_safetensors_to_gguf_roundtrip() {
const auto root = std::filesystem::temp_directory_path() / "audiocpp_gguf_tensor_source_test";
std::filesystem::remove_all(root);
std::filesystem::create_directories(root);
const auto safetensors = root / "model.safetensors";
const auto gguf = root / "model.gguf";
std::vector<float> matrix(64);
for (size_t i = 0; i < matrix.size(); ++i) matrix[i] = static_cast<float>(i) / 64.0F - 0.5F;
const std::string long_embedding_name =
"model.language_model.layers.really_long_component_name.embed_tokens.weight";
engine::io::write_safetensors_file(safetensors, {
{"projection.weight", "F32", {2, 32}, bytes_for(matrix)},
{long_embedding_name, "F32", {2, 32}, bytes_for(matrix)},
{"projection.bias", "F32", {2}, bytes_for(std::vector<float>{1.25F, -2.5F})},
{"scalar.scale", "F32", {}, bytes_for(std::vector<float>{0.5F})},
{"step", "I64", {1}, bytes_for(std::vector<int64_t>{42})},
{"num_batches_tracked", "I64", {}, bytes_for(std::vector<int64_t>{7})},
{"singleton.conv.weight", "F32", {2, 32, 1}, bytes_for(matrix)},
});
std::filesystem::create_directories(root / "tokenizer");
const std::string binary_sidecar("abc\0def", 7);
{
std::ofstream output(root / "tokenizer" / "tokenizer.model", std::ios::binary);
output.write(binary_sidecar.data(), static_cast<std::streamsize>(binary_sidecar.size()));
}
engine::assets::convert_tensor_source_to_gguf(
safetensors,
gguf,
engine::assets::TensorStorageType::Q8_0);
const auto source = engine::assets::open_tensor_source(gguf);
engine::test::require(source->has_tensor("projection.weight"), "GGUF is missing projection.weight");
engine::test::require(source->has_tensor(long_embedding_name), "GGUF lost the long logical tensor name");
engine::test::require_eq(source->require_metadata("projection.weight").dtype, std::string("q8_0"), "matrix dtype");
engine::test::require_eq(source->require_metadata(long_embedding_name).dtype, std::string("f16"), "embedding dtype");
engine::test::require(
source->require_f32("scalar.scale", std::vector<int64_t>{}) == std::vector<float>{0.5F},
"rank-0 F32 scalar value");
engine::test::require(source->require_metadata("scalar.scale").shape.empty(), "rank-0 F32 scalar shape");
engine::test::require_eq(source->require_i64_scalar("step"), int64_t{42}, "I64 scalar");
engine::test::require_eq(source->require_i64_scalar("num_batches_tracked"), int64_t{7}, "rank-0 I64 scalar");
engine::test::require(
source->require_metadata("num_batches_tracked").shape.empty(),
"GGUF did not preserve rank-0 scalar shape");
engine::test::require(
source->require_metadata("singleton.conv.weight").shape == std::vector<int64_t>({2, 32, 1}),
"GGUF lost an exact singleton tensor dimension");
const auto bias = source->require_f32("projection.bias", {2});
engine::test::require_close(bias[0], 1.25F, 0.0F, "bias[0]");
engine::test::require_close(bias[1], -2.5F, 0.0F, "bias[1]");
const auto quantized = source->require_f32("projection.weight", {2, 32});
for (size_t i = 0; i < matrix.size(); ++i) {
engine::test::require_close(quantized[i], matrix[i], 0.01F, "quantized matrix value");
}
const auto prepared = engine::assets::prepare_model_directory(gguf);
engine::test::require(prepared.standalone_gguf.has_value(), "GGUF sidecars were not detected");
engine::test::require_eq(
engine::io::read_text_file(prepared.model_root / "tokenizer" / "tokenizer.model"),
binary_sidecar,
"binary nested GGUF sidecar");
std::filesystem::remove_all(root);
}
void test_source_policy_cache_and_views() {
using namespace engine::assets;
const auto root = std::filesystem::temp_directory_path() / "audiocpp_source_policy_test";
std::filesystem::create_directories(root);
const auto file = root / "model.safetensors";
engine::io::write_safetensors_file(file, {
{"block/weight", "F32", {2, 2}, bytes_for(std::vector<float>{1, 2, 3, 4})},
{"block/step", "I64", {}, bytes_for(std::vector<int64_t>{42})},
{"norm.weight_g", "F32", {2, 1}, bytes_for(std::vector<float>{1, 2})},
{"norm.weight_v", "F32", {2, 2}, bytes_for(std::vector<float>{3, 4, 3, 4})},
});
const auto index = root / "model.safetensors.index.json";
std::ofstream(index) << R"({"weight_map":{"block/weight":"model.safetensors","block/step":"model.safetensors","norm.weight_g":"model.safetensors","norm.weight_v":"model.safetensors"}})";
const auto gguf = root / "model.gguf";
convert_tensor_source_to_gguf(file, gguf, TensorStorageType::F32, true, false);
// Bundle cache synchronization and source storage synchronization are separate opt-ins.
ResourceBundle bundle(root, ResourceBundleOptions{true});
bundle.add_file("primary", file);
bundle.add_file("alias", file);
bundle.add_tensor_source("prefix", file, "block");
const auto original = bundle.open_tensor_source("primary");
const auto shared = bundle.open_tensor_source("primary", TensorSourceOptions{true});
engine::test::require(original != shared, "synchronized resource reused a legacy cached source");
engine::test::require(bundle.open_tensor_source("alias") == original,
"legacy canonical-path cache behavior changed");
engine::test::require(bundle.open_tensor_source("alias", TensorSourceOptions{true}) == shared,
"synchronized canonical-path cache did not retain source identity");
std::vector<std::future<void>> jobs;
for (int worker = 0; worker < 8; ++worker) {
jobs.push_back(std::async(std::launch::async, [&] {
for (int wave = 0; wave < 50; ++wave) {
const auto source = bundle.open_tensor_source("prefix", TensorSourceOptions{true});
engine::test::require(source->require_i64_scalar("step") == 42, "prefixed source policy lost data");
shared->release_storage();
}
}));
}
for (auto & job : jobs) { job.get(); }
// Indexed shards and both view types retain the underlying source's storage lock.
for (const auto & path : {file, gguf, index}) {
const auto source = open_tensor_source(path, TensorSourceOptions{true});
const auto prefix = make_prefixed_tensor_source(source, "block");
const auto folded = make_weight_norm_folded_tensor_source(source, {"norm.weight"});
const auto expected = folded->require_f32("norm.weight");
auto release = std::async(std::launch::async, [&] {
for (int i = 0; i < 100; ++i) { source->release_storage(); }
});
for (int i = 0; i < 100; ++i) {
engine::test::require(prefix->require_i64_scalar("step") == 42, "indexed/prefixed scalar changed");
engine::test::require(folded->require_f32("norm.weight") == expected, "folded view lost source synchronization");
}
release.get();
}
std::filesystem::remove_all(root);
}
void test_concurrent_storage_release() {
const auto root = std::filesystem::temp_directory_path() / "audiocpp_concurrent_tensor_source_test";
std::filesystem::create_directories(root);
const auto safetensors = root / "model.safetensors";
const auto gguf = root / "model.gguf";
std::vector<float> values(262144);
for (size_t i = 0; i < values.size(); ++i) values[i] = static_cast<float>(i % 128);
const auto expected = bytes_for(values);
engine::io::write_safetensors_file(safetensors, {
{"weight", "F32", {512, 512}, expected},
{"step", "I64", {}, bytes_for(std::vector<int64_t>{42})},
});
engine::assets::convert_tensor_source_to_gguf(
safetensors, gguf, engine::assets::TensorStorageType::F32, true, false);
for (const auto & path : {safetensors, gguf}) {
const auto source = engine::assets::open_tensor_source(path, engine::assets::TensorSourceOptions{true});
source->release_storage();
std::atomic<bool> start{false};
std::atomic<bool> failed{false};
auto read = [&] {
while (!start.load()) std::this_thread::yield();
try {
for (int i = 0; i < 100; ++i) {
const auto tensor = source->require_tensor_data("weight");
if (tensor.bytes.size() != expected.size() ||
std::memcmp(tensor.bytes.data(), expected.data(), expected.size()) != 0 ||
source->require_i64_scalar("step") != 42) failed.store(true);
}
} catch (...) { failed.store(true); }
};
std::thread first(read);
std::thread second(read);
std::thread release([&] {
while (!start.load()) std::this_thread::yield();
for (int i = 0; i < 1000; ++i) {
source->release_storage();
std::this_thread::yield();
}
});
start.store(true);
first.join();
second.join();
release.join();
engine::test::require(!failed.load(), "concurrent read/release changed tensor data");
}
std::filesystem::remove_all(root);
}
void test_packed_multi_source_gguf() {
const auto root = std::filesystem::temp_directory_path() / "audiocpp_packed_gguf_test";
std::filesystem::remove_all(root);
const auto model_root = root / "model";
const auto shared_root = root / "shared";
std::filesystem::create_directories(model_root);
std::filesystem::create_directories(shared_root);
engine::io::write_safetensors_file(model_root / "gpt.safetensors", {
{"layer.weight", "F32", {2, 2}, bytes_for(std::vector<float>{1, 2, 3, 4})},
});
write_rank0_safetensors_with_null_metadata(
model_root / "campplus.safetensors", "head.bn1.num_batches_tracked", 11);
{
std::ofstream output(shared_root / "preprocessor_config.json", std::ios::binary);
output << "{\"feature_size\":128}";
}
const auto gguf = model_root / "packed.gguf";
engine::assets::convert_tensor_sources_to_gguf(
{
{model_root / "gpt.safetensors", "gpt"},
{model_root / "campplus.safetensors", "campplus"},
},
gguf,
engine::assets::TensorStorageType::F16,
false,
true,
model_root,
{{shared_root / "preprocessor_config.json", "preprocessor_config.json"}});
const auto source = engine::assets::open_tensor_source(gguf);
engine::test::require(source->has_tensor("gpt/layer.weight"), "packed GGUF lost GPT namespace");
engine::test::require(
source->require_metadata("campplus/head.bn1.num_batches_tracked").shape.empty(),
"packed GGUF lost rank-0 component scalar");
engine::test::require_eq(
source->require_i64_scalar("campplus/head.bn1.num_batches_tracked"),
int64_t{11},
"packed GGUF rank-0 scalar value");
const auto campplus = engine::assets::open_tensor_source(gguf, "campplus");
engine::test::require(
campplus->has_tensor("head.bn1.num_batches_tracked"),
"packed GGUF namespace view did not strip its component prefix");
engine::test::require_eq(
campplus->require_i64_scalar("head.bn1.num_batches_tracked"),
int64_t{11},
"packed GGUF namespace scalar value");
const auto prepared = engine::assets::prepare_model_directory(gguf);
engine::test::require_eq(
engine::io::read_text_file(prepared.model_root / "preprocessor_config.json"),
std::string("{\"feature_size\":128}"),
"explicit packed GGUF sidecar");
bool duplicate_destination_rejected = false;
try {
engine::assets::convert_tensor_sources_to_gguf(
{{model_root / "gpt.safetensors", "gpt"}},
model_root / "duplicate-sidecar.gguf",
engine::assets::TensorStorageType::F16,
false,
true,
model_root,
{
{shared_root / "preprocessor_config.json", "preprocessor_config.json"},
{shared_root / "preprocessor_config.json", "preprocessor_config.json"},
});
} catch (const std::runtime_error &) {
duplicate_destination_rejected = true;
}
engine::test::require(
duplicate_destination_rejected,
"packed GGUF accepted duplicate embedded sidecar destinations");
std::filesystem::remove_all(root);
}
void test_all_rank0_gguf() {
const auto root = std::filesystem::temp_directory_path() / "audiocpp_rank0_only_gguf_test";
std::filesystem::remove_all(root);
std::filesystem::create_directories(root);
const auto safetensors = root / "scalar.safetensors";
const auto gguf = root / "scalar.gguf";
write_rank0_safetensors_with_null_metadata(safetensors, "num_batches_tracked", 23);
engine::assets::convert_tensor_source_to_gguf(safetensors, gguf, engine::assets::TensorStorageType::F16, false,
false);
const auto source = engine::assets::open_tensor_source(gguf);
engine::test::require(source->tensors().front().shape.empty(), "rank-0-only GGUF lost scalar rank");
engine::test::require_eq(source->require_i64_scalar("num_batches_tracked"), int64_t{23},
"rank-0-only GGUF scalar value");
std::filesystem::remove_all(root);
}
void test_embedded_model_spec_roundtrip_and_precedence() {
const auto root = std::filesystem::temp_directory_path() / "audiocpp_embedded_model_spec_test";
std::filesystem::remove_all(root);
std::filesystem::create_directories(root);
const auto safetensors = root / "model.safetensors";
const auto gguf = root / "model.gguf";
const auto override_spec = root / "override.json";
engine::io::write_safetensors_file(safetensors, {
{"weight", "F32", {1}, bytes_for(std::vector<float>{1.0F})},
});
const std::string spec_json = R"json({
"family":"embedded_test",
"sources":[{
"format":"gguf",
"roots":{"weights":"$gguf"},
"tensors":{"weights":"weights:"}
}]
})json";
engine::assets::convert_tensor_sources_to_gguf({{safetensors, ""}}, gguf, engine::assets::TensorStorageType::F16,
false, false, {}, {},
engine::assets::GgufEmbeddedModelSpec{"embedded_test", spec_json});
const auto embedded = engine::assets::read_gguf_embedded_model_spec(gguf);
engine::test::require(embedded.has_value(), "GGUF lost its embedded model spec");
engine::test::require_eq(embedded->family, std::string("embedded_test"), "embedded spec family");
engine::test::require_eq(embedded->json, spec_json, "embedded spec JSON");
{
engine::model_spec::ScopedSpecOverride scope(std::nullopt, gguf);
engine::test::require_eq(engine::model_spec::default_spec_path("embedded_test"),
std::filesystem::path("@gguf") / "embedded_test.json",
"embedded GGUF spec precedence");
bool family_mismatch_rejected = false;
try {
(void)engine::model_spec::default_spec_path("another_family");
} catch (const std::runtime_error &) {
family_mismatch_rejected = true;
}
engine::test::require(family_mismatch_rejected, "embedded GGUF spec accepted a different family");
}
const auto uppercase_gguf = root / "RENAMED.GGUF";
std::filesystem::copy_file(gguf, uppercase_gguf);
{
engine::model_spec::ScopedSpecOverride scope(std::nullopt, uppercase_gguf);
const auto spec_path = engine::model_spec::default_spec_path("embedded_test");
const auto resources = engine::model_spec::load_resource_bundle(uppercase_gguf, spec_path);
engine::test::require(resources.open_tensor_source("weights")->has_tensor("weight"),
"uppercase standalone GGUF did not use its embedded package spec");
}
{
std::ofstream output(override_spec, std::ios::binary);
output << spec_json;
}
{
engine::model_spec::ScopedSpecOverride scope(override_spec, gguf);
engine::test::require_eq(engine::model_spec::default_spec_path("embedded_test"),
std::filesystem::weakly_canonical(override_spec),
"explicit package spec override precedence");
}
std::filesystem::remove_all(root);
}
// Published GGUF packages install under their release name (vevo2-q8_0.gguf, not model.gguf)
// into a directory that the WebUI and the server config hand over as the model path. That
// directory has to resolve to its GGUF, or the spec falls back to the safetensors source and
// fails on config files the GGUF package never ships (issue #113).
void test_release_named_gguf_directory_selects_the_gguf_source() {
const auto root = std::filesystem::temp_directory_path() / "audiocpp_named_gguf_directory_test";
std::filesystem::remove_all(root);
const auto model_dir = root / "Vevo2-GGUF";
std::filesystem::create_directories(model_dir);
const auto safetensors = root / "weights.safetensors";
const auto gguf = model_dir / "vevo2-q8_0.gguf";
engine::io::write_safetensors_file(safetensors, {
{"weight", "F32", {1}, bytes_for(std::vector<float>{1.0F})},
});
const std::string spec_json = R"json({
"family":"named_gguf_test",
"sources":[{
"format":"gguf",
"roots":{"weights":"$gguf"},
"tensors":{"weights":"weights:"}
},{
"format":"safetensors",
"roots":{"model":"."},
"files":{"config":"model:config.json"},
"tensors":{"weights":"model:model.safetensors"}
}]
})json";
engine::assets::convert_tensor_sources_to_gguf({{safetensors, ""}}, gguf, engine::assets::TensorStorageType::F16,
false, false, {}, {},
engine::assets::GgufEmbeddedModelSpec{"named_gguf_test", spec_json});
engine::test::require_eq(engine::assets::find_directory_gguf(model_dir).value_or(""), gguf,
"sole release-named GGUF in a model directory");
{
engine::model_spec::ScopedSpecOverride scope(std::nullopt, model_dir);
engine::test::require_eq(engine::model_spec::default_spec_path("named_gguf_test"),
std::filesystem::path("@gguf") / "named_gguf_test.json",
"embedded spec of a release-named GGUF in a model directory");
const auto resources = engine::model_spec::load_resource_bundle(
model_dir, engine::model_spec::default_spec_path("named_gguf_test"));
engine::test::require(resources.open_tensor_source("weights")->has_tensor("weight"),
"release-named GGUF directory did not select the GGUF source");
}
// model.gguf keeps winning, so a directory carrying both stays on its documented entry point.
const auto default_named = model_dir / "model.gguf";
std::filesystem::copy_file(gguf, default_named);
engine::test::require_eq(engine::assets::find_directory_gguf(model_dir).value_or(""), default_named,
"model.gguf precedence over a release-named sibling");
std::filesystem::remove(default_named);
// Two release-named GGUFs cannot be narrowed down to one; that must be said plainly rather
// than silently falling back to the safetensors source.
std::filesystem::copy_file(gguf, model_dir / "vevo2-f16.gguf");
engine::test::require(!engine::assets::find_directory_gguf(model_dir).has_value(),
"ambiguous GGUF directory resolved to a single file");
bool ambiguity_reported = false;
try {
engine::model_spec::ScopedSpecOverride scope(std::nullopt, model_dir);
(void)engine::model_spec::load_resource_bundle_for_family(model_dir, "named_gguf_test");
} catch (const std::runtime_error & error) {
const std::string message = error.what();
ambiguity_reported = message.find("contains 2 GGUF files") != std::string::npos &&
message.find("vevo2-f16.gguf") != std::string::npos &&
message.find("vevo2-q8_0.gguf") != std::string::npos;
}
engine::test::require(ambiguity_reported, "ambiguous GGUF directory did not report its candidates");
std::filesystem::remove_all(root);
}
void test_package_spec_errors_name_selected_spec() {
const auto root = std::filesystem::temp_directory_path() / "audiocpp_package_spec_error_test";
std::filesystem::remove_all(root);
std::filesystem::create_directories(root / "model");
const auto malformed_spec = root / "malformed.json";
const auto missing_file_spec = root / "missing_file.json";
{
std::ofstream output(malformed_spec, std::ios::binary);
output << R"json({"family":"broken","sources":[)json";
}
{
std::ofstream output(missing_file_spec, std::ios::binary);
output << R"json({
"family":"missing_file_test",
"sources":[{
"format":"safetensors",
"roots":{"model":"."},
"files":{"config":"model:config.json"}
}]
})json";
}
bool malformed_named = false;
try {
(void)engine::model_spec::load_resource_bundle(root / "model", malformed_spec);
} catch (const std::runtime_error & error) {
const std::string message = error.what();
malformed_named = message.find("failed to parse model spec") != std::string::npos &&
message.find(malformed_spec.string()) != std::string::npos;
}
engine::test::require(malformed_named, "malformed package spec error did not name the selected spec");
bool selected_source_named = false;
try {
(void)engine::model_spec::load_resource_bundle(root / "model", missing_file_spec);
} catch (const std::runtime_error & error) {
const std::string message = error.what();
selected_source_named = message.find("using model spec") != std::string::npos &&
message.find(missing_file_spec.string()) != std::string::npos &&
message.find("source 'safetensors'") != std::string::npos &&
message.find("missing model package file 'config'") != std::string::npos;
}
engine::test::require(selected_source_named, "resource error did not name the selected spec and source");
std::filesystem::remove_all(root);
}
// The name reader is a shortcut past the content copy, not past the validation: a
// package whose byte ranges do not fit its blob is malformed whether the caller
// wants the names or the bytes. --inspect reads the names, and used to report a
// count for a package no loader could ever read (PR #579 review).
void test_malformed_embedded_sidecars_are_rejected_by_the_name_reader() {
const auto root = std::filesystem::temp_directory_path() / "audiocpp_malformed_sidecar_test";
std::filesystem::remove_all(root);
std::filesystem::create_directories(root);
const auto safetensors = root / "model.safetensors";
const auto gguf = root / "model.gguf";
engine::io::write_safetensors_file(safetensors, {
{"weight", "F32", {1}, bytes_for(std::vector<float>{1.0F})},
});
{
std::ofstream output(root / "config.json", std::ios::binary);
output << "{}";
}
engine::assets::convert_tensor_sources_to_gguf({{safetensors, ""}}, gguf, engine::assets::TensorStorageType::F16,
false, true, root, {{root / "config.json", "config.json"}});
engine::test::require_eq(engine::assets::gguf_embedded_sidecar_names(gguf).size(), size_t{1},
"intact package sidecar names");
auto * ctx = gguf_init_from_file(gguf.string().c_str(), {true, nullptr});
engine::test::require(ctx != nullptr, "open fixture GGUF");
const uint64_t offsets[] = {0, 999};
gguf_set_arr_data(ctx, "audiocpp.embedded_files.offsets", GGUF_TYPE_UINT64, offsets, 2);
engine::test::require(gguf_write_to_file(ctx, gguf.string().c_str(), true), "write malformed fixture");
gguf_free(ctx);
bool names_rejected = false;
try {
(void)engine::assets::gguf_embedded_sidecar_names(gguf);
} catch (const std::runtime_error &) {
names_rejected = true;
}
engine::test::require(names_rejected, "name reader accepted an out-of-range sidecar byte range");
bool presence_rejected = false;
try {
(void)engine::assets::gguf_has_embedded_sidecars(gguf);
} catch (const std::runtime_error &) {
presence_rejected = true;
}
engine::test::require(presence_rejected, "presence check accepted an out-of-range sidecar byte range");
std::filesystem::remove_all(root);
}
void test_weight_adapter_conversion() {
using namespace engine;
const auto root = std::filesystem::temp_directory_path() / "audiocpp_gguf_adapter_test";
std::filesystem::remove_all(root);
std::filesystem::create_directories(root);
const auto original = root / "original.safetensors";
io::write_safetensors_file(original, {
{"projection.weight", "F32", {2, 32}, bytes_for(std::vector<float>(64, 0.25F))},
{"untouched.weight", "F32", {2, 32}, bytes_for(std::vector<float>(64, 0.5F))},
{"step", "F32", {}, bytes_for(std::vector<float>{42.0F})},
});
const auto base_path = root / "base.gguf";
assets::convert_tensor_sources_to_gguf({{original, ""}}, base_path, assets::TensorStorageType::F16,
false, false);
const auto base = assets::open_tensor_source(base_path);
const auto original_bytes = base->require_tensor_data("projection.weight").bytes;
auto adapter = std::make_shared<assets::WeightAdapter>();
adapter->updates.emplace("projection.weight", assets::WeightUpdate{
assets::LowRankWeightUpdate{std::vector<float>(32, 0.5F), {1.0F, -1.0F}, 1, 32, 2},
0.5F, assets::WeightMergeMode::DeltaF32});
const auto overlay = assets::make_weight_adapter_source(base, *adapter);
int index = 0;
for (auto type : {assets::TensorStorageType::Native, assets::TensorStorageType::F16,
assets::TensorStorageType::BF16, assets::TensorStorageType::Q8_0}) {
assets::GgufConversionOptions options;
options.weight_adapter = adapter;
const auto output = root / (std::to_string(index++) + ".gguf");
assets::convert_tensor_sources_to_gguf({{base_path, ""}}, output, type, false, false,
{}, {}, std::nullopt, options);
const auto actual = assets::open_tensor_source(output);
test::require(actual->require_tensor_data("step").bytes == base->require_tensor_data("step").bytes,
"Adapter conversion must preserve untouched scalar metadata");
for (const auto & name : {"projection.weight", "untouched.weight"}) {
const auto expected = overlay->require_tensor(name, type, {2, 32});
test::require(actual->require_tensor_data(name).bytes == expected.bytes,
"GGUF adapter must merge before dtype conversion, including orig");
}
}
test::require(base->require_tensor_data("projection.weight").bytes == original_bytes,
"GGUF adapter conversion changed base weights");
auto replacement = std::make_shared<assets::WeightAdapter>();
replacement->replacements.emplace("projection.weight",
assets::WeightReplacement{{2, 32}, std::vector<float>(64, 0.1001F)});
assets::GgufConversionOptions replacement_options;
replacement_options.weight_adapter = replacement;
replacement_options.weight_adapter_merge_mode = assets::WeightMergeMode::RoundedBF16Delta;
const auto replacement_path = root / "replacement.gguf";
assets::convert_tensor_sources_to_gguf({{base_path, ""}}, replacement_path,
assets::TensorStorageType::F32, false, false, {}, {}, std::nullopt, replacement_options);
const auto rounded = assets::open_tensor_source(replacement_path)->require_f32("projection.weight");
const float want = ggml_bf16_to_fp32(ggml_fp32_to_bf16(0.1001F));
test::require(std::all_of(rounded.begin(), rounded.end(), [want](float value) { return value == want; }),
"Replacement-only export must retain the selected BF16 merge policy");
std::filesystem::remove_all(root);
}
void test_deferred_packed_weights() {
using namespace engine;
const auto root = std::filesystem::temp_directory_path() / "audiocpp_deferred_packed_test";
std::filesystem::create_directories(root);
const auto path = root / "weights.safetensors";
std::vector<float> a(64), b(32);
for (size_t i = 0; i < a.size(); ++i) a[i] = (static_cast<float>(i) - 21) / 32;
for (size_t i = 0; i < b.size(); ++i) b[i] = (static_cast<float>(i) + 3) / 64;
io::write_safetensors_file(path, {
{"a.weight", "F32", {2, 32}, bytes_for(a)},
{"b.weight", "F32", {1, 32}, bytes_for(b)},
{"a.bias", "F32", {2}, bytes_for(std::vector<float>{1, -2})},
{"b.bias", "F32", {1}, bytes_for(std::vector<float>{3})},
});
const auto source = assets::open_tensor_source(path);
core::BackendConfig backend;
backend.type = core::BackendType::Cpu;
core::ExecutionContext execution(backend);
for (const auto type : {assets::TensorStorageType::Native, assets::TensorStorageType::F32, assets::TensorStorageType::F16,
assets::TensorStorageType::BF16, assets::TensorStorageType::Q8_0}) {
core::BackendWeightStore eager(execution.backend(), backend.type, "test.eager", 1024 * 1024);
core::BackendWeightStore deferred(execution.backend(), backend.type, "test.deferred", 1024 * 1024);
modules::PackedLinearWeightsConfig config{32,
{{"a.weight", "a.bias", 2}, {"b.weight", "b.bias", 1}}, true};
const auto expected = modules::PackedLinearWeightsBuilder(config).build(eager, *source, type);
config.defer_upload = true;
const auto actual = modules::PackedLinearWeightsBuilder(config).build(deferred, *source, type);
eager.upload();
deferred.upload();
for (const auto & pair : {std::make_pair(expected.weight.tensor, actual.weight.tensor),
std::make_pair(expected.bias->tensor, actual.bias->tensor)}) {
const size_t bytes = ggml_nbytes(pair.first);
test::require(bytes == ggml_nbytes(pair.second), "deferred packed tensor size");
std::vector<std::byte> lhs(bytes), rhs(bytes);
ggml_backend_tensor_get(pair.first, lhs.data(), 0, bytes);
ggml_backend_tensor_get(pair.second, rhs.data(), 0, bytes);
test::require(lhs == rhs, "deferred packed weights must preserve exact bytes");
}
}
for (const auto & parts : std::vector<std::vector<core::BackendWeightStore::TensorPart>>{
{}, {{"a.weight", {3, 32}}},
{{"a.weight", {2, 32}}, {"b.weight", {1, 64}}}}) {
core::BackendWeightStore invalid(execution.backend(), backend.type, "test.invalid", 1024 * 1024);
bool rejected = false;
try {
invalid.load_tensor(*source, parts, assets::TensorStorageType::F32);
} catch (const std::runtime_error &) {
rejected = true;
}
test::require(rejected, "invalid packed tensor sources must be rejected before upload");
}
std::filesystem::remove_all(root);
}
class CountingTensorSource final : public engine::assets::TensorSource {
public:
std::filesystem::path path{"counting://weight_norm"};
mutable int direct_uploads = 0;
const std::filesystem::path & source_path() const noexcept override { return path; }
bool has_tensor(std::string_view name) const noexcept override {
return name == "plain.weight" || name == "norm.weight_v" || name == "norm.weight_g";
}
engine::assets::TensorMetadata require_metadata(std::string_view name) const override {
if (name == "plain.weight") return {"plain.weight", "F32", {2}};
if (name == "norm.weight_v") return {"norm.weight_v", "F32", {2, 2}};
if (name == "norm.weight_g") return {"norm.weight_g", "F32", {2}};
throw std::runtime_error("missing tensor: " + std::string(name));
}
std::vector<engine::assets::TensorMetadata> tensors() const override {
return {require_metadata("plain.weight"), require_metadata("norm.weight_v"), require_metadata("norm.weight_g")};
}
engine::assets::RawTensorData require_tensor_data(std::string_view name) const override {
const auto values = require_f32(name, std::nullopt);
engine::assets::RawTensorData data;
data.metadata = require_metadata(name);
data.bytes.resize(values.size() * sizeof(float));
std::memcpy(data.bytes.data(), values.data(), data.bytes.size());
return data;
}
std::vector<float> require_f32(
std::string_view name,
const std::optional<std::vector<int64_t>> & expected_shape) const override {
const auto metadata = require_metadata(name);
if (expected_shape && *expected_shape != metadata.shape) throw std::runtime_error("shape mismatch");
if (name == "plain.weight") return {3.0F, 4.0F};
if (name == "norm.weight_v") return {3.0F, 4.0F, 0.0F, 2.0F};
if (name == "norm.weight_g") return {10.0F, 6.0F};
throw std::runtime_error("missing tensor: " + std::string(name));
}
std::optional<std::vector<float>> optional_f32(
std::string_view name,
const std::optional<std::vector<int64_t>> & expected_shape) const override {
if (!has_tensor(name)) return std::nullopt;
return require_f32(name, expected_shape);
}
int64_t require_i64_scalar(std::string_view) const override { throw std::runtime_error("no scalar"); }
void set_backend_tensor(
ggml_tensor * tensor,
std::string_view name,
engine::assets::TensorStorageType type,
const std::vector<int64_t> & shape) const override {
++direct_uploads;
const auto metadata = require_metadata(name);
if (type != engine::assets::TensorStorageType::F32 || shape != metadata.shape)
throw std::runtime_error("unexpected direct upload type or shape");
const auto values = require_f32(name, shape);
ggml_backend_tensor_set(tensor, values.data(), 0, values.size() * sizeof(float));
}
};
void test_weight_norm_folded_source_uses_direct_backend_upload() {
using namespace engine;
using namespace engine::assets;
auto backing = std::make_shared<CountingTensorSource>();
const auto folded = make_weight_norm_folded_tensor_source(backing, {"norm.weight"});
core::BackendConfig config;
config.type = core::BackendType::Cpu;
config.threads = 2;
core::ExecutionContext execution(config);
core::BackendWeightStore store(execution.backend(), config.type, "test.weight_norm", 1024 * 1024);
auto plain = store.load_tensor(*folded, "plain.weight", TensorStorageType::F32, {2});
auto norm = store.load_tensor(*folded, "norm.weight", TensorStorageType::F32, {2, 2});
store.upload();
ggml_backend_synchronize(execution.backend());
test::require_eq(backing->direct_uploads, 1, "unfolded tensor must delegate its direct backend upload");
std::vector<float> plain_values(2), norm_values(4);
ggml_backend_tensor_get(plain.tensor, plain_values.data(), 0, plain_values.size() * sizeof(float));
ggml_backend_tensor_get(norm.tensor, norm_values.data(), 0, norm_values.size() * sizeof(float));
test::require(plain_values == std::vector<float>({3.0F, 4.0F}), "unfolded direct upload values");
const std::vector<float> expected{6.0F, 8.0F, 0.0F, 6.0F};
for (size_t i = 0; i < expected.size(); ++i)
test::require_close(norm_values[i], expected[i], 1e-6F, "folded backend upload value");
bool mismatch_rejected = false;
try {
(void)store.load_tensor(*folded, "norm.weight", TensorStorageType::F32, {4, 1});
} catch (const std::runtime_error &) {
mismatch_rejected = true;
}
test::require(mismatch_rejected, "folded backend upload must preserve shape validation");
}
} // namespace
int main() {
try {
engine::io::json::enable_serialized_json_parsing();
test_safetensors_to_gguf_roundtrip();
test_concurrent_storage_release();
test_source_policy_cache_and_views();
test_packed_multi_source_gguf();
test_all_rank0_gguf();
test_embedded_model_spec_roundtrip_and_precedence();
test_release_named_gguf_directory_selects_the_gguf_source();
test_package_spec_errors_name_selected_spec();
test_malformed_embedded_sidecars_are_rejected_by_the_name_reader();
test_weight_adapter_conversion();
test_deferred_packed_weights();
test_weight_norm_folded_source_uses_direct_backend_upload();
} catch (const std::exception & error) {
std::cerr << error.what() << '\n';
return 1;
}
std::cout << "gguf_tensor_source_test passed\n";
return 0;
}