Commit 664c62f7 for whisper.cpp

commit 664c62f7a6668b511c280a58d35ccf3d71ebb637
Author: Daniel Müller <d-e-s-o@users.noreply.github.com>
Date:   Tue Nov 10 00:39:35 2026 -0800

    vitisai : route VitisAI backend logging through whisper's log callback (#4163)

    The vitisai backend writes diagnostic messages directly to stderr,
    bypassing whisper's logging infrastructure. What that amounts to is a
    usability nightmare, because:
    - whisper_log_set() may simply not be working depending on which backend
      is in use
    - verbosity control (log levels) are effectively not present
    - client code has to juggle log message management along two axis, the
      whisper_log_set() function and the WHISPER_DEBUG define, as well
      between runtime and build time

    With this change we start sharing the internal logging infrastructure
    between backends. The vitisai backend is switched over to using
    WHISPER_LOG_* macros, which allows for client-side control of verbosity.
    Note that verbose init and timing messages at DEBUG are kept gated by
    WHISPER_DEBUG. Other backends can follow the example if accepted.

    Signed-off-by: Daniel Müller <deso@posteo.net>

diff --git a/src/vitisai/whisper-vitisai-encoder.cpp b/src/vitisai/whisper-vitisai-encoder.cpp
index 707f876f..a6d8f1b0 100644
--- a/src/vitisai/whisper-vitisai-encoder.cpp
+++ b/src/vitisai/whisper-vitisai-encoder.cpp
@@ -8,8 +8,8 @@
 #include "vitisai/whisper-vitisai-helpers.h"
 #include "FlexMLClient.h"
 #include "ggml.h"
+#include "whisper-logging.h"

-#include <cstdio>
 #include <cstdlib>
 #include <cmath>
 #include <memory>
