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>
This commit is contained in:
type-two 2026-09-01 03:33:07 +10:00
parent f782a72367
commit 7342255ead
3 changed files with 47 additions and 25 deletions

View File

@ -39,7 +39,7 @@ if(EXISTS "${KIMODO_GGML_SOURCE_DIR}/CMakeLists.txt")
target_compile_definitions(kimodo PRIVATE KIMODO_HAVE_GGML=1) target_compile_definitions(kimodo PRIVATE KIMODO_HAVE_GGML=1)
if(KIMODO_ENABLE_VULKAN AND TARGET ggml-vulkan) if(KIMODO_ENABLE_VULKAN AND TARGET ggml-vulkan)
target_link_libraries(kimodo PRIVATE ggml-vulkan) target_link_libraries(kimodo PRIVATE ggml-vulkan)
target_compile_definitions(kimodo PRIVATE KIMODO_HAVE_GGML_VULKAN=1) target_compile_definitions(kimodo PRIVATE $<$<TARGET_EXISTS:ggml-vulkan>:KIMODO_HAVE_GGML_VULKAN=1>)
endif() endif()
target_link_libraries(kmd-inspect PRIVATE ggml) target_link_libraries(kmd-inspect PRIVATE ggml)
target_compile_definitions(kmd-inspect PRIVATE KIMODO_HAVE_GGML=1) target_compile_definitions(kmd-inspect PRIVATE KIMODO_HAVE_GGML=1)
@ -50,42 +50,42 @@ if(EXISTS "${KIMODO_GGML_SOURCE_DIR}/CMakeLists.txt")
src/gguf.cpp src/ggml_weights.cpp src/denoiser.cpp src/diffusion.cpp src/gguf.cpp src/ggml_weights.cpp src/denoiser.cpp src/diffusion.cpp
src/motion_rep.cpp src/motion_decode.cpp) src/motion_rep.cpp src/motion_decode.cpp)
target_include_directories(kmd-sample-fixture PRIVATE src) target_include_directories(kmd-sample-fixture PRIVATE src)
target_link_libraries(kmd-sample-fixture PRIVATE ggml ggml-vulkan) target_link_libraries(kmd-sample-fixture PRIVATE ggml $<TARGET_NAME_IF_EXISTS:ggml-vulkan>)
target_compile_definitions(kmd-sample-fixture PRIVATE KIMODO_HAVE_GGML=1 KIMODO_HAVE_GGML_VULKAN=1) target_compile_definitions(kmd-sample-fixture PRIVATE KIMODO_HAVE_GGML=1 $<$<TARGET_EXISTS:ggml-vulkan>:KIMODO_HAVE_GGML_VULKAN=1>)
add_executable(kmd-generate src/generate.cpp) add_executable(kmd-generate src/generate.cpp)
target_link_libraries(kmd-generate PRIVATE kimodo) target_link_libraries(kmd-generate PRIVATE kimodo)
add_executable(kmd-encode src/encode.cpp) add_executable(kmd-encode src/encode.cpp)
target_sources(kmd-encode PRIVATE src/llm_text_encoder.cpp src/llm_tokenizer.cpp) target_sources(kmd-encode PRIVATE src/llm_text_encoder.cpp src/llm_tokenizer.cpp)
target_include_directories(kmd-encode PRIVATE src) target_include_directories(kmd-encode PRIVATE src)
target_link_libraries(kmd-encode PRIVATE ggml ggml-vulkan) target_link_libraries(kmd-encode PRIVATE ggml $<TARGET_NAME_IF_EXISTS:ggml-vulkan>)
target_compile_definitions(kmd-encode PRIVATE KIMODO_HAVE_GGML=1 KIMODO_HAVE_GGML_VULKAN=1) target_compile_definitions(kmd-encode PRIVATE KIMODO_HAVE_GGML=1 $<$<TARGET_EXISTS:ggml-vulkan>:KIMODO_HAVE_GGML_VULKAN=1>)
add_executable(kimodo-llm-layer-parity tests/llm_layer_parity.cpp) add_executable(kimodo-llm-layer-parity tests/llm_layer_parity.cpp)
target_link_libraries(kimodo-llm-layer-parity PRIVATE ggml ggml-vulkan) target_link_libraries(kimodo-llm-layer-parity PRIVATE ggml $<TARGET_NAME_IF_EXISTS:ggml-vulkan>)
target_include_directories(kimodo-llm-layer-parity PRIVATE src) target_include_directories(kimodo-llm-layer-parity PRIVATE src)
target_compile_definitions(kimodo-llm-layer-parity PRIVATE KIMODO_HAVE_GGML_VULKAN=1) target_compile_definitions(kimodo-llm-layer-parity PRIVATE $<$<TARGET_EXISTS:ggml-vulkan>:KIMODO_HAVE_GGML_VULKAN=1>)
add_executable(kimodo-llm-final-norm-parity tests/llm_final_norm_parity.cpp) add_executable(kimodo-llm-final-norm-parity tests/llm_final_norm_parity.cpp)
target_link_libraries(kimodo-llm-final-norm-parity PRIVATE ggml ggml-vulkan) target_link_libraries(kimodo-llm-final-norm-parity PRIVATE ggml $<TARGET_NAME_IF_EXISTS:ggml-vulkan>)
target_compile_definitions(kimodo-llm-final-norm-parity PRIVATE KIMODO_HAVE_GGML_VULKAN=1) target_compile_definitions(kimodo-llm-final-norm-parity PRIVATE $<$<TARGET_EXISTS:ggml-vulkan>:KIMODO_HAVE_GGML_VULKAN=1>)
add_executable(kimodo-llm-tokenizer-test tests/llm_tokenizer_test.cpp src/llm_tokenizer.cpp) add_executable(kimodo-llm-tokenizer-test tests/llm_tokenizer_test.cpp src/llm_tokenizer.cpp)
target_include_directories(kimodo-llm-tokenizer-test PRIVATE src) target_include_directories(kimodo-llm-tokenizer-test PRIVATE src)
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 $<TARGET_NAME_IF_EXISTS:ggml-vulkan>)
add_executable(kimodo-multiprompt-fixture-parity tests/multiprompt_fixture_parity.cpp) 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/sequence.cpp src/motion_rep.cpp src/diffusion.cpp) target_sources(kimodo-multiprompt-fixture-parity PRIVATE src/gguf.cpp src/ggml_weights.cpp src/denoiser.cpp src/sequence.cpp src/motion_rep.cpp src/diffusion.cpp)
target_include_directories(kimodo-multiprompt-fixture-parity PRIVATE src) target_include_directories(kimodo-multiprompt-fixture-parity PRIVATE src)
target_link_libraries(kimodo-multiprompt-fixture-parity PRIVATE ggml ggml-vulkan) target_link_libraries(kimodo-multiprompt-fixture-parity PRIVATE ggml $<TARGET_NAME_IF_EXISTS:ggml-vulkan>)
target_compile_definitions(kimodo-multiprompt-fixture-parity PRIVATE KIMODO_HAVE_GGML=1 KIMODO_HAVE_GGML_VULKAN=1) target_compile_definitions(kimodo-multiprompt-fixture-parity PRIVATE KIMODO_HAVE_GGML=1 $<$<TARGET_EXISTS:ggml-vulkan>:KIMODO_HAVE_GGML_VULKAN=1>)
add_executable(kimodo-stage-fixture-parity tests/stage_fixture_parity.cpp) add_executable(kimodo-stage-fixture-parity tests/stage_fixture_parity.cpp)
target_sources(kimodo-stage-fixture-parity PRIVATE src/gguf.cpp src/ggml_weights.cpp src/denoiser.cpp src/motion_rep.cpp src/diffusion.cpp) target_sources(kimodo-stage-fixture-parity PRIVATE src/gguf.cpp src/ggml_weights.cpp src/denoiser.cpp src/motion_rep.cpp src/diffusion.cpp)
target_include_directories(kimodo-stage-fixture-parity PRIVATE src) target_include_directories(kimodo-stage-fixture-parity PRIVATE src)
target_link_libraries(kimodo-stage-fixture-parity PRIVATE ggml ggml-vulkan) target_link_libraries(kimodo-stage-fixture-parity PRIVATE ggml $<TARGET_NAME_IF_EXISTS:ggml-vulkan>)
target_compile_definitions(kimodo-stage-fixture-parity PRIVATE KIMODO_HAVE_GGML=1 KIMODO_HAVE_GGML_VULKAN=1) target_compile_definitions(kimodo-stage-fixture-parity PRIVATE KIMODO_HAVE_GGML=1 $<$<TARGET_EXISTS:ggml-vulkan>: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)
target_link_libraries(kimodo-llm-text-session-parity PRIVATE ggml ggml-vulkan) target_link_libraries(kimodo-llm-text-session-parity PRIVATE ggml $<TARGET_NAME_IF_EXISTS:ggml-vulkan>)
target_compile_definitions(kimodo-llm-text-session-parity PRIVATE KIMODO_HAVE_GGML=1 KIMODO_HAVE_GGML_VULKAN=1) target_compile_definitions(kimodo-llm-text-session-parity PRIVATE KIMODO_HAVE_GGML=1 $<$<TARGET_EXISTS:ggml-vulkan>:KIMODO_HAVE_GGML_VULKAN=1>)
endif() endif()
include(CTest) include(CTest)
if(BUILD_TESTING AND KIMODO_BUILD_TESTS) if(BUILD_TESTING AND KIMODO_BUILD_TESTS)
@ -109,7 +109,7 @@ if(BUILD_TESTING AND KIMODO_BUILD_TESTS)
COMMAND kimodo-llm-text-session-parity "${KIMODO_LLM_TEXT_BUNDLE}" "${KIMODO_LLM_TEXT_FIXTURE}") COMMAND kimodo-llm-text-session-parity "${KIMODO_LLM_TEXT_BUNDLE}" "${KIMODO_LLM_TEXT_FIXTURE}")
add_executable(kimodo-root-parity tests/root_parity.cpp src/motion_rep.cpp) add_executable(kimodo-root-parity tests/root_parity.cpp src/motion_rep.cpp)
target_include_directories(kimodo-root-parity PRIVATE src) target_include_directories(kimodo-root-parity PRIVATE src)
target_link_libraries(kimodo-root-parity PRIVATE kimodo ggml ggml-vulkan) target_link_libraries(kimodo-root-parity PRIVATE kimodo ggml $<TARGET_NAME_IF_EXISTS:ggml-vulkan>)
add_test(NAME kimodo-root-parity add_test(NAME kimodo-root-parity
COMMAND kimodo-root-parity "${KIMODO_ROOT_PARITY_MODEL}" "${KIMODO_ROOT_PARITY_FIXTURE}" root) COMMAND kimodo-root-parity "${KIMODO_ROOT_PARITY_MODEL}" "${KIMODO_ROOT_PARITY_FIXTURE}" root)
add_test(NAME kimodo-body-parity add_test(NAME kimodo-body-parity
@ -122,15 +122,15 @@ if(BUILD_TESTING AND KIMODO_BUILD_TESTS)
add_executable(kimodo-ggml-weights-test tests/ggml_weights_test.cpp) add_executable(kimodo-ggml-weights-test tests/ggml_weights_test.cpp)
target_sources(kimodo-ggml-weights-test PRIVATE src/gguf.cpp src/ggml_weights.cpp) target_sources(kimodo-ggml-weights-test PRIVATE src/gguf.cpp src/ggml_weights.cpp)
target_include_directories(kimodo-ggml-weights-test PRIVATE src) target_include_directories(kimodo-ggml-weights-test PRIVATE src)
target_link_libraries(kimodo-ggml-weights-test PRIVATE kimodo ggml ggml-vulkan) target_link_libraries(kimodo-ggml-weights-test PRIVATE kimodo ggml $<TARGET_NAME_IF_EXISTS:ggml-vulkan>)
target_compile_definitions(kimodo-ggml-weights-test PRIVATE KIMODO_HAVE_GGML=1 KIMODO_HAVE_GGML_VULKAN=1) target_compile_definitions(kimodo-ggml-weights-test PRIVATE KIMODO_HAVE_GGML=1 $<$<TARGET_EXISTS:ggml-vulkan>:KIMODO_HAVE_GGML_VULKAN=1>)
add_test(NAME kimodo-ggml-weights-test add_test(NAME kimodo-ggml-weights-test
COMMAND kimodo-ggml-weights-test "${KIMODO_ROOT_PARITY_MODEL}") COMMAND kimodo-ggml-weights-test "${KIMODO_ROOT_PARITY_MODEL}")
add_executable(kimodo-denoiser-runtime-test tests/denoiser_runtime_test.cpp) add_executable(kimodo-denoiser-runtime-test tests/denoiser_runtime_test.cpp)
target_sources(kimodo-denoiser-runtime-test PRIVATE src/gguf.cpp src/ggml_weights.cpp src/denoiser.cpp src/motion_rep.cpp src/diffusion.cpp) target_sources(kimodo-denoiser-runtime-test PRIVATE src/gguf.cpp src/ggml_weights.cpp src/denoiser.cpp src/motion_rep.cpp src/diffusion.cpp)
target_include_directories(kimodo-denoiser-runtime-test PRIVATE src) target_include_directories(kimodo-denoiser-runtime-test PRIVATE src)
target_link_libraries(kimodo-denoiser-runtime-test PRIVATE kimodo ggml ggml-vulkan) target_link_libraries(kimodo-denoiser-runtime-test PRIVATE kimodo ggml $<TARGET_NAME_IF_EXISTS:ggml-vulkan>)
target_compile_definitions(kimodo-denoiser-runtime-test PRIVATE KIMODO_HAVE_GGML=1 KIMODO_HAVE_GGML_VULKAN=1) target_compile_definitions(kimodo-denoiser-runtime-test PRIVATE KIMODO_HAVE_GGML=1 $<$<TARGET_EXISTS:ggml-vulkan>:KIMODO_HAVE_GGML_VULKAN=1>)
add_test(NAME kimodo-denoiser-runtime-root add_test(NAME kimodo-denoiser-runtime-root
COMMAND kimodo-denoiser-runtime-test "${KIMODO_ROOT_PARITY_MODEL}" "${KIMODO_ROOT_PARITY_FIXTURE}" root) COMMAND kimodo-denoiser-runtime-test "${KIMODO_ROOT_PARITY_MODEL}" "${KIMODO_ROOT_PARITY_FIXTURE}" root)
add_test(NAME kimodo-denoiser-runtime-body add_test(NAME kimodo-denoiser-runtime-body
@ -140,8 +140,8 @@ if(BUILD_TESTING AND KIMODO_BUILD_TESTS)
add_executable(kimodo-decode-test tests/decode_test.cpp) add_executable(kimodo-decode-test tests/decode_test.cpp)
target_sources(kimodo-decode-test PRIVATE src/gguf.cpp src/ggml_weights.cpp src/motion_decode.cpp) target_sources(kimodo-decode-test PRIVATE src/gguf.cpp src/ggml_weights.cpp src/motion_decode.cpp)
target_include_directories(kimodo-decode-test PRIVATE src) target_include_directories(kimodo-decode-test PRIVATE src)
target_link_libraries(kimodo-decode-test PRIVATE kimodo ggml ggml-vulkan) target_link_libraries(kimodo-decode-test PRIVATE kimodo ggml $<TARGET_NAME_IF_EXISTS:ggml-vulkan>)
target_compile_definitions(kimodo-decode-test PRIVATE KIMODO_HAVE_GGML=1 KIMODO_HAVE_GGML_VULKAN=1) target_compile_definitions(kimodo-decode-test PRIVATE KIMODO_HAVE_GGML=1 $<$<TARGET_EXISTS:ggml-vulkan>:KIMODO_HAVE_GGML_VULKAN=1>)
add_test(NAME kimodo-decode-test COMMAND kimodo-decode-test "${KIMODO_ROOT_PARITY_MODEL}" "${KIMODO_ROOT_PARITY_FIXTURE}") add_test(NAME kimodo-decode-test COMMAND kimodo-decode-test "${KIMODO_ROOT_PARITY_MODEL}" "${KIMODO_ROOT_PARITY_FIXTURE}")
add_executable(kimodo-generate-smoke tests/generate_smoke.cpp) add_executable(kimodo-generate-smoke tests/generate_smoke.cpp)
target_link_libraries(kimodo-generate-smoke PRIVATE kimodo) target_link_libraries(kimodo-generate-smoke PRIVATE kimodo)
@ -149,8 +149,8 @@ if(BUILD_TESTING AND KIMODO_BUILD_TESTS)
add_executable(kimodo-sampler2-test tests/sampler2_test.cpp) add_executable(kimodo-sampler2-test tests/sampler2_test.cpp)
target_sources(kimodo-sampler2-test PRIVATE src/gguf.cpp src/ggml_weights.cpp src/denoiser.cpp src/motion_rep.cpp src/diffusion.cpp) target_sources(kimodo-sampler2-test PRIVATE src/gguf.cpp src/ggml_weights.cpp src/denoiser.cpp src/motion_rep.cpp src/diffusion.cpp)
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 $<TARGET_NAME_IF_EXISTS: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 $<$<TARGET_EXISTS:ggml-vulkan>:KIMODO_HAVE_GGML_VULKAN=1>)
if(IS_DIRECTORY "${KIMODO_MULTIPROMPT_FIXTURE}") if(IS_DIRECTORY "${KIMODO_MULTIPROMPT_FIXTURE}")
add_test(NAME kimodo-multiprompt-fixture-parity add_test(NAME kimodo-multiprompt-fixture-parity
COMMAND kimodo-multiprompt-fixture-parity "${KIMODO_ROOT_PARITY_MODEL}" "${KIMODO_MULTIPROMPT_FIXTURE}") COMMAND kimodo-multiprompt-fixture-parity "${KIMODO_ROOT_PARITY_MODEL}" "${KIMODO_MULTIPROMPT_FIXTURE}")

View File

@ -81,6 +81,17 @@ std::expected<std::unique_ptr<ggml_motion_weights>, std::string> ggml_motion_wei
configure_vulkan_f32_parity(); configure_vulkan_f32_parity();
if (ggml_backend_vk_get_device_count() > 0) result->backend_ = ggml_backend_vk_init(0); 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 #endif
if (!result->backend_) { if (!result->backend_) {
result->backend_ = ggml_backend_cpu_init(); result->backend_ = ggml_backend_cpu_init();

View File

@ -8,7 +8,9 @@
#include <ggml-alloc.h> #include <ggml-alloc.h>
#include <ggml-backend.h> #include <ggml-backend.h>
#include <ggml-cpu.h> #include <ggml-cpu.h>
#if defined(KIMODO_HAVE_GGML_VULKAN)
#include <ggml-vulkan.h> #include <ggml-vulkan.h>
#endif
#include <gguf.h> #include <gguf.h>
#include <algorithm> #include <algorithm>
@ -216,7 +218,16 @@ std::expected<std::unique_ptr<llm_text_encoder>, std::string> llm_text_encoder::
for (int i = 0; i < 32; ++i) { char name[32]; std::snprintf(name, sizeof(name), "layer-%02d.gguf", i); if (!std::filesystem::is_regular_file(path / name)) return std::unexpected("text bundle missing " + std::string(name)); } for (int i = 0; i < 32; ++i) { char name[32]; std::snprintf(name, sizeof(name), "layer-%02d.gguf", i); if (!std::filesystem::is_regular_file(path / name)) return std::unexpected("text bundle missing " + std::string(name)); }
auto result = std::unique_ptr<llm_text_encoder>(new llm_text_encoder); auto result = std::unique_ptr<llm_text_encoder>(new llm_text_encoder);
result->impl_ = std::make_unique<impl>(); result->impl_ = std::make_unique<impl>();
#if defined(KIMODO_HAVE_GGML_VULKAN)
if (use_vulkan() && ggml_backend_vk_get_device_count()) result->impl_->backend = ggml_backend_vk_init(0); if (use_vulkan() && ggml_backend_vk_get_device_count()) result->impl_->backend = ggml_backend_vk_init(0);
#else
// No Vulkan compiled in: fall back to any registered GPU device (e.g.
// Metal on macOS) unless KIMODO_BACKEND=cpu requests the CPU path.
if (use_vulkan()) {
if (auto *device = ggml_backend_dev_by_type(GGML_BACKEND_DEVICE_TYPE_GPU))
result->impl_->backend = ggml_backend_dev_init(device, nullptr);
}
#endif
if (!result->impl_->backend) { if (!result->impl_->backend) {
result->impl_->backend = ggml_backend_cpu_init(); result->impl_->backend = ggml_backend_cpu_init();
if (!result->impl_->backend) return std::unexpected("cannot initialize text backend"); if (!result->impl_->backend) return std::unexpected("cannot initialize text backend");