From 5b62c11e4968e81145472d4870deb8c09885c672 Mon Sep 17 00:00:00 2001 From: Michael Sam Date: Mon, 6 Jul 2026 14:10:25 +0300 Subject: [PATCH] Fixes issue: #3924 Problem: invalid model ftype values reached ggml_ftype_to_ggml_type() and triggered GGML_ASSERT, aborting instead of returning a load error. Change: validate normalized hparams.ftype in whisper_model_load() before calling ggml_ftype_to_ggml_type(). Test: added test-model-load, which writes a minimal invalid model header with ftype = 5 and expects whisper_init_from_file_with_params() to fail cleanly. Verification I ran: git diff --check; direct compiled and ran test-model-load. CMake/CTest note: not run because cmake was unavailable in this environment. --- src/whisper.cpp | 37 +++++++++++++++++++++++++-- tests/CMakeLists.txt | 8 ++++++ tests/test-model-load.cpp | 53 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 tests/test-model-load.cpp diff --git a/src/whisper.cpp b/src/whisper.cpp index 2a95bdb1e8a..ab5a0b00572 100644 --- a/src/whisper.cpp +++ b/src/whisper.cpp @@ -602,6 +602,39 @@ struct whisper_hparams { float eps = 1e-5f; }; +static bool whisper_is_valid_ftype(int32_t ftype) { + switch ((ggml_ftype) ftype) { + case GGML_FTYPE_ALL_F32: + case GGML_FTYPE_MOSTLY_F16: + case GGML_FTYPE_MOSTLY_BF16: + case GGML_FTYPE_MOSTLY_Q4_0: + case GGML_FTYPE_MOSTLY_Q4_1: + case GGML_FTYPE_MOSTLY_Q1_0: + case GGML_FTYPE_MOSTLY_Q5_0: + case GGML_FTYPE_MOSTLY_Q5_1: + case GGML_FTYPE_MOSTLY_Q8_0: + case GGML_FTYPE_MOSTLY_MXFP4: + case GGML_FTYPE_MOSTLY_NVFP4: + case GGML_FTYPE_MOSTLY_Q2_K: + case GGML_FTYPE_MOSTLY_Q3_K: + case GGML_FTYPE_MOSTLY_Q4_K: + case GGML_FTYPE_MOSTLY_Q5_K: + case GGML_FTYPE_MOSTLY_Q6_K: + case GGML_FTYPE_MOSTLY_IQ2_XXS: + case GGML_FTYPE_MOSTLY_IQ2_XS: + case GGML_FTYPE_MOSTLY_IQ3_XXS: + case GGML_FTYPE_MOSTLY_IQ1_S: + case GGML_FTYPE_MOSTLY_IQ1_M: + case GGML_FTYPE_MOSTLY_IQ4_NL: + case GGML_FTYPE_MOSTLY_IQ4_XS: + case GGML_FTYPE_MOSTLY_IQ3_S: + case GGML_FTYPE_MOSTLY_IQ2_S: + return true; + default: + return false; + } +} + // audio encoding layer struct whisper_layer_encoder { // encoder.blocks.*.attn_ln @@ -1552,11 +1585,11 @@ static bool whisper_model_load(struct whisper_model_loader * loader, whisper_con // for the big tensors, we have the option to store the data in 16-bit floats or quantized // in order to save memory and also to speed up the computation - wctx.wtype = ggml_ftype_to_ggml_type((ggml_ftype) (model.hparams.ftype)); - if (wctx.wtype == GGML_TYPE_COUNT) { + if (!whisper_is_valid_ftype(model.hparams.ftype)) { WHISPER_LOG_ERROR("%s: invalid model (bad ftype value %d)\n", __func__, model.hparams.ftype); return false; } + wctx.wtype = ggml_ftype_to_ggml_type((ggml_ftype) (model.hparams.ftype)); WHISPER_LOG_INFO("%s: n_vocab = %d\n", __func__, hparams.n_vocab); WHISPER_LOG_INFO("%s: n_audio_ctx = %d\n", __func__, hparams.n_audio_ctx); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 74a5b142948..075db834d1a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -96,6 +96,14 @@ target_link_libraries(${UTF8_TEST} PRIVATE common) add_test(NAME ${UTF8_TEST} COMMAND ${UTF8_TEST}) set_tests_properties(${UTF8_TEST} PROPERTIES LABELS "unit") +# Model loading unit test +set(MODEL_LOAD_TEST test-model-load) +add_executable(${MODEL_LOAD_TEST} ${MODEL_LOAD_TEST}.cpp) +target_include_directories(${MODEL_LOAD_TEST} PRIVATE ../include ../ggml/include) +target_link_libraries(${MODEL_LOAD_TEST} PRIVATE whisper) +add_test(NAME ${MODEL_LOAD_TEST} COMMAND ${MODEL_LOAD_TEST}) +set_tests_properties(${MODEL_LOAD_TEST} PROPERTIES LABELS "unit") + # VAD test tests VAD in isolation set(VAD_TEST test-vad) add_executable(${VAD_TEST} ${VAD_TEST}.cpp) diff --git a/tests/test-model-load.cpp b/tests/test-model-load.cpp new file mode 100644 index 00000000000..57995932035 --- /dev/null +++ b/tests/test-model-load.cpp @@ -0,0 +1,53 @@ +#include "ggml.h" +#include "whisper.h" + +#include +#include +#include + +static void write_u32(std::ofstream & fout, uint32_t value) { + fout.write(reinterpret_cast(&value), sizeof(value)); +} + +static void write_i32(std::ofstream & fout, int32_t value) { + fout.write(reinterpret_cast(&value), sizeof(value)); +} + +int main() { + const char * path = "invalid-ftype.ggml"; + + { + std::ofstream fout(path, std::ios::binary); + if (!fout) { + return 1; + } + + write_u32(fout, GGML_FILE_MAGIC); + write_i32(fout, 1); // n_vocab + write_i32(fout, 1500); // n_audio_ctx + write_i32(fout, 384); // n_audio_state + write_i32(fout, 6); // n_audio_head + write_i32(fout, 4); // n_audio_layer + write_i32(fout, 448); // n_text_ctx + write_i32(fout, 384); // n_text_state + write_i32(fout, 6); // n_text_head + write_i32(fout, 4); // n_text_layer + write_i32(fout, 80); // n_mels + write_i32(fout, 5); // invalid ftype + + if (!fout) { + return 2; + } + } + + whisper_context_params params = whisper_context_default_params(); + whisper_context * ctx = whisper_init_from_file_with_params(path, params); + if (ctx != nullptr) { + whisper_free(ctx); + std::remove(path); + return 3; + } + + std::remove(path); + return 0; +}