Add multi-prompt trajectory parity replay
This commit is contained in:
parent
00ccd57ed5
commit
e73e557ee1
@ -11,6 +11,7 @@ set(KIMODO_ROOT_PARITY_MODEL "${CMAKE_CURRENT_SOURCE_DIR}/models/kimodo-smplx-rp
|
|||||||
set(KIMODO_ROOT_PARITY_FIXTURE "${CMAKE_CURRENT_SOURCE_DIR}/fixtures/smplx-zero-embedding" CACHE PATH "Fixture directory required by motion parity tests")
|
set(KIMODO_ROOT_PARITY_FIXTURE "${CMAKE_CURRENT_SOURCE_DIR}/fixtures/smplx-zero-embedding" CACHE PATH "Fixture directory required by motion parity tests")
|
||||||
set(KIMODO_LLM_TEXT_BUNDLE "${CMAKE_CURRENT_SOURCE_DIR}/generated/llm2vec-text-bundle" CACHE PATH "Native LLM2Vec GGUF component bundle required by text parity tests")
|
set(KIMODO_LLM_TEXT_BUNDLE "${CMAKE_CURRENT_SOURCE_DIR}/generated/llm2vec-text-bundle" CACHE PATH "Native LLM2Vec GGUF component bundle required by text parity tests")
|
||||||
set(KIMODO_LLM_TEXT_FIXTURE "${CMAKE_CURRENT_SOURCE_DIR}/fixtures/llm2vec-real-prompt" CACHE PATH "Captured LLM2Vec fixture required by text parity tests")
|
set(KIMODO_LLM_TEXT_FIXTURE "${CMAKE_CURRENT_SOURCE_DIR}/fixtures/llm2vec-real-prompt" CACHE PATH "Captured LLM2Vec fixture required by text parity tests")
|
||||||
|
set(KIMODO_MULTIPROMPT_FIXTURE "" CACHE PATH "Optional extracted upstream multi-prompt fixture for raw trajectory parity")
|
||||||
add_library(kimodo src/capi.cpp src/model.cpp src/gguf.cpp src/diffusion.cpp src/motion_rep.cpp src/motion_decode.cpp)
|
add_library(kimodo src/capi.cpp src/model.cpp src/gguf.cpp src/diffusion.cpp src/motion_rep.cpp src/motion_decode.cpp)
|
||||||
add_library(kimodo::kimodo ALIAS kimodo)
|
add_library(kimodo::kimodo ALIAS kimodo)
|
||||||
target_include_directories(kimodo PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>")
|
target_include_directories(kimodo PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>")
|
||||||
@ -70,6 +71,11 @@ if(EXISTS "${KIMODO_GGML_SOURCE_DIR}/CMakeLists.txt")
|
|||||||
target_link_libraries(kimodo-llm-tokenizer-test PRIVATE ggml)
|
target_link_libraries(kimodo-llm-tokenizer-test PRIVATE ggml)
|
||||||
add_executable(kimodo-llm-embedding-parity tests/llm_embedding_parity.cpp)
|
add_executable(kimodo-llm-embedding-parity tests/llm_embedding_parity.cpp)
|
||||||
target_link_libraries(kimodo-llm-embedding-parity PRIVATE ggml ggml-vulkan)
|
target_link_libraries(kimodo-llm-embedding-parity PRIVATE ggml ggml-vulkan)
|
||||||
|
add_executable(kimodo-multiprompt-fixture-parity tests/multiprompt_fixture_parity.cpp)
|
||||||
|
target_sources(kimodo-multiprompt-fixture-parity PRIVATE src/gguf.cpp src/ggml_weights.cpp src/denoiser.cpp src/motion_rep.cpp src/diffusion.cpp)
|
||||||
|
target_include_directories(kimodo-multiprompt-fixture-parity PRIVATE src)
|
||||||
|
target_link_libraries(kimodo-multiprompt-fixture-parity PRIVATE ggml ggml-vulkan)
|
||||||
|
target_compile_definitions(kimodo-multiprompt-fixture-parity PRIVATE KIMODO_HAVE_GGML=1 KIMODO_HAVE_GGML_VULKAN=1)
|
||||||
add_executable(kimodo-llm-text-session-parity
|
add_executable(kimodo-llm-text-session-parity
|
||||||
tests/llm_text_session_parity.cpp src/llm_text_encoder.cpp src/llm_tokenizer.cpp)
|
tests/llm_text_session_parity.cpp src/llm_text_encoder.cpp src/llm_tokenizer.cpp)
|
||||||
target_include_directories(kimodo-llm-text-session-parity PRIVATE src)
|
target_include_directories(kimodo-llm-text-session-parity PRIVATE src)
|
||||||
@ -140,6 +146,10 @@ if(BUILD_TESTING AND KIMODO_BUILD_TESTS)
|
|||||||
target_include_directories(kimodo-sampler2-test PRIVATE src)
|
target_include_directories(kimodo-sampler2-test PRIVATE src)
|
||||||
target_link_libraries(kimodo-sampler2-test PRIVATE kimodo ggml ggml-vulkan)
|
target_link_libraries(kimodo-sampler2-test PRIVATE kimodo ggml ggml-vulkan)
|
||||||
target_compile_definitions(kimodo-sampler2-test PRIVATE KIMODO_HAVE_GGML=1 KIMODO_HAVE_GGML_VULKAN=1)
|
target_compile_definitions(kimodo-sampler2-test PRIVATE KIMODO_HAVE_GGML=1 KIMODO_HAVE_GGML_VULKAN=1)
|
||||||
|
if(IS_DIRECTORY "${KIMODO_MULTIPROMPT_FIXTURE}")
|
||||||
|
add_test(NAME kimodo-multiprompt-fixture-parity
|
||||||
|
COMMAND kimodo-multiprompt-fixture-parity "${KIMODO_ROOT_PARITY_MODEL}" "${KIMODO_MULTIPROMPT_FIXTURE}")
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
if(KIMODO_ENABLE_FUZZERS)
|
if(KIMODO_ENABLE_FUZZERS)
|
||||||
|
|||||||
113
tests/multiprompt_fixture_parity.cpp
Normal file
113
tests/multiprompt_fixture_parity.cpp
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
// Replay the upstream multi-prompt capture with its recorded initial noise.
|
||||||
|
// This isolates the motion transition from cross-framework RNG and text-model
|
||||||
|
// differences, while checking both DDIM trajectories and the final blend.
|
||||||
|
#include "denoiser.hpp"
|
||||||
|
#include "ggml_weights.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr std::size_t features = 273;
|
||||||
|
|
||||||
|
std::vector<float> read(const std::string &path) {
|
||||||
|
std::ifstream input(path, std::ios::binary | std::ios::ate);
|
||||||
|
if (!input || input.tellg() < 0 || input.tellg() % static_cast<std::streamoff>(sizeof(float)))
|
||||||
|
throw std::runtime_error("invalid fixture tensor: " + path);
|
||||||
|
std::vector<float> value(static_cast<std::size_t>(input.tellg()) / sizeof(float));
|
||||||
|
input.seekg(0);
|
||||||
|
input.read(reinterpret_cast<char *>(value.data()), static_cast<std::streamsize>(value.size() * sizeof(float)));
|
||||||
|
if (!input) throw std::runtime_error("short fixture tensor: " + path);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct error { float max_abs = 0; double relative_l2 = 0; };
|
||||||
|
error compare(const std::vector<float> &actual, const std::vector<float> &expected) {
|
||||||
|
if (actual.size() != expected.size()) throw std::runtime_error("fixture shape mismatch");
|
||||||
|
double squared_error = 0, squared_reference = 0;
|
||||||
|
error result;
|
||||||
|
for (std::size_t i = 0; i < actual.size(); ++i) {
|
||||||
|
const float difference = actual[i] - expected[i];
|
||||||
|
result.max_abs = std::max(result.max_abs, std::abs(difference));
|
||||||
|
squared_error += static_cast<double>(difference) * difference;
|
||||||
|
squared_reference += static_cast<double>(expected[i]) * expected[i];
|
||||||
|
}
|
||||||
|
result.relative_l2 = std::sqrt(squared_error / squared_reference);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void unnormalize(std::vector<float> &motion, const std::vector<float> &global_mean,
|
||||||
|
const std::vector<float> &global_std, const std::vector<float> &body_mean,
|
||||||
|
const std::vector<float> &body_std) {
|
||||||
|
for (std::size_t row = 0; row < motion.size() / features; ++row) {
|
||||||
|
auto *value = motion.data() + row * features;
|
||||||
|
for (std::size_t d = 0; d < 5; ++d) value[d] = value[d] * global_std[d] + global_mean[d];
|
||||||
|
for (std::size_t d = 0; d < 268; ++d) value[5 + d] = value[5 + d] * body_std[d] + body_mean[d];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) try {
|
||||||
|
if (argc != 3) {
|
||||||
|
std::fprintf(stderr, "usage: %s MOTION.gguf FIXTURE_DIR\n", argv[0]);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
const std::string directory = std::string(argv[2]) + "/";
|
||||||
|
auto weights = kimodo::detail::ggml_motion_weights::load(argv[1]);
|
||||||
|
if (!weights) throw std::runtime_error(weights.error());
|
||||||
|
|
||||||
|
const auto first = kimodo::detail::sample_motion_from_noise(
|
||||||
|
**weights, read(directory + "segment_00_sampling_input_000.f32"),
|
||||||
|
read(directory + "segment_00_text_features.f32"), 30, 2, 2.F, 2.F);
|
||||||
|
if (!first) throw std::runtime_error(first.error());
|
||||||
|
const auto first_error = compare(*first, read(directory + "segment_00_sampling_output_001.f32"));
|
||||||
|
|
||||||
|
const auto heading = read(directory + "segment_01_first_heading_angle.f32");
|
||||||
|
const auto second = kimodo::detail::sample_motion_from_noise_conditioned(
|
||||||
|
**weights, read(directory + "segment_01_sampling_input_000.f32"),
|
||||||
|
read(directory + "segment_01_text_features.f32"), read(directory + "segment_01_observed_motion.f32"),
|
||||||
|
read(directory + "segment_01_motion_mask.f32"), heading.at(0), 35, 2, 2.F, 2.F);
|
||||||
|
if (!second) throw std::runtime_error(second.error());
|
||||||
|
const auto second_error = compare(*second, read(directory + "segment_01_sampling_output_001.f32"));
|
||||||
|
|
||||||
|
auto global_mean = (*weights)->f32_values("stats.global_root.mean");
|
||||||
|
auto global_std = (*weights)->f32_values("stats.global_root.std");
|
||||||
|
auto body_mean = (*weights)->f32_values("stats.body.mean");
|
||||||
|
auto body_std = (*weights)->f32_values("stats.body.std");
|
||||||
|
if (!global_mean || !global_std || !body_mean || !body_std) throw std::runtime_error("missing motion statistics");
|
||||||
|
auto stitched_first = *first;
|
||||||
|
auto stitched_second = *second;
|
||||||
|
unnormalize(stitched_first, *global_mean, *global_std, *body_mean, *body_std);
|
||||||
|
unnormalize(stitched_second, *global_mean, *global_std, *body_mean, *body_std);
|
||||||
|
constexpr std::size_t overlap = 5;
|
||||||
|
// The captured observed tensor is already translated to local origin;
|
||||||
|
// recover the world origin from the first segment's retained tail.
|
||||||
|
const float origin_x = stitched_first[(30 - overlap) * features];
|
||||||
|
const float origin_z = stitched_first[(30 - overlap) * features + 2];
|
||||||
|
for (std::size_t frame = 0; frame < 35; ++frame) {
|
||||||
|
stitched_second[frame * features] += origin_x;
|
||||||
|
stitched_second[frame * features + 2] += origin_z;
|
||||||
|
}
|
||||||
|
for (std::size_t frame = 0; frame < overlap; ++frame) {
|
||||||
|
const float alpha = 1.F - static_cast<float>(frame) / static_cast<float>(overlap - 1);
|
||||||
|
for (std::size_t d = 0; d < features; ++d)
|
||||||
|
stitched_first[(30 - overlap + frame) * features + d] =
|
||||||
|
alpha * stitched_first[(30 - overlap + frame) * features + d] +
|
||||||
|
(1.F - alpha) * stitched_second[frame * features + d];
|
||||||
|
}
|
||||||
|
stitched_first.insert(stitched_first.end(), stitched_second.begin() + static_cast<std::ptrdiff_t>(overlap * features), stitched_second.end());
|
||||||
|
const auto stitched_error = compare(stitched_first, read(directory + "stitched_motion_rep.f32"));
|
||||||
|
std::printf("segment0 max_abs=%g rel_l2=%g\nsegment1 max_abs=%g rel_l2=%g\nstitched max_abs=%g rel_l2=%g\n",
|
||||||
|
first_error.max_abs, first_error.relative_l2, second_error.max_abs, second_error.relative_l2,
|
||||||
|
stitched_error.max_abs, stitched_error.relative_l2);
|
||||||
|
return (first_error.max_abs <= 3.e-3F && second_error.max_abs <= 3.e-3F && stitched_error.max_abs <= 3.e-3F) ? 0 : 1;
|
||||||
|
} catch (const std::exception &error) {
|
||||||
|
std::fprintf(stderr, "multi-prompt fixture parity error: %s\n", error.what());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user