From 0364178ca2452bd241a4cdd4350d0d7a6ff8c4b4 Mon Sep 17 00:00:00 2001 From: Xuan-Son Nguyen Date: Sat, 5 Apr 2025 17:17:40 +0200 Subject: [PATCH] clip : refactor clip_init, add tests (#12757) * refactor clip_init * fix loading file * fix style * test ok * better test with report * add missing headers * clarify * add KEY_MM_PATCH_MERGE_TYPE * remove bool has_* pattern * Apply suggestions from code review Co-authored-by: Georgi Gerganov * Update examples/llava/clip.cpp Co-authored-by: Georgi Gerganov * use ggml_soft_max_ext * refactor logging system * add minicpm-v-o 2.6 for testing * use nullptr everywhere * fix Yi-VL model --------- Co-authored-by: Georgi Gerganov --- examples/llava/clip-impl.h | 273 ++++++ examples/llava/clip.cpp | 1434 ++++++++++++------------------- examples/llava/clip.h | 3 +- examples/llava/gemma3-cli.cpp | 6 +- examples/llava/llava-cli.cpp | 2 +- examples/llava/minicpmv-cli.cpp | 2 +- examples/llava/qwen2vl-cli.cpp | 2 +- examples/llava/test-1.jpeg | Bin 0 -> 124071 bytes examples/llava/tests.sh | 81 ++ 9 files changed, 916 insertions(+), 887 deletions(-) create mode 100644 examples/llava/clip-impl.h create mode 100644 examples/llava/test-1.jpeg create mode 100755 examples/llava/tests.sh diff --git a/examples/llava/clip-impl.h b/examples/llava/clip-impl.h new file mode 100644 index 00000000..685d6e7e --- /dev/null +++ b/examples/llava/clip-impl.h @@ -0,0 +1,273 @@ +#include "ggml.h" +#include "gguf.h" + +#include +#include +#include +#include +#include +#include + +// Internal header for clip.cpp + +#define KEY_FTYPE "general.file_type" +#define KEY_NAME "general.name" +#define KEY_DESCRIPTION "general.description" +#define KEY_HAS_TEXT_ENC "clip.has_text_encoder" +#define KEY_HAS_VIS_ENC "clip.has_vision_encoder" +#define KEY_HAS_LLAVA_PROJ "clip.has_llava_projector" +#define KEY_HAS_MINICPMV_PROJ "clip.has_minicpmv_projector" +#define KEY_HAS_GLM_PROJ "clip.has_glm_projector" +#define KEY_MINICPMV_VERSION "clip.minicpmv_version" +#define KEY_HAS_QWEN2VL_MERGER "clip.has_qwen2vl_merger" +#define KEY_USE_GELU "clip.use_gelu" +#define KEY_USE_SILU "clip.use_silu" +#define KEY_N_EMBD "clip.%s.embedding_length" +#define KEY_N_FF "clip.%s.feed_forward_length" +#define KEY_N_BLOCK "clip.%s.block_count" +#define KEY_N_HEAD "clip.%s.attention.head_count" +#define KEY_LAYER_NORM_EPS "clip.%s.attention.layer_norm_epsilon" +#define KEY_PROJ_DIM "clip.%s.projection_dim" +#define KEY_TOKENS "tokenizer.ggml.tokens" +#define KEY_N_POSITIONS "clip.text.context_length" +#define KEY_IMAGE_SIZE "clip.vision.image_size" +#define KEY_PATCH_SIZE "clip.vision.patch_size" +#define KEY_IMAGE_MEAN "clip.vision.image_mean" +#define KEY_IMAGE_STD "clip.vision.image_std" +#define KEY_PROJ_TYPE "clip.projector_type" +#define KEY_FEATURE_LAYER "clip.vision.feature_layer" + +#define KEY_MM_PATCH_MERGE_TYPE "clip.vision.mm_patch_merge_type" +#define KEY_IMAGE_GRID_PINPOINTS "clip.vision.image_grid_pinpoints" +#define KEY_IMAGE_CROP_RESOLUTION "clip.vision.image_crop_resolution" + + +// +// tensor name constants +// + +#define TN_TOKEN_EMBD "%s.token_embd.weight" +#define TN_POS_EMBD "%s.position_embd.weight" +#define TN_CLASS_EMBD "v.class_embd" +#define TN_PATCH_EMBD "v.patch_embd.weight" // not rename tensor with ".0" postfix for backwrad compat +#define TN_PATCH_EMBD_1 "v.patch_embd.weight.1" +#define TN_PATCH_BIAS "v.patch_embd.bias" +#define TN_ATTN_K "%s.blk.%d.attn_k.%s" +#define TN_ATTN_Q "%s.blk.%d.attn_q.%s" +#define TN_ATTN_V "%s.blk.%d.attn_v.%s" +#define TN_ATTN_OUTPUT "%s.blk.%d.attn_out.%s" +#define TN_FFN_DOWN "%s.blk.%d.ffn_down.%s" +#define TN_FFN_UP "%s.blk.%d.ffn_up.%s" +#define TN_LN_1 "%s.blk.%d.ln1.%s" +#define TN_LN_2 "%s.blk.%d.ln2.%s" +#define TN_LN_PRE "%s.pre_ln.%s" +#define TN_LN_POST "%s.post_ln.%s" +#define TN_TEXT_PROJ "text_projection.weight" +#define TN_VIS_PROJ "visual_projection.weight" +#define TN_LLAVA_PROJ "mm.%d.%s" +#define TN_MVLM_PROJ_MLP "mm.model.mlp.%d.%s" +#define TN_MVLM_PROJ_BLOCK "mm.model.mb_block.%d.block.%d.%s" +#define TN_MVLM_PROJ_PEG "mm.model.peg.%d.%s" +#define TN_IMAGE_NEWLINE "model.image_newline" +#define TN_MM_INP_PROJ "mm.input_projection.weight" // gemma3 +#define TN_MM_SOFT_EMB_N "mm.soft_emb_norm.weight" // gemma3 + +// mimicpmv +#define TN_MINICPMV_POS_EMBD_K "resampler.pos_embed_k" +#define TN_MINICPMV_QUERY "resampler.query" +#define TN_MINICPMV_PROJ "resampler.proj.weight" +#define TN_MINICPMV_KV_PROJ "resampler.kv.weight" +#define TN_MINICPMV_ATTN "resampler.attn.%s.%s" +#define TN_MINICPMV_LN "resampler.ln_%s.%s" + +#define TN_GLM_ADAPER_CONV "adapter.conv.%s" +#define TN_GLM_ADAPTER_LINEAR "adapter.linear.linear.%s" +#define TN_GLM_ADAPTER_NORM_1 "adapter.linear.norm1.%s" +#define TN_GLM_ADAPTER_D_H_2_4H "adapter.linear.dense_h_to_4h.%s" +#define TN_GLM_ADAPTER_GATE "adapter.linear.gate.%s" +#define TN_GLM_ADAPTER_D_4H_2_H "adapter.linear.dense_4h_to_h.%s" +#define TN_GLM_BOI_W "adapter.boi" +#define TN_GLM_EOI_W "adapter.eoi" + +enum projector_type { + PROJECTOR_TYPE_MLP, + PROJECTOR_TYPE_MLP_NORM, + PROJECTOR_TYPE_LDP, + PROJECTOR_TYPE_LDPV2, + PROJECTOR_TYPE_RESAMPLER, + PROJECTOR_TYPE_GLM_EDGE, + PROJECTOR_TYPE_MERGER, + PROJECTOR_TYPE_GEMMA3, + PROJECTOR_TYPE_UNKNOWN, +}; + +static std::map PROJECTOR_TYPE_NAMES = { + { PROJECTOR_TYPE_MLP, "mlp" }, + { PROJECTOR_TYPE_LDP, "ldp" }, + { PROJECTOR_TYPE_LDPV2, "ldpv2"}, + { PROJECTOR_TYPE_RESAMPLER, "resampler"}, + { PROJECTOR_TYPE_GLM_EDGE, "adapter"}, + { PROJECTOR_TYPE_MERGER, "qwen2vl_merger"}, + { PROJECTOR_TYPE_GEMMA3, "gemma3"}, +}; + +static projector_type clip_projector_type_from_string(const std::string & str) { + for (const auto & pair : PROJECTOR_TYPE_NAMES) { + if (pair.second == str) { + return pair.first; + } + } + return PROJECTOR_TYPE_UNKNOWN; +} + +// +// logging +// + +static void clip_log_callback_default(enum ggml_log_level level, const char * text, void * user_data) { + (void) level; + (void) user_data; + fputs(text, stderr); + fflush(stderr); +} + +struct clip_logger_state { + ggml_log_level verbosity_thold; + ggml_log_callback log_callback; + void * log_callback_user_data; +}; + +extern struct clip_logger_state g_logger_state; + +static void clip_log_internal_v(enum ggml_log_level level, const char * format, va_list args) { + if (format == NULL) { + return; + } + va_list args_copy; + va_copy(args_copy, args); + char buffer[128]; + int len = vsnprintf(buffer, 128, format, args); + if (len < 128) { + g_logger_state.log_callback(level, buffer, g_logger_state.log_callback_user_data); + } else { + char * buffer2 = (char *) calloc(len + 1, sizeof(char)); + vsnprintf(buffer2, len + 1, format, args_copy); + buffer2[len] = 0; + g_logger_state.log_callback(level, buffer2, g_logger_state.log_callback_user_data); + free(buffer2); + } + va_end(args_copy); +} + +static void clip_log_internal(enum ggml_log_level level, const char * format, ...) { + va_list args; + va_start(args, format); + clip_log_internal_v(level, format, args); + va_end(args); +} + +#define LOG_TMPL(level, ...) \ + do { \ + if ((level) >= g_logger_state.verbosity_thold) { \ + clip_log_internal((level), __VA_ARGS__); \ + } \ + } while (0) +#define LOG_INF(...) LOG_TMPL(GGML_LOG_LEVEL_INFO, __VA_ARGS__) +#define LOG_WRN(...) LOG_TMPL(GGML_LOG_LEVEL_WARN, __VA_ARGS__) +#define LOG_ERR(...) LOG_TMPL(GGML_LOG_LEVEL_ERROR, __VA_ARGS__) +#define LOG_DBG(...) LOG_TMPL(GGML_LOG_LEVEL_DEBUG, __VA_ARGS__) +#define LOG_CNT(...) LOG_TMPL(GGML_LOG_LEVEL_CONT, __VA_ARGS__) + +// +// common utils +// + +static std::string string_format(const char * fmt, ...) { + va_list ap; + va_list ap2; + va_start(ap, fmt); + va_copy(ap2, ap); + int size = vsnprintf(NULL, 0, fmt, ap); + GGML_ASSERT(size >= 0 && size < INT_MAX); // NOLINT + std::vector buf(size + 1); + int size2 = vsnprintf(buf.data(), size + 1, fmt, ap2); + GGML_ASSERT(size2 == size); + va_end(ap2); + va_end(ap); + return std::string(buf.data(), buf.size()); +} + +static void string_replace_all(std::string & s, const std::string & search, const std::string & replace) { + if (search.empty()) { + return; + } + std::string builder; + builder.reserve(s.length()); + size_t pos = 0; + size_t last_pos = 0; + while ((pos = s.find(search, last_pos)) != std::string::npos) { + builder.append(s, last_pos, pos - last_pos); + builder.append(replace); + last_pos = pos + search.length(); + } + builder.append(s, last_pos, std::string::npos); + s = std::move(builder); +} + +// +// gguf utils +// + +static std::string gguf_data_to_str(enum gguf_type type, const void * data, int i) { + switch (type) { + case GGUF_TYPE_UINT8: return std::to_string(((const uint8_t *)data)[i]); + case GGUF_TYPE_INT8: return std::to_string(((const int8_t *)data)[i]); + case GGUF_TYPE_UINT16: return std::to_string(((const uint16_t *)data)[i]); + case GGUF_TYPE_INT16: return std::to_string(((const int16_t *)data)[i]); + case GGUF_TYPE_UINT32: return std::to_string(((const uint32_t *)data)[i]); + case GGUF_TYPE_INT32: return std::to_string(((const int32_t *)data)[i]); + case GGUF_TYPE_UINT64: return std::to_string(((const uint64_t *)data)[i]); + case GGUF_TYPE_INT64: return std::to_string(((const int64_t *)data)[i]); + case GGUF_TYPE_FLOAT32: return std::to_string(((const float *)data)[i]); + case GGUF_TYPE_FLOAT64: return std::to_string(((const double *)data)[i]); + case GGUF_TYPE_BOOL: return ((const bool *)data)[i] ? "true" : "false"; + default: return string_format("unknown type %d", type); + } +} + +static std::string gguf_kv_to_str(const struct gguf_context * ctx_gguf, int i) { + const enum gguf_type type = gguf_get_kv_type(ctx_gguf, i); + + switch (type) { + case GGUF_TYPE_STRING: + return gguf_get_val_str(ctx_gguf, i); + case GGUF_TYPE_ARRAY: + { + const enum gguf_type arr_type = gguf_get_arr_type(ctx_gguf, i); + int arr_n = gguf_get_arr_n(ctx_gguf, i); + const void * data = arr_type == GGUF_TYPE_STRING ? nullptr : gguf_get_arr_data(ctx_gguf, i); + std::stringstream ss; + ss << "["; + for (int j = 0; j < arr_n; j++) { + if (arr_type == GGUF_TYPE_STRING) { + std::string val = gguf_get_arr_str(ctx_gguf, i, j); + // escape quotes + string_replace_all(val, "\\", "\\\\"); + string_replace_all(val, "\"", "\\\""); + ss << '"' << val << '"'; + } else if (arr_type == GGUF_TYPE_ARRAY) { + ss << "???"; + } else { + ss << gguf_data_to_str(arr_type, data, j); + } + if (j < arr_n - 1) { + ss << ", "; + } + } + ss << "]"; + return ss.str(); + } + default: + return gguf_data_to_str(type, gguf_get_val_data(ctx_gguf, i), 0); + } +} diff --git a/examples/llava/clip.cpp b/examples/llava/clip.cpp index e315ef57..1145c816 100644 --- a/examples/llava/clip.cpp +++ b/examples/llava/clip.cpp @@ -3,6 +3,7 @@ // I'll gradually clean and extend it // Note: Even when using identical normalized image inputs (see normalize_image_u8_to_f32()) we have a significant difference in resulting embeddings compared to pytorch #include "clip.h" +#include "clip-impl.h" #include "ggml.h" #include "ggml-cpp.h" #include "ggml-cpu.h" @@ -27,17 +28,7 @@ #include #include -#if defined(LLAVA_LOG_OFF) -# define LOG_INF(...) -# define LOG_WRN(...) -# define LOG_ERR(...) -# define LOG_DBG(...) -#else // defined(LLAVA_LOG_OFF) -# define LOG_INF(...) do { fprintf(stdout, __VA_ARGS__); } while (0) -# define LOG_WRN(...) do { fprintf(stderr, __VA_ARGS__); } while (0) -# define LOG_ERR(...) do { fprintf(stderr, __VA_ARGS__); } while (0) -# define LOG_DBG(...) do { fprintf(stdout, __VA_ARGS__); } while (0) -#endif // defined(LLAVA_LOG_OFF) +struct clip_logger_state g_logger_state = {GGML_LOG_LEVEL_CONT, clip_log_callback_default, NULL}; //#define CLIP_DEBUG_FUNCTIONS @@ -58,253 +49,6 @@ struct clip_image_f32 { std::vector buf; }; -static std::string format(const char * fmt, ...) { - va_list ap; - va_list ap2; - va_start(ap, fmt); - va_copy(ap2, ap); - int size = vsnprintf(NULL, 0, fmt, ap); - GGML_ASSERT(size >= 0 && size < INT_MAX); // NOLINT - std::vector buf(size + 1); - int size2 = vsnprintf(buf.data(), size + 1, fmt, ap2); - GGML_ASSERT(size2 == size); - va_end(ap2); - va_end(ap); - return std::string(buf.data(), buf.size()); -} - -// -// key constants -// - -#define KEY_FTYPE "general.file_type" -#define KEY_NAME "general.name" -#define KEY_DESCRIPTION "general.description" -#define KEY_HAS_TEXT_ENC "clip.has_text_encoder" -#define KEY_HAS_VIS_ENC "clip.has_vision_encoder" -#define KEY_HAS_LLAVA_PROJ "clip.has_llava_projector" -#define KEY_HAS_MINICPMV_PROJ "clip.has_minicpmv_projector" -#define KEY_HAS_GLM_PROJ "clip.has_glm_projector" -#define KEY_MINICPMV_VERSION "clip.minicpmv_version" -#define KEY_HAS_QWEN2VL_MERGER "clip.has_qwen2vl_merger" -#define KEY_USE_GELU "clip.use_gelu" -#define KEY_USE_SILU "clip.use_silu" -#define KEY_N_EMBD "clip.%s.embedding_length" -#define KEY_N_FF "clip.%s.feed_forward_length" -#define KEY_N_BLOCK "clip.%s.block_count" -#define KEY_N_HEAD "clip.%s.attention.head_count" -#define KEY_LAYER_NORM_EPS "clip.%s.attention.layer_norm_epsilon" -#define KEY_PROJ_DIM "clip.%s.projection_dim" -#define KEY_TOKENS "tokenizer.ggml.tokens" -#define KEY_N_POSITIONS "clip.text.context_length" -#define KEY_IMAGE_SIZE "clip.vision.image_size" -#define KEY_PATCH_SIZE "clip.vision.patch_size" -#define KEY_IMAGE_MEAN "clip.vision.image_mean" -#define KEY_IMAGE_STD "clip.vision.image_std" -#define KEY_PROJ_TYPE "clip.projector_type" -#define KEY_FEATURE_LAYER "clip.vision.feature_layer" - -#define KEY_MM_PATCH_MERGE_TYPE "clip.vision.mm_patch_merge_type" -#define KEY_IMAGE_GRID_PINPOINTS "clip.vision.image_grid_pinpoints" -#define KEY_IMAGE_CROP_RESOLUTION "clip.vision.image_crop_resolution" - - -// -// tensor name constants -// - -#define TN_TOKEN_EMBD "%s.token_embd.weight" -#define TN_POS_EMBD "%s.position_embd.weight" -#define TN_CLASS_EMBD "v.class_embd" -#define TN_PATCH_EMBD "v.patch_embd.weight" // not rename tensor with ".0" postfix for backwrad compat -#define TN_PATCH_EMBD_1 "v.patch_embd.weight.1" -#define TN_PATCH_BIAS "v.patch_embd.bias" -#define TN_ATTN_K "%s.blk.%d.attn_k.%s" -#define TN_ATTN_Q "%s.blk.%d.attn_q.%s" -#define TN_ATTN_V "%s.blk.%d.attn_v.%s" -#define TN_ATTN_OUTPUT "%s.blk.%d.attn_out.%s" -#define TN_FFN_DOWN "%s.blk.%d.ffn_down.%s" -#define TN_FFN_UP "%s.blk.%d.ffn_up.%s" -#define TN_LN_1 "%s.blk.%d.ln1.%s" -#define TN_LN_2 "%s.blk.%d.ln2.%s" -#define TN_LN_PRE "%s.pre_ln.%s" -#define TN_LN_POST "%s.post_ln.%s" -#define TN_TEXT_PROJ "text_projection.weight" -#define TN_VIS_PROJ "visual_projection.weight" -#define TN_LLAVA_PROJ "mm.%d.%s" -#define TN_MVLM_PROJ_MLP "mm.model.mlp.%d.%s" -#define TN_MVLM_PROJ_BLOCK "mm.model.mb_block.%d.block.%d.%s" -#define TN_MVLM_PROJ_PEG "mm.model.peg.%d.%s" -#define TN_IMAGE_NEWLINE "model.image_newline" -#define TN_MM_INP_PROJ "mm.input_projection.weight" // gemma3 -#define TN_MM_SOFT_EMB_N "mm.soft_emb_norm.weight" // gemma3 - -#define TN_MINICPMV_POS_EMBD_K "resampler.pos_embed_k" -#define TN_MINICPMV_QUERY "resampler.query" -#define TN_MINICPMV_PROJ "resampler.proj.weight" -#define TN_MINICPMV_KV_PROJ "resampler.kv.weight" -#define TN_MINICPMV_ATTN "resampler.attn.%s.%s" -#define TN_MINICPMV_LN "resampler.ln_%s.%s" - -#define TN_GLM_ADAPER_CONV "adapter.conv.%s" -#define TN_GLM_ADAPTER_LINEAR "adapter.linear.linear.%s" -#define TN_GLM_ADAPTER_NORM_1 "adapter.linear.norm1.%s" -#define TN_GLM_ADAPTER_D_H_2_4H "adapter.linear.dense_h_to_4h.%s" -#define TN_GLM_ADAPTER_GATE "adapter.linear.gate.%s" -#define TN_GLM_ADAPTER_D_4H_2_H "adapter.linear.dense_4h_to_h.%s" -#define TN_GLM_BOI_W "adapter.boi" -#define TN_GLM_EOI_W "adapter.eoi" - - -enum projector_type { - PROJECTOR_TYPE_MLP, - PROJECTOR_TYPE_MLP_NORM, - PROJECTOR_TYPE_LDP, - PROJECTOR_TYPE_LDPV2, - PROJECTOR_TYPE_RESAMPLER, - PROJECTOR_TYPE_GLM_EDGE, - PROJECTOR_TYPE_MERGER, - PROJECTOR_TYPE_GEMMA3, - PROJECTOR_TYPE_UNKNOWN, -}; - -static std::map PROJECTOR_TYPE_NAMES = { - { PROJECTOR_TYPE_MLP, "mlp" }, - { PROJECTOR_TYPE_LDP, "ldp" }, - { PROJECTOR_TYPE_LDPV2, "ldpv2"}, - { PROJECTOR_TYPE_RESAMPLER, "resampler"}, - { PROJECTOR_TYPE_GLM_EDGE, "adapter"}, - { PROJECTOR_TYPE_MERGER, "qwen2vl_merger"}, - { PROJECTOR_TYPE_GEMMA3, "gemma3"}, -}; - - -// -// utilities to get data from a gguf file -// - -static int get_key_idx(const gguf_context * ctx, const char * key) { - int i = gguf_find_key(ctx, key); - if (i == -1) { - LOG_ERR("key %s not found in file\n", key); - throw std::runtime_error(format("Missing required key: %s", key)); - } - - return i; -} - -static uint32_t get_u32(const gguf_context * ctx, const std::string & key) { - const int i = get_key_idx(ctx, key.c_str()); - - return gguf_get_val_u32(ctx, i); -} - -static float get_f32(const gguf_context * ctx, const std::string & key) { - const int i = get_key_idx(ctx, key.c_str()); - - return gguf_get_val_f32(ctx, i); -} - -static struct ggml_tensor * get_tensor(struct ggml_context * ctx, const std::string & name) { - struct ggml_tensor * cur = ggml_get_tensor(ctx, name.c_str()); - if (!cur) { - throw std::runtime_error(format("%s: unable to find tensor %s\n", __func__, name.c_str())); - } - - return cur; -} - -static std::string get_ftype(int ftype) { - return ggml_type_name(static_cast(ftype)); -} - -static std::string gguf_data_to_str(enum gguf_type type, const void * data, int i) { - switch (type) { - case GGUF_TYPE_UINT8: return std::to_string(((const uint8_t *)data)[i]); - case GGUF_TYPE_INT8: return std::to_string(((const int8_t *)data)[i]); - case GGUF_TYPE_UINT16: return std::to_string(((const uint16_t *)data)[i]); - case GGUF_TYPE_INT16: return std::to_string(((const int16_t *)data)[i]); - case GGUF_TYPE_UINT32: return std::to_string(((const uint32_t *)data)[i]); - case GGUF_TYPE_INT32: return std::to_string(((const int32_t *)data)[i]); - case GGUF_TYPE_UINT64: return std::to_string(((const uint64_t *)data)[i]); - case GGUF_TYPE_INT64: return std::to_string(((const int64_t *)data)[i]); - case GGUF_TYPE_FLOAT32: return std::to_string(((const float *)data)[i]); - case GGUF_TYPE_FLOAT64: return std::to_string(((const double *)data)[i]); - case GGUF_TYPE_BOOL: return ((const bool *)data)[i] ? "true" : "false"; - default: return format("unknown type %d", type); - } -} - -static void replace_all(std::string & s, const std::string & search, const std::string & replace) { - if (search.empty()) { - return; - } - std::string builder; - builder.reserve(s.length()); - size_t pos = 0; - size_t last_pos = 0; - while ((pos = s.find(search, last_pos)) != std::string::npos) { - builder.append(s, last_pos, pos - last_pos); - builder.append(replace); - last_pos = pos + search.length(); - } - builder.append(s, last_pos, std::string::npos); - s = std::move(builder); -} - -static std::string gguf_kv_to_str(const struct gguf_context * ctx_gguf, int i) { - const enum gguf_type type = gguf_get_kv_type(ctx_gguf, i); - - switch (type) { - case GGUF_TYPE_STRING: - return gguf_get_val_str(ctx_gguf, i); - case GGUF_TYPE_ARRAY: - { - const enum gguf_type arr_type = gguf_get_arr_type(ctx_gguf, i); - int arr_n = gguf_get_arr_n(ctx_gguf, i); - const void * data = arr_type == GGUF_TYPE_STRING ? nullptr : gguf_get_arr_data(ctx_gguf, i); - std::stringstream ss; - ss << "["; - for (int j = 0; j < arr_n; j++) { - if (arr_type == GGUF_TYPE_STRING) { - std::string val = gguf_get_arr_str(ctx_gguf, i, j); - // escape quotes - replace_all(val, "\\", "\\\\"); - replace_all(val, "\"", "\\\""); - ss << '"' << val << '"'; - } else if (arr_type == GGUF_TYPE_ARRAY) { - ss << "???"; - } else { - ss << gguf_data_to_str(arr_type, data, j); - } - if (j < arr_n - 1) { - ss << ", "; - } - } - ss << "]"; - return ss.str(); - } - default: - return gguf_data_to_str(type, gguf_get_val_data(ctx_gguf, i), 0); - } -} - -static void print_tensor_info(const ggml_tensor * tensor, const char * prefix = "") { - size_t tensor_size = ggml_nbytes(tensor); - LOG_INF("%s: n_dims = %d, name = %s, tensor_size=%zu, shape:[%" PRId64 ", %" PRId64 ", %" PRId64 ", %" PRId64 "], type = %s\n", - prefix, ggml_n_dims(tensor), tensor->name, tensor_size, - tensor->ne[0], tensor->ne[1], tensor->ne[2], tensor->ne[3], ggml_type_name(tensor->type)); -} - -static projector_type clip_projector_type_from_string(const std::string & name) { - for (const auto & kv : PROJECTOR_TYPE_NAMES) { // NOLINT - if (kv.second == name) { - return kv.first; - } - } - throw std::runtime_error(format("Unknown projector type: %s", name.c_str())); -} - #ifdef CLIP_DEBUG_FUNCTIONS static void clip_image_write_image_to_ppm(const clip_image_u8& img, const std::string& filename) { std::ofstream file(filename, std::ios::binary); @@ -418,6 +162,11 @@ static void clip_image_convert_f32_to_u8(const clip_image_f32& src, clip_image_u // clip layers // +enum patch_merge_type { + PATCH_MERGE_FLAT, + PATCH_MERGE_SPATIAL_UNPAD, +}; + struct clip_hparams { int32_t image_size; int32_t patch_size; @@ -427,9 +176,9 @@ struct clip_hparams { int32_t n_head; int32_t n_layer; - float eps; + patch_merge_type mm_patch_merge_type = PATCH_MERGE_FLAT; - char mm_patch_merge_type[32] = "flat"; // spatial_unpad or flat (default) + float eps; std::vector image_grid_pinpoints; int32_t image_crop_resolution; @@ -438,44 +187,44 @@ struct clip_hparams { struct clip_layer { // attention - struct ggml_tensor * k_w; - struct ggml_tensor * k_b; - struct ggml_tensor * q_w; - struct ggml_tensor * q_b; - struct ggml_tensor * v_w; - struct ggml_tensor * v_b; + struct ggml_tensor * k_w = nullptr; + struct ggml_tensor * k_b = nullptr; + struct ggml_tensor * q_w = nullptr; + struct ggml_tensor * q_b = nullptr; + struct ggml_tensor * v_w = nullptr; + struct ggml_tensor * v_b = nullptr; - struct ggml_tensor * o_w; - struct ggml_tensor * o_b; + struct ggml_tensor * o_w = nullptr; + struct ggml_tensor * o_b = nullptr; // layernorm 1 - struct ggml_tensor * ln_1_w; - struct ggml_tensor * ln_1_b; + struct ggml_tensor * ln_1_w = nullptr; + struct ggml_tensor * ln_1_b = nullptr; // ff - struct ggml_tensor * ff_i_w; - struct ggml_tensor * ff_i_b; + struct ggml_tensor * ff_i_w = nullptr; + struct ggml_tensor * ff_i_b = nullptr; - struct ggml_tensor * ff_o_w; - struct ggml_tensor * ff_o_b; + struct ggml_tensor * ff_o_w = nullptr; + struct ggml_tensor * ff_o_b = nullptr; // layernorm 2 - struct ggml_tensor * ln_2_w; - struct ggml_tensor * ln_2_b; + struct ggml_tensor * ln_2_w = nullptr; + struct ggml_tensor * ln_2_b = nullptr; }; struct clip_vision_model { struct clip_hparams hparams; // embeddings - struct ggml_tensor * class_embedding; - struct ggml_tensor * patch_embeddings_0; - struct ggml_tensor * patch_embeddings_1; // second Conv2D kernel when we decouple Conv3D along temproal dimension (Qwen2VL) - struct ggml_tensor * patch_bias; - struct ggml_tensor * position_embeddings; + struct ggml_tensor * class_embedding = nullptr; + struct ggml_tensor * patch_embeddings_0 = nullptr; + struct ggml_tensor * patch_embeddings_1 = nullptr; // second Conv2D kernel when we decouple Conv3D along temproal dimension (Qwen2VL) + struct ggml_tensor * patch_bias = nullptr; + struct ggml_tensor * position_embeddings = nullptr; - struct ggml_tensor * pre_ln_w; - struct ggml_tensor * pre_ln_b; + struct ggml_tensor * pre_ln_w = nullptr; + struct ggml_tensor * pre_ln_b = nullptr; std::vector layers; @@ -485,84 +234,84 @@ struct clip_vision_model { struct ggml_tensor * projection; // LLaVA projection - struct ggml_tensor * mm_0_w = NULL; - struct ggml_tensor * mm_0_b = NULL; - struct ggml_tensor * mm_2_w = NULL; - struct ggml_tensor * mm_2_b = NULL; + struct ggml_tensor * mm_0_w = nullptr; + struct ggml_tensor * mm_0_b = nullptr; + struct ggml_tensor * mm_2_w = nullptr; + struct ggml_tensor * mm_2_b = nullptr; - struct ggml_tensor * image_newline = NULL; + struct ggml_tensor * image_newline = nullptr; // Yi type models with mlp+normalization projection - struct ggml_tensor * mm_1_w = NULL; // Yi type models have 0, 1, 3, 4 - struct ggml_tensor * mm_1_b = NULL; - struct ggml_tensor * mm_3_w = NULL; - struct ggml_tensor * mm_3_b = NULL; - struct ggml_tensor * mm_4_w = NULL; - struct ggml_tensor * mm_4_b = NULL; + struct ggml_tensor * mm_1_w = nullptr; // Yi type models have 0, 1, 3, 4 + struct ggml_tensor * mm_1_b = nullptr; + struct ggml_tensor * mm_3_w = nullptr; + struct ggml_tensor * mm_3_b = nullptr; + struct ggml_tensor * mm_4_w = nullptr; + struct ggml_tensor * mm_4_b = nullptr; //GLMV-Edge projection - struct ggml_tensor * mm_model_adapter_conv_w; - struct ggml_tensor * mm_model_adapter_conv_b; - struct ggml_tensor * boi_w; - struct ggml_tensor * eoi_w; + struct ggml_tensor * mm_model_adapter_conv_w = nullptr; + struct ggml_tensor * mm_model_adapter_conv_b = nullptr; + struct ggml_tensor * boi_w = nullptr; + struct ggml_tensor * eoi_w = nullptr; // MobileVLM projection - struct ggml_tensor * mm_model_mlp_1_w; - struct ggml_tensor * mm_model_mlp_1_b; - struct ggml_tensor * mm_model_mlp_3_w; - struct ggml_tensor * mm_model_mlp_3_b; - struct ggml_tensor * mm_model_block_1_block_0_0_w; - struct ggml_tensor * mm_model_block_1_block_0_1_w; - struct ggml_tensor * mm_model_block_1_block_0_1_b; - struct ggml_tensor * mm_model_block_1_block_1_fc1_w; - struct ggml_tensor * mm_model_block_1_block_1_fc1_b; - struct ggml_tensor * mm_model_block_1_block_1_fc2_w; - struct ggml_tensor * mm_model_block_1_block_1_fc2_b; - struct ggml_tensor * mm_model_block_1_block_2_0_w; - struct ggml_tensor * mm_model_block_1_block_2_1_w; - struct ggml_tensor * mm_model_block_1_block_2_1_b; - struct ggml_tensor * mm_model_block_2_block_0_0_w; - struct ggml_tensor * mm_model_block_2_block_0_1_w; - struct ggml_tensor * mm_model_block_2_block_0_1_b; - struct ggml_tensor * mm_model_block_2_block_1_fc1_w; - struct ggml_tensor * mm_model_block_2_block_1_fc1_b; - struct ggml_tensor * mm_model_block_2_block_1_fc2_w; - struct ggml_tensor * mm_model_block_2_block_1_fc2_b; - struct ggml_tensor * mm_model_block_2_block_2_0_w; - struct ggml_tensor * mm_model_block_2_block_2_1_w; - struct ggml_tensor * mm_model_block_2_block_2_1_b; + struct ggml_tensor * mm_model_mlp_1_w = nullptr; + struct ggml_tensor * mm_model_mlp_1_b = nullptr; + struct ggml_tensor * mm_model_mlp_3_w = nullptr; + struct ggml_tensor * mm_model_mlp_3_b = nullptr; + struct ggml_tensor * mm_model_block_1_block_0_0_w = nullptr; + struct ggml_tensor * mm_model_block_1_block_0_1_w = nullptr; + struct ggml_tensor * mm_model_block_1_block_0_1_b = nullptr; + struct ggml_tensor * mm_model_block_1_block_1_fc1_w = nullptr; + struct ggml_tensor * mm_model_block_1_block_1_fc1_b = nullptr; + struct ggml_tensor * mm_model_block_1_block_1_fc2_w = nullptr; + struct ggml_tensor * mm_model_block_1_block_1_fc2_b = nullptr; + struct ggml_tensor * mm_model_block_1_block_2_0_w = nullptr; + struct ggml_tensor * mm_model_block_1_block_2_1_w = nullptr; + struct ggml_tensor * mm_model_block_1_block_2_1_b = nullptr; + struct ggml_tensor * mm_model_block_2_block_0_0_w = nullptr; + struct ggml_tensor * mm_model_block_2_block_0_1_w = nullptr; + struct ggml_tensor * mm_model_block_2_block_0_1_b = nullptr; + struct ggml_tensor * mm_model_block_2_block_1_fc1_w = nullptr; + struct ggml_tensor * mm_model_block_2_block_1_fc1_b = nullptr; + struct ggml_tensor * mm_model_block_2_block_1_fc2_w = nullptr; + struct ggml_tensor * mm_model_block_2_block_1_fc2_b = nullptr; + struct ggml_tensor * mm_model_block_2_block_2_0_w = nullptr; + struct ggml_tensor * mm_model_block_2_block_2_1_w = nullptr; + struct ggml_tensor * mm_model_block_2_block_2_1_b = nullptr; // MobileVLM_V2 projection - struct ggml_tensor * mm_model_mlp_0_w; - struct ggml_tensor * mm_model_mlp_0_b; - struct ggml_tensor * mm_model_mlp_2_w; - struct ggml_tensor * mm_model_mlp_2_b; - struct ggml_tensor * mm_model_peg_0_w; - struct ggml_tensor * mm_model_peg_0_b; + struct ggml_tensor * mm_model_mlp_0_w = nullptr; + struct ggml_tensor * mm_model_mlp_0_b = nullptr; + struct ggml_tensor * mm_model_mlp_2_w = nullptr; + struct ggml_tensor * mm_model_mlp_2_b = nullptr; + struct ggml_tensor * mm_model_peg_0_w = nullptr; + struct ggml_tensor * mm_model_peg_0_b = nullptr; // MINICPMV projection - struct ggml_tensor * mm_model_pos_embed_k; - struct ggml_tensor * mm_model_query; - struct ggml_tensor * mm_model_proj; - struct ggml_tensor * mm_model_kv_proj; - struct ggml_tensor * mm_model_attn_q_w; - struct ggml_tensor * mm_model_attn_q_b; - struct ggml_tensor * mm_model_attn_k_w; - struct ggml_tensor * mm_model_attn_k_b; - struct ggml_tensor * mm_model_attn_v_w; - struct ggml_tensor * mm_model_attn_v_b; - struct ggml_tensor * mm_model_attn_o_w; - struct ggml_tensor * mm_model_attn_o_b; - struct ggml_tensor * mm_model_ln_q_w; - struct ggml_tensor * mm_model_ln_q_b; - struct ggml_tensor * mm_model_ln_kv_w; - struct ggml_tensor * mm_model_ln_kv_b; - struct ggml_tensor * mm_model_ln_post_w; - struct ggml_tensor * mm_model_ln_post_b; + struct ggml_tensor * mm_model_pos_embed_k = nullptr; + struct ggml_tensor * mm_model_query = nullptr; + struct ggml_tensor * mm_model_proj = nullptr; + struct ggml_tensor * mm_model_kv_proj = nullptr; + struct ggml_tensor * mm_model_attn_q_w = nullptr; + struct ggml_tensor * mm_model_attn_q_b = nullptr; + struct ggml_tensor * mm_model_attn_k_w = nullptr; + struct ggml_tensor * mm_model_attn_k_b = nullptr; + struct ggml_tensor * mm_model_attn_v_w = nullptr; + struct ggml_tensor * mm_model_attn_v_b = nullptr; + struct ggml_tensor * mm_model_attn_o_w = nullptr; + struct ggml_tensor * mm_model_attn_o_b = nullptr; + struct ggml_tensor * mm_model_ln_q_w = nullptr; + struct ggml_tensor * mm_model_ln_q_b = nullptr; + struct ggml_tensor * mm_model_ln_kv_w = nullptr; + struct ggml_tensor * mm_model_ln_kv_b = nullptr; + struct ggml_tensor * mm_model_ln_post_w = nullptr; + struct ggml_tensor * mm_model_ln_post_b = nullptr; // gemma3 - struct ggml_tensor * mm_input_proj_w; - struct ggml_tensor * mm_soft_emb_norm_w; + struct ggml_tensor * mm_input_proj_w = nullptr; + struct ggml_tensor * mm_soft_emb_norm_w = nullptr; }; struct clip_ctx { @@ -584,11 +333,6 @@ struct clip_ctx { bool use_silu = false; int32_t ftype = 1; - bool has_class_embedding = true; - bool has_pre_norm = true; - bool has_post_norm = false; - bool has_patch_bias = false; - struct gguf_context * ctx_gguf = nullptr; struct ggml_context * ctx_data = nullptr; @@ -711,8 +455,7 @@ static ggml_cgraph * clip_image_build_graph_siglip(clip_ctx * ctx, const clip_im V = ggml_cont(ctx0, ggml_permute(ctx0, V, 1, 2, 0, 3)); struct ggml_tensor * KQ = ggml_mul_mat(ctx0, K, Q); - KQ = ggml_scale_inplace(ctx0, KQ, 1.0f / sqrtf((float)d_head)); - KQ = ggml_soft_max_inplace(ctx0, KQ); + KQ = ggml_soft_max_ext(ctx0, KQ, nullptr, 1.0f / sqrtf((float)d_head), 0.0f); struct ggml_tensor * KQV = ggml_mul_mat(ctx0, V, KQ); KQV = ggml_reshape_3d(ctx0, KQV, d_head, num_patches, n_head); @@ -751,7 +494,7 @@ static ggml_cgraph * clip_image_build_graph_siglip(clip_ctx * ctx, const clip_im } // post-layernorm - if (ctx->has_post_norm) { + if (model.post_ln_w) { embeddings = ggml_norm(ctx0, embeddings, eps); ggml_set_name(embeddings, "post_ln"); @@ -827,7 +570,7 @@ static ggml_cgraph * clip_image_build_graph_legacy(clip_ctx * ctx, const clip_im const int num_patches = ((image_size_width / patch_size) * (image_size_height / patch_size)); const int patches_w = image_size_width / patch_size; const int patches_h = image_size_height / patch_size; - const int num_positions = num_patches + (ctx->has_class_embedding ? 1 : 0); + const int num_positions = num_patches + (model.class_embedding ? 1 : 0); const int num_position_ids = ctx->has_qwen2vl_merger ? num_positions * 4 : num_positions; const int hidden_size = hparams.hidden_size; const int n_head = hparams.n_head; @@ -879,7 +622,7 @@ static ggml_cgraph * clip_image_build_graph_legacy(clip_ctx * ctx, const clip_im inp = ggml_cont(ctx0, ggml_permute(ctx0, inp, 1, 0, 2, 3)); } - if (ctx->has_patch_bias) { + if (model.patch_bias) { // inp = ggml_add(ctx0, inp, ggml_repeat(ctx0, model.patch_bias, inp)); inp = ggml_add(ctx0, inp, model.patch_bias); } @@ -888,7 +631,7 @@ static ggml_cgraph * clip_image_build_graph_legacy(clip_ctx * ctx, const clip_im if (ctx->has_llava_projector) { // concat class_embeddings and patch_embeddings - if (ctx->has_class_embedding) { + if (model.class_embedding) { embeddings = ggml_new_tensor_3d(ctx0, GGML_TYPE_F32, hidden_size, num_positions, batch_size); ggml_set_name(embeddings, "embeddings"); ggml_set_input(embeddings); @@ -925,7 +668,7 @@ static ggml_cgraph * clip_image_build_graph_legacy(clip_ctx * ctx, const clip_im } // pre-layernorm - if (ctx->has_pre_norm) { + if (model.pre_ln_w) { embeddings = ggml_norm(ctx0, embeddings, eps); ggml_set_name(embeddings, "pre_ln"); @@ -967,7 +710,6 @@ static ggml_cgraph * clip_image_build_graph_legacy(clip_ctx * ctx, const clip_im ctx0, Q, positions, nullptr, d_head/2, mrope_sections, GGML_ROPE_TYPE_VISION, 32768, 10000, 1, 0, 1, 32, 1); } - Q = ggml_scale_inplace(ctx0, Q, 1.0f / sqrt((float)d_head)); Q = ggml_cont(ctx0, ggml_permute(ctx0, Q, 0, 2, 1, 3)); Q = ggml_reshape_3d(ctx0, Q, d_head, num_positions, n_head * batch_size); @@ -991,7 +733,7 @@ static ggml_cgraph * clip_image_build_graph_legacy(clip_ctx * ctx, const clip_im V = ggml_reshape_3d(ctx0, V, num_positions, d_head, n_head * batch_size); struct ggml_tensor * KQ = ggml_mul_mat(ctx0, K, Q); - KQ = ggml_soft_max_inplace(ctx0, KQ); + KQ = ggml_soft_max_ext(ctx0, KQ, nullptr, 1.0f / sqrtf((float)d_head), 0.0f); struct ggml_tensor * KQV = ggml_mul_mat(ctx0, V, KQ); KQV = ggml_reshape_4d(ctx0, KQV, d_head, num_positions, n_head, batch_size); KQV = ggml_permute(ctx0, KQV, 0, 2, 1, 3); @@ -1035,7 +777,7 @@ static ggml_cgraph * clip_image_build_graph_legacy(clip_ctx * ctx, const clip_im } // post-layernorm - if (ctx->has_post_norm) { + if (model.post_ln_w) { embeddings = ggml_norm(ctx0, embeddings, eps); ggml_set_name(embeddings, "post_ln"); @@ -1075,8 +817,10 @@ static ggml_cgraph * clip_image_build_graph_legacy(clip_ctx * ctx, const clip_im embeddings = ggml_add(ctx0, embeddings, model.mm_0_b); embeddings = ggml_gelu(ctx0, embeddings); - embeddings = ggml_mul_mat(ctx0, model.mm_2_w, embeddings); - embeddings = ggml_add(ctx0, embeddings, model.mm_2_b); + if (model.mm_2_w) { + embeddings = ggml_mul_mat(ctx0, model.mm_2_w, embeddings); + embeddings = ggml_add(ctx0, embeddings, model.mm_2_b); + } } else if (ctx->proj_type == PROJECTOR_TYPE_MLP_NORM) { embeddings = ggml_mul_mat(ctx0, model.mm_0_w, embeddings); @@ -1279,7 +1023,6 @@ static ggml_cgraph * clip_image_build_graph_legacy(clip_ctx * ctx, const clip_im } struct ggml_tensor * Q = ggml_add(ctx0, ggml_mul_mat(ctx0, model.mm_model_attn_q_w, q), model.mm_model_attn_q_b); - Q = ggml_scale_inplace(ctx0, Q, 1.0f / sqrt((float)d_head)); struct ggml_tensor * K = ggml_add(ctx0, ggml_mul_mat(ctx0, model.mm_model_attn_k_w, k), model.mm_model_attn_k_b); struct ggml_tensor * V = ggml_add(ctx0, ggml_mul_mat(ctx0, model.mm_model_attn_v_w, v), model.mm_model_attn_v_b); // permute @@ -1293,7 +1036,7 @@ static ggml_cgraph * clip_image_build_graph_legacy(clip_ctx * ctx, const clip_im V = ggml_cont(ctx0, ggml_permute(ctx0, V, 1, 2, 0, 3)); V = ggml_reshape_3d(ctx0, V, num_positions, d_head, n_head * batch_size); struct ggml_tensor * KQ = ggml_mul_mat(ctx0, K, Q); - KQ = ggml_soft_max_inplace(ctx0, KQ); + KQ = ggml_soft_max_ext(ctx0, KQ, nullptr, 1.0f / sqrtf((float)d_head), 0.0f); struct ggml_tensor * KQV = ggml_mul_mat(ctx0, V, KQ); KQV = ggml_reshape_4d(ctx0, KQV, d_head, num_query, n_head, batch_size); KQV = ggml_permute(ctx0, KQV, 0, 2, 1, 3); @@ -1369,549 +1112,395 @@ static ggml_cgraph * clip_image_build_graph(clip_ctx * ctx, const clip_image_f32 } } -// read and create ggml_context containing the tensors and their data -struct clip_ctx * clip_model_load(const char * fname, const int verbosity = 1) { - return clip_init(fname, clip_context_params{ - /* use_gpu */ true, - /* verbosity */ verbosity, - }); -} +struct clip_model_loader { + ggml_context_ptr ctx_meta; + gguf_context_ptr ctx_gguf; -struct clip_ctx * clip_init(const char * fname, struct clip_context_params ctx_params) { - int verbosity = ctx_params.verbosity; - struct ggml_context * meta = NULL; + clip_ctx & ctx_clip; + std::string fname; - struct gguf_init_params params = { - /*.no_alloc = */ true, - /*.ctx = */ &meta, - }; + size_t model_size; // in bytes - struct gguf_context * ctx = gguf_init_from_file(fname, params); - if (!ctx) { - throw std::runtime_error(format("%s: failed to load CLIP model from %s. Does this file exist?\n", __func__, fname)); - } + // TODO @ngxson : we should not pass clip_ctx here, it should be clip_vision_model + clip_model_loader(const char * fname, clip_ctx & ctx_clip) : ctx_clip(ctx_clip), fname(fname) { + struct ggml_context * meta = nullptr; - if (verbosity >= 1) { - const int n_tensors = gguf_get_n_tensors(ctx); - const int n_kv = gguf_get_n_kv(ctx); - const int ftype = get_u32(ctx, KEY_FTYPE); - const std::string ftype_str = get_ftype(ftype); - const int idx_name = gguf_find_key(ctx, KEY_NAME); - if (idx_name != -1) { // make name optional temporarily as some of the uploaded models missing it due to a bug - const std::string name = gguf_get_val_str(ctx, idx_name); - LOG_INF("%s: model name: %s\n", __func__, name.c_str()); - } - const int idx_desc = gguf_find_key(ctx, KEY_DESCRIPTION); - if (idx_desc != -1) { // ditto - const std::string description = gguf_get_val_str(ctx, idx_desc); - LOG_INF("%s: description: %s\n", __func__, description.c_str()); - } - LOG_INF("%s: GGUF version: %d\n", __func__, gguf_get_version(ctx)); - LOG_INF("%s: alignment: %zu\n", __func__, gguf_get_alignment(ctx)); - LOG_INF("%s: n_tensors: %d\n", __func__, n_tensors); - LOG_INF("%s: n_kv: %d\n", __func__, n_kv); - LOG_INF("%s: ftype: %s\n", __func__, ftype_str.c_str()); - LOG_INF("\n"); - } - const int n_tensors = gguf_get_n_tensors(ctx); + struct gguf_init_params params = { + /*.no_alloc = */ true, + /*.ctx = */ &meta, + }; - // kv - const int n_kv = gguf_get_n_kv(ctx); - LOG_INF("%s: loaded meta data with %d key-value pairs and %d tensors from %s\n", - __func__, n_kv, n_tensors, fname); - { - std::map n_type; - - for (int i = 0; i < n_tensors; i++) { - enum ggml_type type = gguf_get_tensor_type(ctx, i); - - n_type[type]++; + ctx_gguf = gguf_context_ptr(gguf_init_from_file(fname, params)); + if (!ctx_gguf.get()) { + throw std::runtime_error(string_format("%s: failed to load CLIP model from %s. Does this file exist?\n", __func__, fname)); } - LOG_INF("%s: Dumping metadata keys/values. Note: KV overrides do not apply in this output.\n", __func__); - for (int i = 0; i < n_kv; i++) { - const char * name = gguf_get_key(ctx, i); - const enum gguf_type type = gguf_get_kv_type(ctx, i); - const std::string type_name = - type == GGUF_TYPE_ARRAY - ? format("%s[%s,%d]", gguf_type_name(type), gguf_type_name(gguf_get_arr_type(ctx, i)), gguf_get_arr_n(ctx, i)) - : gguf_type_name(type); + ctx_meta.reset(meta); - std::string value = gguf_kv_to_str(ctx, i); - const size_t MAX_VALUE_LEN = 40; - if (value.size() > MAX_VALUE_LEN) { - value = format("%s...", value.substr(0, MAX_VALUE_LEN - 3).c_str()); - } - replace_all(value, "\n", "\\n"); + const int n_tensors = gguf_get_n_tensors(ctx_gguf.get()); - LOG_INF("%s: - kv %3d: %42s %-16s = %s\n", __func__, i, name, type_name.c_str(), value.c_str()); + // print gguf info + { + int ftype = -1; + get_u32(KEY_FTYPE, ftype, false); + const std::string ftype_str = ggml_type_name(static_cast(ftype)); + std::string name; + get_string(KEY_NAME, name, false); + std::string description; + get_string(KEY_DESCRIPTION, description, false); + LOG_INF("%s: model name: %s\n", __func__, name.c_str()); + LOG_INF("%s: description: %s\n", __func__, description.c_str()); + LOG_INF("%s: GGUF version: %d\n", __func__, gguf_get_version(ctx_gguf.get())); + LOG_INF("%s: alignment: %zu\n", __func__, gguf_get_alignment(ctx_gguf.get())); + LOG_INF("%s: n_tensors: %d\n", __func__, n_tensors); + LOG_INF("%s: n_kv: %d\n", __func__, (int)gguf_get_n_kv(ctx_gguf.get())); + LOG_INF("%s: ftype: %s\n", __func__, ftype_str.c_str()); + LOG_INF("\n"); } - // print type counts - for (auto & kv : n_type) { - if (kv.second == 0) { - continue; - } - - LOG_INF("%s: - type %4s: %4d tensors\n", __func__, ggml_type_name(kv.first), kv.second); - } - } - - // data - size_t model_size = 0; - { - for (int i = 0; i < n_tensors; ++i) { - const char * name = gguf_get_tensor_name(ctx, i); - const size_t offset = gguf_get_tensor_offset(ctx, i); - enum ggml_type type = gguf_get_tensor_type(ctx, i); - struct ggml_tensor * cur = ggml_get_tensor(meta, name); - size_t tensor_size = ggml_nbytes(cur); - model_size += tensor_size; - if (verbosity >= 3) { - LOG_INF("%s: tensor[%d]: n_dims = %d, name = %s, tensor_size=%zu, offset=%zu, shape:[%" PRIu64 ", %" PRIu64 ", %" PRIu64 ", %" PRIu64 "], type = %s\n", - __func__, i, ggml_n_dims(cur), cur->name, tensor_size, offset, cur->ne[0], cur->ne[1], cur->ne[2], cur->ne[3], ggml_type_name(type)); + // tensors + { + for (int i = 0; i < n_tensors; ++i) { + const char * name = gguf_get_tensor_name(ctx_gguf.get(), i); + const size_t offset = gguf_get_tensor_offset(ctx_gguf.get(), i); + enum ggml_type type = gguf_get_tensor_type(ctx_gguf.get(), i); + struct ggml_tensor * cur = ggml_get_tensor(meta, name); + size_t tensor_size = ggml_nbytes(cur); + model_size += tensor_size; + LOG_DBG("%s: tensor[%d]: n_dims = %d, name = %s, tensor_size=%zu, offset=%zu, shape:[%" PRIu64 ", %" PRIu64 ", %" PRIu64 ", %" PRIu64 "], type = %s\n", + __func__, i, ggml_n_dims(cur), cur->name, tensor_size, offset, cur->ne[0], cur->ne[1], cur->ne[2], cur->ne[3], ggml_type_name(type)); } } } - clip_ctx * new_clip = new clip_ctx(ctx_params); - - // update projector type - { - int idx = gguf_find_key(ctx, KEY_PROJ_TYPE); - if (idx != -1) { - const std::string proj_type = gguf_get_val_str(ctx, idx); - new_clip->proj_type = clip_projector_type_from_string(proj_type); - } else { - new_clip->proj_type = PROJECTOR_TYPE_MLP; - } - - if (new_clip->proj_type == PROJECTOR_TYPE_MLP) { - if (gguf_find_tensor(ctx, format(TN_LLAVA_PROJ, 3, "weight").c_str()) != -1) { - new_clip->proj_type = PROJECTOR_TYPE_MLP_NORM; + void load_hparams() { + // projector type + { + std::string proj_type; + get_string(KEY_PROJ_TYPE, proj_type, false); + if (!proj_type.empty()) { + ctx_clip.proj_type = clip_projector_type_from_string(proj_type); + } + if (ctx_clip.proj_type == PROJECTOR_TYPE_UNKNOWN) { + throw std::runtime_error(string_format("%s: unknown projector type: %s\n", __func__, proj_type.c_str())); } } - } - // model size and capabilities - { - int idx = get_key_idx(ctx, KEY_HAS_TEXT_ENC); - new_clip->has_text_encoder = gguf_get_val_bool(ctx, idx); + // other hparams + { + get_bool(KEY_HAS_TEXT_ENC, ctx_clip.has_text_encoder, false); + get_bool(KEY_HAS_VIS_ENC, ctx_clip.has_vision_encoder, false); + GGML_ASSERT(ctx_clip.has_vision_encoder); + GGML_ASSERT(!ctx_clip.has_text_encoder); - idx = get_key_idx(ctx, KEY_HAS_VIS_ENC); - new_clip->has_vision_encoder = gguf_get_val_bool(ctx, idx); + // legacy keys, use KEY_PROJ_TYPE instead + get_bool(KEY_HAS_LLAVA_PROJ, ctx_clip.has_llava_projector, false); + get_bool(KEY_HAS_MINICPMV_PROJ, ctx_clip.has_minicpmv_projector, false); + get_i32(KEY_MINICPMV_VERSION, ctx_clip.minicpmv_version, false); + get_bool(KEY_HAS_GLM_PROJ, ctx_clip.has_glm_projector, false); + get_bool(KEY_HAS_QWEN2VL_MERGER, ctx_clip.has_qwen2vl_merger, false); + // !!! do NOT extend the list above, use KEY_PROJ_TYPE instead - idx = gguf_find_key(ctx, KEY_HAS_LLAVA_PROJ); - if (idx != -1) { - new_clip->has_llava_projector = gguf_get_val_bool(ctx, idx); - } + get_bool(KEY_USE_GELU, ctx_clip.use_gelu, false); + get_bool(KEY_USE_SILU, ctx_clip.use_silu, false); - idx = gguf_find_key(ctx, KEY_HAS_MINICPMV_PROJ); - if (idx != -1) { - new_clip->has_minicpmv_projector = gguf_get_val_bool(ctx, idx); - } + auto & hparams = ctx_clip.vision_model.hparams; + get_u32(string_format(KEY_N_EMBD, "vision"), hparams.hidden_size); + get_u32(string_format(KEY_N_HEAD, "vision"), hparams.n_head); + get_u32(string_format(KEY_N_FF, "vision"), hparams.n_intermediate); + get_u32(string_format(KEY_N_BLOCK, "vision"), hparams.n_layer); + get_u32(string_format(KEY_PROJ_DIM, "vision"), hparams.projection_dim); + get_f32(string_format(KEY_LAYER_NORM_EPS, "vision"), hparams.eps); + get_u32(KEY_IMAGE_SIZE, hparams.image_size); + get_u32(KEY_PATCH_SIZE, hparams.patch_size); + get_u32(KEY_IMAGE_CROP_RESOLUTION, hparams.image_crop_resolution, false); + get_arr_int(KEY_IMAGE_GRID_PINPOINTS, hparams.image_grid_pinpoints, false); - idx = gguf_find_key(ctx, KEY_MINICPMV_VERSION); - if (idx != -1) { - new_clip->minicpmv_version = gguf_get_val_i32(ctx, idx); - } + { + std::string mm_patch_merge_type; + get_string(KEY_MM_PATCH_MERGE_TYPE, mm_patch_merge_type, false); + if (mm_patch_merge_type == "spatial_unpad") { + hparams.mm_patch_merge_type = PATCH_MERGE_SPATIAL_UNPAD; + } + } - idx = gguf_find_key(ctx, KEY_HAS_GLM_PROJ); - if (idx != -1) { - new_clip->has_glm_projector = gguf_get_val_bool(ctx, idx); - } + { + int idx_mean = gguf_find_key(ctx_gguf.get(), KEY_IMAGE_MEAN); + int idx_std = gguf_find_key(ctx_gguf.get(), KEY_IMAGE_STD); + GGML_ASSERT(idx_mean >= 0 && "image_mean not found"); + GGML_ASSERT(idx_std >= 0 && "image_std not found"); + const float * mean_data = (const float *) gguf_get_arr_data(ctx_gguf.get(), idx_mean); + const float * std_data = (const float *) gguf_get_arr_data(ctx_gguf.get(), idx_std); + for (int i = 0; i < 3; ++i) { + ctx_clip.image_mean[i] = mean_data[i]; + ctx_clip.image_std[i] = std_data[i]; + } + } - idx = gguf_find_key(ctx, KEY_HAS_QWEN2VL_MERGER); - if (idx != -1) { - new_clip->has_qwen2vl_merger = gguf_get_val_bool(ctx, idx); - } - // GGML_ASSERT(new_clip->has_llava_projector); // see monatis/clip.cpp for image and/or text encoding for semantic search + // Load the vision feature layer indices if they are explicitly provided; + // if multiple vision feature layers are present, the values will be concatenated + // to form the final visual features. + // NOTE: gguf conversions should standardize the values of the vision feature layer to + // be non-negative, since we use -1 to mark values as unset here. + std::vector vision_feature_layer; + get_arr_int(KEY_FEATURE_LAYER, vision_feature_layer, false); + // convert std::vector to std::unordered_set + for (auto & layer : vision_feature_layer) { + hparams.vision_feature_layer.insert(layer); + } + // Calculate the deepest feature layer based on hparams and projector type + ctx_clip.max_feature_layer = get_deepest_feature_layer(&ctx_clip); - GGML_ASSERT(new_clip->has_vision_encoder); - GGML_ASSERT(!new_clip->has_text_encoder); - - try { - idx = get_key_idx(ctx, KEY_USE_GELU); - new_clip->use_gelu = gguf_get_val_bool(ctx, idx); - } catch (std::runtime_error & /*e*/) { - new_clip->use_gelu = false; - } - - try { - idx = get_key_idx(ctx, KEY_USE_SILU); - new_clip->use_silu = gguf_get_val_bool(ctx, idx); - } catch (std::runtime_error & /*e*/) { - new_clip->use_silu = false; - } - - if (verbosity >= 1) { - LOG_INF("%s: text_encoder: %d\n", __func__, new_clip->has_text_encoder); - LOG_INF("%s: vision_encoder: %d\n", __func__, new_clip->has_vision_encoder); - LOG_INF("%s: llava_projector: %d\n", __func__, new_clip->has_llava_projector); - LOG_INF("%s: minicpmv_projector: %d\n", __func__, new_clip->has_minicpmv_projector); - LOG_INF("%s: minicpmv_version: %d\n", __func__, new_clip->minicpmv_version); - LOG_INF("%s: glm_projector: %d\n", __func__, new_clip->has_glm_projector); - LOG_INF("%s: model size: %.2f MB\n", __func__, model_size / 1024.0 / 1024.0); - LOG_INF("%s: metadata size: %.2f MB\n", __func__, ggml_get_mem_size(meta) / 1024.0 / 1024.0); + LOG_INF("%s: text_encoder: %d\n", __func__, ctx_clip.has_text_encoder); + LOG_INF("%s: vision_encoder: %d\n", __func__, ctx_clip.has_vision_encoder); + LOG_INF("%s: llava_projector: %d\n", __func__, ctx_clip.has_llava_projector); + LOG_INF("%s: minicpmv_projector: %d\n", __func__, ctx_clip.has_minicpmv_projector); + LOG_INF("%s: minicpmv_version: %d\n", __func__, ctx_clip.minicpmv_version); + LOG_INF("%s: glm_projector: %d\n", __func__, ctx_clip.has_glm_projector); + LOG_INF("%s: model size: %.2f MiB\n", __func__, model_size / 1024.0 / 1024.0); + LOG_INF("%s: metadata size: %.2f MiB\n", __func__, ggml_get_mem_size(ctx_meta.get()) / 1024.0 / 1024.0); } } - LOG_INF("%s: params backend buffer size = % 6.2f MB (%i tensors)\n", __func__, model_size / (1024.0 * 1024.0), n_tensors); + void load_tensors() { + std::map tensor_offset; + std::vector tensors_to_load; - // load tensors - { - std::vector read_buf; + // get offsets + for (int64_t i = 0; i < gguf_get_n_tensors(ctx_gguf.get()); ++i) { + const char * name = gguf_get_tensor_name(ctx_gguf.get(), i); + tensor_offset[name] = gguf_get_data_offset(ctx_gguf.get()) + gguf_get_tensor_offset(ctx_gguf.get(), i); + } + + // create data context struct ggml_init_params params = { - /*.mem_size =*/ (n_tensors + 1) * ggml_tensor_overhead(), + /*.mem_size =*/ (gguf_get_n_tensors(ctx_gguf.get()) + 1) * ggml_tensor_overhead(), /*.mem_buffer =*/ NULL, /*.no_alloc =*/ true, }; - - new_clip->ctx_data = ggml_init(params); - if (!new_clip->ctx_data) { - LOG_ERR("%s: ggml_init() failed\n", __func__); - clip_free(new_clip); - gguf_free(ctx); - return nullptr; + ctx_clip.ctx_data = ggml_init(params); + if (!ctx_clip.ctx_data) { + throw std::runtime_error(string_format("%s: failed to init ggml context\n", __func__)); } - auto fin = std::ifstream(fname, std::ios::binary); - if (!fin) { - LOG_ERR("cannot open model file for loading tensors\n"); - clip_free(new_clip); - gguf_free(ctx); - return nullptr; - } - - // add tensors to context - for (int i = 0; i < n_tensors; ++i) { - const char * name = gguf_get_tensor_name(ctx, i); - struct ggml_tensor * t = ggml_get_tensor(meta, name); - struct ggml_tensor * cur = ggml_dup_tensor(new_clip->ctx_data, t); - ggml_set_name(cur, name); - } - - // alloc memory and offload data - ggml_backend_buffer_type_t buft = ggml_backend_get_default_buffer_type(new_clip->backend); - new_clip->buf = ggml_backend_alloc_ctx_tensors_from_buft(new_clip->ctx_data, buft); - ggml_backend_buffer_set_usage(new_clip->buf, GGML_BACKEND_BUFFER_USAGE_WEIGHTS); - for (int i = 0; i < n_tensors; ++i) { - const char * name = gguf_get_tensor_name(ctx, i); - struct ggml_tensor * cur = ggml_get_tensor(new_clip->ctx_data, name); - const size_t offset = gguf_get_data_offset(ctx) + gguf_get_tensor_offset(ctx, i); - fin.seekg(offset, std::ios::beg); - if (!fin) { - LOG_ERR("%s: failed to seek for tensor %s\n", __func__, name); - clip_free(new_clip); - gguf_free(ctx); - return nullptr; + // helper function + auto get_tensor = [&](const std::string & name, bool required = true) { + struct ggml_tensor * cur = ggml_get_tensor(ctx_meta.get(), name.c_str()); + if (!cur && required) { + throw std::runtime_error(string_format("%s: unable to find tensor %s\n", __func__, name.c_str())); } - int num_bytes = ggml_nbytes(cur); - if (ggml_backend_buft_is_host(buft)) { - // for the CPU and Metal backend, we can read directly into the tensor - fin.read(reinterpret_cast(cur->data), num_bytes); - } else { - // read into a temporary buffer first, then copy to device memory - read_buf.resize(num_bytes); - fin.read(reinterpret_cast(read_buf.data()), num_bytes); - ggml_backend_tensor_set(cur, read_buf.data(), 0, num_bytes); + if (cur) { + tensors_to_load.push_back(cur); + // add tensors to context + struct ggml_tensor * data_tensor = ggml_dup_tensor(ctx_clip.ctx_data, cur); + ggml_set_name(data_tensor, cur->name); + cur = data_tensor; } - } - fin.close(); - } + return cur; + }; - // vision model - if (new_clip->has_vision_encoder) { - // load vision model - auto & vision_model = new_clip->vision_model; - auto & hparams = vision_model.hparams; - hparams.hidden_size = get_u32(ctx, format(KEY_N_EMBD, "vision")); - hparams.n_head = get_u32(ctx, format(KEY_N_HEAD, "vision")); - hparams.n_intermediate = get_u32(ctx, format(KEY_N_FF, "vision")); - hparams.n_layer = get_u32(ctx, format(KEY_N_BLOCK, "vision")); - hparams.image_size = get_u32(ctx, KEY_IMAGE_SIZE); - hparams.patch_size = get_u32(ctx, KEY_PATCH_SIZE); - hparams.projection_dim = get_u32(ctx, format(KEY_PROJ_DIM, "vision")); - hparams.eps = get_f32(ctx, format(KEY_LAYER_NORM_EPS, "vision")); + auto & vision_model = ctx_clip.vision_model; - try { - int idx = get_key_idx(ctx, KEY_IMAGE_GRID_PINPOINTS); - int n = gguf_get_arr_n(ctx, idx); - const int32_t * pinpoints = (const int32_t *)gguf_get_arr_data(ctx, idx); - for (int i = 0; i < n; ++i) { - hparams.image_grid_pinpoints.push_back(pinpoints[i]); - } - } catch (std::runtime_error & /*e*/) { } + vision_model.class_embedding = get_tensor(TN_CLASS_EMBD, false); - // Load the vision feature layer indices if they are explicitly provided; - // if multiple vision feature layers are present, the values will be concatenated - // to form the final visual features. - // NOTE: gguf conversions should standardize the values of the vision feature layer to - // be non-negative, since we use -1 to mark values as unset here. - try { - int idx = get_key_idx(ctx, KEY_FEATURE_LAYER); - int n = gguf_get_arr_n(ctx, idx); + vision_model.pre_ln_w = get_tensor(string_format(TN_LN_PRE, "v", "weight"), false); + vision_model.pre_ln_b = get_tensor(string_format(TN_LN_PRE, "v", "bias"), false); - const int32_t * vision_feature_layer = (const int32_t *)gguf_get_arr_data(ctx, idx); + vision_model.post_ln_w = get_tensor(string_format(TN_LN_POST, "v", "weight"), false); + vision_model.post_ln_b = get_tensor(string_format(TN_LN_POST, "v", "bias"), false); - for (int i = 0; i < n; ++i) { - hparams.vision_feature_layer.insert(vision_feature_layer[i]); - } - } catch (std::runtime_error & /*e*/) { } - - try { - int idx = get_key_idx(ctx, KEY_MM_PATCH_MERGE_TYPE); - strcpy(hparams.mm_patch_merge_type, gguf_get_val_str(ctx, idx)); - } catch (std::runtime_error & /*e*/) { - strcpy(hparams.mm_patch_merge_type, "flat"); + vision_model.patch_bias = get_tensor(TN_PATCH_BIAS, false); + vision_model.patch_embeddings_0 = get_tensor(TN_PATCH_EMBD, false); + vision_model.patch_embeddings_1 = get_tensor(TN_PATCH_EMBD_1, false); + if (vision_model.patch_embeddings_1 == nullptr) { + ctx_clip.has_qwen2vl_merger = false; } - try { - hparams.image_crop_resolution = get_u32(ctx, KEY_IMAGE_CROP_RESOLUTION); // llava-1.6 - } catch(const std::exception& /*e*/) { - hparams.image_crop_resolution = hparams.image_size; - } + vision_model.position_embeddings = get_tensor(string_format(TN_POS_EMBD, "v"), false); - int idx_mean = get_key_idx(ctx, KEY_IMAGE_MEAN); - int idx_std = get_key_idx(ctx, KEY_IMAGE_STD); - - const float * mean_data = (const float *)gguf_get_arr_data(ctx, idx_mean); - const float * std_data = (const float *)gguf_get_arr_data(ctx, idx_std); - - for (int i = 0; i < 3; ++i) { - new_clip->image_mean[i] = mean_data[i]; - new_clip->image_std[i] = std_data[i]; - } - - // Calculate the deepest feature layer based on hparams and projector type - new_clip->max_feature_layer = get_deepest_feature_layer(new_clip); - - if (verbosity >= 2) { - LOG_INF("\n%s: vision model hparams\n", __func__); - LOG_INF("image_size %d\n", hparams.image_size); - LOG_INF("patch_size %d\n", hparams.patch_size); - LOG_INF("v_hidden_size %d\n", hparams.hidden_size); - LOG_INF("v_n_intermediate %d\n", hparams.n_intermediate); - LOG_INF("v_projection_dim %d\n", hparams.projection_dim); - LOG_INF("v_n_head %d\n", hparams.n_head); - LOG_INF("v_n_layer %d\n", hparams.n_layer); - LOG_INF("v_eps %f\n", hparams.eps); - LOG_INF("v_image_mean %f %f %f\n", new_clip->image_mean[0], new_clip->image_mean[1], new_clip->image_mean[2]); - LOG_INF("v_image_std %f %f %f\n", new_clip->image_std[0], new_clip->image_std[1], new_clip->image_std[2]); - LOG_INF("v_image_grid_pinpoints: "); - for (const auto & pp : hparams.image_grid_pinpoints) { - LOG_INF("%d ", pp); - } - LOG_INF("\n"); - LOG_INF("v_vision_feature_layer: "); - for (const auto & feature_layer: hparams.vision_feature_layer) { - LOG_INF("%d ", feature_layer); - } - LOG_INF("\n"); - LOG_INF("v_mm_patch_merge_type: %s\n", hparams.mm_patch_merge_type); - - } - - try { - vision_model.class_embedding = get_tensor(new_clip->ctx_data, TN_CLASS_EMBD); - new_clip->has_class_embedding = true; - } catch (const std::exception& /*e*/) { - new_clip->has_class_embedding = false; - } - - try { - vision_model.pre_ln_w = get_tensor(new_clip->ctx_data, format(TN_LN_PRE, "v", "weight")); - vision_model.pre_ln_b = get_tensor(new_clip->ctx_data, format(TN_LN_PRE, "v", "bias")); - new_clip->has_pre_norm = true; - } catch (std::exception & /*e*/) { - new_clip->has_pre_norm = false; - } - - try { - vision_model.post_ln_w = get_tensor(new_clip->ctx_data, format(TN_LN_POST, "v", "weight")); - vision_model.post_ln_b = get_tensor(new_clip->ctx_data, format(TN_LN_POST, "v", "bias")); - new_clip->has_post_norm = true; - } catch (std::exception & /*e*/) { - new_clip->has_post_norm = false; - } - - try { - vision_model.patch_bias = get_tensor(new_clip->ctx_data, TN_PATCH_BIAS); - new_clip->has_patch_bias = true; - } catch (std::exception & /*e*/) { - new_clip->has_patch_bias = false; - } - - try { - vision_model.patch_embeddings_0 = get_tensor(new_clip->ctx_data, TN_PATCH_EMBD); - } catch(const std::exception& /*e*/) { - vision_model.patch_embeddings_0 = nullptr; - } - - try { - vision_model.position_embeddings = get_tensor(new_clip->ctx_data, format(TN_POS_EMBD, "v")); - } catch(const std::exception& /*e*/) { - vision_model.position_embeddings = nullptr; - } - - try { - vision_model.patch_embeddings_1 = get_tensor(new_clip->ctx_data, TN_PATCH_EMBD_1); - } catch(const std::exception& /*e*/) { - new_clip->has_qwen2vl_merger = false; - } - - // LLaVA projection - if (new_clip->proj_type == PROJECTOR_TYPE_MLP || new_clip->proj_type == PROJECTOR_TYPE_MLP_NORM) { - vision_model.mm_0_w = get_tensor(new_clip->ctx_data, format(TN_LLAVA_PROJ, 0, "weight")); - vision_model.mm_0_b = get_tensor(new_clip->ctx_data, format(TN_LLAVA_PROJ, 0, "bias")); - try { - // Yi-type llava - vision_model.mm_1_w = get_tensor(new_clip->ctx_data, format(TN_LLAVA_PROJ, 1, "weight")); - vision_model.mm_1_b = get_tensor(new_clip->ctx_data, format(TN_LLAVA_PROJ, 1, "bias")); - } catch (std::runtime_error & /*e*/) { } - try { - // missing in Yi-type llava - vision_model.mm_2_w = get_tensor(new_clip->ctx_data, format(TN_LLAVA_PROJ, 2, "weight")); - vision_model.mm_2_b = get_tensor(new_clip->ctx_data, format(TN_LLAVA_PROJ, 2, "bias")); - } catch (std::runtime_error & /*e*/) { } - try { - // Yi-type llava - vision_model.mm_3_w = get_tensor(new_clip->ctx_data, format(TN_LLAVA_PROJ, 3, "weight")); - vision_model.mm_3_b = get_tensor(new_clip->ctx_data, format(TN_LLAVA_PROJ, 3, "bias")); - } catch (std::runtime_error & /*e*/) { } - try { - // Yi-type llava - vision_model.mm_4_w = get_tensor(new_clip->ctx_data, format(TN_LLAVA_PROJ, 4, "weight")); - vision_model.mm_4_b = get_tensor(new_clip->ctx_data, format(TN_LLAVA_PROJ, 4, "bias")); - } catch (std::runtime_error & /*e*/) { } - try { - vision_model.image_newline = get_tensor(new_clip->ctx_data, TN_IMAGE_NEWLINE); - // LOG_INF("%s: image_newline tensor (llava-1.6) found\n", __func__); - } catch (std::runtime_error & /*e*/) { } - } else if (new_clip->proj_type == PROJECTOR_TYPE_LDP) { - // MobileVLM projection - vision_model.mm_model_mlp_1_w = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_MLP, 1, "weight")); - vision_model.mm_model_mlp_1_b = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_MLP, 1, "bias")); - vision_model.mm_model_mlp_3_w = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_MLP, 3, "weight")); - vision_model.mm_model_mlp_3_b = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_MLP, 3, "bias")); - vision_model.mm_model_block_1_block_0_0_w = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_BLOCK, 1, 0, "0.weight")); - vision_model.mm_model_block_1_block_0_1_w = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_BLOCK, 1, 0, "1.weight")); - vision_model.mm_model_block_1_block_0_1_b = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_BLOCK, 1, 0, "1.bias")); - vision_model.mm_model_block_1_block_1_fc1_w = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_BLOCK, 1, 1, "fc1.weight")); - vision_model.mm_model_block_1_block_1_fc1_b = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_BLOCK, 1, 1, "fc1.bias")); - vision_model.mm_model_block_1_block_1_fc2_w = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_BLOCK, 1, 1, "fc2.weight")); - vision_model.mm_model_block_1_block_1_fc2_b = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_BLOCK, 1, 1, "fc2.bias")); - vision_model.mm_model_block_1_block_2_0_w = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_BLOCK, 1, 2, "0.weight")); - vision_model.mm_model_block_1_block_2_1_w = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_BLOCK, 1, 2, "1.weight")); - vision_model.mm_model_block_1_block_2_1_b = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_BLOCK, 1, 2, "1.bias")); - vision_model.mm_model_block_2_block_0_0_w = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_BLOCK, 2, 0, "0.weight")); - vision_model.mm_model_block_2_block_0_1_w = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_BLOCK, 2, 0, "1.weight")); - vision_model.mm_model_block_2_block_0_1_b = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_BLOCK, 2, 0, "1.bias")); - vision_model.mm_model_block_2_block_1_fc1_w = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_BLOCK, 2, 1, "fc1.weight")); - vision_model.mm_model_block_2_block_1_fc1_b = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_BLOCK, 2, 1, "fc1.bias")); - vision_model.mm_model_block_2_block_1_fc2_w = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_BLOCK, 2, 1, "fc2.weight")); - vision_model.mm_model_block_2_block_1_fc2_b = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_BLOCK, 2, 1, "fc2.bias")); - vision_model.mm_model_block_2_block_2_0_w = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_BLOCK, 2, 2, "0.weight")); - vision_model.mm_model_block_2_block_2_1_w = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_BLOCK, 2, 2, "1.weight")); - vision_model.mm_model_block_2_block_2_1_b = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_BLOCK, 2, 2, "1.bias")); - } - else if (new_clip->proj_type == PROJECTOR_TYPE_LDPV2) - { - // MobilVLM_V2 projection - vision_model.mm_model_mlp_0_w = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_MLP, 0, "weight")); - vision_model.mm_model_mlp_0_b = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_MLP, 0, "bias")); - vision_model.mm_model_mlp_2_w = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_MLP, 2, "weight")); - vision_model.mm_model_mlp_2_b = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_MLP, 2, "bias")); - vision_model.mm_model_peg_0_w = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_PEG, 0, "weight")); - vision_model.mm_model_peg_0_b = get_tensor(new_clip->ctx_data, format(TN_MVLM_PROJ_PEG, 0, "bias")); - } - else if (new_clip->proj_type == PROJECTOR_TYPE_RESAMPLER) { - // vision_model.mm_model_pos_embed = get_tensor(new_clip->ctx_data, TN_MINICPMV_POS_EMBD); - vision_model.mm_model_pos_embed_k = get_tensor(new_clip->ctx_data, TN_MINICPMV_POS_EMBD_K); - vision_model.mm_model_query = get_tensor(new_clip->ctx_data, TN_MINICPMV_QUERY); - vision_model.mm_model_proj = get_tensor(new_clip->ctx_data, TN_MINICPMV_PROJ); - vision_model.mm_model_kv_proj = get_tensor(new_clip->ctx_data, TN_MINICPMV_KV_PROJ); - vision_model.mm_model_attn_q_w = get_tensor(new_clip->ctx_data, format(TN_MINICPMV_ATTN, "q", "weight")); - vision_model.mm_model_attn_k_w = get_tensor(new_clip->ctx_data, format(TN_MINICPMV_ATTN, "k", "weight")); - vision_model.mm_model_attn_v_w = get_tensor(new_clip->ctx_data, format(TN_MINICPMV_ATTN, "v", "weight")); - vision_model.mm_model_attn_q_b = get_tensor(new_clip->ctx_data, format(TN_MINICPMV_ATTN, "q", "bias")); - vision_model.mm_model_attn_k_b = get_tensor(new_clip->ctx_data, format(TN_MINICPMV_ATTN, "k", "bias")); - vision_model.mm_model_attn_v_b = get_tensor(new_clip->ctx_data, format(TN_MINICPMV_ATTN, "v", "bias")); - vision_model.mm_model_attn_o_w = get_tensor(new_clip->ctx_data, format(TN_MINICPMV_ATTN, "out", "weight")); - vision_model.mm_model_attn_o_b = get_tensor(new_clip->ctx_data, format(TN_MINICPMV_ATTN, "out", "bias")); - vision_model.mm_model_ln_q_w = get_tensor(new_clip->ctx_data, format(TN_MINICPMV_LN, "q", "weight")); - vision_model.mm_model_ln_q_b = get_tensor(new_clip->ctx_data, format(TN_MINICPMV_LN, "q", "bias")); - vision_model.mm_model_ln_kv_w = get_tensor(new_clip->ctx_data, format(TN_MINICPMV_LN, "kv", "weight")); - vision_model.mm_model_ln_kv_b = get_tensor(new_clip->ctx_data, format(TN_MINICPMV_LN, "kv", "bias")); - vision_model.mm_model_ln_post_w = get_tensor(new_clip->ctx_data, format(TN_MINICPMV_LN, "post", "weight")); - vision_model.mm_model_ln_post_b = get_tensor(new_clip->ctx_data, format(TN_MINICPMV_LN, "post", "bias")); - } - else if (new_clip->proj_type == PROJECTOR_TYPE_GLM_EDGE) { - vision_model.mm_model_adapter_conv_w = get_tensor(new_clip->ctx_data, format(TN_GLM_ADAPER_CONV, "weight")); - vision_model.mm_model_adapter_conv_b = get_tensor(new_clip->ctx_data, format(TN_GLM_ADAPER_CONV, "bias")); - vision_model.mm_model_mlp_0_w = get_tensor(new_clip->ctx_data, format(TN_GLM_ADAPTER_LINEAR,"weight")); - vision_model.mm_model_ln_q_w = get_tensor(new_clip->ctx_data, format(TN_GLM_ADAPTER_NORM_1,"weight")); - vision_model.mm_model_ln_q_b = get_tensor(new_clip->ctx_data, format(TN_GLM_ADAPTER_NORM_1,"bias")); - vision_model.mm_model_mlp_1_w = get_tensor(new_clip->ctx_data, format(TN_GLM_ADAPTER_D_H_2_4H,"weight")); - vision_model.mm_model_mlp_2_w = get_tensor(new_clip->ctx_data, format(TN_GLM_ADAPTER_GATE,"weight")); - vision_model.mm_model_mlp_3_w = get_tensor(new_clip->ctx_data, format(TN_GLM_ADAPTER_D_4H_2_H,"weight")); - vision_model.boi_w = get_tensor(new_clip->ctx_data, TN_GLM_BOI_W); - vision_model.eoi_w = get_tensor(new_clip->ctx_data, TN_GLM_EOI_W); - } - else if (new_clip->proj_type == PROJECTOR_TYPE_MERGER) { - vision_model.mm_0_w = get_tensor(new_clip->ctx_data, format(TN_LLAVA_PROJ, 0, "weight")); - vision_model.mm_0_b = get_tensor(new_clip->ctx_data, format(TN_LLAVA_PROJ, 0, "bias")); - vision_model.mm_1_w = get_tensor(new_clip->ctx_data, format(TN_LLAVA_PROJ, 2, "weight")); - vision_model.mm_1_b = get_tensor(new_clip->ctx_data, format(TN_LLAVA_PROJ, 2, "bias")); - } - else if (new_clip->proj_type == PROJECTOR_TYPE_GEMMA3) { - vision_model.mm_input_proj_w = get_tensor(new_clip->ctx_data, TN_MM_INP_PROJ); - vision_model.mm_soft_emb_norm_w = get_tensor(new_clip->ctx_data, TN_MM_SOFT_EMB_N); - } - else { - std::string proj_type = PROJECTOR_TYPE_NAMES[new_clip->proj_type]; - throw std::runtime_error(format("%s: don't support projector with: %s currently\n", __func__, proj_type.c_str())); - } - - vision_model.layers.resize(hparams.n_layer); - - for (int il = 0; il < hparams.n_layer; ++il) { + // layers + vision_model.layers.resize(vision_model.hparams.n_layer); + for (int il = 0; il < vision_model.hparams.n_layer; ++il) { auto & layer = vision_model.layers[il]; - layer.k_w = get_tensor(new_clip->ctx_data, format(TN_ATTN_K, "v", il, "weight")); - layer.q_w = get_tensor(new_clip->ctx_data, format(TN_ATTN_Q, "v", il, "weight")); - layer.v_w = get_tensor(new_clip->ctx_data, format(TN_ATTN_V, "v", il, "weight")); - layer.o_w = get_tensor(new_clip->ctx_data, format(TN_ATTN_OUTPUT, "v", il, "weight")); - layer.ln_1_w = get_tensor(new_clip->ctx_data, format(TN_LN_1, "v", il, "weight")); - layer.ln_2_w = get_tensor(new_clip->ctx_data, format(TN_LN_2, "v", il, "weight")); - layer.ff_i_w = get_tensor(new_clip->ctx_data, format(TN_FFN_DOWN, "v", il, "weight")); - layer.ff_o_w = get_tensor(new_clip->ctx_data, format(TN_FFN_UP, "v", il, "weight")); - layer.k_b = get_tensor(new_clip->ctx_data, format(TN_ATTN_K, "v", il, "bias")); - layer.q_b = get_tensor(new_clip->ctx_data, format(TN_ATTN_Q, "v", il, "bias")); - layer.v_b = get_tensor(new_clip->ctx_data, format(TN_ATTN_V, "v", il, "bias")); - layer.o_b = get_tensor(new_clip->ctx_data, format(TN_ATTN_OUTPUT, "v", il, "bias")); - layer.ln_1_b = get_tensor(new_clip->ctx_data, format(TN_LN_1, "v", il, "bias")); - layer.ln_2_b = get_tensor(new_clip->ctx_data, format(TN_LN_2, "v", il, "bias")); - layer.ff_i_b = get_tensor(new_clip->ctx_data, format(TN_FFN_DOWN, "v", il, "bias")); - layer.ff_o_b = get_tensor(new_clip->ctx_data, format(TN_FFN_UP, "v", il, "bias")); + layer.k_w = get_tensor(string_format(TN_ATTN_K, "v", il, "weight")); + layer.q_w = get_tensor(string_format(TN_ATTN_Q, "v", il, "weight")); + layer.v_w = get_tensor(string_format(TN_ATTN_V, "v", il, "weight")); + layer.o_w = get_tensor(string_format(TN_ATTN_OUTPUT, "v", il, "weight")); + layer.ln_1_w = get_tensor(string_format(TN_LN_1, "v", il, "weight"), false); + layer.ln_2_w = get_tensor(string_format(TN_LN_2, "v", il, "weight"), false); + layer.ff_i_w = get_tensor(string_format(TN_FFN_DOWN, "v", il, "weight")); + layer.ff_o_w = get_tensor(string_format(TN_FFN_UP, "v", il, "weight")); + layer.k_b = get_tensor(string_format(TN_ATTN_K, "v", il, "bias"), false); + layer.q_b = get_tensor(string_format(TN_ATTN_Q, "v", il, "bias"), false); + layer.v_b = get_tensor(string_format(TN_ATTN_V, "v", il, "bias"), false); + layer.o_b = get_tensor(string_format(TN_ATTN_OUTPUT, "v", il, "bias"), false); + layer.ln_1_b = get_tensor(string_format(TN_LN_1, "v", il, "bias"), false); + layer.ln_2_b = get_tensor(string_format(TN_LN_2, "v", il, "bias"), false); + layer.ff_i_b = get_tensor(string_format(TN_FFN_DOWN, "v", il, "bias"), false); + layer.ff_o_b = get_tensor(string_format(TN_FFN_UP, "v", il, "bias"), false); + } + + switch (ctx_clip.proj_type) { + case PROJECTOR_TYPE_MLP: + case PROJECTOR_TYPE_MLP_NORM: + { + // LLaVA projection + vision_model.mm_0_w = get_tensor(string_format(TN_LLAVA_PROJ, 0, "weight"), false); + vision_model.mm_0_b = get_tensor(string_format(TN_LLAVA_PROJ, 0, "bias"), false); + // Yi-type llava + vision_model.mm_1_w = get_tensor(string_format(TN_LLAVA_PROJ, 1, "weight"), false); + vision_model.mm_1_b = get_tensor(string_format(TN_LLAVA_PROJ, 1, "bias"), false); + // missing in Yi-type llava + vision_model.mm_2_w = get_tensor(string_format(TN_LLAVA_PROJ, 2, "weight"), false); + vision_model.mm_2_b = get_tensor(string_format(TN_LLAVA_PROJ, 2, "bias"), false); + // Yi-type llava + vision_model.mm_3_w = get_tensor(string_format(TN_LLAVA_PROJ, 3, "weight"), false); + vision_model.mm_3_b = get_tensor(string_format(TN_LLAVA_PROJ, 3, "bias"), false); + vision_model.mm_4_w = get_tensor(string_format(TN_LLAVA_PROJ, 4, "weight"), false); + vision_model.mm_4_b = get_tensor(string_format(TN_LLAVA_PROJ, 4, "bias"), false); + if (vision_model.mm_3_w) { + // TODO: this is a hack to support Yi-type llava + ctx_clip.proj_type = PROJECTOR_TYPE_MLP_NORM; + } + vision_model.image_newline = get_tensor(TN_IMAGE_NEWLINE, false); + } break; + case PROJECTOR_TYPE_LDP: + { + // MobileVLM projection + vision_model.mm_model_mlp_1_w = get_tensor(string_format(TN_MVLM_PROJ_MLP, 1, "weight")); + vision_model.mm_model_mlp_1_b = get_tensor(string_format(TN_MVLM_PROJ_MLP, 1, "bias")); + vision_model.mm_model_mlp_3_w = get_tensor(string_format(TN_MVLM_PROJ_MLP, 3, "weight")); + vision_model.mm_model_mlp_3_b = get_tensor(string_format(TN_MVLM_PROJ_MLP, 3, "bias")); + vision_model.mm_model_block_1_block_0_0_w = get_tensor(string_format(TN_MVLM_PROJ_BLOCK, 1, 0, "0.weight")); + vision_model.mm_model_block_1_block_0_1_w = get_tensor(string_format(TN_MVLM_PROJ_BLOCK, 1, 0, "1.weight")); + vision_model.mm_model_block_1_block_0_1_b = get_tensor(string_format(TN_MVLM_PROJ_BLOCK, 1, 0, "1.bias")); + vision_model.mm_model_block_1_block_1_fc1_w = get_tensor(string_format(TN_MVLM_PROJ_BLOCK, 1, 1, "fc1.weight")); + vision_model.mm_model_block_1_block_1_fc1_b = get_tensor(string_format(TN_MVLM_PROJ_BLOCK, 1, 1, "fc1.bias")); + vision_model.mm_model_block_1_block_1_fc2_w = get_tensor(string_format(TN_MVLM_PROJ_BLOCK, 1, 1, "fc2.weight")); + vision_model.mm_model_block_1_block_1_fc2_b = get_tensor(string_format(TN_MVLM_PROJ_BLOCK, 1, 1, "fc2.bias")); + vision_model.mm_model_block_1_block_2_0_w = get_tensor(string_format(TN_MVLM_PROJ_BLOCK, 1, 2, "0.weight")); + vision_model.mm_model_block_1_block_2_1_w = get_tensor(string_format(TN_MVLM_PROJ_BLOCK, 1, 2, "1.weight")); + vision_model.mm_model_block_1_block_2_1_b = get_tensor(string_format(TN_MVLM_PROJ_BLOCK, 1, 2, "1.bias")); + vision_model.mm_model_block_2_block_0_0_w = get_tensor(string_format(TN_MVLM_PROJ_BLOCK, 2, 0, "0.weight")); + vision_model.mm_model_block_2_block_0_1_w = get_tensor(string_format(TN_MVLM_PROJ_BLOCK, 2, 0, "1.weight")); + vision_model.mm_model_block_2_block_0_1_b = get_tensor(string_format(TN_MVLM_PROJ_BLOCK, 2, 0, "1.bias")); + vision_model.mm_model_block_2_block_1_fc1_w = get_tensor(string_format(TN_MVLM_PROJ_BLOCK, 2, 1, "fc1.weight")); + vision_model.mm_model_block_2_block_1_fc1_b = get_tensor(string_format(TN_MVLM_PROJ_BLOCK, 2, 1, "fc1.bias")); + vision_model.mm_model_block_2_block_1_fc2_w = get_tensor(string_format(TN_MVLM_PROJ_BLOCK, 2, 1, "fc2.weight")); + vision_model.mm_model_block_2_block_1_fc2_b = get_tensor(string_format(TN_MVLM_PROJ_BLOCK, 2, 1, "fc2.bias")); + vision_model.mm_model_block_2_block_2_0_w = get_tensor(string_format(TN_MVLM_PROJ_BLOCK, 2, 2, "0.weight")); + vision_model.mm_model_block_2_block_2_1_w = get_tensor(string_format(TN_MVLM_PROJ_BLOCK, 2, 2, "1.weight")); + vision_model.mm_model_block_2_block_2_1_b = get_tensor(string_format(TN_MVLM_PROJ_BLOCK, 2, 2, "1.bias")); + } break; + case PROJECTOR_TYPE_LDPV2: + { + // MobilVLM_V2 projection + vision_model.mm_model_mlp_0_w = get_tensor(string_format(TN_MVLM_PROJ_MLP, 0, "weight")); + vision_model.mm_model_mlp_0_b = get_tensor(string_format(TN_MVLM_PROJ_MLP, 0, "bias")); + vision_model.mm_model_mlp_2_w = get_tensor(string_format(TN_MVLM_PROJ_MLP, 2, "weight")); + vision_model.mm_model_mlp_2_b = get_tensor(string_format(TN_MVLM_PROJ_MLP, 2, "bias")); + vision_model.mm_model_peg_0_w = get_tensor(string_format(TN_MVLM_PROJ_PEG, 0, "weight")); + vision_model.mm_model_peg_0_b = get_tensor(string_format(TN_MVLM_PROJ_PEG, 0, "bias")); + } break; + case PROJECTOR_TYPE_RESAMPLER: + { + // vision_model.mm_model_pos_embed = get_tensor(new_clip->ctx_data, TN_MINICPMV_POS_EMBD); + vision_model.mm_model_pos_embed_k = get_tensor(TN_MINICPMV_POS_EMBD_K); + vision_model.mm_model_query = get_tensor(TN_MINICPMV_QUERY); + vision_model.mm_model_proj = get_tensor(TN_MINICPMV_PROJ); + vision_model.mm_model_kv_proj = get_tensor(TN_MINICPMV_KV_PROJ); + vision_model.mm_model_attn_q_w = get_tensor(string_format(TN_MINICPMV_ATTN, "q", "weight")); + vision_model.mm_model_attn_k_w = get_tensor(string_format(TN_MINICPMV_ATTN, "k", "weight")); + vision_model.mm_model_attn_v_w = get_tensor(string_format(TN_MINICPMV_ATTN, "v", "weight")); + vision_model.mm_model_attn_q_b = get_tensor(string_format(TN_MINICPMV_ATTN, "q", "bias")); + vision_model.mm_model_attn_k_b = get_tensor(string_format(TN_MINICPMV_ATTN, "k", "bias")); + vision_model.mm_model_attn_v_b = get_tensor(string_format(TN_MINICPMV_ATTN, "v", "bias")); + vision_model.mm_model_attn_o_w = get_tensor(string_format(TN_MINICPMV_ATTN, "out", "weight")); + vision_model.mm_model_attn_o_b = get_tensor(string_format(TN_MINICPMV_ATTN, "out", "bias")); + vision_model.mm_model_ln_q_w = get_tensor(string_format(TN_MINICPMV_LN, "q", "weight")); + vision_model.mm_model_ln_q_b = get_tensor(string_format(TN_MINICPMV_LN, "q", "bias")); + vision_model.mm_model_ln_kv_w = get_tensor(string_format(TN_MINICPMV_LN, "kv", "weight")); + vision_model.mm_model_ln_kv_b = get_tensor(string_format(TN_MINICPMV_LN, "kv", "bias")); + vision_model.mm_model_ln_post_w = get_tensor(string_format(TN_MINICPMV_LN, "post", "weight")); + vision_model.mm_model_ln_post_b = get_tensor(string_format(TN_MINICPMV_LN, "post", "bias")); + } break; + case PROJECTOR_TYPE_GLM_EDGE: + { + vision_model.mm_model_adapter_conv_w = get_tensor(string_format(TN_GLM_ADAPER_CONV, "weight")); + vision_model.mm_model_adapter_conv_b = get_tensor(string_format(TN_GLM_ADAPER_CONV, "bias")); + vision_model.mm_model_mlp_0_w = get_tensor(string_format(TN_GLM_ADAPTER_LINEAR,"weight")); + vision_model.mm_model_ln_q_w = get_tensor(string_format(TN_GLM_ADAPTER_NORM_1,"weight")); + vision_model.mm_model_ln_q_b = get_tensor(string_format(TN_GLM_ADAPTER_NORM_1,"bias")); + vision_model.mm_model_mlp_1_w = get_tensor(string_format(TN_GLM_ADAPTER_D_H_2_4H,"weight")); + vision_model.mm_model_mlp_2_w = get_tensor(string_format(TN_GLM_ADAPTER_GATE,"weight")); + vision_model.mm_model_mlp_3_w = get_tensor(string_format(TN_GLM_ADAPTER_D_4H_2_H,"weight")); + vision_model.boi_w = get_tensor(TN_GLM_BOI_W); + vision_model.eoi_w = get_tensor(TN_GLM_EOI_W); + } break; + case PROJECTOR_TYPE_MERGER: + { + vision_model.mm_0_w = get_tensor(string_format(TN_LLAVA_PROJ, 0, "weight")); + vision_model.mm_0_b = get_tensor(string_format(TN_LLAVA_PROJ, 0, "bias")); + vision_model.mm_1_w = get_tensor(string_format(TN_LLAVA_PROJ, 2, "weight")); + vision_model.mm_1_b = get_tensor(string_format(TN_LLAVA_PROJ, 2, "bias")); + } break; + case PROJECTOR_TYPE_GEMMA3: + { + vision_model.mm_input_proj_w = get_tensor(TN_MM_INP_PROJ); + vision_model.mm_soft_emb_norm_w = get_tensor(TN_MM_SOFT_EMB_N); + } break; + default: + GGML_ASSERT(false && "unknown projector type"); + } + + // load data + { + std::vector read_buf; + + auto fin = std::ifstream(fname, std::ios::binary); + if (!fin) { + throw std::runtime_error(string_format("%s: failed to open %s\n", __func__, fname.c_str())); + } + + // alloc memory and offload data + ggml_backend_buffer_type_t buft = ggml_backend_get_default_buffer_type(ctx_clip.backend); + ctx_clip.buf = ggml_backend_alloc_ctx_tensors_from_buft(ctx_clip.ctx_data, buft); + ggml_backend_buffer_set_usage(ctx_clip.buf, GGML_BACKEND_BUFFER_USAGE_WEIGHTS); + for (auto & t : tensors_to_load) { + struct ggml_tensor * cur = ggml_get_tensor(ctx_clip.ctx_data, t->name); + const size_t offset = tensor_offset[t->name]; + fin.seekg(offset, std::ios::beg); + if (!fin) { + throw std::runtime_error(string_format("%s: failed to seek for tensor %s\n", __func__, t->name)); + } + size_t num_bytes = ggml_nbytes(cur); + if (ggml_backend_buft_is_host(buft)) { + // for the CPU and Metal backend, we can read directly into the tensor + fin.read(reinterpret_cast(cur->data), num_bytes); + } else { + // read into a temporary buffer first, then copy to device memory + read_buf.resize(num_bytes); + fin.read(reinterpret_cast(read_buf.data()), num_bytes); + ggml_backend_tensor_set(cur, read_buf.data(), 0, num_bytes); + } + } + fin.close(); + + LOG_DBG("%s: loaded %zu tensors from %s\n", __func__, tensors_to_load.size(), fname.c_str()); } } - ggml_free(meta); - - new_clip->ctx_gguf = ctx; - - // measure mem requirement and allocate - { - new_clip->buf_compute_meta.resize(GGML_DEFAULT_GRAPH_SIZE * ggml_tensor_overhead() + ggml_graph_overhead()); + void alloc_compute_meta() { + ctx_clip.buf_compute_meta.resize(GGML_DEFAULT_GRAPH_SIZE * ggml_tensor_overhead() + ggml_graph_overhead()); clip_image_f32_batch batch; batch.size = 1; batch.data = nullptr; - ggml_cgraph * gf = clip_image_build_graph(new_clip, &batch, nullptr, false); - ggml_backend_sched_reserve(new_clip->sched.get(), gf); - for (size_t i = 0; i < new_clip->backend_ptrs.size(); ++i) { - ggml_backend_t backend = new_clip->backend_ptrs[i]; - ggml_backend_buffer_type_t buft = new_clip->backend_buft[i]; - size_t size = ggml_backend_sched_get_buffer_size(new_clip->sched.get(), backend); + ggml_cgraph * gf = clip_image_build_graph(&ctx_clip, &batch, nullptr, false); + ggml_backend_sched_reserve(ctx_clip.sched.get(), gf); + for (size_t i = 0; i < ctx_clip.backend_ptrs.size(); ++i) { + ggml_backend_t backend = ctx_clip.backend_ptrs[i]; + ggml_backend_buffer_type_t buft = ctx_clip.backend_buft[i]; + size_t size = ggml_backend_sched_get_buffer_size(ctx_clip.sched.get(), backend); if (size > 1) { LOG_INF("%s: %10s compute buffer size = %8.2f MiB\n", __func__, ggml_backend_buft_name(buft), @@ -1920,7 +1509,90 @@ struct clip_ctx * clip_init(const char * fname, struct clip_context_params ctx_p } } - return new_clip; + void get_bool(const std::string & key, bool & output, bool required = true) { + const int i = gguf_find_key(ctx_gguf.get(), key.c_str()); + if (i < 0) { + if (required) throw std::runtime_error("Key not found: " + key); + return; + } + output = gguf_get_val_bool(ctx_gguf.get(), i); + } + + void get_i32(const std::string & key, int & output, bool required = true) { + const int i = gguf_find_key(ctx_gguf.get(), key.c_str()); + if (i < 0) { + if (required) throw std::runtime_error("Key not found: " + key); + return; + } + output = gguf_get_val_i32(ctx_gguf.get(), i); + } + + void get_u32(const std::string & key, int & output, bool required = true) { + const int i = gguf_find_key(ctx_gguf.get(), key.c_str()); + if (i < 0) { + if (required) throw std::runtime_error("Key not found: " + key); + return; + } + output = gguf_get_val_u32(ctx_gguf.get(), i); + } + + void get_f32(const std::string & key, float & output, bool required = true) { + const int i = gguf_find_key(ctx_gguf.get(), key.c_str()); + if (i < 0) { + if (required) throw std::runtime_error("Key not found: " + key); + return; + } + output = gguf_get_val_f32(ctx_gguf.get(), i); + } + + void get_string(const std::string & key, std::string & output, bool required = true) { + const int i = gguf_find_key(ctx_gguf.get(), key.c_str()); + if (i < 0) { + if (required) throw std::runtime_error("Key not found: " + key); + return; + } + output = std::string(gguf_get_val_str(ctx_gguf.get(), i)); + } + + void get_arr_int(const std::string & key, std::vector & output, bool required = true) { + const int i = gguf_find_key(ctx_gguf.get(), key.c_str()); + if (i < 0) { + if (required) throw std::runtime_error("Key not found: " + key); + return; + } + int n = gguf_get_arr_n(ctx_gguf.get(), i); + output.resize(n); + const int32_t * values = (const int32_t *)gguf_get_arr_data(ctx_gguf.get(), i); + for (int i = 0; i < n; ++i) { + output[i] = values[i]; + } + } +}; + +// read and create ggml_context containing the tensors and their data +struct clip_ctx * clip_model_load(const char * fname, const int verbosity) { + return clip_init(fname, clip_context_params{ + /* use_gpu */ true, + /* verbosity */ static_cast(verbosity), + }); +} + +struct clip_ctx * clip_init(const char * fname, struct clip_context_params ctx_params) { + g_logger_state.verbosity_thold = ctx_params.verbosity; + clip_ctx * ctx_clip = new clip_ctx(ctx_params); + + try { + clip_model_loader loader(fname, *ctx_clip); + loader.load_hparams(); + loader.load_tensors(); + loader.alloc_compute_meta(); + } catch (const std::exception & e) { + LOG_ERR("%s: failed to load model '%s': %s\n", __func__, fname, e.what()); + delete ctx_clip; + return nullptr; + } + + return ctx_clip; } void clip_add_load_image_size(struct clip_ctx * ctx_clip, struct clip_image_size * load_image_size) { @@ -2292,7 +1964,7 @@ static std::vector> uhd_slice_image(const clip_imag const int multiple = fmin(ceil(ratio), max_slice_nums); std::vector> images; - LOG_INF("%s: multiple %d\n", __func__, multiple); + LOG_DBG("%s: multiple %d\n", __func__, multiple); images.push_back(std::vector()); if (multiple <= 1) { @@ -2307,17 +1979,17 @@ static std::vector> uhd_slice_image(const clip_imag clip_image_u8 * source_image = clip_image_u8_init(); bicubic_resize(*img, *source_image, best_size.first, best_size.second); // source_image = image.copy().resize(best_resize, Image.Resampling.BICUBIC) - LOG_INF("%s: image_size: %d %d; source_image size: %d %d\n", __func__, img->nx, img->ny, best_size.first, best_size.second); + LOG_DBG("%s: image_size: %d %d; source_image size: %d %d\n", __func__, img->nx, img->ny, best_size.first, best_size.second); images[images.size()-1].push_back(source_image); std::pair best_grid = uhd_best_grid(max_slice_nums, multiple, log_ratio); - LOG_INF("%s: image_size: %d %d; best_grid: %d %d\n", __func__, img->nx, img->ny, best_grid.first, best_grid.second); + LOG_DBG("%s: image_size: %d %d; best_grid: %d %d\n", __func__, img->nx, img->ny, best_grid.first, best_grid.second); auto refine_size = uhd_get_refine_size(original_size, best_grid, scale_resolution, patch_size, true); clip_image_u8 * refine_image = clip_image_u8_init(); bicubic_resize(*img, *refine_image, refine_size.first, refine_size.second); - LOG_INF("%s: refine_image_size: %d %d; refine_size: %d %d\n", __func__, refine_image->nx, refine_image->ny, refine_size.first, refine_size.second); + LOG_DBG("%s: refine_image_size: %d %d; refine_size: %d %d\n", __func__, refine_image->nx, refine_image->ny, refine_size.first, refine_size.second); // split_to_patches int width = refine_image->nx; @@ -2425,12 +2097,12 @@ bool clip_image_preprocess(struct clip_ctx * ctx, const clip_image_u8 * img, cli bool pad_to_square = true; if (!ctx->has_vision_encoder) { - LOG_ERR("This gguf file seems to have no vision encoder\n"); + LOG_ERR("%s: This gguf file seems to have no vision encoder\n", __func__); return false; } auto & params = ctx->vision_model.hparams; // The model config actually contains all we need to decide on how to preprocess, here we automatically switch to the new llava-1.6 preprocessing - if (strcmp(params.mm_patch_merge_type, "spatial_unpad") == 0) { + if (params.mm_patch_merge_type == PATCH_MERGE_SPATIAL_UNPAD) { pad_to_square = false; } // free the previous res_imgs if any set @@ -2626,7 +2298,7 @@ int32_t clip_hidden_size(const struct clip_ctx * ctx) { } const char * clip_patch_merge_type(const struct clip_ctx * ctx) { - return ctx->vision_model.hparams.mm_patch_merge_type; + return ctx->vision_model.hparams.mm_patch_merge_type == PATCH_MERGE_SPATIAL_UNPAD ? "spatial_unpad" : "flat"; } const int32_t * clip_image_grid(const struct clip_ctx * ctx) { @@ -2762,7 +2434,7 @@ static std::vector> get_2d_sincos_pos_embed(int embed_dim, co bool clip_image_encode(struct clip_ctx * ctx, const int n_threads, clip_image_f32 * img, float * vec) { if (!ctx->has_vision_encoder) { - LOG_ERR("This gguf file seems to have no vision encoder\n"); + LOG_ERR("%s: This gguf file seems to have no vision encoder\n", __func__); return false; } @@ -2774,7 +2446,7 @@ bool clip_image_encode(struct clip_ctx * ctx, const int n_threads, clip_image_f3 bool clip_image_batch_encode(clip_ctx * ctx, const int n_threads, const clip_image_f32_batch * imgs, float * vec) { if (!ctx->has_vision_encoder) { - LOG_ERR("This gguf file seems to have no vision encoder\n"); + LOG_ERR("%s: This gguf file seems to have no vision encoder\n", __func__); return false; } @@ -2810,7 +2482,7 @@ bool clip_image_batch_encode(clip_ctx * ctx, const int n_threads, const clip_ima } const int patch_size = hparams.patch_size; const int num_patches = ((image_size_width / patch_size) * (image_size_height / patch_size)); - const int num_positions = num_patches + (ctx->has_class_embedding ? 1 : 0); + const int num_positions = num_patches + (model.class_embedding ? 1 : 0); if(ctx->load_image_size==nullptr){ ctx->load_image_size= clip_image_size_init(); } @@ -2895,16 +2567,14 @@ bool clip_image_batch_encode(clip_ctx * ctx, const int n_threads, const clip_ima free(pos_embed_data); } } - else{ - { - if (ctx->has_class_embedding) { - struct ggml_tensor * embeddings = ggml_graph_get_tensor(gf, "embeddings"); + else { + if (model.class_embedding) { + struct ggml_tensor * embeddings = ggml_graph_get_tensor(gf, "embeddings"); - void* zero_mem = malloc(ggml_nbytes(embeddings)); - memset(zero_mem, 0, ggml_nbytes(embeddings)); - ggml_backend_tensor_set(embeddings, zero_mem, 0, ggml_nbytes(embeddings)); - free(zero_mem); - } + void* zero_mem = malloc(ggml_nbytes(embeddings)); + memset(zero_mem, 0, ggml_nbytes(embeddings)); + ggml_backend_tensor_set(embeddings, zero_mem, 0, ggml_nbytes(embeddings)); + free(zero_mem); } if (ctx->has_qwen2vl_merger) { @@ -2952,7 +2622,7 @@ bool clip_image_batch_encode(clip_ctx * ctx, const int n_threads, const clip_ima // The patches vector is used to get rows to index into the embeds with; // we should skip dim 0 only if we have CLS to avoid going out of bounds // when retrieving the rows. - int patch_offset = ctx->has_class_embedding ? 1 : 0; + int patch_offset = model.class_embedding ? 1 : 0; int* patches_data = (int*)malloc(ggml_nbytes(patches)); for (int i = 0; i < num_patches; i++) { patches_data[i] = i + patch_offset; @@ -2993,7 +2663,7 @@ bool clip_model_quantize(const char * fname_inp, const char * fname_out, const i auto * ctx_clip = clip_init(fname_inp, clip_context_params{ /* use_gpu */ false, - /* verbosity */ 2, + /* verbosity */ GGML_LOG_LEVEL_ERROR, }); const auto & ctx_src = ctx_clip->ctx_gguf; @@ -3071,7 +2741,7 @@ bool clip_model_quantize(const char * fname_inp, const char * fname_out, const i f32_data = (float *)conv_buf.data(); break; default: - LOG_ERR("Please use an input file in f32 or f16\n"); + LOG_ERR("%s: Please use an input file in f32 or f16\n", __func__); gguf_free(ctx_out); return false; } @@ -3157,7 +2827,7 @@ int clip_n_mmproj_embd(const struct clip_ctx * ctx) { } std::string proj_type = PROJECTOR_TYPE_NAMES[ctx->proj_type]; - throw std::runtime_error(format("%s: don't support projector with: %s currently\n", __func__, proj_type.c_str())); + throw std::runtime_error(string_format("%s: don't support projector with: %s currently\n", __func__, proj_type.c_str())); } int clip_is_minicpmv(const struct clip_ctx * ctx) { diff --git a/examples/llava/clip.h b/examples/llava/clip.h index 47059ca1..783bdca3 100644 --- a/examples/llava/clip.h +++ b/examples/llava/clip.h @@ -1,6 +1,7 @@ #ifndef CLIP_H #define CLIP_H +#include "ggml.h" #include #include @@ -41,7 +42,7 @@ struct clip_image_f32_batch { struct clip_context_params { bool use_gpu; - int verbosity; + ggml_log_level verbosity; }; // deprecated, use clip_init diff --git a/examples/llava/gemma3-cli.cpp b/examples/llava/gemma3-cli.cpp index 7813ac19..fe577510 100644 --- a/examples/llava/gemma3-cli.cpp +++ b/examples/llava/gemma3-cli.cpp @@ -79,7 +79,11 @@ struct gemma3_context { void init_clip_model(common_params & params) { const char * clip_path = params.mmproj.path.c_str(); - ctx_clip = clip_model_load(clip_path, params.verbosity > 1); + ctx_clip = clip_model_load(clip_path, GGML_LOG_LEVEL_INFO); + if (!ctx_clip) { + LOG_ERR("Failed to load CLIP model from %s\n", clip_path); + exit(1); + } } ~gemma3_context() { diff --git a/examples/llava/llava-cli.cpp b/examples/llava/llava-cli.cpp index a1513134..0fe0e333 100644 --- a/examples/llava/llava-cli.cpp +++ b/examples/llava/llava-cli.cpp @@ -241,7 +241,7 @@ static struct llava_context * llava_init_context(common_params * params, llama_m prompt = "describe the image in detail."; } - auto ctx_clip = clip_model_load(clip_path, /*verbosity=*/ 1); + auto ctx_clip = clip_model_load(clip_path, GGML_LOG_LEVEL_INFO); llama_context_params ctx_params = common_context_params_to_llama(*params); ctx_params.n_ctx = params->n_ctx < 2048 ? 2048 : params->n_ctx; // we need a longer context size to process image embeddings diff --git a/examples/llava/minicpmv-cli.cpp b/examples/llava/minicpmv-cli.cpp index 48fddeaa..5ad970c2 100644 --- a/examples/llava/minicpmv-cli.cpp +++ b/examples/llava/minicpmv-cli.cpp @@ -88,7 +88,7 @@ static struct clip_ctx * clip_init_context(common_params * params) { } struct clip_context_params clip_params = { /* use_gpu */ params->n_gpu_layers != 0, - /* verbosity */ params->verbosity, + /* verbosity */ GGML_LOG_LEVEL_INFO, // TODO: make this configurable }; auto * ctx_clip = clip_init(clip_path, clip_params); return ctx_clip; diff --git a/examples/llava/qwen2vl-cli.cpp b/examples/llava/qwen2vl-cli.cpp index c6481e48..eca7b7f1 100644 --- a/examples/llava/qwen2vl-cli.cpp +++ b/examples/llava/qwen2vl-cli.cpp @@ -330,7 +330,7 @@ static struct llava_context * llava_init_context(common_params * params, llama_m prompt = "describe the image in detail."; } - auto ctx_clip = clip_model_load(clip_path, /*verbosity=*/ 1); + auto ctx_clip = clip_model_load(clip_path, GGML_LOG_LEVEL_INFO); llama_context_params ctx_params = common_context_params_to_llama(*params); ctx_params.n_ctx = params->n_ctx < 2048 ? 2048 : params->n_ctx; // we need a longer context size to process image embeddings diff --git a/examples/llava/test-1.jpeg b/examples/llava/test-1.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..7fdcaaf04b24bd162d9ac5163a458a4ac3bd08df GIT binary patch literal 124071 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

MR!0PNaGa0}`#8eSXxJ*%waC#6ls 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

bD>iY^Q*IT^XURL*0z&=J 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 ZbiybzB5&sYub~5cP7?Y5y2x%8{|9NW@Y4VQ literal 0 HcmV?d00001 diff --git a/examples/llava/tests.sh b/examples/llava/tests.sh new file mode 100755 index 00000000..cc9bda87 --- /dev/null +++ b/examples/llava/tests.sh @@ -0,0 +1,81 @@ +#!/bin/bash + +# make sure we are in the right directory +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +cd $SCRIPT_DIR + +#export LLAMA_CACHE="$SCRIPT_DIR/tmp" + +set -eux + +mkdir -p $SCRIPT_DIR/output + +PROJ_ROOT="$SCRIPT_DIR/../.." +cd $PROJ_ROOT + +############### + +arr_bin=() +arr_hf=() + +add_test() { + local bin=$1 + local hf=$2 + arr_bin+=("$bin") + arr_hf+=("$hf") +} + +add_test "llama-gemma3-cli" "ggml-org/gemma-3-4b-it-GGUF:Q4_K_M" +add_test "llama-llava-cli" "cmp-nct/Yi-VL-6B-GGUF:Q5_K" +add_test "llama-llava-cli" "guinmoon/MobileVLM-3B-GGUF:Q4_K_M" +add_test "llama-llava-cli" "THUDM/glm-edge-v-5b-gguf:Q4_K_M" +add_test "llama-llava-cli" "second-state/Llava-v1.5-7B-GGUF:Q2_K" +add_test "llama-llava-cli" "cjpais/llava-1.6-mistral-7b-gguf:Q3_K" +add_test "llama-llava-cli" "ibm-research/granite-vision-3.2-2b-GGUF:Q4_K_M" +add_test "llama-minicpmv-cli" "second-state/MiniCPM-Llama3-V-2_5-GGUF:Q2_K" # model from openbmb is corrupted +add_test "llama-minicpmv-cli" "openbmb/MiniCPM-V-2_6-gguf:Q2_K" +add_test "llama-minicpmv-cli" "openbmb/MiniCPM-o-2_6-gguf:Q4_0" +add_test "llama-qwen2vl-cli" "bartowski/Qwen2-VL-2B-Instruct-GGUF:Q4_K_M" + +############### + +cmake --build build -j --target "${arr_bin[@]}" + +arr_res=() + +for i in "${!arr_bin[@]}"; do + bin="${arr_bin[$i]}" + hf="${arr_hf[$i]}" + + echo "Running test with binary: $bin and HF model: $hf" + echo "" + echo "" + + output=$("$PROJ_ROOT/build/bin/$bin" -hf "$hf" --image $SCRIPT_DIR/test-1.jpeg -p "what is the publisher name of the newspaper?" --temp 0 2>&1 | tee /dev/tty) + + echo "$output" > $SCRIPT_DIR/output/$bin-$(echo "$hf" | tr '/' '-').log + + if echo "$output" | grep -iq "new york"; then + result="\033[32mOK\033[0m: $bin $hf" + else + result="\033[31mFAIL\033[0m: $bin $hf" + fi + echo -e "$result" + arr_res+=("$result") + + echo "" + echo "" + echo "" + echo "#################################################" + echo "#################################################" + echo "" + echo "" +done + +set +x + +for i in "${!arr_res[@]}"; do + echo -e "${arr_res[$i]}" +done +echo "" +echo "Output logs are saved in $SCRIPT_DIR/output"