char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
163a40796
char/aliceai-t5-moe
1#include "clip.h" 2#include "clip-impl.h" 3#include "mtmd.h" 4#include "mtmd-internal.h" 5#include "mtmd-audio.h" 6#include "mtmd-image.h" 7#include "debug/mtmd-debug.h" 8 9#include "llama.h" 10 11// fix problem with std::min and std::max 12#if defined(_WIN32 ) 13#define WIN32_LEAN_AND_MEAN 14#ifndef NOMINMAX 15# define NOMINMAX 16#endif 17#include <windows.h> 18#endif 19 20#include <algorithm> 21#include <cerrno> 22#include <cstdio> 23#include <cstdlib> 24#include <cstring> 25#include <climits> 26#include <type_traits> 27#include <vector> 28 29// remember to bump this if the serialization format changes 30#define MTMD_SERIALIZATION_VERSION 2 31 32struct mtmd_serialization { 33// note: using 64-bit here for future-proofing 34uint64_t version = MTMD_SERIALIZATION_VERSION ; 35 std::vector < char > data ; 36size_t read_pos = 0 ;// cursor used when reading 37 38// for writing 39mtmd_serialization (uint64_t version ) :version (version ) { 40write (version ); 41 } 42 43// for reading 44mtmd_serialization (uint64_t version ,const char * buf ,size_t len ) { 45// copy buf to data 46data .assign (buf ,buf + len ); 47uint64_t ver_in = read < uint64_t > (); 48if (ver_in != version ) { 49throw std::runtime_error ("version mismatch" ); 50 } 51this -> version = ver_in ; 52 } 53 54template < typename T > 55void write (T value ) { 56 static_assert(std::is_trivially_copyable < T > ::value && !std::is_same < T ,bool > ::value , 57"T must be trivially copyable and not bool" ); 58const char * p = reinterpret_cast < const char *> (& value ); 59data .insert (data .end (),p ,p + sizeof (T )); 60 } 61 62template < typename T > 63T read () { 64 static_assert(std::is_trivially_copyable < T > ::value && !std::is_same < T ,bool > ::value , 65"T must be trivially copyable and not bool" ); 66if (read_pos + sizeof (T )> data .size ()) { 67throw std::runtime_error ("read OOB" ); 68 } 69T value ; 70 std::memcpy (& value ,data .data ()+ read_pos ,sizeof (T )); 71read_pos += sizeof (T ); 72return value ; 73 } 74 75}; 76 77template <> 78void mtmd_serialization::write < bool > (bool value ) { 79write < uint8_t > (value ?1 :0 ); 80} 81template <> 82bool mtmd_serialization::read < bool > () { 83return read < uint8_t > ()!= 0 ; 84} 85 86template <> 87void mtmd_serialization::write < std::string > (std::string value ) { 88write < uint64_t > (value .size ()); 89data .insert (data .end (),value .begin (),value .end ()); 90} 91template <> 92std::string mtmd_serialization::read < std::string > () { 93uint64_t len = read < uint64_t > (); 94if (read_pos + len > data .size ()) { 95throw std::runtime_error ("read_string OOB" ); 96 } 97 std::string str (data .data ()+ read_pos ,len ); 98read_pos += len ; 99return str ; 100} 101 102// only mtmd.cpp needs these, so they're implemented here rather than in clip-impl.h 103void clip_image_f32::serialize (mtmd_serialization & ser )const { 104// remember to bump MTMD_SERIALIZATION_VERSION if this is changed 105// note: buf is intentionally NOT serialized; the loaded clip_image_f32 will always be a placeholder 106ser .write (add_viewsep ); 107ser .write (add_newline ); 108ser .write (lead_pad ); 109ser .write ((int32_t )nx_ ); 110ser .write ((int32_t )ny_ ); 111} 112void clip_image_f32::deserialize (mtmd_serialization & ser ) { 113add_viewsep = ser .read < bool > (); 114add_newline = ser .read < bool > (); 115lead_pad = ser .read < int32_t > (); 116nx_ = ser .read < int32_t > (); 117ny_ = ser .read < int32_t > (); 118buf .clear ();// always a placeholder after loading 119} 120 121void clip_image_f32_batch::serialize (mtmd_serialization & ser )const { 122// remember to bump MTMD_SERIALIZATION_VERSION if this is changed 123ser .write (is_audio ); 124ser .write < uint64_t > (entries .size ()); 125for (const auto & entry :entries ) { 126entry .serialize (ser ); 127 } 128} 129void clip_image_f32_batch::deserialize (mtmd_serialization & ser ) { 130is_audio = ser .read < bool > (); 131uint64_t n = ser .read < uint64_t > (); 132constexpr size_t min_entry_bytes = sizeof (uint8_t )* 2 + sizeof (int32_t )* 2 ; 133if (n > (ser .data .size ()- ser .read_pos ) /min_entry_bytes ) { 134throw std::runtime_error ("entries count exceeds buffer size" ); 135 } 136entries .clear (); 137entries .reserve (n ); 138for (uint64_t i = 0 ;i < n ;i ++ ) { 139clip_image_f32 entry ; 140entry .deserialize (ser ); 141entries .push_back (std::move (entry )); 142 } 143} 144 145// for still image data, layout is RGBRGBRGB... 146// length of data must be nx * ny * 3 bytes 147// 148// for audio bitmap: nx = sample count, ny = 1, layout is F32 F32 F32 ... 149// length of data must be nx * sizeof(float) bytes 150struct mtmd_bitmap { 151uint32_t nx = 0 ; 152uint32_t ny = 0 ; 153 std::string id ;// optional user-defined id, for ex: can be set to image hash, useful for KV cache tracking 154bool is_audio = false;// true if the bitmap is audio 155bool mergeable = false;// [QWEN_VIDEO] set only on frames of the same video 156 157// lazy-loaded bitmap 158mtmd_bitmap_lazy_callback lazy_callback = nullptr ; 159void * lazy_user_data = nullptr ; 160 161mtmd_bitmap (const unsigned char * data ,uint32_t nx ,uint32_t ny ) 162 :nx (nx ),ny (ny ),is_audio (false) { 163if (data ) { 164size_t data_size = (size_t )nx * ny * 3 ; 165this -> data .resize (data_size ); 166 std::memcpy (this -> data .data (),data ,data_size ); 167 } 168 } 169 170mtmd_bitmap (const unsigned char * data ,uint32_t n_samples ) 171 :nx (n_samples ),ny (1 ),is_audio (true) { 172if (data ) { 173size_t data_size = (size_t )nx * sizeof (float ); 174this -> data .resize (data_size ); 175 std::memcpy (this -> data .data (),data ,data_size ); 176 } 177 } 178 179const std::vector < unsigned char > & get_ro_buf ()const { 180return data ; 181 } 182 183bool is_placeholder ()const { 184return data .empty (); 185 } 186 187size_t n_bytes ()const { 188return data .size (); 189 } 190 191bool can_merge_with (const mtmd_bitmap & other )const { 192// [QWEN_VIDEO] can (temporal) merge if both are images with same size 193return mergeable && other .mergeable 194&& !is_audio && !other .is_audio 195&& nx == other .nx && ny == other .ny ; 196 } 197 198private : 199 std::vector < unsigned char > data ; 200}; 201 202// position indexing for decoder model 203enum mtmd_pos_type { 204MTMD_POS_TYPE_NORMAL ,// number of positions equals to number of tokens 205MTMD_POS_TYPE_MROPE ,// qwen-vl mrope style, each image takes max(t,h,w) position indexes 206MTMD_POS_TYPE_HUNYUANVL ,// HunyuanVL mrope + BOI/EOI/newline layout with XD-RoPE dim-3 207MTMD_POS_TYPE_COUNT ,// for validation 208}; 209 210struct mtmd_image_tokens { 211uint32_t nx = 0 ;// number of tokens in x direction 212uint32_t ny = 0 ;// number of tokens in y direction 213mtmd_pos_type pos = MTMD_POS_TYPE_NORMAL ; 214uint32_t image_idx = 0 ;// 0-based position of this image among image chunks in the prompt(used by pos == MTMD_POS_TYPE_HUNYUANVL) 215uint32_t n_temporal_merge = 1 ;// for qwen-vl style temporal merge 216uint32_t n_tokens ()const { 217if (pos == MTMD_POS_TYPE_HUNYUANVL ) { 218// [BOI] [row0 tokens + newline] ... [row(ny-1) tokens + newline] [EOI] 219return (nx + 1 )* ny + 2 ; 220 } 221uint32_t nz = batch_f32 .entries .size (); 222if (n_temporal_merge > 1 ) { 223// [QWEN_VIDEO] this logic is quite ugly, it's mostly to make qwen-vl temporal merge work, can be improved in the future 224// TODO: simplify this by repeating the last frame until it fits the temporal merge 225if (nz %n_temporal_merge != 0 ) { 226nz = nz /n_temporal_merge + 1 ; 227 }else { 228nz = nz /n_temporal_merge ; 229 } 230 } 231return nx * ny * nz ; 232 } 233clip_image_f32_batch batch_f32 ;// preprocessed image patches 234 std::string id ;// optional user-defined ID, useful for KV cache tracking 235 236// true if one of entries in batch_f32 is a placeholder 237bool is_placeholder ()const { 238for (const auto & entry :batch_f32 .entries ) { 239if (entry .is_placeholder ()) { 240return true; 241 } 242 } 243return false; 244 } 245 246bool can_batch_with (const mtmd_image_tokens & other ) { 247return nx == other .nx && ny == other .ny && pos == other .pos ; 248 } 249 250mtmd_image_tokens clone () { 251return mtmd_image_tokens { 252nx , 253ny , 254pos , 255image_idx , 256n_temporal_merge , 257batch_f32 .clone (), 258id 259 }; 260 } 261 262void serialize (mtmd_serialization & ser )const { 263// remember to bump MTMD_SERIALIZATION_VERSION if this is changed 264ser .write (nx ); 265ser .write (ny ); 266ser .write ((uint32_t )pos ); 267ser .write (image_idx ); 268ser .write (n_temporal_merge ); 269ser .write (id ); 270batch_f32 .serialize (ser ); 271 } 272void deserialize (mtmd_serialization & ser ) { 273nx = ser .read < uint32_t > (); 274ny = ser .read < uint32_t > (); 275uint32_t pos_raw = ser .read < uint32_t > (); 276if (pos_raw >=MTMD_POS_TYPE_COUNT ) { 277throw std::runtime_error ("invalid pos type" ); 278 } 279pos = (mtmd_pos_type )pos_raw ; 280image_idx = ser .read < uint32_t > (); 281n_temporal_merge = ser .read < uint32_t > (); 282id = ser .read < std::string > (); 283batch_f32 .deserialize (ser ); 284 } 285}; 286using mtmd_image_tokens_ptr = std::unique_ptr < mtmd_image_tokens > ; 287 288struct mtmd_audio_tokens { 289uint32_t n_tokens = 0 ;// number of tokens 290clip_image_f32_batch batch_f32 ;// preprocessed image patches 291 std::string id ;// optional user-defined ID, useful for KV cache tracking 292 293// true if one of entries in batch_f32 is a placeholder 294bool is_placeholder ()const { 295for (const auto & entry :batch_f32 .entries ) { 296if (entry .is_placeholder ()) { 297return true; 298 } 299 } 300return false; 301 } 302 303mtmd_audio_tokens clone () { 304return mtmd_audio_tokens { 305n_tokens , 306batch_f32 .clone (), 307id 308 }; 309 } 310 311void serialize (mtmd_serialization & ser )const { 312// remember to bump MTMD_SERIALIZATION_VERSION if this is changed 313ser .write (n_tokens ); 314ser .write (id ); 315batch_f32 .serialize (ser ); 316 } 317void deserialize (mtmd_serialization & ser ) { 318n_tokens = ser .read < uint32_t > (); 319id = ser .read < std::string > (); 320batch_f32 .deserialize (ser ); 321 } 322}; 323using mtmd_audio_tokens_ptr = std::unique_ptr < mtmd_audio_tokens > ; 324 325struct mtmd_input_chunk { 326mtmd_input_chunk_type type ; 327 std::vector < llama_token > tokens_text ; 328mtmd_image_tokens_ptr tokens_image ; 329mtmd_audio_tokens_ptr tokens_audio ; 330 331bool can_batch_with (const mtmd_input_chunk & other )const { 332if (type != other .type ) { 333return false; 334 } 335 336if (tokens_image && other .tokens_image ) { 337return tokens_image -> can_batch_with (* other .tokens_image ); 338 } 339 340// TODO: allow batching audio chunks of the same size 341 342return false; 343 } 344 345bool is_placeholder ()const { 346if (type == MTMD_INPUT_CHUNK_TYPE_IMAGE ) { 347return tokens_image && tokens_image -> is_placeholder (); 348 }else if (type == MTMD_INPUT_CHUNK_TYPE_AUDIO ) { 349return tokens_audio && tokens_audio -> is_placeholder (); 350 } 351return false; 352 } 353 354void serialize (mtmd_serialization & ser )const { 355// remember to bump MTMD_SERIALIZATION_VERSION if this is changed 356ser .write ((uint32_t )type ); 357 358ser .write < uint64_t > (tokens_text .size ()); 359for (llama_token tok :tokens_text ) { 360ser .write ((int32_t )tok ); 361 } 362 363ser .write (tokens_image != nullptr ); 364if (tokens_image ) { 365tokens_image -> serialize (ser ); 366 } 367 368ser .write (tokens_audio != nullptr ); 369if (tokens_audio ) { 370tokens_audio -> serialize (ser ); 371 } 372 } 373void deserialize (mtmd_serialization & ser ) { 374uint32_t type_raw = ser .read < uint32_t > (); 375if (type_raw >=MTMD_INPUT_CHUNK_TYPE_COUNT ) { 376throw std::runtime_error ("invalid chunk type" ); 377 } 378type = (mtmd_input_chunk_type )type_raw ; 379 380uint64_t n_tokens_text = ser .read < uint64_t > (); 381// reject before resize() so a tiny corrupted/malicious buffer can't force a huge allocation 382if (n_tokens_text > (ser .data .size ()- ser .read_pos ) /sizeof (int32_t )) { 383throw std::runtime_error ("tokens_text length exceeds buffer size" ); 384 } 385tokens_text .resize (n_tokens_text ); 386for (uint64_t i = 0 ;i < n_tokens_text ;i ++ ) { 387tokens_text [i ]= (llama_token )ser .read < int32_t > (); 388 } 389 390if (ser .read < bool > ()) { 391tokens_image = std::make_unique < mtmd_image_tokens > (); 392tokens_image -> deserialize (ser ); 393 }else { 394tokens_image .reset (); 395 } 396 397if (ser .read < bool > ()) { 398tokens_audio = std::make_unique < mtmd_audio_tokens > (); 399tokens_audio -> deserialize (ser ); 400 }else { 401tokens_audio .reset (); 402 } 403 404// catch buffers where the declared type doesn't match which payload is actually present, 405// so a mismatched chunk can't slip through and null-deref/abort later in an accessor 406if (type == MTMD_INPUT_CHUNK_TYPE_IMAGE && !tokens_image ) { 407throw std::runtime_error ("type is IMAGE but tokens_image is missing" ); 408 } 409if (type == MTMD_INPUT_CHUNK_TYPE_AUDIO && !tokens_audio ) { 410throw std::runtime_error ("type is AUDIO but tokens_audio is missing" ); 411 } 412 } 413}; 414 415struct mtmd_input_chunks { 416 std::vector < mtmd_input_chunk > entries ; 417}; 418 419struct mtmd_batch { 420mtmd_context * ctx ; 421 std::vector < const mtmd_input_chunk *> entries ; 422 std::vector < float > output_embd ;// aggregated output embedding for the whole batch 423mtmd_batch (mtmd_context * ctx ):ctx (ctx ) {} 424int32_t n_tokens ()const { 425int32_t n = 0 ; 426for (const auto * chunk :entries ) { 427n += mtmd_input_chunk_get_n_tokens (chunk ); 428 } 429return n ; 430 } 431}; 432 433// slice template, used by some llava-uhd models to correctly place the special tokens around image embeddings 434// models not having it (llava-1.6) will process embeddings without any special tokens in-between 435enum mtmd_slice_tmpl { 436MTMD_SLICE_TMPL_NONE , 437MTMD_SLICE_TMPL_MINICPMV_2_5 , 438MTMD_SLICE_TMPL_MINICPMV_2_6 , 439MTMD_SLICE_TMPL_LLAMA4 , 440MTMD_SLICE_TMPL_IDEFICS3 , 441MTMD_SLICE_TMPL_LFM2 , 442MTMD_SLICE_TMPL_STEP3VL , 443}; 444 445const char * mtmd_default_marker () { 446return "<__media__>" ; 447} 448 449static clip_flash_attn_type mtmd_get_clip_flash_attn_type (enum llama_flash_attn_type flash_attn_type ) { 450switch (flash_attn_type ) { 451case LLAMA_FLASH_ATTN_TYPE_AUTO :return CLIP_FLASH_ATTN_TYPE_AUTO ; 452case LLAMA_FLASH_ATTN_TYPE_DISABLED :return CLIP_FLASH_ATTN_TYPE_DISABLED ; 453case LLAMA_FLASH_ATTN_TYPE_ENABLED :return CLIP_FLASH_ATTN_TYPE_ENABLED ; 454 } 455return CLIP_FLASH_ATTN_TYPE_AUTO ; 456} 457 458mtmd_context_params mtmd_context_params_default () { 459mtmd_context_params params { 460/* use_gpu */ true, 461/* device */ nullptr , 462/* print_timings */ true, 463/* n_threads */ 4 , 464/* image_marker */ nullptr , 465/* media_marker */ mtmd_default_marker (), 466/* flash_attn_type */ LLAMA_FLASH_ATTN_TYPE_AUTO , 467/* warmup */ true, 468/* image_min_tokens */ -1 , 469/* image_max_tokens */ -1 , 470/* cb_eval */ nullptr , 471/* cb_eval_user_data */ nullptr , 472/* batch_max_tokens */ 1024 , 473/* progress_callback */ nullptr , 474/* progress_callback_user_data */ nullptr , 475 }; 476return params ; 477} 478 479struct mtmd_context { 480struct clip_ctx * ctx_v ;// vision 481struct clip_ctx * ctx_a ;// audio 482 std::vector < float > out_embd ;// image embedding vector 483 484// generation context 485struct clip_ctx * ctx_gen_a ;// audio 486 std::vector < int32_t > gen_out_codes ;// this frame's 16 sampled codes (GEN_CODE) 487 std::vector < float > gen_out_feats ;// this frame's continuous features, if any (GEN_CODE) 488 std::vector < float > gen_out_embd ;// next-step hidden state fed back to backbone (GEN_CODE) 489 std::vector < float > gen_out_audio ;// decoded PCM samples for the current frame (GEN_WAV) 490 std::vector < uint8_t > gen_out_state ;// state to feed into the next GEN_WAV call 491 492bool print_timings ; 493int n_threads ; 494 std::string media_marker ; 495const int n_embd_text = -1 ;// -1 means llm context not provided, skip checking this 496const llama_vocab * vocab = nullptr ;// can be nullptr if text_model is not provided 497mtmd_pos_type pos_type ; 498 499// these are not token, but strings used to mark the beginning and end of image/audio embeddings 500 std::string img_beg ; 501 std::string img_end ; 502 std::string aud_beg ; 503 std::string aud_end ; 504 505// for llava-uhd style models, we need special tokens in-between slices 506// minicpmv calls them "slices", llama 4 calls them "tiles" 507mtmd_slice_tmpl slice_tmpl = MTMD_SLICE_TMPL_NONE ; 508 std::vector < llama_token > tok_ov_img_start ;// overview image 509 std::vector < llama_token > tok_ov_img_end ;// overview image 510 std::vector < llama_token > tok_slices_start ;// start of all slices 511 std::vector < llama_token > tok_slices_end ;// end of all slices 512 std::vector < llama_token > tok_sli_img_start ;// single slice start 513 std::vector < llama_token > tok_sli_img_end ;// single slice end 514 std::vector < llama_token > tok_sli_img_mid ;// between 2 slices 515 std::vector < llama_token > tok_row_end ;// end of row 516bool tok_row_end_trail = false; 517bool ov_img_first = false; 518 519// string template for slice image delimiters with row/col (idefics3) 520 std::string sli_img_start_tmpl ; 521 522 std::unique_ptr < mtmd_audio_preprocessor > audio_preproc ; 523 std::unique_ptr < mtmd_image_preprocessor > image_preproc ; 524 525// batching 526int32_t batch_max_tokens ; 527 528// TODO @ngxson : add timings 529 530mtmd_context (const char * mmproj_fname , 531const llama_model * text_model , 532const mtmd_context_params & ctx_params , 533bool no_alloc = false) : 534print_timings (ctx_params .print_timings ), 535n_threads (ctx_params .n_threads ), 536media_marker (ctx_params .media_marker ), 537n_embd_text (text_model ?llama_model_n_embd_inp (text_model ) :-1 ), 538vocab (text_model ?llama_model_get_vocab (text_model ) :nullptr ), 539batch_max_tokens (ctx_params .batch_max_tokens ) 540 { 541if (ctx_params .image_marker != nullptr ) { 542throw std::runtime_error ("custom image_marker is not supported anymore, use media_marker instead" ); 543 } 544 545if (media_marker .empty ()) { 546throw std::runtime_error ("media_marker must not be empty" ); 547 } 548 549if (text_model ) { 550auto decoder_rope_type = llama_model_rope_type (text_model ); 551switch (decoder_rope_type ) { 552case LLAMA_ROPE_TYPE_NONE : 553case LLAMA_ROPE_TYPE_NORM : 554case LLAMA_ROPE_TYPE_NEOX : 555 { 556pos_type = MTMD_POS_TYPE_NORMAL ; 557 }break ; 558case LLAMA_ROPE_TYPE_MROPE : 559case LLAMA_ROPE_TYPE_IMROPE : 560 { 561pos_type = MTMD_POS_TYPE_MROPE ; 562 }break ; 563default : 564throw std::runtime_error (string_format ("unsupported decoder rope type: %d\n" ,decoder_rope_type )); 565 } 566 } 567 568clip_context_params ctx_clip_params { 569/* use_gpu */ ctx_params .use_gpu , 570/* device */ ctx_params .device , 571/* flash_attn_type */ mtmd_get_clip_flash_attn_type (ctx_params .flash_attn_type ), 572/* image_min_tokens */ ctx_params .image_min_tokens , 573/* image_max_tokens */ ctx_params .image_max_tokens , 574/* warmup */ ctx_params .warmup , 575/* cb_eval */ ctx_params .cb_eval , 576/* cb_eval_user_data */ ctx_params .cb_eval_user_data , 577/* no_alloc */ no_alloc , 578/* progress_callback */ ctx_params .progress_callback , 579/* progress_callback_user_data */ ctx_params .progress_callback_user_data , 580 }; 581 582auto res = clip_init (mmproj_fname ,ctx_clip_params ); 583ctx_v = res .ctx_v ; 584ctx_a = res .ctx_a ; 585ctx_gen_a = res .ctx_gen_a ; 586if (!ctx_v && !ctx_a ) { 587throw std::runtime_error (string_format ("Failed to load CLIP model from %s\n" ,mmproj_fname )); 588 } 589 590// if both vision and audio mmproj are present, we need to validate their n_embd 591if (ctx_v && ctx_a ) { 592int n_embd_v = clip_n_mmproj_embd (ctx_v ); 593int n_embd_a = clip_n_mmproj_embd (ctx_a ); 594if (n_embd_v != n_embd_a ) { 595throw std::runtime_error (string_format ( 596"mismatch between vision and audio mmproj (n_embd_v = %d, n_embd_a = %d)\n" , 597n_embd_v ,n_embd_a )); 598 } 599 } 600 601// since we already validate n_embd of vision and audio mmproj, 602// we can safely assume that they are the same 603int n_embd_clip = clip_n_mmproj_embd (ctx_v ?ctx_v :ctx_a ); 604if (n_embd_text > 0 && n_embd_text != n_embd_clip ) { 605throw std::runtime_error (string_format ( 606"mismatch between text model (n_embd = %d) and mmproj (n_embd = %d)\n" 607"hint: you may be using wrong mmproj\n" , 608n_embd_text ,n_embd_clip )); 609 } 610if (ctx_gen_a ) { 611int n_embd_gen = clip_n_mmproj_embd (ctx_gen_a ); 612if (n_embd_text > 0 && n_embd_text != n_embd_gen ) { 613throw std::runtime_error (string_format ( 614"mismatch between text model (n_embd = %d) and gen-audio mmproj (n_embd = %d)\n" 615"hint: you may be using wrong mmproj\n" , 616n_embd_text ,n_embd_gen )); 617 } 618 } 619if (ctx_v ) { 620init_vision (); 621 } 622if (ctx_a ) { 623init_audio (); 624 } 625 } 626 627void init_vision () { 628GGML_ASSERT (ctx_v != nullptr ); 629image_preproc .reset (); 630 631projector_type proj = clip_get_projector_type (ctx_v ); 632 633switch (proj ) { 634case PROJECTOR_TYPE_MLP : 635case PROJECTOR_TYPE_MLP_NORM : 636case PROJECTOR_TYPE_LDP : 637case PROJECTOR_TYPE_LDPV2 : 638case PROJECTOR_TYPE_COGVLM : 639case PROJECTOR_TYPE_JANUS_PRO : 640case PROJECTOR_TYPE_GLM_EDGE : 641 { 642bool has_pinpoints = !clip_get_hparams (ctx_v )-> image_res_candidates .empty (); 643if (has_pinpoints ) { 644image_preproc = std::make_unique < mtmd_image_preprocessor_llava_uhd > (ctx_v ); 645 }else { 646image_preproc = std::make_unique < mtmd_image_preprocessor_fixed_size > (ctx_v ); 647 } 648 }break ; 649case PROJECTOR_TYPE_MINICPMV : 650 { 651int minicpmv_version = clip_get_hparams (ctx_v )-> minicpmv_version ; 652if (minicpmv_version == 2 ) { 653// minicpmv 2.5 format: 654// <image> (overview) </image><slice><image> (slice) </image><image> (slice) </image>\n ... </slice> 655slice_tmpl = MTMD_SLICE_TMPL_MINICPMV_2_5 ; 656tok_ov_img_start = {lookup_token ("<image>" )}; 657tok_ov_img_end = {lookup_token ("</image>" )}; 658tok_slices_start = {lookup_token ("<slice>" )}; 659tok_slices_end = {lookup_token ("</slice>" )}; 660tok_sli_img_start = tok_ov_img_start ; 661tok_sli_img_end = tok_ov_img_end ; 662tok_row_end = {lookup_token ("\n" )}; 663tok_row_end_trail = false;// no trailing end-of-row token 664ov_img_first = true; 665 }else if (minicpmv_version == 3 || minicpmv_version == 4 || minicpmv_version == 5 || minicpmv_version == 6 || minicpmv_version == 100045 ) { 666// minicpmv 2.6 format: 667// <image> (overview) </image><slice> (slice) </slice><slice> (slice) </slice>\n ... 668slice_tmpl = MTMD_SLICE_TMPL_MINICPMV_2_6 ; 669tok_ov_img_start = {lookup_token ("<image>" )}; 670tok_ov_img_end = {lookup_token ("</image>" )}; 671tok_sli_img_start = {lookup_token ("<slice>" )}; 672tok_sli_img_end = {lookup_token ("</slice>" )}; 673tok_row_end = {lookup_token ("\n" )}; 674tok_row_end_trail = false;// no trailing end-of-row token 675ov_img_first = true; 676 677 }else if (minicpmv_version != 0 ) { 678throw std::runtime_error (string_format ("unsupported minicpmv version: %d\n" ,minicpmv_version )); 679 } 680image_preproc = std::make_unique < mtmd_image_preprocessor_llava_uhd > (ctx_v ); 681 }break ; 682case PROJECTOR_TYPE_MINICPMV4_6 : 683 { 684slice_tmpl = MTMD_SLICE_TMPL_MINICPMV_2_6 ; 685tok_ov_img_start = {lookup_token ("<image>" )}; 686tok_ov_img_end = {lookup_token ("</image>" )}; 687tok_sli_img_start = {lookup_token ("<slice>" )}; 688tok_sli_img_end = {lookup_token ("</slice>" )}; 689tok_row_end = {lookup_token ("\n" )}; 690tok_row_end_trail = false;// no trailing end-of-row token 691ov_img_first = true; 692image_preproc = std::make_unique < mtmd_image_preprocessor_minicpmv > (ctx_v ); 693 }break ; 694case PROJECTOR_TYPE_QWEN2VL : 695case PROJECTOR_TYPE_QWEN25VL : 696case PROJECTOR_TYPE_QWEN3VL : 697case PROJECTOR_TYPE_MIMOVL : 698 { 699// <|vision_start|> ... (image embeddings) ... <|vision_end|> 700img_beg = "<|vision_start|>" ; 701img_end = "<|vision_end|>" ; 702image_preproc = std::make_unique < mtmd_image_preprocessor_dyn_size > (ctx_v ); 703 }break ; 704case PROJECTOR_TYPE_MINIMAX_M3 : 705 { 706// ]<]start of image[>[ ... (image embeddings) ... ]<]end of image[>[ 707img_beg = "]<]start of image[>[" ; 708img_end = "]<]end of image[>[" ; 709image_preproc = std::make_unique < mtmd_image_preprocessor_dyn_size > (ctx_v ); 710 }break ; 711case PROJECTOR_TYPE_MUSE_GLIMMER : 712 { 713img_beg = "<|image_start|>" ; 714img_end = "<|image_end|>" ; 715image_preproc = std::make_unique < mtmd_image_preprocessor_muse_glimmer > (ctx_v ); 716 }break ; 717case PROJECTOR_TYPE_YOUTUVL : 718 { 719// <|vision_start|> ... (image embeddings) ... <|vision_end|> 720img_beg = "<|vision_start|>" ; 721img_end = "<|vision_end|>" ; 722image_preproc = std::make_unique < mtmd_image_preprocessor_youtuvl > (ctx_v ); 723 }break ; 724case PROJECTOR_TYPE_YASA2 : 725 { 726img_beg = "<image>" ; 727img_end = "</image>" ; 728// Currently only supprots single-tile preprocessing: any input is downscaled 729// to one image_size x image_size tile (64 output tokens via 8x8 adaptive avg 730// pool). 731// However, the model itself supports llava-uhd multi-tile tiling for high-res 732// images. This will be implemented in a future PR (dispatch on has_pinpoints 733// - see LDP/COGVLM branch above) and emit image_grid_pinpoints in the conversion 734// script. 735image_preproc = std::make_unique < mtmd_image_preprocessor_fixed_size > (ctx_v ); 736 }break ; 737case PROJECTOR_TYPE_GEMMA3 : 738case PROJECTOR_TYPE_GEMMA3NV : 739 { 740// <start_of_image> ... (image embeddings) ... <end_of_image> 741img_beg = "<start_of_image>" ; 742img_end = "<end_of_image>" ; 743image_preproc = std::make_unique < mtmd_image_preprocessor_fixed_size > (ctx_v ); 744 }break ; 745case PROJECTOR_TYPE_IDEFICS3 : 746 { 747// https://github.com/huggingface/transformers/blob/a42ba80fa520c784c8f11a973ca9034e5f859b79/src/transformers/models/idefics3/processing_idefics3.py#L192-L215 748slice_tmpl = MTMD_SLICE_TMPL_IDEFICS3 ; 749tok_ov_img_start = {lookup_token ("\n\n" ),lookup_token ("<fake_token_around_image>" ),lookup_token ("<global-img>" )}; 750tok_ov_img_end = {lookup_token ("<fake_token_around_image>" )}; 751tok_row_end = {lookup_token ("\n" )}; 752sli_img_start_tmpl = "<fake_token_around_image><row_%d_col_%d>" ; 753image_preproc = std::make_unique < mtmd_image_preprocessor_idefics3 > (ctx_v ); 754 }break ; 755case PROJECTOR_TYPE_PIXTRAL : 756 { 757// https://github.com/huggingface/transformers/blob/1cd110c6cb6a6237614130c470e9a902dbc1a4bd/docs/source/en/model_doc/pixtral.md 758img_end = "[IMG_END]" ; 759image_preproc = std::make_unique < mtmd_image_preprocessor_dyn_size > (ctx_v ); 760 }break ; 761case PROJECTOR_TYPE_PHI4 : 762 { 763// Phi-4 uses media marker insertion only. Keep image boundary text empty. 764image_preproc = std::make_unique < mtmd_image_preprocessor_dyn_size > (ctx_v ); 765 }break ; 766case PROJECTOR_TYPE_LLAMA4 : 767 { 768// (more details in mtmd_context constructor) 769img_beg = "<|image_start|>" ; 770img_end = "<|image_end|>" ; 771LOG_WRN ("%s: llama 4 vision is known to have degraded quality:\n" 772" https://github.com/ggml-org/llama.cpp/pull/13282\n" ,__func__ ); 773image_preproc = std::make_unique < mtmd_image_preprocessor_llava_uhd > (ctx_v ); 774ov_img_first = false; 775 }break ; 776case PROJECTOR_TYPE_STEP3VL : 777 { 778// Step3 format: 779// <patch_start> (patch) <patch_end> [<patch_newline>] 780// ... (all patch rows) 781// <im_start> (overview) <im_end> 782slice_tmpl = MTMD_SLICE_TMPL_STEP3VL ; 783tok_ov_img_start = {lookup_token ("<im_start>" )}; 784tok_ov_img_end = {lookup_token ("<im_end>" )}; 785tok_sli_img_start = {lookup_token ("<patch_start>" )}; 786tok_sli_img_end = {lookup_token ("<patch_end>" )}; 787tok_row_end = {lookup_token ("<patch_newline>" )}; 788tok_row_end_trail = false; 789ov_img_first = false;// patches first, overview last 790image_preproc = std::make_unique < mtmd_image_preprocessor_step3vl > (ctx_v ); 791 }break ; 792case PROJECTOR_TYPE_INTERNVL : 793 { 794// <img> ... (image embeddings) ... </img> 795img_beg = "<img>" ; 796img_end = "</img>" ; 797image_preproc = std::make_unique < mtmd_image_preprocessor_internvl > (ctx_v ); 798ov_img_first = false; 799 }break ; 800case PROJECTOR_TYPE_KIMIVL : 801 { 802// <|media_start|> ... (image embeddings) ... <|media_end|> 803img_beg = "<|media_start|>" ; 804img_end = "<|media_end|>" ; 805image_preproc = std::make_unique < mtmd_image_preprocessor_dyn_size > (ctx_v ); 806 }break ; 807case PROJECTOR_TYPE_KIMIK25 : 808 { 809// GLM-5.2-V reuses the Kimi-K2.5 vision encoder and projector, but marks 810// images with its own tokens, so decide based on the text model vocab 811if (lookup_token ("<|begin_of_image|>" )!= LLAMA_TOKEN_NULL ) { 812// <|begin_of_image|> ... (image embeddings) ... <|end_of_image|> 813img_beg = "<|begin_of_image|>" ; 814img_end = "<|end_of_image|>" ; 815 }else { 816// <|media_begin|> ... (image embeddings) ... <|media_end|> 817img_beg = "<|media_begin|>" ; 818img_end = "<|media_end|>" ; 819 } 820image_preproc = std::make_unique < mtmd_image_preprocessor_dyn_size > (ctx_v ); 821 }break ; 822case PROJECTOR_TYPE_LIGHTONOCR : 823 { 824// <|im_start|> ... (image embeddings) ... <|im_end|> 825img_beg = "<|im_start|>" ; 826img_end = "<|im_end|>" ; 827image_preproc = std::make_unique < mtmd_image_preprocessor_longest_edge > (ctx_v ); 828 }break ; 829case PROJECTOR_TYPE_DEEPSEEK4V : 830 { 831// no vocab tokens are added; the start/end/newline markers are learned embeddings emitted by the encoder 832image_preproc = std::make_unique < mtmd_image_preprocessor_deepseek4v > (ctx_v ); 833 }break ; 834case PROJECTOR_TYPE_DOTS_OCR : 835case PROJECTOR_TYPE_DOTS3NOTE_V : 836 { 837// <|img|> ... (image embeddings) ... <|endofimg|> 838img_beg = "<|img|>" ; 839img_end = "<|endofimg|>" ; 840image_preproc = std::make_unique < mtmd_image_preprocessor_dyn_size > (ctx_v ); 841 }break ; 842case PROJECTOR_TYPE_NEMOTRON_V2_VL : 843 { 844image_preproc = std::make_unique < mtmd_image_preprocessor_fixed_size > (ctx_v ); 845 }break ; 846case PROJECTOR_TYPE_LFM2 : 847 { 848// multi-tile: 849// <|image_start|> 850// <|img_row_1_col_1|> (tile) <|img_row_1_col_2|> (tile) ... 851// <|img_thumbnail|> (thumbnail) 852// <|image_end|> 853// single-tile: 854// <|image_start|> (image) <|image_end|> 855img_beg = "<|image_start|>" ; 856img_end = "<|image_end|>" ; 857slice_tmpl = MTMD_SLICE_TMPL_LFM2 ; 858sli_img_start_tmpl = "<|img_row_%d_col_%d|>" ; 859tok_ov_img_start = {lookup_token ("<|img_thumbnail|>" )}; 860ov_img_first = false; 861image_preproc = std::make_unique < mtmd_image_preprocessor_lfm2 > (ctx_v ); 862 }break ; 863case PROJECTOR_TYPE_GLM4V : 864 { 865// <|begin_of_image|> ... (image embeddings) ... <|end_of_image|> 866img_beg = "<|begin_of_image|>" ; 867img_end = "<|end_of_image|>" ; 868image_preproc = std::make_unique < mtmd_image_preprocessor_dyn_size > (ctx_v ); 869 }break ; 870case PROJECTOR_TYPE_PADDLEOCR : 871 { 872// <|IMAGE_START|> ... (image embeddings) ... <|IMAGE_END|> 873img_beg = "<|IMAGE_START|>" ; 874img_end = "<|IMAGE_END|>" ; 875image_preproc = std::make_unique < mtmd_image_preprocessor_dyn_size > (ctx_v ); 876 }break ; 877case PROJECTOR_TYPE_GEMMA4V : 878case PROJECTOR_TYPE_GEMMA4UV : 879 { 880// <|image> ... (image embeddings) ... <image|> 881img_beg = "<|image>" ; 882img_end = "<image|>" ; 883image_preproc = std::make_unique < mtmd_image_preprocessor_dyn_size > (ctx_v ); 884 }break ; 885case PROJECTOR_TYPE_DEEPSEEKOCR : 886case PROJECTOR_TYPE_DEEPSEEKOCR2 : 887 { 888img_end = "\n" ;// prevent empty batch on llama-server 889image_preproc = std::make_unique < mtmd_image_preprocessor_deepseekocr > (ctx_v ); 890ov_img_first = false; 891 }break ; 892case PROJECTOR_TYPE_HUNYUANVL : 893 { 894// note: these use fullwidth | (U+FF5C) and ▁ (U+2581) to match the tokenizer vocabulary 895img_beg = "<|hy_place▁holder▁no▁100|>" ; 896img_end = "<|hy_place▁holder▁no▁101|>" ; 897image_preproc = std::make_unique < mtmd_image_preprocessor_dyn_size > (ctx_v ); 898 }break ; 899case PROJECTOR_TYPE_EXAONE4_5 : 900 { 901// <vision> ... (image embeddings) ... </vision> 902img_beg = "<vision>" ; 903img_end = "</vision>" ; 904image_preproc = std::make_unique < mtmd_image_preprocessor_dyn_size > (ctx_v ); 905 }break ; 906case PROJECTOR_TYPE_GRANITE4_VISION : 907 { 908// ... (image embeddings) \n ... 909img_beg = "" ; 910img_end = "\n" ; 911image_preproc = std::make_unique < mtmd_image_preprocessor_granite > (ctx_v ); 912 }break ; 913default : 914throw std::runtime_error (string_format ("%s: unexpected vision projector type %d\n" ,__func__ ,proj )); 915 } 916 917GGML_ASSERT (image_preproc != nullptr ); 918 } 919 920void init_audio () { 921GGML_ASSERT (ctx_a != nullptr ); 922audio_preproc .reset (); 923 924projector_type proj = clip_get_projector_type (ctx_a ); 925 926LOG_WRN ("%s: audio input is in experimental stage and may have reduced quality:\n" 927" https://github.com/ggml-org/llama.cpp/discussions/13759\n" ,__func__ ); 928 929// set preprocessor 930switch (proj ) { 931case PROJECTOR_TYPE_QWEN2A : 932case PROJECTOR_TYPE_QWEN25O : 933 { 934// <|audio_bos|> ... (embeddings) ... <|audio_eos|> 935aud_beg = "<|audio_bos|>" ; 936aud_end = "<|audio_eos|>" ; 937audio_preproc = std::make_unique < mtmd_audio_preprocessor_whisper > (ctx_a ); 938 }break ; 939case PROJECTOR_TYPE_QWEN3A : 940 { 941aud_beg = "<|audio_start|>" ; 942aud_end = "<|audio_end|>" ; 943audio_preproc = std::make_unique < mtmd_audio_preprocessor_qwen3a > (ctx_a ); 944 }break ; 945case PROJECTOR_TYPE_VOXTRAL : 946 { 947// [BEGIN_AUDIO] ... (embeddings) ... 948aud_beg = "[BEGIN_AUDIO]" ; 949audio_preproc = std::make_unique < mtmd_audio_preprocessor_whisper > (ctx_a ); 950 }break ; 951case PROJECTOR_TYPE_MUSIC_FLAMINGO : 952 { 953// <sound> ... (embeddings) ... 954aud_beg = "<sound>" ; 955audio_preproc = std::make_unique < mtmd_audio_preprocessor_whisper > (ctx_a ); 956 }break ; 957case PROJECTOR_TYPE_ULTRAVOX : 958case PROJECTOR_TYPE_GLMA : 959case PROJECTOR_TYPE_MERALION : 960 { 961audio_preproc = std::make_unique < mtmd_audio_preprocessor_whisper > (ctx_a ); 962 }break ; 963case PROJECTOR_TYPE_LFM2A : 964 { 965audio_preproc = std::make_unique < mtmd_audio_preprocessor_conformer > (ctx_a ); 966 }break ; 967case PROJECTOR_TYPE_GRANITE_SPEECH : 968 { 969audio_preproc = std::make_unique < mtmd_audio_preprocessor_granite_speech > (ctx_a ); 970 }break ; 971case PROJECTOR_TYPE_GEMMA4A : 972 { 973aud_beg = "<|audio>" ; 974aud_end = "<audio|>" ; 975audio_preproc = std::make_unique < mtmd_audio_preprocessor_gemma4a > (ctx_a ); 976 }break ; 977case PROJECTOR_TYPE_PARAKEET : 978 { 979audio_preproc = std::make_unique < mtmd_audio_preprocessor_parakeet > (ctx_a ); 980 }break ; 981case PROJECTOR_TYPE_GEMMA4UA : 982 { 983aud_beg = "<|audio>" ; 984aud_end = "<audio|>" ; 985audio_preproc = std::make_unique < mtmd_audio_preprocessor_gemma4ua > (ctx_a ); 986 }break ; 987case PROJECTOR_TYPE_DOTS3NOTE_A : 988 { 989// <|audio_comp_start|> ... (embeddings) ... <|audio_comp_end|> 990aud_beg = "<|audio_comp_start|>" ; 991aud_end = "<|audio_comp_end|>" ; 992audio_preproc = std::make_unique < mtmd_audio_preprocessor_dots3note > (ctx_a ); 993 }break ; 994case PROJECTOR_TYPE_MIMO_AUDIO : 995 { 996aud_beg = "<|mimo_audio_start|>" ; 997aud_end = "<|mimo_audio_end|>" ; 998audio_preproc = std::make_unique < mtmd_audio_preprocessor_mimo_audio > (ctx_a ); 999 }break ; 1000case PROJECTOR_TYPE_QWEN3TTS_SPKENC : 1001 { 1002audio_preproc = std::make_unique < mtmd_audio_preprocessor_qwen3tts_spk > (ctx_a ); 1003 }break ; 1004case PROJECTOR_TYPE_POCKETTTS_SPKENC : 1005 { 1006audio_preproc = std::make_unique < mtmd_audio_preprocessor_pockettts > (ctx_a ); 1007 }break ; 1008default : 1009throw std::runtime_error (string_format ("%s: unexpected audio projector type %d\n" ,__func__ ,proj )); 1010 } 1011 1012// initialize audio preprocessor 1013GGML_ASSERT (audio_preproc != nullptr ); 1014audio_preproc -> initialize (); 1015 } 1016 1017// get clip ctx based on chunk type 1018clip_ctx * get_clip_ctx (const mtmd_input_chunk * chunk )const { 1019if (chunk -> type == MTMD_INPUT_CHUNK_TYPE_IMAGE ) { 1020return ctx_v ; 1021 }else if (chunk -> type == MTMD_INPUT_CHUNK_TYPE_AUDIO ) { 1022return ctx_a ; 1023 } 1024GGML_ABORT ("unknown chunk type" ); 1025 } 1026 1027projector_type proj_type_v ()const { 1028return ctx_v ?clip_get_projector_type (ctx_v ) :PROJECTOR_TYPE_UNKNOWN ; 1029 } 1030 1031projector_type proj_type_a ()const { 1032return ctx_a ?clip_get_projector_type (ctx_a ) :PROJECTOR_TYPE_UNKNOWN ; 1033 } 1034 1035int64_t n_embd_out ()const { 1036if (ctx_v ) { 1037return clip_n_mmproj_embd (ctx_v ); 1038 }else if (ctx_a ) { 1039return clip_n_mmproj_embd (ctx_a ); 1040 }else { 1041throw std::runtime_error ("no CLIP model loaded" ); 1042 } 1043 } 1044 1045 ~mtmd_context () { 1046clip_free (ctx_a ); 1047clip_free (ctx_v ); 1048clip_free (ctx_gen_a ); 1049 } 1050 1051private : 1052llama_token lookup_token (const std::string & token_text ) { 1053if (vocab == nullptr ) { 1054// TODO @ngxson : this case is currently hit by mtmd_get_memory_usage 1055// but we should reconsider this if this case is needed in other places in the future 1056return LLAMA_TOKEN_NULL ; 1057 } 1058const int n_vocab = llama_vocab_n_tokens (vocab ); 1059for (int i = 0 ;i < n_vocab ;i ++ ) { 1060if (token_to_piece (vocab ,i , true)== token_text ) { 1061return i ; 1062 } 1063 } 1064return LLAMA_TOKEN_NULL ; 1065 } 1066 1067 std::string token_to_piece (const llama_vocab * vocab ,llama_token token ,bool special ) { 1068if (vocab == nullptr ) { 1069throw std::runtime_error ("llama_vocab is not provided" ); 1070 } 1071 std::string piece ; 1072piece .resize (piece .capacity ());// using string internal cache, 15 bytes + '\n' 1073const int n_chars = llama_token_to_piece (vocab ,token ,& piece [0 ],piece .size (),0 ,special ); 1074if (n_chars < 0 ) { 1075piece .resize (- n_chars ); 1076int check = llama_token_to_piece (vocab ,token ,& piece [0 ],piece .size (),0 ,special ); 1077GGML_ASSERT (check == - n_chars ); 1078 }else { 1079piece .resize (n_chars ); 1080 } 1081return piece ; 1082 } 1083}; 1084 1085mtmd_context * mtmd_init_from_file (const char * mmproj_fname , 1086const struct llama_model * text_model , 1087const struct mtmd_context_params ctx_params ) { 1088try { 1089return new mtmd_context (mmproj_fname ,text_model ,ctx_params ); 1090 }catch (const std::exception & e ) { 1091LOG_ERR ("%s: error: %s\n" ,__func__ ,e .what ()); 1092return nullptr ; 1093 } 1094} 1095 1096void mtmd_free (mtmd_context * ctx ) { 1097delete ctx ; 1098} 1099 1100std::vector < std::vector < const mtmd_bitmap *>> mtmd_group_mergeable_bitmaps (std::vector < mtmd_internal_part > & parts ,int n_merge ) { 1101 std::vector < std::vector < const mtmd_bitmap *>> output ; 1102for (size_t i = 0 ;i < parts .size ();i ++ ) { 1103if (parts [i ].bitmap == nullptr ) { 1104continue ;// text part 1105 } 1106const bool has_next = n_merge > 1 && i + 1 < parts .size ()&& parts [i + 1 ].bitmap != nullptr ; 1107if (has_next && parts [i ].bitmap -> can_merge_with (* parts [i + 1 ].bitmap )) { 1108LOG_DBG ("%s: merging 2 frames at part index %zu and %zu\n" ,__func__ ,i ,i + 1 ); 1109output .push_back ({parts [i ].bitmap ,parts [i + 1 ].bitmap }); 1110parts .erase (parts .begin ()+ i + 1 ); 1111continue ; 1112 } 1113LOG_DBG ("%s: no merging for part index %zu\n" ,__func__ ,i ); 1114output .push_back ({parts [i ].bitmap }); 1115 } 1116return output ; 1117} 1118 1119struct mtmd_tokenizer { 1120const mtmd_context * ctx ; 1121 1122 std::string input_text ;// note: can contain null bytes; do not use c_str() 1123bool add_special ; 1124bool parse_special ; 1125const llama_vocab * vocab ; 1126 1127using part = mtmd_internal_part ; 1128 std::vector < part > parts ; 1129// these will be freed when mtmd_tokenizer finishes 1130 std::vector < mtmd::bitmap > bm_from_lazy ;// TODO @ngxson : refactor, free bm_from_lazy progressively 1131 std::vector < const char *> text_from_lazy ; 1132 1133mtmd_input_chunks cur ; 1134uint32_t n_images_added = 0 ;// 0-based index assigned to the next image chunk 1135 1136 ~mtmd_tokenizer () { 1137// note: mtmd::bitmap is already RAII 1138for (auto & str :text_from_lazy ) { 1139free ((void * )str ); 1140 } 1141 } 1142 1143mtmd_tokenizer (const mtmd_context * ctx , 1144const mtmd_input_text * text , 1145const mtmd_bitmap * const * bmps , 1146size_t n_bitmaps ) :ctx (ctx ) { 1147add_special = text -> add_special ; 1148parse_special = text -> parse_special ; 1149vocab = ctx -> vocab ; 1150 1151input_text .assign (text -> text ,text -> text_len ); 1152 1153 std::vector < const mtmd_bitmap *> bitmaps (bmps ,bmps + n_bitmaps ); 1154auto parts_str = split_text (input_text ,ctx -> media_marker ); 1155size_t i_bm = 0 ; 1156for (const auto & part :parts_str ) { 1157if (part == ctx -> media_marker ) { 1158if (i_bm >=bitmaps .size ()) { 1159throw std::runtime_error (string_format ("number of media markers in text (%zu) exceeds number of bitmaps (%zu)" ,i_bm + 1 ,bitmaps .size ())); 1160 } 1161parts .push_back ({"" ,bitmaps [i_bm ++ ]}); 1162 }else { 1163parts .push_back ({std::move (part ),nullptr ,parse_special }); 1164 } 1165 } 1166 1167size_t n_markers = 0 ; 1168for (const auto & part :parts ) { 1169if (part .bitmap != nullptr ) { 1170n_markers ++ ; 1171 } 1172 } 1173if (n_markers != bitmaps .size ()) { 1174throw std::runtime_error (string_format ("number of media markers in text (%zu) does not match number of bitmaps (%zu)" ,n_markers ,bitmaps .size ())); 1175 } 1176 1177expand_lazy_bitmaps (); 1178 } 1179 1180mtmd_tokenizer (const mtmd_context * ctx , 1181const mtmd_input_part * const * input_parts , 1182size_t n_parts , 1183bool add_special ) :ctx (ctx ) { 1184this -> add_special = add_special ; 1185parse_special = true;// only used for text returned by lazy bitmaps 1186vocab = ctx -> vocab ; 1187 1188for (size_t i = 0 ;i < n_parts ;i ++ ) { 1189const mtmd_input_part * p = input_parts [i ]; 1190if (p -> text != nullptr ) { 1191parts .push_back ({std::string (p -> text -> text ,p -> text -> text_len ),nullptr ,p -> text -> parse_special }); 1192 }else { 1193parts .push_back ({"" ,p -> bitmap }); 1194 } 1195 } 1196 1197expand_lazy_bitmaps (); 1198 } 1199 1200void expand_lazy_bitmaps () { 1201 std::vector < part > expanded ; 1202expanded .reserve (parts .size ()); 1203for (auto & p :parts ) { 1204if (p .bitmap != nullptr && p .bitmap -> lazy_callback ) { 1205LOG_DBG ("%s: expanding lazy bitmap\n" ,__func__ ); 1206for (size_t i = 0 ;;i ++ ) { 1207char * out_str = nullptr ; 1208mtmd_bitmap * out_bm = nullptr ; 1209int res = p .bitmap -> lazy_callback (i , 1210p .bitmap -> lazy_user_data , 1211& out_bm , 1212& out_str ); 1213if (out_bm && out_str ) { 1214throw std::runtime_error (string_format ("lazy callback cannot return both bitmap and text" )); 1215 } 1216if (res == 0 ) { 1217// OK, append the returned chunk; lazy part is not yet added 1218if (out_bm ) { 1219auto & ptr = bm_from_lazy .emplace_back (out_bm );// remember to free it later 1220expanded .push_back ({"" ,ptr .ptr .get ()}); 1221LOG_DBG ("%s: lazy callback returned bitmap with dimensions %d x %d\n" ,__func__ ,out_bm -> nx ,out_bm -> ny ); 1222 }else if (out_str ) { 1223auto & ptr = text_from_lazy .emplace_back (out_str );// remember to free it later 1224expanded .push_back ({ptr ,nullptr ,parse_special }); 1225LOG_DBG ("%s: lazy callback returned text: %s\n" ,__func__ ,out_str ); 1226 } 1227 }else if (res == -1 ) { 1228// EOF: lazy part removes itself (not added to expanded) 1229break ; 1230 }else if (res == -2 ) { 1231// error 1232throw std::runtime_error (string_format ("lazy callback returned error" )); 1233 } 1234 } 1235 }else { 1236expanded .push_back (std::move (p )); 1237 } 1238 } 1239parts = std::move (expanded ); 1240 } 1241 1242int32_t tokenize (mtmd_input_chunks * output ) { 1243cur .entries .clear (); 1244 1245// [QWEN_VIDEO] handle frame merging for models that support it (i.e. qwen-vl) 1246int n_merge_frames = 1 ; 1247if (ctx -> ctx_v ) { 1248n_merge_frames = clip_model_n_temporal_merge (ctx -> ctx_v ); 1249GGML_ASSERT (n_merge_frames <=2 && "we only support merging maximum 2 images for now; open an issue if this model supports merging more" ); 1250 } 1251 1252auto merged_bitmaps = mtmd_group_mergeable_bitmaps (parts ,n_merge_frames ); 1253 1254size_t i_bm = 0 ; 1255for (const auto & p :parts ) { 1256if (p .bitmap != nullptr ) { 1257if (i_bm >=merged_bitmaps .size ()) { 1258LOG_ERR ("%s: error: number of bitmaps (%zu) does not match number of markers (%zu)\n" , 1259__func__ ,merged_bitmaps .size (),parts .size ()- 1 ); 1260return 1 ; 1261 } 1262auto bmps = merged_bitmaps [i_bm ++ ]; 1263int32_t res = add_media (bmps ); 1264if (res != 0 ) { 1265return res ; 1266 } 1267 }else { 1268add_text (p .text ,p .parse_special ); 1269 } 1270 } 1271 1272if (vocab != nullptr ) { 1273if (add_special && llama_vocab_get_add_bos (vocab )) { 1274// if first chunk is text, we add BOS token to first text chunk 1275// otherwise, create a new text chunk with BOS token 1276if (!cur .entries .empty ()&& cur .entries [0 ].type == MTMD_INPUT_CHUNK_TYPE_TEXT ) { 1277// add BOS token to the beginning of first text chunk 1278cur .entries [0 ].tokens_text .insert (cur .entries [0 ].tokens_text .begin (),llama_vocab_bos (vocab )); 1279 }else { 1280// create a new text chunk with BOS token at the beginning 1281mtmd_input_chunk bos_chunk { 1282MTMD_INPUT_CHUNK_TYPE_TEXT , 1283 {llama_vocab_bos (vocab )}, 1284nullptr ,// image tokens 1285nullptr ,// audio tokens 1286 }; 1287cur .entries .insert (cur .entries .begin (), std::move (bos_chunk )); 1288 } 1289 } 1290 1291if (add_special && llama_vocab_get_add_eos (vocab )) { 1292// if last chunk is text, we add EOS token to it 1293add_text ({llama_vocab_eos (vocab )}); 1294 } 1295 } 1296 1297if (i_bm != merged_bitmaps .size ()) { 1298LOG_ERR ("%s: error: number of bitmaps (%zu) does not match number of markers (%zu)\n" , 1299__func__ ,merged_bitmaps .size (),parts .size ()- 1 ); 1300return 1 ; 1301 } 1302 1303* output = std::move (cur ); 1304 1305return 0 ; 1306 } 1307 1308void add_text (const std::string & txt ,bool parse_special ) { 1309if (vocab == nullptr ) { 1310throw std::runtime_error ("llama_vocab is not provided" ); 1311 } 1312LOG_DBG ("%s: %s\n" ,__func__ ,txt .c_str ()); 1313auto tokens = mtmd_tokenize_text_internal (vocab ,txt ,/* add_special */ false,parse_special ); 1314add_text (tokens ); 1315 } 1316 1317void add_text (const std::vector < llama_token > & tokens ) { 1318if (tokens .empty ()) { 1319return ; 1320 } 1321// if last entry is also a text chunk, add tokens to it instead of creating new chunk 1322if (!cur .entries .empty ()&& cur .entries .back ().type == MTMD_INPUT_CHUNK_TYPE_TEXT ) { 1323cur .entries .back ().tokens_text .insert ( 1324cur .entries .back ().tokens_text .end (), 1325tokens .begin (), 1326tokens .end ()); 1327 }else { 1328mtmd_input_chunk chunk { 1329MTMD_INPUT_CHUNK_TYPE_TEXT , 1330tokens , 1331nullptr ,// image tokens 1332nullptr ,// audio tokens 1333 }; 1334cur .entries .emplace_back (std::move (chunk )); 1335 } 1336 } 1337 1338int32_t add_media (std::vector < const mtmd_bitmap *> & bitmaps ) { 1339GGML_ASSERT (!bitmaps .empty ()); 1340 1341// note: only one type of media is supported per call, caller should enforce this 1342const bool is_vision = !bitmaps [0 ]-> is_audio ; 1343 1344if (is_vision ) { 1345// handle image 1346 1347if (!ctx -> ctx_v ) { 1348LOG_ERR ("%s: error: model does not support vision input\n" ,__func__ ); 1349return 2 ; 1350 } 1351 1352if (!ctx -> img_beg .empty ()) { 1353add_text (ctx -> img_beg , true);// add image begin token 1354 } 1355 1356// TODO @ngxson : this is quite hacky because preprocessor only support batch with one single element, that need to be fixed in the future (e.g. by changing the preprocessor interface always take single input) 1357 1358mtmd_image_preproc_out preproc_out ; 1359 1360for (const auto * bmp :bitmaps ) { 1361// sanity check 1362GGML_ASSERT (!bmp -> is_audio ); 1363GGML_ASSERT (ctx -> image_preproc != nullptr ); 1364if (bmp -> nx <=0 || bmp -> ny <=0 ) { 1365LOG_ERR ("%s: error: invalid bitmap dimensions: nx = %d, ny = %d\n" , 1366__func__ ,bmp -> nx ,bmp -> ny ); 1367return 2 ; 1368 } 1369 1370// convert mtmd_bitmap to clip_image_u8 1371clip_image_u8 img_u8 ; 1372img_u8 .set_size ( 1373 {(int )bmp -> nx , (int )bmp -> ny }, 1374bmp -> is_placeholder ()); 1375img_u8 .cpy_buf (bmp -> get_ro_buf ()); 1376 1377// preprocess image 1378mtmd_image_preproc_out tmp_preproc_out = ctx -> image_preproc -> preprocess (img_u8 ); 1379 1380// move entries and grid dimensions to the "global" preproc_out 1381for (auto & entry :tmp_preproc_out .entries ) { 1382preproc_out .entries .emplace_back (std::move (entry )); 1383 } 1384 1385// for llava-uhd style, we need to handle grid too 1386// we don't care about overwriting these values for now because the case where bitmaps.size() > 1 is only for frame merging (qwen-vl), not supported by llava-uhd 1387if ((tmp_preproc_out .grid_x > 0 && tmp_preproc_out .grid_y > 0 ) 1388|| tmp_preproc_out .has_overview ()) { 1389GGML_ASSERT (bitmaps .size ()== 1 ); 1390preproc_out .grid_x = tmp_preproc_out .grid_x ; 1391preproc_out .grid_y = tmp_preproc_out .grid_y ; 1392preproc_out .overview = std::move (tmp_preproc_out .overview ); 1393 } 1394 } 1395 1396LOG_DBG ("%s: preproc_out has %zu entries, grid_x = %d, grid_y = %d, has_overview = %d\n" , 1397__func__ ,preproc_out .entries .size (),preproc_out .grid_x ,preproc_out .grid_y , 1398preproc_out .has_overview () ?1 :0 ); 1399 1400// handle llava-uhd style preprocessing 1401// (output either a grid, or overview-only) 1402const bool has_tiling_grid = (preproc_out .grid_x > 0 && preproc_out .grid_y > 0 ) 1403|| preproc_out .has_overview (); 1404 1405if (has_tiling_grid ) { 1406// [QWEN_VIDEO] we do not support "frame merging" for llama-uhd style, so no batching for now 1407GGML_ASSERT (bitmaps .size ()== 1 ); 1408 1409const int n_col = preproc_out .grid_x ; 1410const int n_row = preproc_out .grid_y ; 1411 1412// split batch into chunks of single images 1413auto chunks = split_batch_to_chunk (std::move (preproc_out ),bitmaps [0 ]-> id ); 1414GGML_ASSERT (chunks .size ()> 0 ); 1415 1416// NOTE: preproc_out is invalidated after this point, do not use it anymore 1417 1418// split_batch_to_chunk must always put the overview image first 1419auto ov_chunk = std::move (chunks .front ()); 1420chunks .erase (chunks .begin ()); 1421 1422// add overview image (first) 1423if (ctx -> ov_img_first ) { 1424add_text (ctx -> tok_ov_img_start ); 1425cur .entries .emplace_back (std::move (ov_chunk )); 1426add_text (ctx -> tok_ov_img_end ); 1427 } 1428 1429// add slices (or tiles) 1430if (!chunks .empty ()) { 1431LOG_DBG ("%s: adding %d slices (%d rows x %d cols)\n" ,__func__ , (int )chunks .size (),n_row ,n_col ); 1432GGML_ASSERT ((int )chunks .size ()== n_row * n_col ); 1433add_text (ctx -> tok_slices_start ); 1434for (int y = 0 ;y < n_row ;y ++ ) { 1435for (int x = 0 ;x < n_col ;x ++ ) { 1436const bool is_last_in_row = (x == n_col - 1 ); 1437if (!ctx -> tok_sli_img_start .empty ()) { 1438add_text (ctx -> tok_sli_img_start ); 1439 }else if (!ctx -> sli_img_start_tmpl .empty ()) { 1440// If using a template to preceed a slice image 1441const size_t sz = std::snprintf (nullptr ,0 ,ctx -> sli_img_start_tmpl .c_str (),y + 1 ,x + 1 )+ 1 ; 1442 std::unique_ptr < char []> buf (new char [sz ]); 1443 std::snprintf (buf .get (),sz ,ctx -> sli_img_start_tmpl .c_str (),y + 1 ,x + 1 ); 1444add_text (std::string (buf .get (),buf .get ()+ sz - 1 ), true); 1445 } 1446 1447auto & curr_chunk = chunks [y * n_col + x ]; 1448auto & curr_batch = curr_chunk .tokens_image -> batch_f32 ; 1449if (curr_batch .entries .size ()!= 1 ) { 1450throw std::runtime_error (string_format ("%s: expect 1 image in batch_f32" ,__func__ )); 1451 } 1452 1453LOG_DBG ("%s: adding slice image at row %d col %d\n" ,__func__ ,y ,x ); 1454cur .entries .emplace_back (std::move (curr_chunk )); 1455 1456add_text (ctx -> tok_sli_img_end ); 1457if (!is_last_in_row ) { 1458add_text (ctx -> tok_sli_img_mid ); 1459 } 1460 } 1461if ((y != n_row - 1 || ctx -> tok_row_end_trail )) { 1462add_text (ctx -> tok_row_end ); 1463 } 1464 } 1465add_text (ctx -> tok_slices_end ); 1466 } 1467 1468// add overview image (last) 1469if (!ctx -> ov_img_first ) { 1470add_text (ctx -> tok_ov_img_start ); 1471cur .entries .emplace_back (std::move (ov_chunk )); 1472add_text (ctx -> tok_ov_img_end ); 1473 } 1474 }else { 1475 1476if (preproc_out .entries .size ()== 0 ) { 1477LOG_ERR ("%s: no image tokens produced by preprocessor (ref: https://github.com/ggml-org/llama.cpp/pull/24769)\n" ,__func__ ); 1478return 2 ; 1479 } 1480 1481if (ctx -> proj_type_v ()== PROJECTOR_TYPE_DEEPSEEK4V ) { 1482// the text model perceives input in blocks of N tokens (N = COMPRESS_PAD_TO = 4, same as the CSA compress ratio) 1483// image need to be aligned to block size, while adding IMAGE_PAD embeddings to the beginning 1484// TODO @ngxson : maybe refactor this in the future 1485constexpr int32_t align = 4 ; 1486size_t n_past = 0 ; 1487for (const auto & e :cur .entries ) { 1488n_past += mtmd_input_chunk_get_n_tokens (& e ); 1489 } 1490preproc_out .entries [0 ].lead_pad = align - 1 - (int32_t )(n_past %align ); 1491 } 1492 1493size_t n_tokens = 0 ; 1494for (auto & e :preproc_out .entries ) { 1495n_tokens += clip_n_output_tokens (ctx -> ctx_v ,& e ); 1496if (clip_model_n_temporal_merge (ctx -> ctx_v )== 2 ) { 1497// [QWEN_VIDEO] pair input is merged to the same embd, so only count as one image 1498break ; 1499 } 1500 } 1501 1502mtmd_image_tokens_ptr image_tokens (new mtmd_image_tokens ); 1503 1504// [QWEN_VIDEO] improve this in the future 1505image_tokens -> n_temporal_merge = clip_model_n_temporal_merge (ctx -> ctx_v ); 1506 1507if (mtmd_decode_use_mrope (ctx )) { 1508// for Qwen2VL, we need this information for M-RoPE decoding positions 1509image_tokens -> nx = clip_n_output_tokens_x (ctx -> ctx_v ,& preproc_out .entries [0 ]); 1510image_tokens -> ny = clip_n_output_tokens_y (ctx -> ctx_v ,& preproc_out .entries [0 ]); 1511 }else { 1512// other models, we only need the total number of tokens 1513image_tokens -> nx = n_tokens ; 1514image_tokens -> ny = 1 ; 1515 } 1516image_tokens -> pos = ctx -> pos_type ; 1517// HunyuanVL wraps the image grid with BOI/EOI and adds one newline per row, 1518// and uses XD-RoPE (dim-3 = image index). Override the position type so that 1519// n_tokens() and mtmd_image_tokens_get_decoder_pos pick the HunyuanVL layout. 1520if (ctx -> proj_type_v ()== PROJECTOR_TYPE_HUNYUANVL ) { 1521image_tokens -> pos = MTMD_POS_TYPE_HUNYUANVL ; 1522image_tokens -> image_idx = n_images_added ; 1523GGML_ASSERT (n_tokens == (size_t )image_tokens -> n_tokens ()); 1524 } 1525 1526clip_image_f32_batch batch_f32 ; 1527batch_f32 .is_audio = false; 1528batch_f32 .entries = std::move (preproc_out .entries ); 1529// do NOT use preproc_out from this point on, it's moved 1530 1531image_tokens -> batch_f32 = std::move (batch_f32 ); 1532image_tokens -> id = bitmaps [0 ]-> id ;// optional 1533 1534LOG_DBG ("image_tokens->nx = %d\n" ,image_tokens -> nx ); 1535LOG_DBG ("image_tokens->ny = %d\n" ,image_tokens -> ny ); 1536LOG_DBG ("batch_f32 size = %d\n" , (int )image_tokens -> batch_f32 .entries .size ()); 1537 1538mtmd_input_chunk chunk { 1539MTMD_INPUT_CHUNK_TYPE_IMAGE , 1540 {},// text tokens 1541 std::move (image_tokens ), 1542nullptr ,// audio tokens 1543 }; 1544cur .entries .emplace_back (std::move (chunk )); 1545 } 1546 1547if (!ctx -> img_end .empty ()) { 1548add_text (ctx -> img_end , true);// add image end token 1549 } 1550 1551// advance image-chunk counter so the next image gets the next XD-RoPE dim-3 slot 1552n_images_added ++ ; 1553 1554 }else { 1555// handle audio 1556 1557GGML_ASSERT (bitmaps .size ()== 1 );// no batching support for now 1558auto & bitmap = bitmaps [0 ]; 1559 1560if (!ctx -> ctx_a ) { 1561LOG_ERR ("%s: error: model does not support audio input\n" ,__func__ ); 1562return 2 ; 1563 } 1564 1565if (bitmap -> nx == 0 ) { 1566LOG_ERR ("%s: error: empty audio data\n" ,__func__ ); 1567return 2 ; 1568 } 1569 1570if (!ctx -> aud_beg .empty ()) { 1571add_text (ctx -> aud_beg , true);// add audio begin token 1572 } 1573 1574// sanity check 1575GGML_ASSERT (ctx -> audio_preproc != nullptr ); 1576 1577// preprocess audio 1578 std::vector < mtmd_audio_mel > mel_spec_chunks ; 1579 { 1580 std::vector < float > dummy ; 1581const float * samples = nullptr ; 1582size_t n_samples = 0 ; 1583if (bitmap -> is_placeholder ()) { 1584// TODO @ngxson : skip underlay processing if bitmap is placeholder 1585GGML_ASSERT (bitmap -> ny == 1 ); 1586 1587dummy .resize (bitmap -> nx ); 1588samples = dummy .data (); 1589n_samples = dummy .size (); 1590 }else { 1591const auto & buf = bitmap -> get_ro_buf (); 1592GGML_ASSERT (buf .size ()> sizeof (float )); 1593GGML_ASSERT (buf .size () %sizeof (float )== 0 ); 1594 1595samples = (const float * )buf .data (); 1596n_samples = buf .size () /sizeof (float ); 1597 } 1598bool ok = ctx -> audio_preproc -> preprocess (samples ,n_samples ,mel_spec_chunks ); 1599if (!ok ) { 1600LOG_ERR ("Unable to preprocess audio\n" ); 1601return 2 ; 1602 } 1603 } 1604 1605// consider each mel_spec as a separate audio chunk 1606// TODO: maybe support batching, but this may come with memory cost 1607for (auto & mel_spec :mel_spec_chunks ) { 1608const bool is_placeholder = mel_spec .data .empty (); 1609 1610// Validate dimensions fit in clip_image_size (int) 1611GGML_ASSERT (mel_spec .n_len <=INT32_MAX && mel_spec .n_len >=0 ); 1612GGML_ASSERT (mel_spec .n_mel <=INT32_MAX && mel_spec .n_mel >=0 ); 1613clip_image_f32 mel_f32 ; 1614mel_f32 .set_size ( 1615 {(int )mel_spec .n_len , (int )mel_spec .n_mel }, 1616is_placeholder ,/* is_audio */ true); 1617mel_f32 .cpy_buf (mel_spec .data ); 1618 1619size_t n_tokens = clip_n_output_tokens (ctx -> ctx_a ,& mel_f32 ); 1620 1621clip_image_f32_batch batch_f32 ; 1622batch_f32 .is_audio = true; 1623batch_f32 .entries .push_back (std::move (mel_f32 )); 1624 1625mtmd_audio_tokens_ptr audio_tokens (new mtmd_audio_tokens ); 1626audio_tokens -> n_tokens = n_tokens ; 1627audio_tokens -> batch_f32 = std::move (batch_f32 ); 1628audio_tokens -> id = bitmap -> id ;// optional 1629 1630LOG_DBG ("audio_tokens->n_tokens = %d\n" ,audio_tokens -> n_tokens ); 1631 1632mtmd_input_chunk chunk { 1633MTMD_INPUT_CHUNK_TYPE_AUDIO , 1634 {},// text tokens 1635nullptr ,// image tokens 1636 std::move (audio_tokens ), 1637 }; 1638cur .entries .emplace_back (std::move (chunk )); 1639 } 1640 1641if (!ctx -> aud_end .empty ()) { 1642add_text (ctx -> aud_end , true);// add audio end token 1643 } 1644 } 1645 1646return 0 ; 1647 } 1648 1649 std::vector < mtmd_input_chunk > split_batch_to_chunk (mtmd_image_preproc_out && preproc_out ,const std::string & id ) { 1650 std::vector < mtmd_input_chunk > chunks ; 1651 1652auto process_chunk = [& ](clip_image_f32 && img ) { 1653mtmd_image_tokens_ptr image_tokens (new mtmd_image_tokens ); 1654image_tokens -> nx = clip_n_output_tokens (ctx -> ctx_v ,& img ); 1655image_tokens -> ny = 1 ; 1656image_tokens -> batch_f32 .entries .push_back (std::move (img )); 1657image_tokens -> id = id ; 1658 1659GGML_ASSERT (image_tokens -> nx > 0 ); 1660 1661mtmd_input_chunk chunk { 1662MTMD_INPUT_CHUNK_TYPE_IMAGE , 1663 {},// text tokens 1664 std::move (image_tokens ), 1665nullptr ,// audio tokens 1666 }; 1667chunks .emplace_back (std::move (chunk )); 1668 }; 1669 1670// overview image first 1671auto & overview = preproc_out .overview ; 1672if (overview .nx ()== 0 || overview .ny ()== 0 ) { 1673throw std::runtime_error (string_format ("%s: invalid overview image for llava-uhd style preprocessing\n" ,__func__ )); 1674 } 1675process_chunk (std::move (preproc_out .overview )); 1676 1677// then, process slices 1678for (auto & entry :preproc_out .entries ) { 1679if (entry .nx ()== 0 || entry .ny ()== 0 ) { 1680throw std::runtime_error (string_format ("%s: invalid image slice for llava-uhd style preprocessing\n" ,__func__ )); 1681 } 1682process_chunk (std::move (entry )); 1683 } 1684 1685return chunks ; 1686 } 1687 1688// for example: "a <__media__> b <__media__> c" --> "a", "<__media__>", "b", "<__media__>", "c" 1689static std::vector < std::string > split_text (const std::string & input ,const std::string & delimiter ) { 1690 std::vector < std::string > result ; 1691if (input .empty ()) { 1692return result ; 1693 } 1694size_t start = 0 ; 1695size_t pos = 0 ; 1696while ((pos = input .find (delimiter ,start ))!= std::string::npos ) { 1697if (pos > start ) { 1698result .push_back (input .substr (start ,pos - start )); 1699 } 1700result .push_back (delimiter ); 1701start = pos + delimiter .length (); 1702 } 1703if (start < input .length ()) { 1704result .push_back (input .substr (start )); 1705 } 1706return result ; 1707 } 1708 1709// copied from common_tokenize 1710static std::vector < llama_token > mtmd_tokenize_text_internal ( 1711const struct llama_vocab * vocab , 1712const std::string & text , 1713bool add_special , 1714bool parse_special ) { 1715if (vocab == nullptr ) { 1716throw std::runtime_error ("llama_vocab is not provided" ); 1717 } 1718// upper limit for the number of tokens 1719int n_tokens = text .length ()+ 2 * add_special ; 1720 std::vector < llama_token > result (n_tokens ); 1721n_tokens = llama_tokenize (vocab ,text .data (),text .length (),result .data (),result .size (),add_special ,parse_special ); 1722if (n_tokens == std::numeric_limits < int32_t > ::min ()) { 1723throw std::runtime_error ("Tokenization failed: input text too large, tokenization result exceeds int32_t limit" ); 1724 } 1725if (n_tokens < 0 ) { 1726result .resize (- n_tokens ); 1727int check = llama_tokenize (vocab ,text .data (),text .length (),result .data (),result .size (),add_special ,parse_special ); 1728GGML_ASSERT (check == - n_tokens ); 1729 }else { 1730result .resize (n_tokens ); 1731 } 1732return result ; 1733 } 1734}; 1735 1736int32_t mtmd_tokenize (const mtmd_context * ctx , 1737mtmd_input_chunks * output , 1738const mtmd_input_text * text , 1739const mtmd_bitmap * const * bitmaps , 1740size_t n_bitmaps ) { 1741try { 1742mtmd_tokenizer tokenizer (ctx ,text ,bitmaps ,n_bitmaps ); 1743return tokenizer .tokenize (output ); 1744 }catch (const std::exception & e ) { 1745LOG_ERR ("%s: error: %s\n" ,__func__ ,e .what ()); 1746return 2 ; 1747 } 1748} 1749 1750int32_t mtmd_tokenize_from_parts (const mtmd_context * ctx , 1751mtmd_input_chunks * output , 1752const mtmd_input_part * const * parts , 1753size_t n_parts , 1754bool add_special ) { 1755for (size_t i = 0 ;i < n_parts ;i ++ ) { 1756if ((parts [i ]-> text == nullptr )== (parts [i ]-> bitmap == nullptr )) { 1757LOG_ERR ("%s: part %zu must have either text or bitmap set, not both\n" ,__func__ ,i ); 1758return 1 ; 1759 } 1760if (parts [i ]-> text != nullptr && parts [i ]-> text -> text == nullptr ) { 1761LOG_ERR ("%s: part %zu has null text pointer\n" ,__func__ ,i ); 1762return 1 ; 1763 } 1764 } 1765try { 1766mtmd_tokenizer tokenizer (ctx ,parts ,n_parts ,add_special ); 1767return tokenizer .tokenize (output ); 1768 }catch (const std::exception & e ) { 1769LOG_ERR ("%s: error: %s\n" ,__func__ ,e .what ()); 1770return 2 ; 1771 } 1772} 1773 1774static int32_t mtmd_encode_impl (mtmd_context * ctx ,const mtmd_image_tokens * image_tokens , std::vector < float > & out_embd ) { 1775clip_ctx * ctx_clip = ctx -> ctx_v ; 1776if (!ctx_clip ) { 1777LOG_ERR ("%s: this API does not support non-vision input, please use mtmd_encode_chunk instead\n" ,__func__ ); 1778return 1 ; 1779 } 1780 1781int n_embd_out = ctx -> n_embd_out (); 1782auto n_tokens_out = image_tokens -> n_tokens (); 1783out_embd .resize ((size_t )n_embd_out * n_tokens_out ); 1784 1785if (image_tokens -> is_placeholder ()) { 1786LOG_ERR ("%s: image tokens batch is placeholder\n" ,__func__ ); 1787return 1 ; 1788 } 1789 1790bool ok = clip_image_batch_encode ( 1791ctx_clip , 1792ctx -> n_threads , 1793& image_tokens -> batch_f32 , 1794out_embd ); 1795 1796return ok ?0 :1 ; 1797} 1798 1799static int32_t mtmd_encode_chunk_impl (mtmd_context * ctx ,const mtmd_input_chunk * chunk , std::vector < float > & out_embd ) { 1800if (chunk -> type == MTMD_INPUT_CHUNK_TYPE_TEXT ) { 1801LOG_WRN ("mtmd_encode_chunk has no effect for text chunks\n" ); 1802return 0 ; 1803 }else if (chunk -> type == MTMD_INPUT_CHUNK_TYPE_IMAGE ) { 1804if (!ctx -> ctx_v ) { 1805LOG_ERR ("%s: model does not support vision input\n" ,__func__ ); 1806return 1 ; 1807 } 1808if (chunk -> tokens_image == nullptr ) { 1809LOG_ERR ("%s: image tokens are null\n" ,__func__ ); 1810return 1 ; 1811 } 1812if (chunk -> tokens_image -> is_placeholder ()) { 1813LOG_ERR ("%s: image tokens batch is placeholder\n" ,__func__ ); 1814return 1 ; 1815 } 1816return mtmd_encode_impl (ctx ,chunk -> tokens_image .get (),out_embd ); 1817 }else if (chunk -> type == MTMD_INPUT_CHUNK_TYPE_AUDIO ) { 1818if (!ctx -> ctx_a ) { 1819LOG_ERR ("%s: model does not support audio input\n" ,__func__ ); 1820return 1 ; 1821 } 1822if (chunk -> tokens_audio == nullptr ) { 1823LOG_ERR ("%s: audio tokens are null\n" ,__func__ ); 1824return 1 ; 1825 } 1826if (chunk -> tokens_audio -> is_placeholder ()) { 1827LOG_ERR ("%s: audio tokens batch is placeholder\n" ,__func__ ); 1828return 1 ; 1829 } 1830int n_mmproj_embd = ctx -> n_embd_out (); 1831out_embd .resize ((size_t )chunk -> tokens_audio -> n_tokens * n_mmproj_embd ); 1832bool ok = clip_image_batch_encode ( 1833ctx -> ctx_a , 1834ctx -> n_threads , 1835& chunk -> tokens_audio -> batch_f32 , 1836out_embd ); 1837return ok ?0 :1 ; 1838 } 1839 1840LOG_ERR ("%s: unknown chunk type %d\n" ,__func__ , (int )chunk -> type ); 1841return 1 ; 1842} 1843 1844int32_t mtmd_encode_chunk (mtmd_context * ctx ,const mtmd_input_chunk * chunk ) { 1845// this is the non-batching version 1846try { 1847return mtmd_encode_chunk_impl (ctx ,chunk ,ctx -> out_embd ); 1848 }catch (const std::exception & e ) { 1849LOG_ERR ("%s: error: %s\n" ,__func__ ,e .what ()); 1850return 1 ; 1851 } 1852} 1853 1854int32_t mtmd_encode (mtmd_context * ctx ,const mtmd_image_tokens * image_tokens ) { 1855try { 1856return mtmd_encode_impl (ctx ,image_tokens ,ctx -> out_embd ); 1857 }catch (const std::exception & e ) { 1858LOG_ERR ("%s: error: %s\n" ,__func__ ,e .what ()); 1859return 1 ; 1860 } 1861} 1862 1863float * mtmd_get_output_embd (mtmd_context * ctx ) { 1864return ctx -> out_embd .data (); 1865} 1866 1867// 1868// audio generation 1869// 1870 1871mtmd_gen_audio_info mtmd_gen_audio_get_info (const mtmd_context * ctx ) { 1872mtmd_gen_audio_info info {}; 1873info .model_variant = "" ; 1874if (!ctx -> ctx_gen_a ) { 1875info .type = MTMD_GEN_AUDIO_TYPE_NONE ; 1876return info ; 1877 } 1878info .model_variant = clip_get_hparams (ctx -> ctx_gen_a )-> gen_model_variant .c_str (); 1879switch (clip_get_projector_type (ctx -> ctx_gen_a )) { 1880case PROJECTOR_TYPE_QWEN3TTS_GEN : 1881info .type = MTMD_GEN_AUDIO_TYPE_QWEN3TTS ; 1882info .sample_rate = 24000 ; 1883break ; 1884case PROJECTOR_TYPE_POCKETTTS_GEN : 1885info .type = MTMD_GEN_AUDIO_TYPE_POCKETTTS ; 1886info .sample_rate = 24000 ; 1887break ; 1888default : 1889info .type = MTMD_GEN_AUDIO_TYPE_NONE ; 1890break ; 1891 } 1892return info ; 1893} 1894 1895mtmd_gen_inp mtmd_gen_inp_default (const mtmd_context * ctx ) { 1896mtmd_gen_inp inp {}; 1897inp .type = MTMD_GEN_PROCESS_TYPE_GEN_CODE ; 1898inp .seed = UINT32_MAX ; 1899if (!ctx -> ctx_gen_a ) { 1900return inp ; 1901 } 1902 1903switch (clip_get_projector_type (ctx -> ctx_gen_a )) { 1904case PROJECTOR_TYPE_QWEN3TTS_GEN : 1905// https://huggingface.co/Qwen/Qwen3-TTS-12Hz-1.7B-Base/blob/main/generation_config.json 1906inp .top_k = 50 ; 1907inp .top_p = 1.0f ; 1908inp .temp = 0.9f ;// TODO: handle this on graph 1909break ; 1910case PROJECTOR_TYPE_POCKETTTS_GEN : 1911// https://github.com/kyutai-labs/pocket-tts/blob/main/pocket_tts/default_parameters.py 1912inp .top_k = 50 ; 1913inp .top_p = 1.0f ; 1914inp .temp = 0.7f ; 1915break ; 1916default : 1917break ; 1918 } 1919return inp ; 1920} 1921 1922static int32_t mtmd_gen_audio_process_impl (mtmd_context * ctx ,const mtmd_gen_inp * inp ,mtmd_gen_out * out ) { 1923clip_ctx * ctx_clip = ctx -> ctx_gen_a ; 1924if (!ctx_clip ) { 1925LOG_ERR ("%s: model does not support audio generation\n" ,__func__ ); 1926return 1 ; 1927 } 1928 1929* out = {}; 1930 1931if (inp -> type == MTMD_GEN_PROCESS_TYPE_GEN_CODE ) { 1932const size_t n_embd = (size_t )clip_n_mmproj_embd (ctx_clip ); 1933 1934clip_image_f32 hidden_state ; 1935hidden_state .set_size ({(int )n_embd ,1 }, false, true); 1936hidden_state .cpy_buf (std::vector < float > (inp -> embd ,inp -> embd + n_embd )); 1937 1938clip_image_f32_batch batch ; 1939batch .is_audio = true; 1940batch .entries .push_back (std::move (hidden_state )); 1941 1942 std::vector < float > out_embd (n_embd ); 1943 std::vector < int32_t > out_codes ; 1944 std::vector < float > out_feats ; 1945bool is_eos = false; 1946 1947clip_encode_params params ; 1948params .imgs = & batch ; 1949params .n_threads = ctx -> n_threads ; 1950params .gen_process = CLIP_GEN_PROCESS_GEN_CODE ; 1951params .out_embd = & out_embd ; 1952params .out_codes = & out_codes ; 1953params .out_feats = & out_feats ; 1954params .code0 = inp -> code0 ; 1955params .top_k = inp -> top_k ; 1956params .top_p = inp -> top_p ; 1957params .seed = inp -> seed ; 1958params .temp = inp -> temp ; 1959params .out_is_eos = & is_eos ; 1960 1961if (!clip_encode (ctx_clip ,& params )) { 1962LOG_ERR ("%s: clip_encode failed (gen_code)\n" ,__func__ ); 1963return 1 ; 1964 } 1965 1966ctx -> gen_out_embd = std::move (out_embd ); 1967ctx -> gen_out_codes = std::move (out_codes ); 1968ctx -> gen_out_feats = std::move (out_feats ); 1969 1970out -> embd = ctx -> gen_out_embd .data (); 1971out -> codes = ctx -> gen_out_codes .data (); 1972out -> n_codes = ctx -> gen_out_codes .size (); 1973out -> feats = ctx -> gen_out_feats .data (); 1974out -> n_feats = ctx -> gen_out_feats .size (); 1975out -> is_eos = is_eos ; 1976return 0 ; 1977 } 1978 1979// MTMD_GEN_PROCESS_TYPE_GEN_WAV 1980const bool has_codes = inp -> codes && inp -> n_codes > 0 ; 1981const bool has_feats = inp -> feats && inp -> n_feats > 0 ; 1982if (has_codes == has_feats ) { 1983LOG_ERR ("%s: gen_wav requires exactly one of codes or feats\n" ,__func__ ); 1984return 1 ; 1985 } 1986 std::vector < int32_t > in_codes ; 1987 std::vector < float > in_feats ; 1988if (has_codes ) { 1989in_codes .assign (inp -> codes ,inp -> codes + inp -> n_codes ); 1990 }else { 1991in_feats .assign (inp -> feats ,inp -> feats + inp -> n_feats ); 1992 } 1993 std::vector < uint8_t > in_state ; 1994if (inp -> state_data ) { 1995in_state .assign (inp -> state_data ,inp -> state_data + inp -> state_size ); 1996 } 1997 1998// gen_wav has no hidden-state input, the batch entry is an unused placeholder 1999// TODO @ngxson : some models in the future may require hidden-state input, need to update this code later 2000clip_image_f32 dummy ; 2001dummy .set_size ({1 ,1 }, false, true); 2002dummy .cpy_buf (std::vector < float > (1 ,0.0f )); 2003 2004clip_image_f32_batch batch ; 2005batch .is_audio = true; 2006batch .entries .push_back (std::move (dummy )); 2007 2008clip_encode_params params ; 2009params .imgs = & batch ; 2010params .n_threads = ctx -> n_threads ; 2011params .gen_process = CLIP_GEN_PROCESS_GEN_WAV ; 2012// gen_wav draws no randomness, but keep the seed so it does not reseed mid-generation 2013params .seed = inp -> seed ; 2014params .codes = has_codes ?& in_codes :nullptr ; 2015params .feats = has_feats ?& in_feats :nullptr ; 2016params .out_audio = & ctx -> gen_out_audio ; 2017params .state_in = inp -> state_data ?& in_state :nullptr ; 2018params .state_out = & ctx -> gen_out_state ; 2019 2020if (!clip_encode (ctx_clip ,& params )) { 2021LOG_ERR ("%s: clip_encode failed (code2wav)\n" ,__func__ ); 2022return 1 ; 2023 } 2024 2025out -> audio = ctx -> gen_out_audio .data (); 2026out -> n_samples = ctx -> gen_out_audio .size (); 2027out -> state_data = (const char * )ctx -> gen_out_state .data (); 2028out -> state_size = ctx -> gen_out_state .size (); 2029 2030return 0 ; 2031} 2032 2033int32_t mtmd_gen_audio_process (mtmd_context * ctx ,const struct mtmd_gen_inp * inp ,struct mtmd_gen_out * out ) { 2034try { 2035return mtmd_gen_audio_process_impl (ctx ,inp ,out ); 2036 }catch (const std::exception & e ) { 2037LOG_ERR ("%s: error: %s\n" ,__func__ ,e .what ()); 2038return 1 ; 2039 } 2040} 2041 2042mtmd_batch * mtmd_batch_init (mtmd_context * ctx ) { 2043return new mtmd_batch (ctx ); 2044} 2045 2046void mtmd_batch_free (mtmd_batch * batch ) { 2047if (batch ) { 2048delete batch ; 2049 } 2050} 2051 2052int32_t mtmd_batch_add_chunk (mtmd_batch * batch ,const mtmd_input_chunk * chunk ) { 2053if (chunk -> type == MTMD_INPUT_CHUNK_TYPE_TEXT ) { 2054LOG_ERR ("%s: text chunk is not supported in batch\n" ,__func__ ); 2055return 1 ; 2056 } 2057 2058auto * ctx = batch -> ctx -> get_clip_ctx (chunk ); 2059if (!ctx ) { 2060LOG_ERR ("%s: model does not support input chunk type %d\n" ,__func__ , (int )chunk -> type ); 2061return 1 ; 2062 } 2063 2064if (batch -> entries .empty ()) { 2065// batch must have at least one chunk 2066batch -> entries .push_back (chunk ); 2067return 0 ; 2068 } 2069 2070if (!clip_support_batch (ctx )) { 2071// if no batching support, batch can only have one single chunk 2072return 2 ;// "batch too large" error code 2073 } 2074 2075int32_t new_n_tokens = batch -> n_tokens ()+ (int32_t )mtmd_input_chunk_get_n_tokens (chunk ); 2076if (new_n_tokens > batch -> ctx -> batch_max_tokens ) { 2077return 2 ;// "batch too large" error code 2078 } 2079 2080auto & first_chunk = batch -> entries [0 ]; 2081if (first_chunk -> can_batch_with (* chunk )) { 2082batch -> entries .push_back (chunk ); 2083return 0 ; 2084 } 2085 2086return 3 ;// "cannot batch" error code 2087} 2088 2089static int32_t mtmd_batch_encode_impl (mtmd_batch * batch ) { 2090if (batch -> entries .empty ()) { 2091LOG_ERR ("%s: batch is empty\n" ,__func__ ); 2092return 1 ; 2093 } 2094for (const auto * chunk :batch -> entries ) { 2095if (chunk -> is_placeholder ()) { 2096LOG_ERR ("%s: chunk is placeholder\n" ,__func__ ); 2097return 1 ; 2098 } 2099 } 2100 2101// represent the whole batch as one single chunk 2102 mtmd::input_chunk_ptr batch_chunk (mtmd_input_chunk_copy (batch -> entries [0 ])); 2103if (batch_chunk -> tokens_image ) { 2104auto & b0_f32 = batch_chunk -> tokens_image -> batch_f32 ; 2105// copy all entries from other chunks into the first chunk's batch_f32 2106// note: skip first entry because it's already in batch_chunk 2107for (size_t ic = 1 ;ic < batch -> entries .size ();ic ++ ) { 2108auto & chunk = batch -> entries [ic ]; 2109GGML_ASSERT (chunk -> tokens_image ); 2110auto b1_f32 = chunk -> tokens_image -> batch_f32 .clone (); 2111for (size_t i = 0 ;i < b1_f32 .entries .size ();i ++ ) { 2112b0_f32 .entries .push_back (std::move (b1_f32 .entries [i ])); 2113 } 2114 } 2115 }else if (batch_chunk -> tokens_audio ) { 2116auto & b0_f32 = batch_chunk -> tokens_audio -> batch_f32 ; 2117// copy all entries from other chunks into the first chunk's batch_f32 2118// note: skip first entry because it's already in batch_chunk 2119for (size_t ic = 1 ;ic < batch -> entries .size ();ic ++ ) { 2120auto & chunk = batch -> entries [ic ]; 2121GGML_ASSERT (chunk -> tokens_audio ); 2122auto b1_f32 = chunk -> tokens_audio -> batch_f32 .clone (); 2123for (size_t i = 0 ;i < b1_f32 .entries .size ();i ++ ) { 2124b0_f32 .entries .push_back (std::move (b1_f32 .entries [i ])); 2125 } 2126 } 2127 }else { 2128LOG_ERR ("%s: unsupported chunk type\n" ,__func__ ); 2129return 1 ; 2130 } 2131 2132LOG_DBG ("%s: encoding batch with %zu entries and total %zu tokens\n" , 2133__func__ ,batch -> entries .size (),mtmd_input_chunk_get_n_tokens (batch_chunk .get ())); 2134int32_t res = mtmd_encode_chunk_impl ( 2135batch -> ctx , 2136batch_chunk .get (), 2137batch -> output_embd ); 2138return res ; 2139} 2140 2141int32_t mtmd_batch_encode (mtmd_batch * batch ) { 2142try { 2143return mtmd_batch_encode_impl (batch ); 2144 }catch (const std::exception & e ) { 2145LOG_ERR ("%s: error: %s\n" ,__func__ ,e .what ()); 2146return 1 ; 2147 } 2148} 2149 2150float * mtmd_batch_get_output_embd (mtmd_batch * batch ,const mtmd_input_chunk * chunk ) { 2151if (batch -> output_embd .empty ()) { 2152LOG_ERR ("%s: batch has not been encoded yet\n" ,__func__ ); 2153return nullptr ; 2154 } 2155size_t offset = 0 ; 2156const size_t n_embd = batch -> ctx -> n_embd_out (); 2157for (const auto * c :batch -> entries ) { 2158size_t offset_prev = offset ; 2159size_t n_tokens = mtmd_input_chunk_get_n_tokens (c ); 2160offset += n_tokens * n_embd ; 2161GGML_ASSERT (offset_prev < batch -> output_embd .size ()); 2162GGML_ASSERT (offset <=batch -> output_embd .size ()); 2163if (c == chunk ) { 2164return & batch -> output_embd .data ()[offset_prev ]; 2165 } 2166 } 2167return nullptr ;// not found 2168} 2169 2170bool mtmd_decode_use_non_causal (const mtmd_context * ctx ,const mtmd_input_chunk * chunk ) { 2171auto proj_type = ctx -> proj_type_v (); 2172if (chunk && chunk -> type == MTMD_INPUT_CHUNK_TYPE_AUDIO ) { 2173proj_type = ctx -> proj_type_a (); 2174 } 2175switch (proj_type ) { 2176case PROJECTOR_TYPE_GEMMA4V : 2177// E2B (n_embd = 1536) and E4B (n_embd = 2560) always use causal 2178return ctx -> n_embd_text != 1536 && ctx -> n_embd_text != 2560 ; 2179case PROJECTOR_TYPE_GEMMA4UV : 2180case PROJECTOR_TYPE_GEMMA3 : 2181case PROJECTOR_TYPE_DEEPSEEK4V : 2182return true; 2183default : 2184return false; 2185 } 2186} 2187 2188bool mtmd_decode_use_mrope (const mtmd_context * ctx ) { 2189return ctx -> pos_type == MTMD_POS_TYPE_MROPE ; 2190} 2191 2192bool mtmd_support_vision (const mtmd_context * ctx ) { 2193return ctx -> ctx_v != nullptr ; 2194} 2195 2196bool mtmd_support_audio (const mtmd_context * ctx ) { 2197return ctx -> ctx_a != nullptr ; 2198} 2199 2200int mtmd_get_audio_sample_rate (const mtmd_context * ctx ) { 2201if (!ctx -> ctx_a ) { 2202return -1 ; 2203 } 2204return clip_get_hparams (ctx -> ctx_a )-> audio_sample_rate ; 2205} 2206 2207const char * mtmd_get_marker (const mtmd_context * ctx ) { 2208return ctx -> media_marker .c_str (); 2209} 2210 2211// 2212// public API functions 2213// 2214 2215// mtmd_bitmap 2216 2217mtmd_bitmap * mtmd_bitmap_init (uint32_t nx , 2218uint32_t ny , 2219const unsigned char * data ) { 2220mtmd_bitmap * bitmap = new mtmd_bitmap (data ,nx ,ny ); 2221return bitmap ; 2222} 2223 2224mtmd_bitmap * mtmd_bitmap_init_from_audio (size_t n_samples , 2225const float * data ) { 2226mtmd_bitmap * bitmap = new mtmd_bitmap ((const unsigned char * )data ,n_samples ); 2227GGML_ASSERT (bitmap -> is_audio ); 2228if (!bitmap -> is_placeholder ()) { 2229GGML_ASSERT (bitmap -> get_ro_buf ().size ()== n_samples * sizeof (float )); 2230 } 2231return bitmap ; 2232} 2233 2234uint32_t mtmd_bitmap_get_nx (const mtmd_bitmap * bitmap ) { 2235return bitmap -> nx ; 2236} 2237 2238uint32_t mtmd_bitmap_get_ny (const mtmd_bitmap * bitmap ) { 2239return bitmap -> ny ; 2240} 2241 2242const unsigned char * mtmd_bitmap_get_data (const mtmd_bitmap * bitmap ) { 2243if (bitmap -> is_placeholder ()) { 2244return nullptr ; 2245 } 2246return bitmap -> get_ro_buf ().data (); 2247} 2248 2249size_t mtmd_bitmap_get_n_bytes (const mtmd_bitmap * bitmap ) { 2250if (bitmap -> is_placeholder ()) { 2251return 0 ; 2252 } 2253return bitmap -> get_ro_buf ().size (); 2254} 2255 2256bool mtmd_bitmap_is_audio (const mtmd_bitmap * bitmap ) { 2257return bitmap -> is_audio ; 2258} 2259 2260const char * mtmd_bitmap_get_id (const mtmd_bitmap * bitmap ) { 2261return bitmap -> id .c_str (); 2262} 2263 2264void mtmd_bitmap_set_id (mtmd_bitmap * bitmap ,const char * id ) { 2265if (id ) { 2266bitmap -> id = std::string (id ); 2267 }else { 2268bitmap -> id .clear (); 2269 } 2270} 2271 2272void mtmd_bitmap_set_mergeable (mtmd_bitmap * bitmap ,bool mergeable ) { 2273bitmap -> mergeable = mergeable ; 2274} 2275 2276mtmd_bitmap * mtmd_bitmap_init_lazy (const mtmd_context * ctx , 2277const char * id , 2278void * user_data , 2279mtmd_bitmap_lazy_callback callback ) { 2280GGML_UNUSED (ctx );// reserved for future use 2281mtmd_bitmap * bitmap = new mtmd_bitmap (nullptr ,0 ,0 ); 2282bitmap -> lazy_callback = callback ; 2283bitmap -> lazy_user_data = user_data ; 2284mtmd_bitmap_set_id (bitmap ,id ); 2285return bitmap ; 2286} 2287 2288void mtmd_bitmap_free (mtmd_bitmap * bitmap ) { 2289if (bitmap ) { 2290delete bitmap ; 2291 } 2292} 2293 2294// mtmd_input_chunks 2295 2296mtmd_input_chunks * mtmd_input_chunks_init () { 2297return new mtmd_input_chunks ; 2298} 2299 2300size_t mtmd_input_chunks_size (const mtmd_input_chunks * chunks ) { 2301return chunks -> entries .size (); 2302} 2303 2304const mtmd_input_chunk * mtmd_input_chunks_get (const mtmd_input_chunks * chunks ,size_t idx ) { 2305if (idx >=chunks -> entries .size ()) { 2306return nullptr ; 2307 } 2308return & chunks -> entries [idx ]; 2309} 2310 2311void mtmd_input_chunks_free (mtmd_input_chunks * chunks ) { 2312if (chunks ) { 2313delete chunks ; 2314 } 2315} 2316 2317// mtmd_input_chunk 2318 2319enum mtmd_input_chunk_type mtmd_input_chunk_get_type (const mtmd_input_chunk * chunk ) { 2320return chunk -> type ; 2321} 2322 2323const llama_token * mtmd_input_chunk_get_tokens_text (const mtmd_input_chunk * chunk ,size_t * n_tokens_output ) { 2324if (chunk -> type == MTMD_INPUT_CHUNK_TYPE_TEXT ) { 2325* n_tokens_output = chunk -> tokens_text .size (); 2326return chunk -> tokens_text .data (); 2327 } 2328* n_tokens_output = 0 ; 2329return nullptr ; 2330} 2331 2332const mtmd_image_tokens * mtmd_input_chunk_get_tokens_image (const mtmd_input_chunk * chunk ) { 2333if (chunk -> type == MTMD_INPUT_CHUNK_TYPE_IMAGE ) { 2334return chunk -> tokens_image .get (); 2335 } 2336return nullptr ; 2337} 2338 2339size_t mtmd_input_chunk_get_n_tokens (const mtmd_input_chunk * chunk ) { 2340if (chunk -> type == MTMD_INPUT_CHUNK_TYPE_TEXT ) { 2341return chunk -> tokens_text .size (); 2342 }else if (chunk -> type == MTMD_INPUT_CHUNK_TYPE_IMAGE ) { 2343return mtmd_image_tokens_get_n_tokens (chunk -> tokens_image .get ()); 2344 }else if (chunk -> type == MTMD_INPUT_CHUNK_TYPE_AUDIO ) { 2345return chunk -> tokens_audio -> n_tokens ; 2346 }else { 2347GGML_ABORT ("invalid chunk type" ); 2348 } 2349} 2350 2351llama_pos mtmd_input_chunk_get_n_pos (const mtmd_input_chunk * chunk ) { 2352if (chunk -> type == MTMD_INPUT_CHUNK_TYPE_TEXT ) { 2353return chunk -> tokens_text .size (); 2354 }else if (chunk -> type == MTMD_INPUT_CHUNK_TYPE_IMAGE ) { 2355return mtmd_image_tokens_get_n_pos (chunk -> tokens_image .get ()); 2356 }else if (chunk -> type == MTMD_INPUT_CHUNK_TYPE_AUDIO ) { 2357return chunk -> tokens_audio -> n_tokens ; 2358 }else { 2359GGML_ABORT ("invalid chunk type" ); 2360 } 2361} 2362 2363const char * mtmd_input_chunk_get_id (const mtmd_input_chunk * chunk ) { 2364if (chunk -> type == MTMD_INPUT_CHUNK_TYPE_IMAGE ) { 2365return chunk -> tokens_image -> id .c_str (); 2366 }else if (chunk -> type == MTMD_INPUT_CHUNK_TYPE_AUDIO ) { 2367return chunk -> tokens_audio -> id .c_str (); 2368 } 2369return nullptr ; 2370} 2371 2372mtmd_input_chunk * mtmd_input_chunk_copy (const mtmd_input_chunk * chunk ) { 2373mtmd_input_chunk * copy = new mtmd_input_chunk { 2374chunk -> type , 2375chunk -> tokens_text , 2376nullptr , 2377nullptr , 2378 }; 2379if (chunk -> tokens_image ) { 2380// copy the image tokens 2381copy -> tokens_image = mtmd_image_tokens_ptr (new mtmd_image_tokens ()); 2382* copy -> tokens_image = chunk -> tokens_image -> clone (); 2383 } 2384if (chunk -> tokens_audio ) { 2385// copy the audio tokens 2386copy -> tokens_audio = mtmd_audio_tokens_ptr (new mtmd_audio_tokens ()); 2387* copy -> tokens_audio = chunk -> tokens_audio -> clone (); 2388 } 2389return copy ; 2390} 2391 2392void mtmd_input_chunk_free (mtmd_input_chunk * chunk ) { 2393if (chunk ) { 2394delete chunk ; 2395 } 2396} 2397 2398// returns 0 on success 2399static int32_t mtmd_input_chunk_save_impl (const mtmd_input_chunk * chunk , std::vector < char > & out_buf ) { 2400try { 2401mtmd_serialization ser (MTMD_SERIALIZATION_VERSION ); 2402chunk -> serialize (ser ); 2403out_buf = std::move (ser .data ); 2404return 0 ; 2405 }catch (const std::exception & e ) { 2406LOG_ERR ("%s: %s\n" ,__func__ ,e .what ()); 2407return -1 ; 2408 } 2409} 2410 2411mtmd_input_chunk * mtmd_input_chunk_get_placeholder (const mtmd_input_chunk * chunk ) { 2412// this is hacky, but still faster than copy the whole batch data 2413 std::vector < char > buf ; 2414if (mtmd_input_chunk_save_impl (chunk ,buf )!= 0 ) { 2415return nullptr ; 2416 } 2417return mtmd_input_chunk_load (buf .data (),buf .size ()); 2418} 2419 2420int32_t mtmd_input_chunk_save (const mtmd_input_chunk * chunk ,char * out_buf ,size_t out_len ,size_t * expected_out_len ) { 2421 std::vector < char > buf ; 2422if (mtmd_input_chunk_save_impl (chunk ,buf )!= 0 ) { 2423return -1 ; 2424 } 2425if (expected_out_len ) { 2426* expected_out_len = buf .size (); 2427 } 2428if (!out_buf ) { 2429// caller is only querying the required size 2430return 0 ; 2431 } 2432if (out_len < buf .size ()) { 2433LOG_ERR ("%s: out_buf is too small, need %zu bytes, got %zu\n" ,__func__ ,buf .size (),out_len ); 2434return -1 ; 2435 } 2436 std::memcpy (out_buf ,buf .data (),buf .size ()); 2437return 0 ; 2438} 2439 2440mtmd_input_chunk * mtmd_input_chunk_load (const char * buf ,size_t len ) { 2441try { 2442mtmd_serialization ser (MTMD_SERIALIZATION_VERSION ,buf ,len ); 2443 mtmd::input_chunk_ptr chunk (new mtmd_input_chunk ()); 2444chunk -> deserialize (ser ); 2445return chunk .release (); 2446 }catch (const std::exception & e ) { 2447LOG_ERR ("%s: %s\n" ,__func__ ,e .what ()); 2448return nullptr ; 2449 } 2450} 2451 2452// mtmd_image_tokens 2453 2454size_t mtmd_image_tokens_get_n_tokens (const mtmd_image_tokens * image_tokens ) { 2455return image_tokens -> n_tokens (); 2456} 2457 2458size_t mtmd_image_tokens_get_nx (const mtmd_image_tokens * image_tokens ) { 2459return image_tokens -> nx ; 2460} 2461 2462size_t mtmd_image_tokens_get_ny (const mtmd_image_tokens * image_tokens ) { 2463return image_tokens -> ny ; 2464} 2465 2466mtmd_decoder_pos mtmd_image_tokens_get_decoder_pos (const mtmd_image_tokens * image_tokens ,llama_pos pos_0 ,size_t i ) { 2467mtmd_decoder_pos pos ; 2468switch (image_tokens -> pos ) { 2469case MTMD_POS_TYPE_MROPE : 2470 { 2471pos .t = pos_0 ; 2472pos .x = pos_0 + (i %image_tokens -> nx ); 2473pos .y = pos_0 + (i /image_tokens -> nx ); 2474pos .z = 0 ;// unused for now 2475 }break ; 2476case MTMD_POS_TYPE_NORMAL : 2477 { 2478pos .t = pos_0 + i ; 2479pos .x = pos_0 + i ; 2480pos .y = pos_0 + i ; 2481pos .z = pos_0 + i ; 2482 }break ; 2483case MTMD_POS_TYPE_HUNYUANVL : 2484 { 2485// HunyuanVL layout: [BOI] [row0 tokens + newline] ... [row(ny-1) tokens + newline] [EOI] 2486// Total = 1 + ny*(nx+1) + 1. BOI and EOI use sequential positions in every dim; 2487// content and row-newline tokens use (row, col) with XD-RoPE dim-3 = image_idx. 2488const uint32_t nx = image_tokens -> nx ; 2489const uint32_t n_total = image_tokens -> n_tokens (); 2490if (i == 0 ) { 2491// BOI 2492pos .t = pos_0 + i ; 2493pos .x = pos_0 + i ; 2494pos .y = pos_0 + i ; 2495pos .z = pos_0 + i ; 2496 }else if (i == n_total - 1 ) { 2497// EOI 2498pos .t = pos_0 + i ; 2499pos .x = pos_0 + i ; 2500pos .y = pos_0 + i ; 2501pos .z = pos_0 + i ; 2502 }else { 2503// content token at (row, col), or the trailing newline of a row (col == nx) 2504// section 0 = sequential, section 1 = w(col), section 2 = h(row), section 3 = image_count. 2505// set_position_mrope_2d writes .y -> section 1 and .x -> section 2 2506const uint32_t offset = (uint32_t )i - 1 ; 2507const uint32_t row = offset / (nx + 1 ); 2508const uint32_t col = offset % (nx + 1 ); 2509pos .t = pos_0 + i ; 2510pos .x = row ; 2511pos .y = col ; 2512pos .z = image_tokens -> image_idx ; 2513 } 2514 }break ; 2515default : 2516GGML_ABORT ("invalid position type" ); 2517 } 2518return pos ; 2519} 2520 2521const char * mtmd_image_tokens_get_id (const mtmd_image_tokens * image_tokens ) { 2522return image_tokens -> id .c_str (); 2523} 2524 2525llama_pos mtmd_image_tokens_get_n_pos (const mtmd_image_tokens * image_tokens ) { 2526switch (image_tokens -> pos ) { 2527case MTMD_POS_TYPE_MROPE : 2528return std::max (image_tokens -> nx ,image_tokens -> ny ); 2529case MTMD_POS_TYPE_NORMAL : 2530return image_tokens -> n_tokens (); 2531case MTMD_POS_TYPE_HUNYUANVL : 2532// HunyuanVL: the sequential (dim-0) position advances by the full token count 2533// (includes BOI/EOI and row newline tokens), not by max(nx, ny) 2534return image_tokens -> n_tokens (); 2535default : 2536GGML_ABORT ("invalid position type" ); 2537 } 2538} 2539 2540// test function 2541 2542mtmd_input_chunks * mtmd_test_create_input_chunks () { 2543mtmd_input_chunks * chunks = mtmd_input_chunks_init (); 2544if (!chunks ) { 2545return nullptr ; 2546 } 2547 2548// create a text chunk 2549 std::vector < llama_token > tokens_text = {1 ,2 ,3 ,4 ,5 }; 2550mtmd_input_chunk chunk_text { 2551MTMD_INPUT_CHUNK_TYPE_TEXT , 2552 std::move (tokens_text ), 2553nullptr ,// image tokens 2554nullptr ,// audio tokens 2555 }; 2556chunks -> entries .emplace_back (std::move (chunk_text )); 2557 2558// create an image chunk 2559mtmd_image_tokens_ptr image_tokens (new mtmd_image_tokens ); 2560image_tokens -> nx = 4 ; 2561image_tokens -> ny = 4 ; 2562image_tokens -> batch_f32 .entries .resize (16 ); 2563image_tokens -> id = "image_1" ; 2564mtmd_input_chunk chunk_image { 2565MTMD_INPUT_CHUNK_TYPE_IMAGE , 2566 {},// text tokens 2567 std::move (image_tokens ), 2568nullptr ,// audio tokens 2569 }; 2570chunks -> entries .emplace_back (std::move (chunk_image )); 2571 2572return chunks ; 2573} 2574 2575void mtmd_log_set (ggml_log_callback log_callback ,void * user_data ) { 2576g_logger_state .log_callback = log_callback ?log_callback :clip_log_callback_default ; 2577g_logger_state .log_callback_user_data = user_data ; 2578} 2579 2580struct mtmd_caps mtmd_get_cap_from_file (const char * fname ) { 2581try { 2582auto tmp = clip_get_cap (fname ); 2583mtmd_caps cap ; 2584cap .inp_audio = tmp .has_audio ; 2585cap .inp_vision = tmp .has_vision ; 2586return cap ; 2587 }catch (const std::exception & e ) { 2588LOG_ERR ("%s: failed to get capabilities from file '%s': %s\n" ,__func__ ,fname ,e .what ()); 2589return mtmd_caps { false, false }; 2590 } 2591} 2592 2593// 2594// Debugging API (NOT intended for public use) 2595// 2596 2597static void mtmd_debug_encode_impl (mtmd_context * ctx ,clip_ctx * ctx_clip ,clip_image_f32 & image ) { 2598clip_set_debug_output_embeddings (ctx_clip , true); 2599int n_mmproj_embd = clip_n_mmproj_embd (ctx_clip ); 2600int n_tokens = clip_n_output_tokens (ctx_clip ,& image ); 2601 std::vector < float > embd_output (n_tokens * n_mmproj_embd ,0.0f ); 2602bool ok = clip_image_encode ( 2603ctx_clip , 2604ctx -> n_threads , 2605& image , 2606embd_output ); 2607if (!ok ) { 2608LOG_ERR ("%s: failed to encode image\n" ,__func__ ); 2609 } 2610} 2611 2612void mtmd_debug_encode_image (mtmd_context * ctx ,const std::vector < std::vector < float >> & image ) { 2613if (!ctx -> ctx_v ) { 2614LOG_ERR ("%s: model does not support vision input\n" ,__func__ ); 2615return ; 2616 } 2617const int img_sz = (int )image .size (); 2618 std::vector < float > img_buf ; 2619img_buf .reserve (img_sz * img_sz ); 2620for (const auto & row :image ) { 2621img_buf .insert (img_buf .end (),row .begin (),row .end ()); 2622 } 2623clip_image_f32 inp_image ; 2624inp_image .set_size ({img_sz ,img_sz }, false, false); 2625inp_image .cpy_buf (img_buf ); 2626LOG_INF ("%s: created input image with nx=%d, ny=%d\n" ,__func__ ,img_sz ,img_sz ); 2627mtmd_debug_encode_impl (ctx ,ctx -> ctx_v ,inp_image ); 2628} 2629 2630void mtmd_debug_encode_audio (mtmd_context * ctx ,const std::vector < float > & input ) { 2631if (!ctx -> ctx_a ) { 2632LOG_ERR ("%s: model does not support audio input\n" ,__func__ ); 2633return ; 2634 } 2635int n_mel = clip_get_hparams (ctx -> ctx_a )-> n_mel_bins ; 2636const int audio_nx = (int )input .size (); 2637 std::vector < float > audio_buf (audio_nx * n_mel ); 2638for (int i = 0 ;i < audio_nx ;i ++ ) { 2639for (int j = 0 ;j < n_mel ;j ++ ) { 2640audio_buf [j * audio_nx + i ]= input [i ]; 2641 } 2642 } 2643clip_image_f32 inp_audio ; 2644inp_audio .set_size ({audio_nx ,n_mel }, false, true); 2645inp_audio .cpy_buf (audio_buf ); 2646LOG_INF ("%s: created input audio with nx=%d, ny=%d\n" ,__func__ ,audio_nx ,n_mel ); 2647mtmd_debug_encode_impl (ctx ,ctx -> ctx_a ,inp_audio ); 2648} 2649 2650void mtmd_debug_preprocess_image (mtmd_context * ctx ,const std::vector < uint8_t > & rgb_values ,int nx ,int ny ) { 2651if (!ctx -> ctx_v ) { 2652LOG_ERR ("%s: model does not support vision input\n" ,__func__ ); 2653return ; 2654 } 2655clip_image_u8 img_u8 ; 2656img_u8 .set_size ({nx ,ny }, false); 2657img_u8 .cpy_buf (rgb_values ); 2658GGML_ASSERT (ctx -> image_preproc != nullptr ); 2659mtmd_image_preproc_out preproc_out = ctx -> image_preproc -> preprocess (img_u8 ); 2660 2661clip_image_f32_batch batch_f32 ; 2662batch_f32 .is_audio = false; 2663for (auto & entry :preproc_out .entries ) { 2664batch_f32 .entries .push_back (std::move (entry )); 2665 } 2666 2667LOG_INF ("%s: preprocessed image to batch_f32 with %d entries\n" ,__func__ , (int )batch_f32 .entries .size ()); 2668for (size_t i = 0 ;i < batch_f32 .entries .size ();i ++ ) { 2669LOG_INF ("%s: entry %zu has nx=%d, ny=%d\n" ,__func__ ,i ,batch_f32 .entries [i ].nx (),batch_f32 .entries [i ].ny ()); 2670// TODO: better way to dump entry content? 2671 } 2672} 2673 2674void mtmd_debug_preprocess_audio (mtmd_context * ctx ,const std::vector < float > & samples ) { 2675if (!ctx -> ctx_a ) { 2676LOG_ERR ("%s: model does not support audio input\n" ,__func__ ); 2677return ; 2678 } 2679 std::vector < mtmd_audio_mel > mel_spec_chunks ; 2680bool ok = ctx -> audio_preproc -> preprocess (samples .data (),samples .size (),mel_spec_chunks ); 2681if (!ok ) { 2682LOG_ERR ("%s: failed to preprocess audio\n" ,__func__ ); 2683return ; 2684 } 2685LOG_INF ("%s: preprocessed audio to %zu mel spec chunks\n" ,__func__ ,mel_spec_chunks .size ()); 2686for (size_t i = 0 ;i < mel_spec_chunks .size ();i ++ ) { 2687LOG_INF ("%s: mel spec chunk %zu has n_len=%d, n_mel=%d\n" ,__func__ ,i ,mel_spec_chunks [i ].n_len ,mel_spec_chunks [i ].n_mel ); 2688 2689// dump mel entries: data is stored as [n_mel][n_len] (mel-major) 2690const auto & mel = mel_spec_chunks [i ]; 2691for (int m = 0 ;m < mel .n_mel ;m ++ ) { 2692for (int t = 0 ;t < mel .n_len ;t ++ ) { 2693LOG_INF ("mel[%zu][m=%d][t=%d] = %f\n" ,i ,m ,t ,mel .data [m * mel .n_len + t ]); 2694 } 2695 } 2696 } 2697} 2698 2699static void stub_log_callback (enum ggml_log_level ,const char * ,void * ) { 2700// do nothing 2701} 2702 2703std::map < ggml_backend_dev_t ,size_t > mtmd_get_memory_usage (const char * mmproj_fname , 2704struct mtmd_context_params ctx_params ) { 2705 mtmd::context_ptr ctx ; 2706auto saved_log_callback = g_logger_state .log_callback ; 2707auto saved_log_user_data = g_logger_state .log_callback_user_data ; 2708 2709ctx_params .progress_callback = nullptr ; 2710 2711try { 2712mtmd_log_set (stub_log_callback ,nullptr );// suppress logging 2713ctx .reset (new mtmd_context (mmproj_fname ,nullptr ,ctx_params , true)); 2714mtmd_log_set (saved_log_callback ,saved_log_user_data );// restore log callback 2715 std::map < ggml_backend_dev_t ,size_t > total_mem ; 2716auto merge = [& ](const struct clip_ctx * c ) { 2717for (auto & [dev ,size ] :clip_get_mem_usage (c )) { 2718total_mem [dev ]+= size ; 2719 } 2720 }; 2721if (ctx -> ctx_v ) { 2722merge (ctx -> ctx_v ); 2723 } 2724if (ctx -> ctx_a ) { 2725merge (ctx -> ctx_a ); 2726 } 2727return total_mem ; 2728 }catch (const std::exception & e ) { 2729mtmd_log_set (saved_log_callback ,saved_log_user_data );// restore log callback 2730LOG_ERR ("%s: error: %s\n" ,__func__ ,e .what ()); 2731return {}; 2732 } 2733}