kimodo-mrp.ccc/src/ggml_weights.cpp
type-two 7342255ead macOS/Metal support: conditional Vulkan links, GPU via ggml device registry
- CMakeLists: wrap ggml-vulkan links in TARGET_NAME_IF_EXISTS genexes and gate
  KIMODO_HAVE_GGML_VULKAN defines on the target existing, so
  -DKIMODO_ENABLE_VULKAN=OFF builds on macOS (Metal/Accelerate via ggml defaults)
- llm_text_encoder: guard the Vulkan include/init; without Vulkan, pick any
  registered GPU device (Metal on macOS) unless KIMODO_BACKEND=cpu
- ggml_weights: same registry fallback for the motion denoiser

5s clip @100 DDIM steps: 265s (CPU) -> 30s (M5 Metal) -> 16s (M3 Ultra warm).
No NaNs; root trajectory within ~5cm of the CPU reference over a 3.7m walk.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 03:33:07 +10:00

138 lines
6.2 KiB
C++

#include "ggml_weights.hpp"
#include "gguf.hpp"
#include <algorithm>
#include <cerrno>
#include <cstdlib>
#include <fstream>
#include <limits>
#include <thread>
#include <vector>
#include <ggml.h>
#include <ggml-alloc.h>
#include <ggml-backend.h>
#include <ggml-cpu.h>
#include <gguf.h>
#if defined(KIMODO_HAVE_GGML_VULKAN)
#include <ggml-vulkan.h>
#endif
namespace kimodo::detail {
namespace {
void configure_vulkan_f32_parity() noexcept {
#if defined(__unix__)
// Kimodo's reference model is F32. Current Vulkan cooperative-matrix
// paths convert F32 inputs to FP16 on this GPU, which breaks parity.
// Keep callers free to supply their own stricter environment, but make
// the correct reference-first path the default.
setenv("GGML_VK_DISABLE_COOPMAT", "1", 0);
setenv("GGML_VK_DISABLE_COOPMAT2", "1", 0);
setenv("GGML_VK_DISABLE_F16", "1", 0);
#endif
}
// GGML's CPU backend defaults to four threads. That is a sensible library
// default, but makes a full diffusion sample use only a small fraction of a
// typical workstation. Honour an explicit cap for predictable deployment
// and otherwise use the machine's advertised concurrency.
int cpu_thread_count() noexcept {
constexpr unsigned fallback = 4;
unsigned threads = std::thread::hardware_concurrency();
if (threads == 0) threads = fallback;
if (const char *value = std::getenv("KIMODO_THREADS")) {
char *end = nullptr;
errno = 0;
const long requested = std::strtol(value, &end, 10);
if (errno == 0 && end != value && *end == '\0' && requested > 0 &&
requested <= std::numeric_limits<int>::max()) {
threads = static_cast<unsigned>(requested);
}
}
return static_cast<int>(std::min<unsigned>(threads, std::numeric_limits<int>::max()));
}
} // namespace
std::expected<std::unique_ptr<ggml_motion_weights>, std::string> ggml_motion_weights::load(std::string_view path) {
auto checked = read_gguf_header(path);
if (!checked) return std::unexpected(checked.error());
if (auto valid = validate_motion_gguf(*checked); !valid) return std::unexpected(valid.error());
auto result = std::unique_ptr<ggml_motion_weights>(new ggml_motion_weights);
result->skeleton_ = checked->strings.at("kimodo.skeleton");
result->motion_dim_ = static_cast<size_t>(checked->uints.at("kimodo.motion_dim"));
result->body_dim_ = static_cast<size_t>(checked->uints.at("kimodo.body_dim"));
gguf_init_params params{true, &result->context_};
result->gguf_ = gguf_init_from_file(std::string(path).c_str(), params);
if (!result->gguf_ || !result->context_) return std::unexpected("GGML could not load checked motion GGUF");
// Vulkan is the normal inference path. Keep the CPU backend as a
// portability fallback, including for CI systems without a Vulkan ICD.
#if defined(KIMODO_HAVE_GGML_VULKAN)
// Retain a deterministic CPU escape hatch for parity triage. It is not
// a performance mode; a captured fixture can establish whether a drift
// belongs to the GGML graph or specifically to Vulkan.
const bool force_cpu = [] {
const char *value = std::getenv("KIMODO_BACKEND");
return value && std::string_view(value) == "cpu";
}();
if (!force_cpu) {
configure_vulkan_f32_parity();
if (ggml_backend_vk_get_device_count() > 0) result->backend_ = ggml_backend_vk_init(0);
}
#else
// No Vulkan compiled in: try any registered GPU device (e.g. Metal on
// macOS), keeping KIMODO_BACKEND=cpu as the deterministic escape hatch.
const bool force_cpu = [] {
const char *value = std::getenv("KIMODO_BACKEND");
return value && std::string_view(value) == "cpu";
}();
if (!force_cpu) {
if (auto *device = ggml_backend_dev_by_type(GGML_BACKEND_DEVICE_TYPE_GPU))
result->backend_ = ggml_backend_dev_init(device, nullptr);
}
#endif
if (!result->backend_) {
result->backend_ = ggml_backend_cpu_init();
if (!result->backend_) return std::unexpected("GGML CPU backend initialization failed");
ggml_backend_cpu_set_n_threads(result->backend_, cpu_thread_count());
}
result->buffer_ = ggml_backend_alloc_ctx_tensors(result->context_, result->backend_);
if (!result->buffer_) return std::unexpected("GGML motion weight allocation failed");
std::ifstream input(std::string(path), std::ios::binary);
if (!input) return std::unexpected("cannot reopen motion GGUF");
const size_t data_start = gguf_get_data_offset(result->gguf_);
std::vector<char> scratch(8U*1024U*1024U);
for (int64_t i=0;i<gguf_get_n_tensors(result->gguf_);++i) {
auto *tensor = ggml_get_tensor(result->context_, gguf_get_tensor_name(result->gguf_, i));
if (!tensor || tensor->type != GGML_TYPE_F32) return std::unexpected("motion GGUF contains an invalid non-F32 tensor");
const size_t bytes=ggml_nbytes(tensor), offset=gguf_get_tensor_offset(result->gguf_, i);
input.seekg(static_cast<std::streamoff>(data_start+offset));
for(size_t done=0;done<bytes;) {
const size_t chunk=std::min(scratch.size(), bytes-done);
input.read(scratch.data(), static_cast<std::streamsize>(chunk));
if (!input) return std::unexpected("short tensor data in motion GGUF");
ggml_backend_tensor_set(tensor, scratch.data(), done, chunk); done+=chunk;
}
}
return result;
}
ggml_motion_weights::~ggml_motion_weights() {
if (buffer_) ggml_backend_buffer_free(buffer_);
if (gguf_) gguf_free(gguf_);
if (context_) ggml_free(context_);
if (backend_) ggml_backend_free(backend_);
}
ggml_tensor *ggml_motion_weights::tensor(std::string_view name) const {
return context_ ? ggml_get_tensor(context_, std::string(name).c_str()) : nullptr;
}
std::expected<std::vector<float>, std::string> ggml_motion_weights::f32_values(std::string_view name) const {
auto *value = tensor(name);
if (!value || value->type != GGML_TYPE_F32) return std::unexpected("missing F32 GGML tensor: " + std::string(name));
std::vector<float> result(static_cast<size_t>(ggml_nelements(value)));
ggml_backend_tensor_get(value, result.data(), 0, result.size()*sizeof(float));
return result;
}
} // namespace kimodo::detail