diff --git a/.github/workflows/validate_wrapper.yml b/.github/workflows/validate_wrapper.yml index ce1f8db..36e58a9 100644 --- a/.github/workflows/validate_wrapper.yml +++ b/.github/workflows/validate_wrapper.yml @@ -78,11 +78,22 @@ jobs: fi wrapper-contract: + name: ${{ matrix.upstream == 'pinned' && 'wrapper-contract' || 'wrapper-contract (v0.4.0)' }} runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + upstream: [pinned, v0.4.0] steps: - uses: actions/checkout@v7 with: submodules: recursive + persist-credentials: false + - name: Select the candidate upstream release + if: matrix.upstream == 'v0.4.0' + run: | + git -C third_party/llama.cpp fetch --depth=1 origin tag v0.4.0 + git -C third_party/llama.cpp checkout --detach 5266f24da75dc449bd56cbed7addb9c8e4a6a73e - name: Configure wrapper contract tests run: >- cmake -S . -B build/wrapper-contract -G Ninja @@ -96,6 +107,7 @@ jobs: cmake --build build/wrapper-contract --target llamadart_speculative_api_test llamadart_tts_api_test + llamadart_mtmd_compat_test llamadart_tts_smoke - name: Run wrapper contract tests run: ctest --test-dir build/wrapper-contract --output-on-failure diff --git a/CMakeLists.txt b/CMakeLists.txt index 4df9762..6a23037 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -138,6 +138,21 @@ if (APPLE) endif() add_subdirectory(third_party/llama.cpp) + +# Probe declarations without calling upstream symbols or running target code. +# Recheck after switching upstream revisions in an existing build. +include(CheckCXXSourceCompiles) +include(CMakePushCheckState) +cmake_push_check_state(RESET) +set(CMAKE_REQUIRED_INCLUDES + "${CMAKE_CURRENT_SOURCE_DIR}/third_party/llama.cpp/include" + "${CMAKE_CURRENT_SOURCE_DIR}/third_party/llama.cpp/ggml/include" + "${CMAKE_CURRENT_SOURCE_DIR}/third_party/llama.cpp/tools/mtmd") +unset(LLAMADART_MTMD_HELPER_HAS_OPTIONS CACHE) +check_cxx_source_compiles("#include +int main() { return sizeof(decltype(mtmd_helper_init_opt_default())) == 0; }" + LLAMADART_MTMD_HELPER_HAS_OPTIONS) +cmake_pop_check_state() if (TARGET ggml-vulkan AND LLAMADART_SPIRV_HEADERS_INCLUDE) target_include_directories(ggml-vulkan SYSTEM PRIVATE "${LLAMADART_SPIRV_HEADERS_INCLUDE}") endif() @@ -158,6 +173,8 @@ endif() llamadart_attach_android_cpu_backend_score(ggml-cpu) add_library(llamadart_lib SHARED "src/llama_dart_wrapper.cpp") +target_compile_definitions(llamadart_lib PRIVATE + LLAMADART_MTMD_HELPER_HAS_OPTIONS=$) if (MINGW) target_link_options(llamadart_lib PRIVATE -Wl,--export-all-symbols) @@ -203,6 +220,14 @@ if (LLAMADART_BUILD_TESTS) target_link_libraries(llamadart_tts_api_test PRIVATE llamadart_lib) target_include_directories(llamadart_tts_api_test PRIVATE src) add_test(NAME llamadart_tts_api_test COMMAND llamadart_tts_api_test) + + add_executable(llamadart_mtmd_compat_test tests/mtmd_compat_test.cpp) + target_compile_features(llamadart_mtmd_compat_test PRIVATE cxx_std_17) + target_include_directories(llamadart_mtmd_compat_test PRIVATE src) + target_compile_definitions(llamadart_mtmd_compat_test PRIVATE + LLAMADART_MTMD_HELPER_HAS_OPTIONS=$) + target_link_libraries(llamadart_mtmd_compat_test PRIVATE mtmd) + add_test(NAME llamadart_mtmd_compat_test COMMAND llamadart_mtmd_compat_test) endif() if (LLAMADART_BUILD_TTS_SMOKE) diff --git a/README.md b/README.md index e50d9a0..f577ef7 100644 --- a/README.md +++ b/README.md @@ -256,7 +256,8 @@ cmake -S . -B build/tts-smoke -G Ninja \ -DLLAMADART_BUILD_TESTS=ON \ -DLLAMADART_BUILD_TTS_SMOKE=ON cmake --build build/tts-smoke --target \ - llamadart_speculative_api_test llamadart_tts_api_test llamadart_tts_smoke + llamadart_speculative_api_test llamadart_tts_api_test \ + llamadart_mtmd_compat_test llamadart_tts_smoke ctest --test-dir build/tts-smoke --output-on-failure build/tts-smoke/llamadart_tts_smoke \ /path/to/Qwen3-TTS-model.gguf \ diff --git a/src/llama_dart_mtmd_compat.h b/src/llama_dart_mtmd_compat.h new file mode 100644 index 0000000..aa5a545 --- /dev/null +++ b/src/llama_dart_mtmd_compat.h @@ -0,0 +1,15 @@ +#pragma once + +#include "mtmd-helper.h" + +// v0.4.0 added helper options; retain builds against the published v0.3.0 +// headers and use upstream's defaults when the options API is available. +static inline mtmd_helper_bitmap_wrapper llama_dart_bitmap_from_buffer( + mtmd_context *ctx, const unsigned char *buffer, size_t size) { +#if LLAMADART_MTMD_HELPER_HAS_OPTIONS + return mtmd_helper_bitmap_init_from_buf( + ctx, buffer, size, false, mtmd_helper_init_opt_default()); +#else + return mtmd_helper_bitmap_init_from_buf(ctx, buffer, size, false); +#endif +} diff --git a/src/llama_dart_wrapper.cpp b/src/llama_dart_wrapper.cpp index c02d341..8bfbb23 100644 --- a/src/llama_dart_wrapper.cpp +++ b/src/llama_dart_wrapper.cpp @@ -1,5 +1,6 @@ #include "llama_dart_wrapper.h" #include "llama_dart_mtp_internal.h" +#include "llama_dart_mtmd_compat.h" #include "llama_dart_speculative_compat.h" #include "common.h" @@ -837,8 +838,8 @@ LLAMADART_API enum llama_dart_tts_status llama_dart_tts_start( llama_memory_seq_rm(llama_get_memory(tts->llama), tts->sequence_id, 0, -1); tts->owns_sequence = true; if (request->speaker_audio_length > 0) { - mtmd_helper_bitmap_wrapper wrapper = mtmd_helper_bitmap_init_from_buf( - tts->mtmd, request->speaker_audio, request->speaker_audio_length, false); + mtmd_helper_bitmap_wrapper wrapper = llama_dart_bitmap_from_buffer( + tts->mtmd, request->speaker_audio, request->speaker_audio_length); if (wrapper.bitmap == nullptr || !mtmd_bitmap_is_audio(wrapper.bitmap)) { if (wrapper.bitmap != nullptr) { mtmd_bitmap_free(wrapper.bitmap); diff --git a/tests/mtmd_compat_test.cpp b/tests/mtmd_compat_test.cpp new file mode 100644 index 0000000..026fcf8 --- /dev/null +++ b/tests/mtmd_compat_test.cpp @@ -0,0 +1,20 @@ +#include "llama_dart_mtmd_compat.h" + +#include + +int main() { + // A real one-pixel PPM exercises buffer forwarding and non-placeholder + // decoding without a model. TTS rejects this non-audio result at its call site. + const unsigned char image[] = "P6\n1 1\n255\n\xff\x00\x00"; + auto result = llama_dart_bitmap_from_buffer(nullptr, image, sizeof(image) - 1); + if (!result.bitmap || result.video_ctx || mtmd_bitmap_is_audio(result.bitmap) || + mtmd_bitmap_get_nx(result.bitmap) != 1 || + mtmd_bitmap_get_ny(result.bitmap) != 1 || + !mtmd_bitmap_get_data(result.bitmap) || + mtmd_bitmap_get_data(result.bitmap)[0] != 255) { + std::fprintf(stderr, "media helper did not preserve decoded image bytes\n"); + return 1; + } + mtmd_bitmap_free(result.bitmap); + return 0; +}