@@ -65,7 +65,7 @@ static bool whisper_vitisai_get_cached_io_tensors(

 struct whisper_vitisai_context * whisper_vitisai_init(const char * path_model) {
     if (!path_model) {
-        std::fprintf(stderr, "%s: path_model is null\n", __func__);
+        WHISPER_LOG_ERROR("%s: path_model is null\n", __func__);
         return nullptr;
     }

@@ -95,26 +95,22 @@ struct whisper_vitisai_context * whisper_vitisai_init(const char * path_model) {
             options.extOptions["fbs_buffer_size"] = ctx->fbs_buffer_size;
             options.extOptions["cache_dir"] = std::string(".");
         } else {
-            std::fprintf(stderr, "%s: Failed to mmap rai file '%s'\n", __func__, ctx->model_path.c_str());
+            WHISPER_LOG_ERROR("%s: Failed to mmap rai file '%s'\n", __func__, ctx->model_path.c_str());
             delete ctx;
             return nullptr;
         }
     } else {
         options.deviceName = "stx";
-#if defined(WHISPER_DEBUG)
-        std::fprintf(stderr, "%s: Using default device name 'stx'\n", __func__);
-#endif
+        WHISPER_LOG_DEBUG("%s: using default device name 'stx'\n", __func__);
     }

     if (model_is_rai) {
 #if WHISPER_FLEXMLRT_LEGACY_RAI_OVERRIDES
         options.deviceName = "stx";
         options.subgraphName = "vaiml_par_0";
-#if defined(WHISPER_DEBUG)
-        std::fprintf(stderr,
+        WHISPER_LOG_DEBUG(
                 "%s: legacy FlexMLRT compile configuration detected; applying RAI overrides (device='stx', subgraph='vaiml_par_0')\n",
                 __func__);
-#endif // defined(WHISPER_DEBUG)
 #endif
     }

@@ -148,31 +144,29 @@ struct whisper_vitisai_context * whisper_vitisai_init(const char * path_model) {

 #if defined(WHISPER_DEBUG)
         {
-            std::fprintf(stderr, "%s: model has %zu input tensor(s)\n", __func__, input_tensors.size());
+            WHISPER_LOG_DEBUG("%s: model has %zu input tensor(s)\n", __func__, input_tensors.size());
             for (int i = 0; i < (int) input_tensors.size(); ++i) {
                 const auto & meta = input_tensors[i].getMetadata();
-                std::fprintf(stderr, "%s:   input[%d] name='%s' size=%zu shape=",
-                        __func__, i, meta.name.c_str(), (size_t) meta.size);
-                whisper_vitisai_helpers::whisper_vitisai_print_shape(meta.shape);
-                std::fprintf(stderr, "\n");
+                WHISPER_LOG_DEBUG("%s:   input[%d] name='%s' size=%zu shape=%s\n",
+                        __func__, i, meta.name.c_str(), (size_t) meta.size,
+                        whisper_vitisai_helpers::whisper_vitisai_shape_string(meta.shape).c_str());
             }

-            std::fprintf(stderr, "%s: model has %zu output tensor(s)\n", __func__, output_tensors.size());
+            WHISPER_LOG_DEBUG("%s: model has %zu output tensor(s)\n", __func__, output_tensors.size());
             for (int i = 0; i < (int) output_tensors.size(); ++i) {
                 const auto & meta = output_tensors[i].getMetadata();
-                std::fprintf(stderr, "%s:   output[%d] name='%s' size=%zu shape=",
-                        __func__, i, meta.name.c_str(), (size_t) meta.size);
-                whisper_vitisai_helpers::whisper_vitisai_print_shape(meta.shape);
-                std::fprintf(stderr, "\n");
+                WHISPER_LOG_DEBUG("%s:   output[%d] name='%s' size=%zu shape=%s\n",
+                        __func__, i, meta.name.c_str(), (size_t) meta.size,
+                        whisper_vitisai_helpers::whisper_vitisai_shape_string(meta.shape).c_str());
             }

-            std::fprintf(stderr, "%s: input index: mel=%d\n", __func__, ctx->mel_in_idx);
-            std::fprintf(stderr, "%s: output indices: embd_enc=%d cross_k=%d cross_v=%d\n",
+            WHISPER_LOG_DEBUG("%s: input index: mel=%d\n", __func__, ctx->mel_in_idx);
+            WHISPER_LOG_DEBUG("%s: output indices: embd_enc=%d cross_k=%d cross_v=%d\n",
                     __func__, ctx->embd_enc_out_idx, ctx->cross_k_out_idx, ctx->cross_v_out_idx);
         }
 #endif
     } catch (const std::exception & e) {
-        std::fprintf(stderr, "%s: Exception during Vitis AI runner creation: %s\n", __func__, e.what());
+        WHISPER_LOG_ERROR("%s: Exception during Vitis AI runner creation: %s\n", __func__, e.what());
         whisper_vitisai_free(ctx);
         return nullptr;
     }
@@ -188,9 +182,7 @@ void whisper_vitisai_free(struct whisper_vitisai_context * ctx) {
         return;
     }

-#if defined(WHISPER_DEBUG)
-    std::fprintf(stderr, "%s: releasing Vitis AI context for model '%s'\n", __func__, ctx->model_path.c_str());
-#endif
+    WHISPER_LOG_DEBUG("%s: releasing Vitis AI context for model '%s'\n", __func__, ctx->model_path.c_str());
     if (ctx->fbs_buffer) {
         whisper_vitisai_helpers::unmap_rai_file(ctx->fbs_buffer, ctx->fbs_buffer_size);
     }
@@ -206,34 +198,34 @@ static int whisper_vitisai_forward_impl(
         void * cross_k_data,
         void * cross_v_data) {
     if (!ctx || !mel || !out) {
-        std::fprintf(stderr, "%s: ctx/mel/out must not be null\n", __func__);
+        WHISPER_LOG_ERROR("%s: ctx/mel/out must not be null\n", __func__);
         return 0;
     }

     const bool with_cross = (cross_k_data != nullptr || cross_v_data != nullptr);
     if (with_cross && (!cross_k_data || !cross_v_data)) {
-        std::fprintf(stderr, "%s: cross_k_data/cross_v_data must both be set\n", __func__);
+        WHISPER_LOG_ERROR("%s: cross_k_data/cross_v_data must both be set\n", __func__);
         return 0;
     }

     if (ggml_n_dims(mel) != 2) {
-        std::fprintf(stderr, "%s: mel tensor expected to have 2 dims, got %d\n", __func__, ggml_n_dims(mel));
+        WHISPER_LOG_ERROR("%s: mel tensor expected to have 2 dims, got %d\n", __func__, ggml_n_dims(mel));
         return 0;
     }

     if (ggml_n_dims(out) != 2) {
-        std::fprintf(stderr, "%s: out tensor expected to have 2 dims, got %d\n", __func__, ggml_n_dims(out));
+        WHISPER_LOG_ERROR("%s: out tensor expected to have 2 dims, got %d\n", __func__, ggml_n_dims(out));
         return 0;
     }

     if (ctx->embd_enc_out_idx < 0 || ctx->embd_enc_out_idx >= (int) output_tensors.size()) {
-        std::fprintf(stderr, "%s: invalid embd_enc output index %d for %zu output tensor(s)\n",
+        WHISPER_LOG_ERROR("%s: invalid embd_enc output index %d for %zu output tensor(s)\n",
                 __func__, ctx->embd_enc_out_idx, output_tensors.size());
         return 0;
     }

     if (ctx->mel_in_idx < 0 || ctx->mel_in_idx >= (int) input_tensors.size()) {
-        std::fprintf(stderr, "%s: invalid mel input index %d for %zu input tensor(s)\n",
+        WHISPER_LOG_ERROR("%s: invalid mel input index %d for %zu input tensor(s)\n",
                 __func__, ctx->mel_in_idx, input_tensors.size());
         return 0;
     }
@@ -266,7 +258,7 @@ static int whisper_vitisai_forward_impl(
     if (with_cross) {
         if (ctx->cross_k_out_idx < 0 || ctx->cross_k_out_idx >= (int) output_tensors.size() ||
                 ctx->cross_v_out_idx < 0 || ctx->cross_v_out_idx >= (int) output_tensors.size()) {
-            std::fprintf(stderr, "%s: invalid cross output indices cross_k=%d cross_v=%d for %zu output tensor(s)\n",
+            WHISPER_LOG_ERROR("%s: invalid cross output indices cross_k=%d cross_v=%d for %zu output tensor(s)\n",
                     __func__, ctx->cross_k_out_idx, ctx->cross_v_out_idx, output_tensors.size());
             return 0;
         }
@@ -292,13 +284,11 @@ static int whisper_vitisai_forward_impl(
     try {
         ctx->runner->forward(input_tensors, output_tensors);
         clear_bound_data();
-#if defined(WHISPER_DEBUG)
-        std::fprintf(stderr, "%s: Vitis AI model inference %scompleted.\n",
+        WHISPER_LOG_DEBUG("%s: Vitis AI model inference %scompleted.\n",
                 __func__, with_cross ? "(encoder + cross proj) " : "");
-#endif
     } catch (const std::exception & e) {
         clear_bound_data();
-        std::fprintf(stderr, "%s: Exception during model inference: %s\n", __func__, e.what());
+        WHISPER_LOG_ERROR("%s: Exception during model inference: %s\n", __func__, e.what());
         return 0;
     }

@@ -309,7 +299,7 @@ int whisper_vitisai_encode(struct whisper_vitisai_context * ctx, struct ggml_ten
     std::vector<flexmlrt::client::ErtTensorType> * input_tensors_cached = nullptr;
     std::vector<flexmlrt::client::ErtTensorType> * output_tensors_cached = nullptr;
     if (!whisper_vitisai_get_cached_io_tensors(ctx, input_tensors_cached, output_tensors_cached)) {
-        std::fprintf(stderr, "%s: failed to acquire Vitis AI I/O tensors\n", __func__);
+        WHISPER_LOG_ERROR("%s: failed to acquire Vitis AI I/O tensors\n", __func__);
         return 0;
     }

@@ -352,24 +342,24 @@ int whisper_vitisai_encode_with_cross(
         int n_text_head,
         bool flash_attn) {
     if (!ctx || !mel || !embd_enc || !kv_cross_k || !kv_cross_v) {
-        std::fprintf(stderr, "%s: ctx/mel/embd_enc/kv_cross_k/kv_cross_v must not be null\n", __func__);
+        WHISPER_LOG_ERROR("%s: ctx/mel/embd_enc/kv_cross_k/kv_cross_v must not be null\n", __func__);
         return 0;
     }

     if (n_text_layer <= 0 || n_ctx <= 0 || n_text_state <= 0 || n_text_head <= 0) {
-        std::fprintf(stderr, "%s: invalid shape parameters layer=%d ctx=%d state=%d head=%d\n",
+        WHISPER_LOG_ERROR("%s: invalid shape parameters layer=%d ctx=%d state=%d head=%d\n",
                 __func__, n_text_layer, n_ctx, n_text_state, n_text_head);
         return 0;
     }

     if ((n_text_state % n_text_head) != 0) {
-        std::fprintf(stderr, "%s: invalid head configuration state=%d head=%d\n",
+        WHISPER_LOG_ERROR("%s: invalid head configuration state=%d head=%d\n",
                 __func__, n_text_state, n_text_head);
         return 0;
     }

     if (kv_cross_k->type != kv_cross_v->type) {
-        std::fprintf(stderr, "%s: kv_cross type mismatch k=%s v=%s\n",
+        WHISPER_LOG_ERROR("%s: kv_cross type mismatch k=%s v=%s\n",
                 __func__,
                 whisper_vitisai_helpers::whisper_kv_type_name(kv_cross_k->type),
                 whisper_vitisai_helpers::whisper_kv_type_name(kv_cross_v->type));
@@ -385,7 +375,7 @@ int whisper_vitisai_encode_with_cross(
     const bool kv_is_f32 = kv_type == GGML_TYPE_F32;
     const bool kv_is_f16 = kv_type == GGML_TYPE_F16;
     if (!kv_is_f32 && !kv_is_f16) {
-        std::fprintf(stderr, "%s: unsupported kv_cross tensor type '%s'\n",
+        WHISPER_LOG_ERROR("%s: unsupported kv_cross tensor type '%s'\n",
                 __func__, whisper_vitisai_helpers::whisper_kv_type_name(kv_type));
         return 0;
     }
@@ -396,7 +386,7 @@ int whisper_vitisai_encode_with_cross(
     std::vector<flexmlrt::client::ErtTensorType> * input_tensors_cached = nullptr;
     std::vector<flexmlrt::client::ErtTensorType> * output_tensors_cached = nullptr;
     if (!whisper_vitisai_get_cached_io_tensors(ctx, input_tensors_cached, output_tensors_cached)) {
-        std::fprintf(stderr, "%s: failed to acquire Vitis AI I/O tensors\n", __func__);
+        WHISPER_LOG_ERROR("%s: failed to acquire Vitis AI I/O tensors\n", __func__);
         return 0;
     }
     std::vector<flexmlrt::client::ErtTensorType> input_tensors = *input_tensors_cached;
@@ -404,7 +394,7 @@ int whisper_vitisai_encode_with_cross(

     if (ctx->cross_k_out_idx < 0 || ctx->cross_k_out_idx >= (int) output_tensors.size() ||
             ctx->cross_v_out_idx < 0 || ctx->cross_v_out_idx >= (int) output_tensors.size()) {
-        std::fprintf(stderr, "%s: invalid cross output indices cross_k=%d cross_v=%d for %zu output tensor(s)\n",
+        WHISPER_LOG_ERROR("%s: invalid cross output indices cross_k=%d cross_v=%d for %zu output tensor(s)\n",
                 __func__, ctx->cross_k_out_idx, ctx->cross_v_out_idx, output_tensors.size());
         return 0;
     }
@@ -417,17 +407,17 @@ int whisper_vitisai_encode_with_cross(
     }

     if (ctx->cross_k_expected_bytes == 0 || ctx->cross_v_expected_bytes == 0) {
-        std::fprintf(stderr, "%s: missing cross output metadata sizes\n", __func__);
+        WHISPER_LOG_ERROR("%s: missing cross output metadata sizes\n", __func__);
         return 0;
     }
     if (ctx->cross_k_expected_bytes != ctx->cross_v_expected_bytes) {
-        std::fprintf(stderr, "%s: cross output metadata size mismatch k=%zu v=%zu\n",
+        WHISPER_LOG_ERROR("%s: cross output metadata size mismatch k=%zu v=%zu\n",
                 __func__, ctx->cross_k_expected_bytes, ctx->cross_v_expected_bytes);
         return 0;
     }
     const size_t expected_cross_bytes = (size_t) n_text_layer * req_layer_elems * sizeof(float);
     if (ctx->cross_k_expected_bytes != expected_cross_bytes) {
-        std::fprintf(stderr,
+        WHISPER_LOG_ERROR(
                 "%s: cross output size mismatch (model=%zu B, expected=%zu B for layer=%d ctx=%d state=%d)\n",
                 __func__, ctx->cross_k_expected_bytes, expected_cross_bytes, n_text_layer, n_ctx, n_state);
         return 0;
@@ -440,7 +430,7 @@ int whisper_vitisai_encode_with_cross(
             ? (size_t)n_text_layer * elem_size * (size_t)n_state * (size_t)n_ctx_pad
             : (size_t)n_text_layer * elem_size * req_layer_elems;
     if (ggml_nbytes(kv_cross_k) < required_kv_bytes || ggml_nbytes(kv_cross_v) < required_kv_bytes) {
-        std::fprintf(stderr,
+        WHISPER_LOG_ERROR(
                 "%s: kv_cross buffers are too small (required=%zu B, k=%zu B, v=%zu B)\n",
                 __func__, required_kv_bytes, ggml_nbytes(kv_cross_k), ggml_nbytes(kv_cross_v));
         return 0;
@@ -518,13 +508,10 @@ int whisper_vitisai_encode_with_cross(

         WHISPER_DBG_TIMER(t_post_end);

-#if defined(WHISPER_DEBUG)
-        const size_t model_ctx = (size_t) n_ctx;
-        std::fprintf(stderr, "%s: vitisai enc+cross forward time = %8.2f ms\n", __func__, (t_fwd_end - t_fwd_start) / 1000.0f);
-        std::fprintf(stderr, "%s: kv_cross post-process time     = %8.2f ms (flash, req_ctx=%d, model_ctx=%zu, req_ctx_pad=%d, kv_type=%s)\n",
-                __func__, (t_post_end - t_post_start) / 1000.0f, n_ctx, model_ctx, n_ctx_pad,
+        WHISPER_LOG_DEBUG("%s: vitisai enc+cross forward time = %8.2f ms\n", __func__, (t_fwd_end - t_fwd_start) / 1000.0f);
+        WHISPER_LOG_DEBUG("%s: kv_cross post-process time     = %8.2f ms (flash, req_ctx=%d, model_ctx=%zu, req_ctx_pad=%d, kv_type=%s)\n",
+                __func__, (t_post_end - t_post_start) / 1000.0f, n_ctx, (size_t) n_ctx, n_ctx_pad,
                 whisper_vitisai_helpers::whisper_kv_type_name(kv_type));
-#endif
     } else {
         // Non-flash: model outputs contiguous [ctx, state] per layer.
         WHISPER_DBG_TIMER(t_fwd_start);
@@ -558,13 +545,10 @@ int whisper_vitisai_encode_with_cross(

         WHISPER_DBG_TIMER(t_post_end);

-#if defined(WHISPER_DEBUG)
-        const size_t model_ctx = (size_t) n_ctx;
-        std::fprintf(stderr, "%s: vitisai enc+cross forward time = %8.2f ms\n", __func__, (t_fwd_end - t_fwd_start) / 1000.0f);
-        std::fprintf(stderr, "%s: kv_cross post-process time     = %8.2f ms (non-flash, req_ctx=%d, model_ctx=%zu, kv_type=%s)\n",
-                __func__, (t_post_end - t_post_start) / 1000.0f, n_ctx, model_ctx,
+        WHISPER_LOG_DEBUG("%s: vitisai enc+cross forward time = %8.2f ms\n", __func__, (t_fwd_end - t_fwd_start) / 1000.0f);
+        WHISPER_LOG_DEBUG("%s: kv_cross post-process time     = %8.2f ms (non-flash, req_ctx=%d, model_ctx=%zu, kv_type=%s)\n",
+                __func__, (t_post_end - t_post_start) / 1000.0f, n_ctx, (size_t) n_ctx,
                 whisper_vitisai_helpers::whisper_kv_type_name(kv_type));
-#endif
     }

     return 1;
diff --git a/src/vitisai/whisper-vitisai-helpers.cpp b/src/vitisai/whisper-vitisai-helpers.cpp
index 41763461..c622cd52 100644
--- a/src/vitisai/whisper-vitisai-helpers.cpp
+++ b/src/vitisai/whisper-vitisai-helpers.cpp
@@ -5,6 +5,7 @@
 #endif

 #include "vitisai/whisper-vitisai-helpers.h"
+#include "whisper-logging.h"

 #include <algorithm>
 #include <cstdio>
@@ -23,21 +24,21 @@ bool map_rai_file(const char * path, uint8_t ** buffer, size_t * size) {
 #ifdef _WIN32
     HANDLE hFile = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
     if (hFile == INVALID_HANDLE_VALUE) {
-        std::fprintf(stderr, "%s: %d: Failed to open rai file '%s'\n", __func__, __LINE__, path);
+        WHISPER_LOG_ERROR("%s: %d: Failed to open rai file '%s'\n", __func__, __LINE__, path);
         return false;
     }

     LARGE_INTEGER fileSize;
     if (!GetFileSizeEx(hFile, &fileSize)) {
         CloseHandle(hFile);
-        std::fprintf(stderr, "%s: %d: Failed to get file size for rai file '%s'\n", __func__, __LINE__, path);
+        WHISPER_LOG_ERROR("%s: %d: Failed to get file size for rai file '%s'\n", __func__, __LINE__, path);
         return false;
     }

     HANDLE hMapping = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, fileSize.QuadPart, NULL);
     if (hMapping == NULL) {
         CloseHandle(hFile);
-        std::fprintf(stderr, "%s: %d: Failed to create file mapping for rai file '%s'\n", __func__, __LINE__, path);
+        WHISPER_LOG_ERROR("%s: %d: Failed to create file mapping for rai file '%s'\n", __func__, __LINE__, path);
         return false;
     }

@@ -45,7 +46,7 @@ bool map_rai_file(const char * path, uint8_t ** buffer, size_t * size) {
     if (*buffer == NULL) {
         CloseHandle(hMapping);
         CloseHandle(hFile);
-        std::fprintf(stderr, "%s: %d: Failed to map rai file '%s'\n", __func__, __LINE__, path);
+        WHISPER_LOG_ERROR("%s: %d: Failed to map rai file '%s'\n", __func__, __LINE__, path);
         return false;
     }
     CloseHandle(hMapping);
@@ -55,21 +56,21 @@ bool map_rai_file(const char * path, uint8_t ** buffer, size_t * size) {
 #else
     FILE * fd = fopen(path, "rb");
     if (!fd) {
-        std::fprintf(stderr, "%s: %d: Failed to open rai file '%s'\n", __func__, __LINE__, path);
+        WHISPER_LOG_ERROR("%s: %d: Failed to open rai file '%s'\n", __func__, __LINE__, path);
         return false;
     }

     struct stat st;
     if (fstat(fileno(fd), &st) == -1) {
         fclose(fd);
-        std::fprintf(stderr, "%s: %d: Failed to get file size for rai file '%s'\n", __func__, __LINE__, path);
+        WHISPER_LOG_ERROR("%s: %d: Failed to get file size for rai file '%s'\n", __func__, __LINE__, path);
         return false;
     }

     *buffer = (uint8_t *) mmap(nullptr, st.st_size, PROT_READ, MAP_PRIVATE, fileno(fd), 0);
     if (*buffer == MAP_FAILED) {
         fclose(fd);
-        std::fprintf(stderr, "%s: %d: Failed to mmap rai file '%s'\n", __func__, __LINE__, path);
+        WHISPER_LOG_ERROR("%s: %d: Failed to mmap rai file '%s'\n", __func__, __LINE__, path);
         return false;
     }
     fclose(fd);
@@ -140,14 +141,14 @@ static bool whisper_vitisai_validate_tensor_dtype(
         ggml_type runtime_dtype) {
     ggml_type expected_runtime_dtype = GGML_TYPE_COUNT;
     if (!whisper_flexml_dtype_to_ggml_type(model_dtype, &expected_runtime_dtype)) {
-        std::fprintf(stderr,
+        WHISPER_LOG_ERROR(
                 "%s: unsupported model dtype for %s: %s (supported: Float32/Float16/BFloat16)\n",
                 __func__, tensor_name, whisper_flexml_dtype_name(model_dtype));
         return false;
     }

     if (runtime_dtype != expected_runtime_dtype) {
-        std::fprintf(stderr,
+        WHISPER_LOG_ERROR(
                 "%s: %s dtype mismatch (runtime=%s, model=%s)\n",
                 __func__, tensor_name, ggml_type_name(runtime_dtype), whisper_flexml_dtype_name(model_dtype));
         return false;
@@ -189,7 +190,7 @@ static bool whisper_validate_shape(
         const std::vector<size_t> & expected_shape) {
     const std::vector<size_t> shape = whisper_canonical_shape(model_shape);
     if (shape != expected_shape) {
-        std::fprintf(stderr,
+        WHISPER_LOG_ERROR(
                 "%s: %s shape mismatch (runtime expected=%s, model=%s)\n",
                 __func__,
                 tensor_name,
@@ -230,12 +231,12 @@ bool whisper_vitisai_bind_tensor_data(
     const size_t model_bytes = meta.size;
     const size_t runtime_bytes = ggml_nbytes(runtime_tensor);
     if (model_bytes == 0 || runtime_bytes == 0) {
-        std::fprintf(stderr, "%s: %s sizes must be non-zero (model=%zu, runtime=%zu)\n",
+        WHISPER_LOG_ERROR("%s: %s sizes must be non-zero (model=%zu, runtime=%zu)\n",
                 __func__, tensor_name, model_bytes, runtime_bytes);
         return false;
     }
     if (runtime_bytes != model_bytes) {
-        std::fprintf(stderr,
+        WHISPER_LOG_ERROR(
                 "%s: %s tensor size mismatch (runtime=%zu B, model=%zu B). "
                 "VitisAI .rai requires exact context match; use matching -ac/model artifact.\n",
                 __func__, tensor_name, runtime_bytes, model_bytes);
@@ -247,7 +248,7 @@ bool whisper_vitisai_bind_tensor_data(
 }

 bool whisper_vitisai_resolve_io_binding(
-        [[maybe_unused]] const char * caller,
+        const char * caller,
         const std::vector<flexmlrt::client::ErtTensorType> & input_tensors,
         const std::vector<flexmlrt::client::ErtTensorType> & output_tensors,
         whisper_vitisai_io_binding * binding,
@@ -274,9 +275,7 @@ bool whisper_vitisai_resolve_io_binding(
         }
     }
     if (!found_named_mel) {
-#if defined(WHISPER_DEBUG)
-        std::fprintf(stderr, "%s: WARNING: mel input not found by name; falling back to input[0]\n", caller);
-#endif
+        WHISPER_LOG_WARN("%s: mel input not found by name; falling back to input[0]\n", caller);
     }

     if (output_tensors.empty()) {
@@ -295,9 +294,7 @@ bool whisper_vitisai_resolve_io_binding(
     }

     if (binding->embd_enc_out_idx < 0) {
-#if defined(WHISPER_DEBUG)
-        std::fprintf(stderr, "%s: WARNING: embd_enc output not found by name; falling back to output[0]\n", caller);
-#endif
+        WHISPER_LOG_WARN("%s: embd_enc output not found by name; falling back to output[0]\n", caller);
         binding->embd_enc_out_idx = 0;
     }

@@ -356,7 +353,7 @@ bool whisper_vitisai_all_tensors_claimed(
         const std::vector<bool> & claimed) {
     for (size_t i = 0; i < tensors.size(); ++i) {
         if (!claimed[i]) {
-            std::fprintf(stderr,
+            WHISPER_LOG_ERROR(
                     "%s: unsupported extra %s tensor at index %zu (name='%s'); strict contract expects only mapped %ss\n",
                     caller, tensor_kind, i, tensors[i].getMetadata().name.c_str(), tensor_kind);
             return false;
diff --git a/src/vitisai/whisper-vitisai-helpers.h b/src/vitisai/whisper-vitisai-helpers.h
index d50a2f4a..38bc3f04 100644
--- a/src/vitisai/whisper-vitisai-helpers.h
+++ b/src/vitisai/whisper-vitisai-helpers.h
@@ -5,7 +5,6 @@

 #include <cstddef>
 #include <cstdint>
-#include <cstdio>
 #include <string>
 #include <vector>

@@ -33,16 +32,19 @@ bool whisper_vitisai_bind_tensor_data(
         const std::vector<size_t> & expected_shape,
         flexmlrt::client::ErtTensorType & io_tensor);

-#if defined(WHISPER_DEBUG)
+// Formats a tensor shape for logging, e.g. "[1, 80, 3000]".
 template <typename T>
-void whisper_vitisai_print_shape(const std::vector<T> & shape) {
-    std::fprintf(stderr, "[");
+std::string whisper_vitisai_shape_string(const std::vector<T> & shape) {
+    std::string out = "[";
     for (size_t i = 0; i < shape.size(); ++i) {
-        std::fprintf(stderr, "%s%lld", i == 0 ? "" : ", ", (long long) shape[i]);
+        if (i > 0) {
+            out += ", ";
+        }
+        out += std::to_string((long long) shape[i]);
     }
-    std::fprintf(stderr, "]");
+    out += "]";
+    return out;
 }
-#endif

 // Model IO tensor indices and metadata sizes resolved once at init time.
 struct whisper_vitisai_io_binding {
diff --git a/src/whisper-logging.h b/src/whisper-logging.h
new file mode 100644
index 00000000..9a873408
--- /dev/null
+++ b/src/whisper-logging.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include "ggml.h"
+
+//
+// logging
+//
+// Internal logging interface shared between whisper.cpp and the optional
+// backends compiled into the library. All WHISPER_LOG_* messages are routed
+// through the callback installed with whisper_log_set().
+//
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+GGML_ATTRIBUTE_FORMAT(2, 3)
+void whisper_log_internal(ggml_log_level level, const char * format, ...);
+
+#ifdef __cplusplus
+}
+#endif
+
+#define WHISPER_LOG_ERROR(...) whisper_log_internal(GGML_LOG_LEVEL_ERROR, __VA_ARGS__)
+#define WHISPER_LOG_WARN(...)  whisper_log_internal(GGML_LOG_LEVEL_WARN , __VA_ARGS__)
+#define WHISPER_LOG_INFO(...)  whisper_log_internal(GGML_LOG_LEVEL_INFO , __VA_ARGS__)
+
+// define this to enable verbose trace logging - useful for debugging purposes
+//#define WHISPER_DEBUG
+
+#if defined(WHISPER_DEBUG)
+#define WHISPER_LOG_DEBUG(...) whisper_log_internal(GGML_LOG_LEVEL_DEBUG, __VA_ARGS__)
+#else
+#define WHISPER_LOG_DEBUG(...)
+#endif
diff --git a/src/whisper.cpp b/src/whisper.cpp
index 2fac6479..1c7771fc 100644
--- a/src/whisper.cpp
+++ b/src/whisper.cpp
@@ -1,5 +1,6 @@
 #include "whisper.h"
 #include "whisper-arch.h"
+#include "whisper-logging.h"

 #include "ggml.h"
 #include "ggml-cpp.h"
@@ -106,37 +107,12 @@ static void byteswap_tensor(ggml_tensor * tensor) {
 #define BYTESWAP_TENSOR(t) do {} while (0)
 #endif

-#ifdef __GNUC__
-#ifdef __MINGW32__
-#define WHISPER_ATTRIBUTE_FORMAT(...) __attribute__((format(gnu_printf, __VA_ARGS__)))
-#else
-#define WHISPER_ATTRIBUTE_FORMAT(...) __attribute__((format(printf, __VA_ARGS__)))
-#endif
-#else
-#define WHISPER_ATTRIBUTE_FORMAT(...)
-#endif
-
 //
 // logging
 //

-WHISPER_ATTRIBUTE_FORMAT(2, 3)
-static void whisper_log_internal        (ggml_log_level level, const char * format, ...);
 static void whisper_log_callback_default(ggml_log_level level, const char * text, void * user_data);

-#define WHISPER_LOG_ERROR(...) whisper_log_internal(GGML_LOG_LEVEL_ERROR, __VA_ARGS__)
-#define WHISPER_LOG_WARN(...)  whisper_log_internal(GGML_LOG_LEVEL_WARN , __VA_ARGS__)
-#define WHISPER_LOG_INFO(...)  whisper_log_internal(GGML_LOG_LEVEL_INFO , __VA_ARGS__)
-
-// define this to enable verbose trace logging - useful for debugging purposes
-//#define WHISPER_DEBUG
-
-#if defined(WHISPER_DEBUG)
-#define WHISPER_LOG_DEBUG(...) whisper_log_internal(GGML_LOG_LEVEL_DEBUG, __VA_ARGS__)
-#else
-#define WHISPER_LOG_DEBUG(...)
-#endif
-
 #define WHISPER_ASSERT(x) \
     do { \
         if (!(x)) { \
@@ -9386,7 +9362,7 @@ const char * whisper_version(void) {
 }

 GGML_ATTRIBUTE_FORMAT(2, 3)
-static void whisper_log_internal(ggml_log_level level, const char * format, ...) {
+void whisper_log_internal(ggml_log_level level, const char * format, ...) {
     va_list args;
     va_start(args, format);
     char buffer[1024];