char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
de8656bd9
char/aliceai-t5-moe
1#include "mtmd-image.h" 2 3#include <algorithm> 4#include <cmath> 5#include <vector> 6 7void mtmd_image_preproc_out::append (const clip_hparams & hparams ,const clip_image_u8 & img ,bool normalized ) { 8clip_image_f32 dst ; 9dst .from_u8 (img ); 10if (normalized ) { 11dst .normalize (hparams .image_mean ,hparams .image_std ); 12 } 13entries .push_back (std::move (dst )); 14} 15 16void mtmd_image_preproc_out::append (const clip_hparams & hparams ,const std::vector < clip_image_u8 > & imgs ,bool normalized ) { 17for (const auto & img :imgs ) { 18append (hparams ,img ,normalized ); 19 } 20} 21 22void mtmd_image_preproc_out::append (const clip_hparams & hparams ,clip_image_f32 & img ,bool normalized ) { 23if (normalized ) { 24img .normalize (hparams .image_mean ,hparams .image_std ); 25 } 26entries .push_back (std::move (img )); 27} 28 29void mtmd_image_preproc_out::append_overview (const clip_hparams & hparams ,const clip_image_u8 & img ,bool normalized ) { 30overview .from_u8 (img ); 31if (normalized ) { 32overview .normalize (hparams .image_mean ,hparams .image_std ); 33 } 34} 35 36// set of tools to manipulate images 37// in the future, we can have HW acceleration by allowing this struct to access 3rd party lib like imagick or opencv 38struct img_tool { 39static void resize ( 40const clip_image_u8 & src , 41clip_image_u8 & dst , 42const clip_image_size & target_resolution , 43resize_algo algo , 44pad_style padding = PAD_CEIL , 45 std::array < uint8_t ,3 > pad_color = { 0 , 0 , 0 }) { 46dst . set_size ( target_resolution , src . is_placeholder ()); 47 48if ( src . is_placeholder ()) { 49// no-op for placeholder image, just set the size and return 50return ; 51} 52 53if ( dst . get_size () == src . get_size ()) { 54// no resize needed, simple copy 55dst . cpy_buf ( src . get_ro_buf ()); 56return ; 57} 58 59if ( padding == PAD_NONE ) { 60// direct resize 61resize_pillow ( src , dst , target_resolution . width , target_resolution . height , algo ); 62} else { 63// resize with padding 64clip_image_u8 resized_image ; 65float scale_w = static_cast < float > ( target_resolution . width ) / src . get_size (). width ; 66float scale_h = static_cast < float > ( target_resolution . height ) / src . get_size (). height ; 67float scale = std:: min ( scale_w , scale_h ); 68 69int new_width , new_height ; 70if ( padding == PAD_NEAREST ) { 71new_width = std:: min ( static_cast < int > (std:: round ( src . get_size (). width * scale )), target_resolution . width ); 72new_height = std:: min ( static_cast < int > (std:: round ( src . get_size (). height * scale )), target_resolution . height ); 73} else { 74new_width = std:: min ( static_cast < int > (std:: ceil ( src . get_size (). width * scale )), target_resolution . width ); 75new_height = std:: min ( static_cast < int > (std:: ceil ( src . get_size (). height * scale )), target_resolution . height ); 76} 77 78resize_pillow ( src , resized_image , new_width , new_height , algo ); 79 80// fill dst with pad_color 81fill ( dst , pad_color ); 82 83int offset_x , offset_y ; 84if ( padding == PAD_NEAREST ) { 85offset_x = static_cast < int > (std:: round (( target_resolution . width - new_width ) / 2.0f )); 86offset_y = static_cast < int > (std:: round (( target_resolution . height - new_height ) / 2.0f )); 87} else { 88offset_x = ( target_resolution . width - new_width ) / 2 ; 89offset_y = ( target_resolution . height - new_height ) / 2 ; 90} 91composite ( dst , resized_image , offset_x , offset_y ); 92} 93} 94 95static void crop ( const clip_image_u8 & image , clip_image_u8 & dst , int x , int y , int w , int h ) { 96GGML_ASSERT ( x >= 0 && y >= 0 && w > 0 && h > 0 ); 97GGML_ASSERT ( x + w <= image . get_size (). width && y + h <= image . get_size (). height ); 98dst . set_size ({ w , h }, image . is_placeholder ()); 99 100if ( image . is_placeholder ()) { 101// no-op for placeholder image, just set the size and return 102return ; 103} 104 105for (int i = 0 ; i < h ; ++ i ) { 106for (int j = 0 ; j < w ; ++ j ) { 107dst . set_pixel ( j , i , image . get_pixel ( x + j , y + i )); 108} 109} 110} 111 112struct calc_size_opt { 113int align_size = 1 ; 114int min_pixels = 0 ; // 0 = disabled 115int max_pixels = 0 ; // 0 = disabled 116// applied before min/max_pixels, so min_pixels can push an edge back above longest_edge 117int longest_edge = 0 ; // 0 = disabled 118}; 119 120// calculate the size of the **resized** image, while preserving the aspect ratio and 121// aligning to the nearest multiple of align_size ("smart_resize" in transformers code) 122static clip_image_size calc_size_preserved_ratio ( const clip_image_size & inp_size , const calc_size_opt & opts ) { 123GGML_ASSERT ( opts . align_size > 0 ); 124const int width = inp_size . width ; 125const int height = inp_size . height ; 126if ( width <= 0 || height <= 0 ) { 127return { 0 , 0 }; 128} 129 130auto round_by_factor = [ f = opts . align_size ](float x ) { return static_cast < int > (std:: round ( x / static_cast < float > ( f ))) * f ; }; 131auto ceil_by_factor = [ f = opts . align_size ](float x ) { return static_cast < int > (std:: ceil ( x / static_cast < float > ( f ))) * f ; }; 132auto floor_by_factor = [ f = opts . align_size ](float x ) { return static_cast < int > (std:: floor ( x / static_cast < float > ( f ))) * f ; }; 133 134int w_bar , h_bar ; 135if ( opts . longest_edge > 0 ) { 136const float scale = std:: min ( static_cast < float > ( opts . longest_edge ) / width , 137static_cast < float > ( opts . longest_edge ) / height ); 138w_bar = ceil_by_factor ( width * scale ); 139h_bar = ceil_by_factor ( height * scale ); 140} else { 141// always align up first 142w_bar = std:: max ( opts . align_size , round_by_factor ( width )); 143h_bar = std:: max ( opts . align_size , round_by_factor ( height )); 144} 145 146if ( opts . max_pixels > 0 && h_bar * w_bar > opts . max_pixels ) { 147const auto beta = std:: sqrt ( static_cast < float > ( height ) * width / opts . max_pixels ); 148h_bar = std:: max ( opts . align_size , floor_by_factor ( height / beta )); 149w_bar = std:: max ( opts . align_size , floor_by_factor ( width / beta )); 150} else if ( opts . min_pixels > 0 && h_bar * w_bar < opts . min_pixels ) { 151const auto beta = std:: sqrt ( static_cast < float > ( opts . min_pixels ) / ( static_cast < float > ( height ) * width )); 152h_bar = ceil_by_factor ( height * beta ); 153w_bar = ceil_by_factor ( width * beta ); 154} 155 156return { w_bar , h_bar }; 157} 158 159// draw src image into dst image at offset (offset_x, offset_y) 160static void composite (clip_image_u8 & dst , const clip_image_u8 & src , int offset_x , int offset_y ) { 161if ( src . is_placeholder ()) { 162// no-op for placeholder image 163return ; 164} 165 166const auto src_size = src . get_size (); 167const auto dst_size = dst . get_size (); 168for (int y = 0 ; y < src_size . height ; ++ y ) { 169for (int x = 0 ; x < src_size . width ; ++ x ) { 170int dx = x + offset_x ; 171int dy = y + offset_y ; 172// skip pixels that would be out of bounds in the destination 173if ( dx < 0 || dy < 0 || dx >= dst_size . width || dy >= dst_size . height ) { 174continue ; 175} 176dst . set_pixel ( dx , dy , src . get_pixel ( x , y )); 177} 178} 179} 180 181// fill the image with a solid color 182static void fill (clip_image_u8 & img , const std::array < uint8_t, 3 > & color ) { 183if ( img . is_placeholder ()) { 184// no-op for placeholder image 185return ; 186} 187 188const auto size = img . get_size (); 189for (int y = 0 ; y < size . height ; ++ y ) { 190for (int x = 0 ; x < size . width ; ++ x ) { 191img . set_pixel ( x , y , color ); 192} 193} 194} 195 196private : 197// Pillow-compatible separable resampling (Bilinear, Bicubic and Lanczos) 198// Adapted from https://github.com/python-pillow/Pillow/blob/main/src/libImaging/Resample.c 199// 200// Key properties: 201// 1. Separable filtering: horizontal pass followed by vertical pass 202// 2. Pre-computes normalized filter coefficients for each output pixel 203// 3. Fixed-point integer arithmetic (22 fractional bits) for speed and determinism 204static bool resize_pillow ( 205const clip_image_u8 & img , 206clip_image_u8 & dst , 207int target_width , 208int target_height , 209resize_algo algo ) { 210// Fixed-point precision: 22 bits = 32 (int32_t) - 8 (uint8_t pixels) - 2 (headroom for accumulation) 211// This allows encoding fractional weights as integers: weight * 2^22 212const int PRECISION_BITS = 32 - 8 - 2 ; 213 214// Filter support radius 215double filter_support ; 216switch ( algo ) { 217case RESIZE_ALGO_BILINEAR : filter_support = 1.0 ; break ; 218case RESIZE_ALGO_BICUBIC : filter_support = 2.0 ; break ; 219case RESIZE_ALGO_LANCZOS : filter_support = 3.0 ; break ; 220default : 221throw std:: runtime_error ( "Unsupported resize algorithm" ); 222} 223 224// Returns filter weight for distance x from pixel center 225// Note: for bicubic, Pillow uses a = -0.5 while GGML/PyTorch use a = -0.75 226auto resample_filter = [ algo ](double x ) -> double { 227if ( algo == RESIZE_ALGO_LANCZOS ) { 228if ( -3.0 <= x && x < 3.0 ) { 229auto sinc = [](double v ) { 230if ( v == 0.0 ) { 231return 1.0 ; 232} 233const double pi_v = v * 3.141592653589793238462643383279502884 ; 234return std:: sin ( pi_v ) / pi_v ; 235}; 236return sinc ( x ) * sinc ( x / 3.0 ); 237} 238return 0.0 ; 239} 240 241if ( x < 0.0 ) { 242x = - x ; 243} 244 245if ( algo == RESIZE_ALGO_BILINEAR ) { 246return x < 1.0 ? 1.0 - x : 0.0 ; 247} 248 249constexpr double a = -0.5 ; 250if ( x < 1.0 ) { 251return (( a + 2.0 ) * x - ( a + 3.0 )) * x * x + 1 ; 252} 253if ( x < 2.0 ) { 254return ((( x - 5 ) * x + 8 ) * x - 4 ) * a ; 255} 256return 0.0 ; // Zero outside [-2, 2] 257}; 258 259// Clipping function for 8-bit values 260auto clip8 = [](int val ) -> uint8_t { 261if ( val < 0 ) return 0 ; 262if ( val > 255 ) return 255 ; 263return static_cast < uint8_t > ( val ); 264}; 265 266// Precompute filter coefficients for ONE dimension (horizontal or vertical) 267// 268// Parameters: 269// inSize - Number of pixels in input dimension (e.g., src_width or src_height) 270// outSize - Number of pixels in output dimension (e.g., target_width or target_height) 271// bounds - [OUTPUT] Array of size outSize*2 storing input pixel ranges: 272// bounds[xx*2+0] = first input pixel index for output pixel xx (xmin) 273// bounds[xx*2+1] = number of input pixels for output pixel xx (xcnt) 274// weights - [OUTPUT] Array of size outSize*ksize storing fixed-point filter weights: 275// kk[xx*ksize + x] = weight for input pixel x contributing to output pixel xx 276// 277// Returns: kernel size (ksize) - number of input pixels that contribute to each output pixel 278auto precompute_weights = [ & ](int inSize , int outSize , 279std::vector < int > & bounds , std::vector < int32_t > & weights ) -> int { 280GGML_ASSERT ( inSize > 0 && outSize > 0 ); 281double support , scale , filterscale ; 282double center , ww , ss ; 283int xx , x , ksize , xmin , xmax ; 284 285// Calculate scaling factor: ratio of input range to output size 286filterscale = scale = static_cast < double > ( inSize ) / outSize ; 287// For upsampling (scale < 1), keep filterscale = 1 to maintain filter sharpness 288// For downsampling (scale > 1), widen filter to prevent aliasing 289if ( filterscale < 1.0 ) { 290filterscale = 1.0 ; 291} 292 293// Determine filter support radius and kernel size 294support = filter_support * filterscale ; // Widen filter when downsampling 295ksize = static_cast < int > (std:: ceil ( support )) * 2 + 1 ; // Total pixels in kernel 296 297std::vector < double > pre_weights (outSize * ksize ); // Temporary weights 298bounds . resize ( outSize * 2 ); 299 300 301// For each output pixel, compute its filter coefficients 302for ( xx = 0 ; xx < outSize ; xx ++ ) { 303// Calculate the center position in input space (pixel-center convention: +0.5) 304center = ( xx + 0.5 ) * scale ; 305ww = 0.0 ; // Sum of weights for normalization 306ss = 1.0 / filterscale ; // Scale factor for filter function 307 308// Determine the range of input pixels that contribute to this output pixel 309xmin = static_cast < int > ( center - support + 0.5 ); 310if ( xmin < 0 ) { 311xmin = 0 ; 312} 313 314xmax = static_cast < int > ( center + support + 0.5 ); 315if ( xmax > inSize ) { 316xmax = inSize ; 317} 318 319xmax -= xmin ; 320 321// Compute filter weights for each contributing input pixel 322for ( x = 0 ; x < xmax ; x ++ ) { 323// Distance from input pixel center to output pixel center in input space 324double w = resample_filter (( x + xmin - center + 0.5 ) * ss ); 325pre_weights [ xx * ksize + x ] = w ; 326ww += w ; // Accumulate for normalization 327} 328 329// Normalize weights to sum to 1.0 (preserves brightness) 330for ( x = 0 ; x < xmax ; x ++ ) { 331if ( ww != 0.0 ) { 332pre_weights [ xx * ksize + x ] /= ww ; 333} 334} 335 336// Zero-pad remaining kernel positions 337for (; x < ksize ; x ++ ) { 338pre_weights [ xx * ksize + x ] = 0 ; 339} 340 341// Store input pixel range for this output pixel 342bounds [ xx * 2 + 0 ] = xmin ; 343bounds [ xx * 2 + 1 ] = xmax ; 344} 345 346// Convert floating-point coefficients to fixed-point integers 347// Formula: int32 = round(float * 2^PRECISION_BITS) 348weights . resize ( outSize * ksize ); 349 350const double fxp_scale = std:: ldexp ( 1.0 , PRECISION_BITS ); // 1.0 * 2^PRECISION_BITS 351 352for (int i = 0 ; i < outSize * ksize ; i ++ ) { 353// Pillow adds +/- 0.5 then truncates toward zero; std::round would round twice 354const double rounded = pre_weights [ i ] * fxp_scale + ( pre_weights [ i ] < 0 ? -0.5 : 0.5 ); 355weights [ i ] = static_cast < int32_t > ( rounded ); 356} 357 358return ksize ; 359}; 360 361// Horizontal resampling pass 362// Resizes width from src to out_nx, preserving height 363auto resample_horizontal = [ & ]( const uint8_t * src , int in_nx , int in_ny , 364int out_nx , 365int ksize , const std::vector < int > & bounds , const std::vector < int32_t > & weights ) { 366std::vector < uint8_t > out ((size_t) out_nx * in_ny * 3 ); 367 368// Process each row independently 369for (int yy = 0 ; yy < in_ny ; yy ++ ) { 370const uint8_t * src_row = src + (size_t) yy * in_nx * 3 ; 371uint8_t * dst_row = out . data () + (size_t) yy * out_nx * 3 ; 372 373// For each output pixel in this row 374for (int xx = 0 ; xx < out_nx ; xx ++ ) { 375const int xmin = bounds [ xx * 2 + 0 ]; // First input pixel index 376const int xcnt = bounds [ xx * 2 + 1 ]; // Number of input pixels 377const int32_t * k = & weights [ xx * ksize ]; 378const uint8_t * p = src_row + (size_t) xmin * 3 ; 379 380// Accumulators for RGB channels, with rounding bias (0.5 in fixed-point) 381int32_t ss0 = 1 << ( PRECISION_BITS - 1 ); 382int32_t ss1 = 1 << ( PRECISION_BITS - 1 ); 383int32_t ss2 = 1 << ( PRECISION_BITS - 1 ); 384 385// Convolve: sum weighted input pixels 386for (int x = 0 ; x < xcnt ; x ++ ) { 387ss0 += p [ 0 ] * k [ x ]; 388ss1 += p [ 1 ] * k [ x ]; 389ss2 += p [ 2 ] * k [ x ]; 390p += 3 ; 391} 392 393// Convert back from fixed-point (divide by 2^PRECISION_BITS) and clamp to [0,255] 394dst_row [ xx * 3 + 0 ] = clip8 ( ss0 >> PRECISION_BITS ); 395dst_row [ xx * 3 + 1 ] = clip8 ( ss1 >> PRECISION_BITS ); 396dst_row [ xx * 3 + 2 ] = clip8 ( ss2 >> PRECISION_BITS ); 397} 398} 399 400return out ; 401}; 402 403// Vertical resampling pass 404// Resizes height from src to out_ny, preserving width 405// Accumulates whole rows at once (contiguous access, auto-vectorizes well) 406auto resample_vertical = [ & ]( const uint8_t * src , int in_nx , 407int out_ny , 408int ksize , const std::vector < int > & bounds , const std::vector < int32_t > & weight ) { 409const size_t row_elems = (size_t) in_nx * 3 ; 410std::vector < uint8_t > out (row_elems * out_ny ); 411std::vector < int32_t > acc (row_elems); 412 413// For each output row 414for (int yy = 0 ; yy < out_ny ; yy ++ ) { 415const int ymin = bounds [ yy * 2 + 0 ]; // First input row index 416const int ycnt = bounds [ yy * 2 + 1 ]; // Number of input rows 417const int32_t * k = & weight [ yy * ksize ]; 418 419// Rounding bias (0.5 in fixed-point) 420std:: fill ( acc . begin (), acc . end (), 1 << ( PRECISION_BITS - 1 )); 421 422// Convolve: accumulate each weighted input row 423for (int y = 0 ; y < ycnt ; y ++ ) { 424const uint8_t * src_row = src + (size_t) ( ymin + y ) * row_elems ; 425const int32_t w = k [ y ]; 426for (size_t i = 0 ; i < row_elems ; i ++ ) { 427acc [ i ] += src_row [ i ] * w ; 428} 429} 430 431// Convert back from fixed-point and clamp to [0,255] 432uint8_t * dst_row = out . data () + (size_t) yy * row_elems ; 433for (size_t i = 0 ; i < row_elems ; i ++ ) { 434dst_row [ i ] = clip8 ( acc [ i ] >> PRECISION_BITS ); 435} 436} 437 438return out ; 439}; 440 441// Main resampling logic using separable two-pass approach 442const int src_width = img . get_size (). width ; 443const int src_height = img . get_size (). height ; 444 445bool need_horizontal = ( target_width != src_width ); 446bool need_vertical = ( target_height != src_height ); 447 448// Precompute filter coefficients for both dimensions 449std::vector < int > bounds_horiz , bounds_vert ; 450std::vector < int32_t > weights_horiz , weights_vert ; 451int ksize_horiz = 0 , ksize_vert = 0 ; 452 453if ( need_horizontal ) { 454ksize_horiz = precompute_weights ( src_width , target_width , bounds_horiz , weights_horiz ); 455} 456 457if ( need_vertical ) { 458ksize_vert = precompute_weights ( src_height , target_height , bounds_vert , weights_vert ); 459} 460 461// Perform two-pass resampling 462const uint8_t * src = img . get_ro_buf (). data (); 463if ( need_horizontal && need_vertical ) { 464auto temp = resample_horizontal ( src , src_width , src_height , target_width , ksize_horiz , bounds_horiz , weights_horiz ); 465dst . set_size ({ target_width , target_height }, false); 466dst . cpy_buf ( resample_vertical ( temp . data (), target_width , target_height , ksize_vert , bounds_vert , weights_vert )); 467} else if ( need_horizontal ) { 468dst . set_size ({ target_width , src_height }, false); 469dst . cpy_buf ( resample_horizontal ( src , src_width , src_height , target_width , ksize_horiz , bounds_horiz , weights_horiz )); 470} else if ( need_vertical ) { 471dst . set_size ({ src_width , target_height }, false); 472dst . cpy_buf ( resample_vertical ( src , src_width , target_height , ksize_vert , bounds_vert , weights_vert )); 473} else { 474// No resizing needed - direct copy 475dst . set_size ( img . get_size (), false); 476dst . cpy_buf ( img . get_ro_buf ()); 477} 478 479return true; 480} 481}; 482 483 484// 485// mtmd_image_preprocessor_llava_uhd 486// 487 488mtmd_image_preproc_out mtmd_image_preprocessor_llava_uhd:: preprocess ( const clip_image_u8 & img ) const { 489const clip_image_size original_size = img . get_size (); 490auto const inst = get_slice_instructions ( original_size ); 491auto sliced = slice_image ( img , inst ); 492 493mtmd_image_preproc_out output ; 494output . append_overview ( hparams , sliced . overview , true); 495output . append ( hparams , sliced . slices , true); 496output . grid_x = inst . grid_size . width ; 497output . grid_y = inst . grid_size . height ; 498 499return output ; 500} 501 502mtmd_image_preprocessor_llava_uhd::slice_instructions mtmd_image_preprocessor_llava_uhd:: get_slice_instructions ( const clip_image_size & original_size ) const { 503mtmd_image_preprocessor_llava_uhd::slice_instructions res ; 504// align slices by patch_size * n_merge so an integer number of merger output tokens fits per slice 505const int n_merge = hparams . n_merge ; 506const int patch_size = hparams . patch_size * n_merge ; 507const int slice_size = hparams . image_size ; 508const int original_width = original_size . width ; 509const int original_height = original_size . height ; 510 511const bool has_slices = original_size . width > slice_size || original_size . height > slice_size ; 512const bool has_pinpoints = ! hparams . image_res_candidates . empty (); 513 514if (! has_slices ) { 515// skip slicing logic 516res . overview_size = clip_image_size{ slice_size , slice_size }; 517res . refined_size = clip_image_size{ 0 , 0 }; 518res . grid_size = clip_image_size{ 0 , 0 }; 519 520return res ; 521} 522 523if ( has_pinpoints ) { 524// has pinpoints, use them to calculate the grid size (e.g. llava-1.6) 525auto refine_size = select_best_resolution ( 526original_size , 527hparams . image_res_candidates ); 528res . overview_size = clip_image_size{ slice_size , slice_size }; 529res . refined_size = refine_size ; 530res . grid_size = clip_image_size{ 0 , 0 }; 531 532LOG_DBG ( "%s: using pinpoints for slicing\n" , __func__ ); 533LOG_DBG ( "%s: original size: %d x %d, overview size: %d x %d, refined size: %d x %d\n" , 534__func__ , original_width , original_height , 535res . overview_size . width , res . overview_size . height , 536res . refined_size . width , res . refined_size . height ); 537 538for (int y = 0 ; y < refine_size . height ; y += slice_size ) { 539for (int x = 0 ; x < refine_size . width ; x += slice_size ) { 540slice_coordinates slice ; 541slice . x = x ; 542slice . y = y ; 543slice . size . width = std:: min ( slice_size , refine_size . width - x ); 544slice . size . height = std:: min ( slice_size , refine_size . height - y ); 545res . slices . push_back ( slice ); 546LOG_DBG ( "%s: slice %d: x=%d, y=%d, size=%dx%d\n" , 547__func__ , (int) res . slices . size () - 1 , 548slice . x , slice . y , slice . size . width , slice . size . height ); 549} 550} 551 552res . grid_size . height = refine_size . height / slice_size ; 553res . grid_size . width = refine_size . width / slice_size ; 554LOG_DBG ( "%s: grid size: %d x %d\n" , __func__ , res . grid_size . width , res . grid_size . height ); 555 556return res ; 557} 558 559// no pinpoints, dynamically calculate the grid size (e.g. minicpmv) 560 561auto best_size = get_best_resize ( original_size , slice_size , patch_size , ! has_slices ); 562res . overview_size = best_size ; 563 564{ 565const int max_slice_nums = 9 ; // TODO: this is only used by minicpmv, maybe remove it 566const float log_ratio = log ((float) original_width / original_height ); 567const float ratio = (float) original_width * original_height / ( slice_size * slice_size ); 568const int multiple = fmin ( ceil ( ratio ), max_slice_nums ); 569 570auto best_grid = get_best_grid ( max_slice_nums , multiple , log_ratio ); 571auto refine_size = get_refine_size ( original_size , best_grid , slice_size , patch_size , true); 572res . grid_size = best_grid ; 573res . refined_size = refine_size ; 574 575LOG_DBG ( "%s: original size: %d x %d, overview size: %d x %d, refined size: %d x %d, grid size: %d x %d\n" , 576__func__ , original_width , original_height , 577res . overview_size . width , res . overview_size . height , 578res . refined_size . width , res . refined_size . height , 579res . grid_size . width , res . grid_size . height ); 580 581int width = refine_size . width ; 582int height = refine_size . height ; 583int grid_x = int( width / best_grid . width ); 584int grid_y = int( height / best_grid . height ); 585for (int patches_y = 0 , ic = 0 ; 586patches_y < refine_size . height && ic < best_grid . height ; 587patches_y += grid_y , ic += 1 ) { 588for (int patches_x = 0 , jc = 0 ; 589patches_x < refine_size . width && jc < best_grid . width ; 590patches_x += grid_x , jc += 1 ) { 591slice_coordinates slice ; 592slice . x = patches_x ; 593slice . y = patches_y ; 594slice . size . width = grid_x ; 595slice . size . height = grid_y ; 596res . slices . push_back ( slice ); 597LOG_DBG ( "%s: slice %d: x=%d, y=%d, size=%dx%d\n" , 598__func__ , (int) res . slices . size () - 1 , 599slice . x , slice . y , slice . size . width , slice . size . height ); 600} 601} 602} 603 604return res ; 605} 606 607mtmd_image_preprocessor_llava_uhd::slice_output mtmd_image_preprocessor_llava_uhd:: slice_image ( const clip_image_u8 & img , const mtmd_image_preprocessor_llava_uhd::slice_instructions & inst ) const { 608slice_output output ; 609 610// resize to overview size 611img_tool:: resize ( img , output . overview , inst . overview_size , hparams . image_resize_algo_ov , 612hparams . image_pad_ov , hparams . image_pad_color_ov ); 613 614if ( inst . slices . empty ()) { 615// no slices, just return the overview image 616return output ; 617} 618 619// resize to refined size 620clip_image_u8 refined_img ; 621img_tool:: resize ( img , refined_img , inst . refined_size , hparams . image_resize_algo_rf , 622hparams . image_pad_rf , hparams . image_pad_color_rf ); 623 624// create slices 625for ( const auto & slice : inst . slices ) { 626int x = slice . x ; 627int y = slice . y ; 628int w = slice . size . width ; 629int h = slice . size . height ; 630 631clip_image_u8 img_slice ; 632img_tool:: crop ( refined_img , img_slice , x , y , w , h ); 633output . slices . push_back (std:: move ( img_slice )); 634} 635 636return output ; 637} 638 639clip_image_size mtmd_image_preprocessor_llava_uhd:: get_best_resize ( const clip_image_size & original_size , int scale_resolution , int patch_size , bool allow_upscale ) const { 640int width = original_size . width ; 641int height = original_size . height ; 642if (( width * height > scale_resolution * scale_resolution ) || allow_upscale ) { 643float r = static_cast < float > ( width ) / height ; 644height = static_cast < int > ( scale_resolution / std:: sqrt ( r )); 645width = static_cast < int > ( height * r ); 646} 647clip_image_size res ; 648res . width = ensure_divide ( width , patch_size ); 649res . height = ensure_divide ( height , patch_size ); 650return res ; 651} 652 653clip_image_size mtmd_image_preprocessor_llava_uhd:: resize_maintain_aspect_ratio ( const clip_image_size & orig , const clip_image_size & target_max ) const { 654float scale_width = static_cast < float > ( target_max . width ) / orig . width ; 655float scale_height = static_cast < float > ( target_max . height ) / orig . height ; 656float scale = std:: min ( scale_width , scale_height ); 657return clip_image_size{ 658static_cast < int > ( orig . width * scale ), 659static_cast < int > ( orig . height * scale ), 660}; 661} 662 663clip_image_size mtmd_image_preprocessor_llava_uhd:: select_best_resolution ( const clip_image_size & original_size , const std::vector < clip_image_size > & possible_resolutions ) const { 664clip_image_size best_fit ; 665int min_wasted_area = std::numeric_limits < int > :: max (); 666int max_effective_resolution = 0 ; 667 668for ( const clip_image_size & candidate : possible_resolutions ) { 669auto target_size = resize_maintain_aspect_ratio ( original_size , candidate ); 670int effective_resolution = std:: min ( 671target_size . width * target_size . height , 672original_size . width * original_size . height ); 673int wasted_area = ( candidate . width * candidate . height ) - effective_resolution ; 674 675if ( effective_resolution > max_effective_resolution || ( effective_resolution == max_effective_resolution && wasted_area < min_wasted_area )) { 676max_effective_resolution = effective_resolution ; 677min_wasted_area = wasted_area ; 678best_fit = candidate ; 679} 680 681LOG_DBG ( "%s: candidate: %d x %d, target: %d x %d, wasted: %d, effective: %d\n" , __func__ , candidate . width , candidate . height , target_size . width , target_size . height , wasted_area , effective_resolution ); 682} 683 684return best_fit ; 685} 686 687int mtmd_image_preprocessor_llava_uhd:: ensure_divide (int length , int patch_size ) const { 688return std:: max ( static_cast < int > (std:: round ( static_cast < float > ( length ) / patch_size ) * patch_size ), patch_size ); 689} 690 691clip_image_size mtmd_image_preprocessor_llava_uhd:: get_refine_size ( const clip_image_size & original_size , const clip_image_size & grid , int scale_resolution , int patch_size , bool allow_upscale ) const { 692int width = original_size . width ; 693int height = original_size . height ; 694int grid_x = grid . width ; 695int grid_y = grid . height ; 696 697int refine_width = ensure_divide ( width , grid_x ); 698int refine_height = ensure_divide ( height , grid_y ); 699 700clip_image_size grid_size ; 701grid_size . width = refine_width / grid_x ; 702grid_size . height = refine_height / grid_y ; 703 704auto best_grid_size = get_best_resize ( grid_size , scale_resolution , patch_size , allow_upscale ); 705int best_grid_width = best_grid_size . width ; 706int best_grid_height = best_grid_size . height ; 707 708clip_image_size refine_size ; 709refine_size . width = best_grid_width * grid_x ; 710refine_size . height = best_grid_height * grid_y ; 711return refine_size ; 712} 713 714clip_image_size mtmd_image_preprocessor_llava_uhd:: get_best_grid ( const int max_slice_nums , const int multiple , const float log_ratio ) const { 715std::vector < int > candidate_split_grids_nums ; 716for (int i : { multiple - 1 , multiple , multiple + 1 }) { 717if ( i == 1 || i > max_slice_nums ) { 718continue ; 719} 720candidate_split_grids_nums . push_back ( i ); 721} 722 723std::vector < clip_image_size > candidate_grids ; 724for (int split_grids_nums : candidate_split_grids_nums ) { 725int m = 1 ; 726while ( m <= split_grids_nums ) { 727if ( split_grids_nums % m == 0 ) { 728candidate_grids . push_back (clip_image_size{ m , split_grids_nums / m }); 729} 730++ m ; 731} 732} 733 734clip_image_size best_grid { 1 , 1 }; 735float min_error = std::numeric_limits < float > :: infinity (); 736for ( const auto & grid : candidate_grids ) { 737float error = std:: abs ( log_ratio - std:: log ( 1.0 * grid . width / grid . height )); 738if ( error < min_error ) { 739best_grid = grid ; 740min_error = error ; 741} 742} 743return best_grid ; 744} 745 746// 747// mtmd_image_preprocessor_fixed_size 748// 749 750mtmd_image_preproc_out mtmd_image_preprocessor_fixed_size:: preprocess ( const clip_image_u8 & img ) const { 751clip_image_u8 resized_image ; 752int sz = hparams . image_size ; 753img_tool:: resize ( img , resized_image , { sz , sz }, 754hparams . image_resize_algo , 755hparams . image_resize_pad , 756hparams . image_pad_color ); 757mtmd_image_preproc_out output ; 758output . append ( hparams , resized_image , true); 759return output ; 760} 761 762// 763// mtmd_image_preprocessor_dyn_size 764// 765 766mtmd_image_preproc_out mtmd_image_preprocessor_dyn_size:: preprocess ( const clip_image_u8 & img ) const { 767GGML_ASSERT ( hparams . image_min_pixels > 0 && hparams . image_max_pixels > 0 ); 768clip_image_u8 resized_image ; 769const clip_image_size original_size = img . get_size (); 770// the original pixtral model doesn't have n_merge 771const int cur_merge = hparams . n_merge ; 772const clip_image_size target_size = img_tool:: calc_size_preserved_ratio ( 773original_size , 774{ 775/* align_size */ hparams . patch_size * cur_merge , 776/* min_pixels */ hparams . image_min_pixels , 777/* max_pixels */ hparams . image_max_pixels , 778/* longest_edge */ 0 , 779}); 780img_tool:: resize ( img , resized_image , target_size , 781hparams . image_resize_algo , 782hparams . image_resize_pad , 783hparams . image_pad_color ); 784mtmd_image_preproc_out output ; 785output . append ( hparams , resized_image , true); 786return output ; 787} 788 789// 790// mtmd_image_preprocessor_longest_edge 791// 792 793mtmd_image_preproc_out mtmd_image_preprocessor_longest_edge:: preprocess ( const clip_image_u8 & img ) const { 794GGML_ASSERT ( hparams . image_longest_edge > 0 ); 795clip_image_u8 resized_image ; 796const clip_image_size original_size = img . get_size (); 797// the original pixtral model doesn't have n_merge 798const int cur_merge = hparams . n_merge == 0 ? 1 : hparams . n_merge ; 799const clip_image_size target_size = img_tool:: calc_size_preserved_ratio ( 800original_size , 801{ 802/* align_size */ hparams . patch_size * cur_merge , 803/* min_pixels */ std:: max ( 0 , hparams . image_min_pixels ), 804/* max_pixels */ std:: max ( 0 , hparams . image_max_pixels ), 805/* longest_edge */ hparams . image_longest_edge , 806}); 807img_tool:: resize ( img , resized_image , target_size , 808hparams . image_resize_algo , 809hparams . image_resize_pad , 810hparams . image_pad_color ); 811mtmd_image_preproc_out output ; 812output . append ( hparams , resized_image , true); 813return output ; 814} 815 816// 817// mtmd_image_preprocessor_minicpmv 818// 819 820mtmd_image_preprocessor_llava_uhd::slice_instructions mtmd_image_preprocessor_minicpmv:: get_slice_instructions ( const clip_image_size & original_size ) const { 821if ( hparams . n_merge == 2 ) { 822const int slice_size = hparams . image_size ; 823const float ratio = (float) original_size . width * original_size . height / ( slice_size * slice_size ); 824if ( ratio <= 1.0f ) { 825mtmd_image_preprocessor_llava_uhd::slice_instructions inst ; 826const int patch_size = hparams . patch_size * hparams . n_merge ; 827inst . overview_size = get_best_resize ( original_size , slice_size , patch_size , true); 828inst . refined_size = clip_image_size{ 0 , 0 }; 829inst . grid_size = clip_image_size{ 0 , 0 }; 830return inst ; 831} 832} 833return mtmd_image_preprocessor_llava_uhd:: get_slice_instructions ( original_size ); 834} 835 836// 837// mtmd_image_preprocessor_lfm2 838// 839 840mtmd_image_preproc_out mtmd_image_preprocessor_lfm2:: preprocess ( const clip_image_u8 & img ) const { 841auto const inst = get_slice_instructions ( img . get_size ()); 842if (! inst . slices . empty ()) { 843return mtmd_image_preprocessor_llava_uhd:: preprocess ( img ); 844} 845 846// single tile: no thumbnail 847// note: not using output.overview here because it will emit <|img_thumbnail|> token, which we don't want in this case 848auto sliced = slice_image ( img , inst ); 849mtmd_image_preproc_out output ; 850output . append ( hparams , sliced . overview , true); 851return output ; 852} 853 854bool mtmd_image_preprocessor_lfm2:: should_tile ( 855const clip_hparams & hparams , 856const clip_image_size & original_size ) { 857const int align_size = hparams . patch_size * hparams . n_merge ; 858 859const auto round_by_factor = [ align_size ](float x ) { 860// see https://github.com/ggml-org/llama.cpp/pull/27057#discussion_r3796264887 861return static_cast < int > (std:: nearbyint ( static_cast < double > ( x ) / align_size )) * align_size ; 862}; 863 864const int h_bar = std:: max ( hparams . patch_size , round_by_factor ( original_size . height )); 865const int w_bar = std:: max ( hparams . patch_size , round_by_factor ( original_size . width )); 866 867return static_cast < double > ( h_bar ) * static_cast < double > ( w_bar ) > 868static_cast < double > ( hparams . image_max_pixels ) * max_pixels_tolerance ; 869} 870 871mtmd_image_preprocessor_llava_uhd::slice_instructions mtmd_image_preprocessor_lfm2:: get_slice_instructions ( const clip_image_size & original_size ) const { 872mtmd_image_preprocessor_llava_uhd::slice_instructions inst ; 873const int align_size = hparams . patch_size * hparams . n_merge ; 874inst . overview_size = img_tool:: calc_size_preserved_ratio ( 875original_size , 876{ align_size , hparams . image_min_pixels , hparams . image_max_pixels , 0 }); 877 878const bool needs_tiling = should_tile ( hparams , original_size ); 879 880if (! needs_tiling ) { 881inst . refined_size = clip_image_size{ 0 , 0 }; 882inst . grid_size = clip_image_size{ 0 , 0 }; 883return inst ; 884} 885 886const clip_image_size grid = get_grid_layout ( original_size . height , original_size . width ); 887 888inst . grid_size = grid ; 889inst . refined_size = clip_image_size{ tile_size * grid . width , tile_size * grid . height }; 890 891LOG_DBG ( "%s: original size: %d x %d, overview size: %d x %d, refined size: %d x %d, grid size: %d x %d\n" , 892__func__ , 893original_size . width , original_size . height , 894inst . overview_size . width , inst . overview_size . height , 895inst . refined_size . width , inst . refined_size . height , 896grid . width , grid . height ); 897 898for (int row = 0 ; row < grid . height ; row ++ ) { 899for (int col = 0 ; col < grid . width ; col ++ ) { 900mtmd_image_preprocessor_llava_uhd::slice_coordinates slice ; 901slice . x = col * tile_size ; 902slice . y = row * tile_size ; 903slice . size = clip_image_size{ tile_size , tile_size }; 904inst . slices . push_back ( slice ); 905LOG_DBG ( "%s: slice %d: x=%d, y=%d, size=%d x %d\n" , 906__func__ , (int) inst . slices . size () - 1 , 907slice . x , slice . y , slice . size . width , slice . size . height ); 908} 909} 910 911return inst ; 912} 913 914clip_image_size mtmd_image_preprocessor_lfm2:: find_closest_aspect_ratio ( 915float aspect_ratio , 916const std::vector < clip_image_size > & target_ratios , 917int width , int height ) const { 918float best_ratio_diff = std::numeric_limits < float > :: max (); 919clip_image_size best_ratio = { 1 , 1 }; 920const float area = static_cast < float > ( width * height ); 921 922for ( const auto & ratio : target_ratios ) { 923const float target_aspect_ratio = static_cast < float > ( ratio . width ) / ratio . height ; 924const float ratio_diff = std:: abs ( aspect_ratio - target_aspect_ratio ); 925if ( ratio_diff < best_ratio_diff ) { 926best_ratio_diff = ratio_diff ; 927best_ratio = ratio ; 928} else if ( ratio_diff == best_ratio_diff ) { 929const float target_area = static_cast < float > ( tile_size * tile_size * ratio . width * ratio . height ); 930if ( area > 0.5f * target_area ) { 931best_ratio = ratio ; 932} 933} 934} 935return best_ratio ; 936} 937 938std::vector < clip_image_size > mtmd_image_preprocessor_lfm2:: get_target_ratios () const { 939std::vector < clip_image_size > ratios ; 940for (int n = min_tiles ; n <= max_tiles ; n ++ ) { 941for (int w = 1 ; w <= n ; w ++ ) { 942for (int h = 1 ; h <= n ; h ++ ) { 943if ( w * h >= min_tiles && w * h <= max_tiles ) { 944bool found = false; 945for ( const auto & r : ratios ) { 946if ( r . width == w && r . height == h ) { 947found = true; 948break ; 949} 950} 951if (! found ) { 952ratios . push_back ({ w , h }); 953} 954} 955} 956} 957} 958std:: sort ( ratios . begin (), ratios . end (), []( const clip_image_size & a , const clip_image_size & b ) { 959return a . width * a . height < b . width * b . height ; 960}); 961return ratios ; 962} 963 964clip_image_size mtmd_image_preprocessor_lfm2:: get_grid_layout (int height , int width ) const { 965const float aspect_ratio = static_cast < float > ( width ) / height ; 966const auto ratios = get_target_ratios (); 967return find_closest_aspect_ratio ( aspect_ratio , ratios , width , height ); 968} 969 970// 971// mtmd_image_preprocessor_idefics3 972// 973 974mtmd_image_preproc_out mtmd_image_preprocessor_idefics3:: preprocess ( const clip_image_u8 & img ) const { 975// The refined size has two steps: 976// 1. Resize w/ aspect-ratio preserving such that the longer side is 977// the preprocessor longest size 978// 2. Resize w/out preserving aspect ratio such that both sides are 979// multiples of image_size (always rounding up) 980// 981// CITE: https://github.com/huggingface/transformers/blob/main/src/transformers/models/idefics3/image_processing_idefics3.py#L737 982const clip_image_size original_size = img . get_size (); 983 984// old gguf files have no preprocessor longest size, custom token limits also need the generic size below 985if ( hparams . image_longest_edge > 0 && hparams . image_min_pixels <= 0 && hparams . image_max_pixels <= 0 ) { 986const int tile_size = hparams . image_size ; 987const int longest_edge = hparams . image_longest_edge ; 988const double aspect_ratio = (double) original_size . width / original_size . height ; 989 990clip_image_size resized_size ; 991if ( original_size . width >= original_size . height ) { 992resized_size . width = longest_edge ; 993resized_size . height = (int) ( longest_edge / aspect_ratio ); 994resized_size . height += resized_size . height % 2 ; 995} else { 996resized_size . height = longest_edge ; 997resized_size . width = (int) ( longest_edge * aspect_ratio ); 998resized_size . width += resized_size . width % 2 ; 999} 1000 1001const int grid_x = ( resized_size . width + tile_size - 1 ) / tile_size ; 1002const int grid_y = ( resized_size . height + tile_size - 1 ) / tile_size ; 1003const clip_image_size refined_size = clip_image_size{ grid_x * tile_size , grid_y * tile_size }; 1004 1005clip_image_u8 resized_img ; 1006img_tool:: resize ( img , resized_img , resized_size , hparams . image_resize_algo , PAD_NONE ); 1007 1008clip_image_u8 refined_img ; 1009img_tool:: resize ( resized_img , refined_img , refined_size , hparams . image_resize_algo , PAD_NONE ); 1010 1011clip_image_u8 overview ; 1012img_tool:: resize ( refined_img , overview , { tile_size , tile_size }, hparams . image_resize_algo , PAD_NONE ); 1013 1014std::vector < clip_image_u8 > slices ; 1015for (int y = 0 ; y < grid_y ; y ++ ) { 1016for (int x = 0 ; x < grid_x ; x ++ ) { 1017clip_image_u8 slice ; 1018img_tool:: crop ( refined_img , slice , x * tile_size , y * tile_size , tile_size , tile_size ); 1019slices . push_back (std:: move ( slice )); 1020} 1021} 1022 1023LOG_DBG ( "%s: grid size: %d x %d (%d tiles) + overview\n" , __func__ , grid_x , grid_y , grid_x * grid_y ); 1024 1025mtmd_image_preproc_out output ; 1026output . append_overview ( hparams , overview , true); 1027output . append ( hparams , slices , true); 1028output . grid_x = grid_x ; 1029output . grid_y = grid_y ; 1030return output ; 1031} 1032 1033const clip_image_size refined_size = img_tool:: calc_size_preserved_ratio ( 1034original_size , 1035{ hparams . image_size , std:: max ( 0 , hparams . image_min_pixels ), std:: max ( 0 , hparams . image_max_pixels ), hparams . image_longest_edge }); 1036// LOG_INF("%s: original size: %d x %d, refined size: %d x %d\n", 1037// __func__, original_size.width, original_size.height, 1038// refined_size.width, refined_size.height); 1039 1040mtmd_image_preprocessor_llava_uhd::slice_instructions instructions ; 1041instructions . overview_size = clip_image_size{ hparams . image_size , hparams . image_size }; 1042instructions . refined_size = refined_size ; 1043instructions . grid_size = clip_image_size{ 1044static_cast < int > (std:: ceil ( static_cast < float > ( refined_size . width ) / hparams . image_size )), 1045static_cast < int > (std:: ceil ( static_cast < float > ( refined_size . height ) / hparams . image_size )), 1046}; 1047for (int y = 0 ; y < refined_size . height ; y += hparams . image_size ) { 1048for (int x = 0 ; x < refined_size . width ; x += hparams . image_size ) { 1049// LOG_INF("%s: adding slice at x=%d, y=%d\n", __func__, x, y); 1050instructions . slices . push_back (mtmd_image_preprocessor_llava_uhd::slice_coordinates{ 1051/* x */ x , 1052/* y */ y , 1053/* size */ clip_image_size{ 1054std:: min ( hparams . image_size , refined_size . width - x ), 1055std:: min ( hparams . image_size , refined_size . height - y ) 1056} 1057}); 1058} 1059} 1060auto sliced = slice_image ( img , instructions ); 1061 1062mtmd_image_preproc_out output ; 1063output . append_overview ( hparams , sliced . overview , true); 1064output . append ( hparams , sliced . slices , true); 1065output . grid_x = instructions . grid_size . width ; 1066output . grid_y = instructions . grid_size . height ; 1067return output ; 1068} 1069 1070// 1071// mtmd_image_preprocessor_internvl 1072// 1073 1074mtmd_image_preproc_out mtmd_image_preprocessor_internvl:: preprocess ( const clip_image_u8 & img ) const { 1075GGML_ASSERT (! hparams . image_res_candidates . empty ()); 1076const clip_image_size original_size = img . get_size (); 1077auto const inst = get_slice_instructions ( original_size ); 1078auto sliced = slice_image ( img , inst ); 1079 1080mtmd_image_preproc_out output ; 1081// InternVL: slices first, then overview 1082output . append ( hparams , sliced . slices , true); 1083output . append_overview ( hparams , sliced . overview , true); 1084output . grid_x = inst . grid_size . width ; 1085output . grid_y = inst . grid_size . height ; 1086return output ; 1087} 1088 1089// 1090// mtmd_image_preprocessor_deepseekocr 1091// 1092 1093std::vector < clip_image_size > mtmd_image_preprocessor_deepseekocr:: get_target_ratios () const { 1094std::vector < clip_image_size > ratios ; 1095for (int n = min_tiles ; n <= max_tiles ; n ++ ) { 1096for (int w = 1 ; w <= n ; w ++ ) { 1097for (int h = 1 ; h <= n ; h ++ ) { 1098if ( w * h < min_tiles || w * h > max_tiles ) { 1099continue ; 1100} 1101bool found = false; 1102for ( const auto & r : ratios ) { 1103if ( r . width == w && r . height == h ) { 1104found = true; 1105break ; 1106} 1107} 1108if (! found ) { 1109ratios . push_back ({ w , h }); 1110} 1111} 1112} 1113} 1114std:: sort ( ratios . begin (), ratios . end (), []( const clip_image_size & a , const clip_image_size & b ) { 1115return a . width * a . height < b . width * b . height ; 1116}); 1117return ratios ; 1118} 1119 1120clip_image_size mtmd_image_preprocessor_deepseekocr:: find_closest_aspect_ratio ( 1121float aspect_ratio , 1122const std::vector < clip_image_size > & target_ratios , 1123int width , 1124int height ) const { 1125float best_ratio_diff = std::numeric_limits < float > :: max (); 1126clip_image_size best_ratio = { 1 , 1 }; 1127const float area = static_cast < float > ( width * height ); 1128 1129for ( const auto & ratio : target_ratios ) { 1130const float target_aspect_ratio = static_cast < float > ( ratio . width ) / ratio . height ; 1131const float ratio_diff = std:: abs ( aspect_ratio - target_aspect_ratio ); 1132if ( ratio_diff < best_ratio_diff ) { 1133best_ratio_diff = ratio_diff ; 1134best_ratio = ratio ; 1135} else if ( ratio_diff == best_ratio_diff ) { 1136const float target_area = static_cast < float > ( tile_size * tile_size * ratio . width * ratio . height ); 1137if ( area > 0.5f * target_area ) { 1138best_ratio = ratio ; 1139} 1140} 1141} 1142return best_ratio ; 1143} 1144 1145// 1146// DeepSeek-V4-Flash-Vision (deepseek4v) 1147// 1148// port of load_image / safe_resize / solve_resize_ratio / grid_tokens from inference/image_processor.py 1149// the resize solver picks the largest target size (multiple of patch_size) whose LLM token block fits max_n_token 1150// 1151 1152// ref: grid_tokens() 1153mtmd_image_preprocessor_deepseek4v::grid_info mtmd_image_preprocessor_deepseek4v:: grid_tokens (int best_height , int best_width , int patch_size , int r ) { 1154grid_info g ; 1155g . n_llm_h = (( best_height / patch_size ) + r - 1 ) / r ; 1156g . n_llm_w = (( best_width / patch_size ) + r - 1 ) / r ; 1157g . n_tokens = dsv4_get_block_layout ( g . n_llm_w , g . n_llm_h , 0 ). n_out ; 1158return g ; 1159} 1160 1161// ref: solve_resize_ratio() 1162void mtmd_image_preprocessor_deepseek4v:: solve_resize_ratio (int height , int width , int p , int r , int max_n_token , 1163int & best_height , int & best_width ) { 1164const double ratio = (double) height / width ; 1165const double max_w_f = std:: sqrt (( max_n_token - 2 ) / ratio + 0.25 ) - 0.5 ; 1166const double max_h_f = max_w_f * ratio ; 1167if ( max_w_f < 1.0 ) { 1168const int max_w = 1 ; 1169int max_h = ( max_n_token - 2 ) / ( max_w + 1 ); 1170if ( max_h % 2 == 1 ) { 1171max_h -= 1 ; 1172} 1173best_width = max_w * p * r ; 1174best_height = max_h * p * r ; 1175} else if ( max_h_f < 2.0 ) { 1176const int max_h = 2 ; 1177// guard tiny budgets; cannot be hit with the current lower bound on max_n_token 1178const int max_w = std:: max ((( max_n_token - 2 ) / max_h ) - 1 , 2 ); 1179best_width = max_w * p * r ; 1180best_height = max_h * p * r ; 1181} else { 1182const int max_w_i = (int) std:: floor ( max_w_f ); 1183int max_h_i = (int) std:: floor ( max_h_f ); 1184if ( max_h_i % 2 == 1 ) { 1185max_h_i -= 1 ; 1186} 1187const double beta = std:: min ( 1188(double) max_w_i * p * r / width , 1189(double) max_h_i * p * r / height ); 1190best_width = (int) std:: floor ( width * beta / p ) * p ; 1191best_height = (int) std:: floor ( height * beta / p ) * p ; 1192} 1193} 1194 1195// ref: safe_resize() 1196void mtmd_image_preprocessor_deepseek4v:: safe_resize (int height , int width , int & best_height , int & best_width , 1197int p , int r , int max_n_token ) { 1198max_n_token -= 4 - 1 ; // reserve room for the position-dependent lead pads (COMPRESS_PAD_TO - 1) 1199grid_info g = grid_tokens ( best_height , best_width , p , r ); 1200int budget = max_n_token ; 1201while ( g . n_tokens > max_n_token ) { 1202solve_resize_ratio ( height , width , p , r , budget , best_height , best_width ); 1203g = grid_tokens ( best_height , best_width , p , r ); 1204budget -= 1 ; 1205} 1206} 1207 1208// ref: load_image() 1209mtmd_image_preproc_out mtmd_image_preprocessor_deepseek4v:: preprocess ( const clip_image_u8 & img ) const { 1210mtmd_image_preproc_out out ; 1211 1212const int p = hparams . patch_size ; 1213const int r = hparams . n_merge ; 1214const int max_n_token = hparams . dsv4_max_n_token ; 1215const int max_wh = hparams . dsv4_max_wh_ratio ; 1216 1217const clip_image_size orig = img . get_size (); 1218int width = orig . width ; 1219int height = orig . height ; 1220if ( max_wh > 0 && width > height * max_wh ) { 1221width = height * max_wh ; 1222} 1223if ( hparams . image_min_pixels > 0 && width * height > 0 1224&& width * height < hparams . image_min_pixels ) { 1225const double up = std:: sqrt ((double) hparams . image_min_pixels / ((double) width * height )); 1226width = (int) ( width * up ); 1227height = (int) ( height * up ); 1228} 1229int best_width = CLIP_ALIGN ( width , p ); 1230int best_height = CLIP_ALIGN ( height , p ); 1231safe_resize ( height , width , best_height , best_width , p , r , max_n_token ); 1232 1233clip_image_u8 resized ; 1234if ( max_wh > 0 && orig . width >= max_wh * orig . height ) { 1235// extreme aspect ratio: plain stretch resize, no padding 1236img_tool:: resize ( img , resized , { best_width , best_height }, hparams . image_resize_algo , PAD_NONE ); 1237} else { 1238// aspect-preserving resize + centered padding (PIL ImageOps.pad) 1239img_tool:: resize ( img , resized , { best_width , best_height }, hparams . image_resize_algo , 1240PAD_NEAREST , hparams . image_pad_color ); 1241} 1242 1243out . append ( hparams , resized ); 1244return out ; 1245} 1246 1247mtmd_image_preproc_out mtmd_image_preprocessor_deepseekocr:: preprocess ( const clip_image_u8 & img ) const { 1248mtmd_image_preproc_out output ; 1249int grid_w = 0 ; 1250int grid_h = 0 ; 1251const auto img_size = img . get_size (); 1252 1253// global view: aspect-preserving fit-and-pad to base_size 1254clip_image_u8 padded ; 1255img_tool:: resize ( img , padded , 1256{ base_size , base_size }, 1257RESIZE_ALGO_BICUBIC , 1258PAD_NEAREST , 1259hparams . image_pad_color ); 1260output . append_overview ( hparams , padded , true); 1261output . overview . add_viewsep = true; 1262 1263// if this condition doesn't hold, the output is overview only, no tiles 1264if ( img_size . width > tile_size || img_size . height > tile_size ) { 1265const float aspect_ratio = static_cast < float > ( img_size . width ) / img_size . height ; 1266const auto target_ratios = get_target_ratios (); 1267const clip_image_size grid = 1268find_closest_aspect_ratio ( aspect_ratio , target_ratios , img_size . width , img_size . height ); 1269grid_w = grid . width ; 1270grid_h = grid . height ; 1271 1272clip_image_u8 refined ; 1273img_tool:: resize ( img , refined , { tile_size * grid_w , tile_size * grid_h }, RESIZE_ALGO_BICUBIC , 1274PAD_NONE ); 1275 1276for (int row = 0 ; row < grid_h ; row ++ ) { 1277if ( fuse_row ) { 1278// concat all tiles in this row into a single image, along the H axis 1279// output image size: w = tile_size, h = tile_size * grid_w 1280// this is to ensure the whole row is always processed together 1281clip_image_u8 row_img ; 1282row_img . set_size ({ tile_size , tile_size * grid_w }, false); 1283for (int col = 0 ; col < grid_w ; col ++ ) { 1284for (int py = 0 ; py < tile_size ; py ++ ) { 1285for (int px = 0 ; px < tile_size ; px ++ ) { 1286row_img . set_pixel ( px , col * tile_size + py , 1287refined . get_pixel ( col * tile_size + px , row * tile_size + py )); 1288} 1289} 1290} 1291output . append ( hparams , row_img , true); 1292} else { 1293for (int col = 0 ; col < grid_w ; col ++ ) { 1294clip_image_u8 tile ; 1295img_tool:: crop ( refined , tile , col * tile_size , row * tile_size , tile_size , tile_size ); 1296output . append ( hparams , tile , true); 1297} 1298} 1299} 1300if ( fuse_row ) { 1301grid_w = 1 ; // each fused row is one image; a single output column 1302} 1303} 1304 1305LOG_DBG ( "%s: grid size: %d x %d (%d tiles) + global view\n" , __func__ , grid_w , grid_h , grid_w * grid_h ); 1306LOG_DBG ( "%s: overview size: %d x %d\n" , __func__ , padded . get_size (). width , padded . get_size (). height ); 1307 1308output . grid_x = grid_w ; 1309output . grid_y = grid_h ; 1310return output ; 1311} 1312 1313// 1314// mtmd_image_preprocessor_step3vl 1315// 1316 1317void mtmd_image_preprocessor_step3vl:: img_u8_resize_bilinear_to_f32 ( 1318const clip_image_u8 & src , 1319clip_image_f32 & dst , 1320int target_width , 1321int target_height , 1322const float mean [ 3 ], 1323const float std [ 3 ]) const { 1324const auto src_size = src . get_size (); 1325if ( src_size . width == target_width && src_size . height == target_height ) { 1326dst . from_u8 ( src ); 1327dst . normalize ( mean , std ); 1328return ; 1329} 1330 1331dst . set_size ({ target_width , target_height }, false, false); 1332 1333if ( src . is_placeholder ()) { 1334// no-op for placeholder image, just set the size and return 1335return ; 1336} 1337 1338const float scale_x = static_cast < float > ( src_size . width ) / target_width ; 1339const float scale_y = static_cast < float > ( src_size . height ) / target_height ; 1340 1341std::vector < float > local_buf ((size_t) 3 * (size_t) target_width * (size_t) target_height ); 1342 1343for (int y = 0 ; y < target_height ; ++ y ) { 1344const float src_y = ( static_cast < float > ( y ) + 0.5f ) * scale_y - 0.5f ; 1345const int y0_floor = static_cast < int > (std:: floor ( src_y )); 1346const int y0 = std:: max ( 0 , std:: min ( y0_floor , src_size . height - 1 )); 1347const int y1 = std:: max ( 0 , std:: min ( y0_floor + 1 , src_size . height - 1 )); 1348const float ly = src_y - y0_floor ; 1349 1350for (int x = 0 ; x < target_width ; ++ x ) { 1351const float src_x = ( static_cast < float > ( x ) + 0.5f ) * scale_x - 0.5f ; 1352const int x0_floor = static_cast < int > (std:: floor ( src_x )); 1353const int x0 = std:: max ( 0 , std:: min ( x0_floor , src_size . width - 1 )); 1354const int x1 = std:: max ( 0 , std:: min ( x0_floor + 1 , src_size . width - 1 )); 1355const float lx = src_x - x0_floor ; 1356 1357const auto p00 = src . get_pixel ( x0 , y0 ); 1358const auto p01 = src . get_pixel ( x1 , y0 ); 1359const auto p10 = src . get_pixel ( x0 , y1 ); 1360const auto p11 = src . get_pixel ( x1 , y1 ); 1361 1362const size_t idx_dst = (size_t) 3 * ((size_t) y * (size_t) target_width + (size_t) x ); 1363for (int c = 0 ; c < 3 ; ++ c ) { 1364const float v00 = ( static_cast < float > ( p00 [ c ]) / 255.0f - mean [ c ]) / std [ c ]; 1365const float v01 = ( static_cast < float > ( p01 [ c ]) / 255.0f - mean [ c ]) / std [ c ]; 1366const float v10 = ( static_cast < float > ( p10 [ c ]) / 255.0f - mean [ c ]) / std [ c ]; 1367const float v11 = ( static_cast < float > ( p11 [ c ]) / 255.0f - mean [ c ]) / std [ c ]; 1368 1369const float top = v00 + ( v01 - v00 ) * lx ; 1370const float bot = v10 + ( v11 - v10 ) * lx ; 1371local_buf [ idx_dst + c ] = top + ( bot - top ) * ly ; 1372} 1373} 1374} 1375dst . cpy_buf ( local_buf ); 1376} 1377 1378int mtmd_image_preprocessor_step3vl:: get_image_longest_edge ( const clip_hparams & params ) { 1379return params . image_longest_edge > 0 ? params . image_longest_edge : default_image_longest_edge ; 1380} 1381 1382int mtmd_image_preprocessor_step3vl:: determine_window_size ( const clip_hparams & params , int longer , int shorter ) { 1383const int image_size = params . image_size ; 1384const int crop_size = default_image_crop_size ; 1385const float aspect_ratio = static_cast < float > ( longer ) / shorter ; 1386 1387if ( longer <= image_size ) { 1388return aspect_ratio > small_aspect_ratio_limit ? shorter : 0 ; 1389} 1390 1391return aspect_ratio > wide_aspect_ratio_limit ? std:: min ( shorter , crop_size ) : crop_size ; 1392} 1393 1394int mtmd_image_preprocessor_step3vl:: calc_crop_extent (int length , int window_size ) { 1395const float ratio = static_cast < float > ( length ) / window_size ; 1396if ( ratio < 1.0f ) { 1397return length ; 1398} 1399 1400const float decimal = ratio - std:: floor ( ratio ); 1401const int rounded = decimal > crop_rounding_threshold 1402? static_cast < int > (std:: floor ( ratio )) + 1 1403: static_cast < int > (std:: floor ( ratio )); 1404return window_size * rounded ; 1405} 1406 1407std::vector < int > mtmd_image_preprocessor_step3vl:: calc_grid (int length , int window_size ) { 1408const int n = length <= window_size 1409? 1 1410: static_cast < int > (std:: ceil ( static_cast < float > ( length - window_size ) / window_size + 1.0f )); 1411std::vector < int > starts (n); 1412 1413for (int i = 0 ; i < n ; ++ i ) { 1414starts [ i ] = window_size * i ; 1415} 1416 1417if ( n > 1 && starts . back () + window_size > length ) { 1418starts . back () = length - window_size ; 1419} 1420 1421return starts ; 1422} 1423 1424clip_image_u8 mtmd_image_preprocessor_step3vl:: prepare_image ( const clip_image_u8 & img , const clip_hparams & params ) { 1425clip_image_u8 resized = img ; 1426const auto img_size = img . get_size (); 1427const float aspect_ratio = img_size . height > 0 ? static_cast < float > ( img_size . width ) / img_size . height : 1.0f ; 1428if (std:: min ( img_size . width , img_size . height ) < 32 && 1429( aspect_ratio > wide_aspect_ratio_limit || 1430aspect_ratio < 1.0f / wide_aspect_ratio_limit )) { 1431const int square_size = std:: max ( img_size . width , img_size . height ); 1432clip_image_u8 padded ; 1433padded . set_size ({ square_size , square_size }, false); 1434img_tool:: fill ( padded , { 0 , 0 , 0 }); 1435img_tool:: composite ( padded , img , 0 , 0 ); 1436resized = std:: move ( padded ); 1437} 1438 1439const int max_image_size = get_image_longest_edge ( params ); 1440const auto resized_size = resized . get_size (); 1441if (std:: max ( resized_size . width , resized_size . height ) > max_image_size ) { 1442const float scale = static_cast < float > ( max_image_size ) / std:: max ( resized_size . width , resized_size . height ); 1443const clip_image_size new_size = { 1444std:: max ( 1 , static_cast < int > (std:: floor ( resized_size . width * scale ))), 1445std:: max ( 1 , static_cast < int > (std:: floor ( resized_size . height * scale ))), 1446}; 1447clip_image_u8 scaled ; 1448img_tool:: resize ( resized , scaled , new_size , RESIZE_ALGO_BILINEAR , PAD_NONE ); 1449resized = std:: move ( scaled ); 1450} 1451 1452return resized ; 1453} 1454 1455clip_image_u8 mtmd_image_preprocessor_step3vl:: crop_with_black_padding ( const clip_image_u8 & image , int x , int y , int w , int h ) { 1456clip_image_u8 dst ; 1457dst . set_size ({ w , h }, false); 1458img_tool:: fill ( dst , { 0 , 0 , 0 }); 1459 1460const auto img_size = image . get_size (); 1461const int src_x0 = std:: max ( 0 , x ); 1462const int src_y0 = std:: max ( 0 , y ); 1463const int src_x1 = std:: min ( img_size . width , x + w ); 1464const int src_y1 = std:: min ( img_size . height , y + h ); 1465 1466if ( src_x0 >= src_x1 || src_y0 >= src_y1 ) { 1467return dst ; 1468} 1469 1470const int dst_x0 = src_x0 - x ; 1471const int dst_y0 = src_y0 - y ; 1472 1473for (int yy = 0 ; yy < src_y1 - src_y0 ; ++ yy ) { 1474for (int xx = 0 ; xx < src_x1 - src_x0 ; ++ xx ) { 1475dst . set_pixel ( dst_x0 + xx , dst_y0 + yy , image . get_pixel ( src_x0 + xx , src_y0 + yy )); 1476} 1477} 1478 1479return dst ; 1480} 1481 1482mtmd_image_preprocessor_step3vl::slice_instructions mtmd_image_preprocessor_step3vl:: build_slice_instructions ( 1483const clip_hparams & params , 1484const clip_image_size & prepared_size ) { 1485slice_instructions instructions ; 1486instructions . overview_size = prepared_size ; 1487 1488const int window_size = determine_window_size ( 1489params , 1490std:: max ( prepared_size . width , prepared_size . height ), 1491std:: min ( prepared_size . width , prepared_size . height )); 1492if ( window_size <= 0 ) { 1493instructions . refined_size = clip_image_size{ 0 , 0 }; 1494instructions . grid_size = clip_image_size{ 0 , 0 }; 1495return instructions ; 1496} 1497 1498const int crop_width = calc_crop_extent ( prepared_size . width , window_size ); 1499const int crop_height = calc_crop_extent ( prepared_size . height , window_size ); 1500instructions . refined_size = clip_image_size{ crop_width , crop_height }; 1501 1502const auto xs = calc_grid ( crop_width , window_size ); 1503const auto ys = calc_grid ( crop_height , window_size ); 1504instructions . grid_size = clip_image_size{ 1505static_cast < int > ( xs . size ()), 1506static_cast < int > ( ys . size ()), 1507}; 1508 1509for (int y : ys ) { 1510for (int x : xs ) { 1511instructions . slices . push_back (slice_coordinates{ 1512/* x */ x , 1513/* y */ y , 1514/* size */ clip_image_size{ window_size , window_size }, 1515}); 1516} 1517} 1518 1519return instructions ; 1520} 1521 1522mtmd_image_preproc_out mtmd_image_preprocessor_step3vl:: preprocess ( const clip_image_u8 & img ) const { 1523clip_image_u8 prepared = prepare_image ( img , hparams ); 1524const auto instructions = build_slice_instructions ( hparams , prepared . get_size ()); 1525 1526mtmd_image_preproc_out output ; 1527// overview (normalized f32, already includes mean/std) 1528img_u8_resize_bilinear_to_f32 ( 1529prepared , 1530output . overview , 1531hparams . image_size , 1532hparams . image_size , 1533hparams . image_mean , 1534hparams . image_std ); 1535 1536if ( instructions . slices . empty ()) { 1537output . grid_x = 0 ; 1538output . grid_y = 0 ; 1539return output ; 1540} 1541 1542clip_image_u8 img_for_crop = prepared ; 1543const auto prepared_size = prepared . get_size (); 1544if ( instructions . refined_size . width != prepared_size . width || instructions . refined_size . height != prepared_size . height ) { 1545clip_image_u8 refined ; 1546img_tool:: resize ( prepared , refined , instructions . refined_size , RESIZE_ALGO_BILINEAR , PAD_NONE ); 1547img_for_crop = std:: move ( refined ); 1548} 1549 1550const int crop_size = default_image_crop_size ; 1551for ( const auto & slice : instructions . slices ) { 1552// If the requested patch extends past the source image, pad the out-of-bounds area with black. 1553clip_image_u8 patch = crop_with_black_padding ( img_for_crop , slice . x , slice . y , slice . size . width , slice . size . height ); 1554 1555clip_image_f32 patch_f32 ; 1556img_u8_resize_bilinear_to_f32 ( 1557patch , 1558patch_f32 , 1559crop_size , 1560crop_size , 1561hparams . image_mean , 1562hparams . image_std ); 1563output . append ( hparams , patch_f32 , false); 1564} 1565 1566output . grid_x = instructions . grid_size . width ; 1567output . grid_y = instructions . grid_size . height ; 1568 1569return output ; 1570} 1571 1572// 1573// mtmd_image_preprocessor_youtuvl 1574// 1575 1576mtmd_image_preproc_out mtmd_image_preprocessor_youtuvl:: preprocess ( const clip_image_u8 & img ) const { 1577const int patch_size = hparams . patch_size ; // typically 16 1578const int merge_size = hparams . n_merge ; // typically 2 1579const int align_size = patch_size * merge_size ; // 32 1580 1581const int max_num_patches = hparams . image_max_pixels > 0 ? 1582hparams . image_max_pixels / ( patch_size * patch_size ) : 256 ; 1583 1584// Linear search for optimal scale to fit within max_num_patches 1585const auto img_size = img . get_size (); 1586float scale = 1.0f ; 1587int target_height = img_size . height ; 1588int target_width = img_size . width ; 1589 1590auto get_scaled_image_size = [ align_size ](float scale , int size ) -> int { 1591float scaled_size = size * scale ; 1592// Round up to nearest multiple of align_size 1593int aligned = static_cast < int > (std:: ceil ( scaled_size / align_size )) * align_size ; 1594// Ensure at least one patch 1595return std:: max ( align_size , aligned ); 1596}; 1597 1598// Linear search with 0.02 step size 1599while ( scale > 0.0f ) { 1600target_height = get_scaled_image_size ( scale , img_size . height ); 1601target_width = get_scaled_image_size ( scale , img_size . width ); 1602 1603int num_patches_h = target_height / patch_size ; 1604int num_patches_w = target_width / patch_size ; 1605int num_patches = num_patches_h * num_patches_w ; 1606 1607if ( num_patches > max_num_patches ) { 1608scale -= 0.02f ; 1609} else { 1610break ; 1611} 1612} 1613 1614clip_image_size new_size = { target_width , target_height }; 1615 1616// Resize the image 1617clip_image_u8 resized ; 1618img_tool:: resize ( img , resized , new_size , hparams . image_resize_algo , hparams . image_resize_pad ); 1619 1620mtmd_image_preproc_out output ; 1621output . append ( hparams , resized , true); 1622return output ; 1623} 1624 1625mtmd_image_preproc_out mtmd_image_preprocessor_granite:: preprocess ( const clip_image_u8 & img ) const { 1626GGML_ASSERT (! hparams . image_res_candidates . empty ()); 1627 1628const clip_image_size orig_size = img . get_size (); 1629const int tile_size = hparams . image_size ; 1630GGML_ASSERT ( tile_size > 0 ); 1631 1632// llava-next always encodes an overview plus a grid of tiles, even for small images 1633const clip_image_size refined_size = select_best_resolution ( orig_size , hparams . image_res_candidates ); 1634const int grid_x = refined_size . width / tile_size ; 1635const int grid_y = refined_size . height / tile_size ; 1636 1637// the tiles are stacked on the Y axis, a big grid overflows the stacked image height 1638GGML_ASSERT ( grid_x >= 0 && grid_x <= 1024 && grid_y >= 0 && grid_y <= 1024 ); 1639 1640clip_image_u8 overview ; 1641img_tool:: resize ( img , overview , { tile_size , tile_size }, hparams . image_resize_algo_ov , 1642hparams . image_pad_ov , hparams . image_pad_color_ov ); 1643 1644clip_image_u8 refined ; 1645img_tool:: resize ( img , refined , refined_size , hparams . image_resize_algo_rf , 1646hparams . image_pad_rf , hparams . image_pad_color_rf ); 1647 1648// stack the overview and the tiles on the Y axis, so the whole grid goes through one graph 1649clip_image_u8 stacked ; 1650stacked . set_size ({ tile_size , tile_size * ( 1 + grid_x * grid_y )}, false); 1651auto copy_tile = [ & ]( const clip_image_u8 & src , int src_x , int src_y , int dst_idx ) { 1652for (int py = 0 ; py < tile_size ; py ++ ) { 1653for (int px = 0 ; px < tile_size ; px ++ ) { 1654stacked . set_pixel ( px , dst_idx * tile_size + py , src . get_pixel ( src_x + px , src_y + py )); 1655} 1656} 1657}; 1658copy_tile ( overview , 0 , 0 , 0 ); 1659for (int ty = 0 ; ty < grid_y ; ty ++ ) { 1660for (int tx = 0 ; tx < grid_x ; tx ++ ) { 1661copy_tile ( refined , tx * tile_size , ty * tile_size , 1 + ty * grid_x + tx ); 1662} 1663} 1664 1665LOG_DBG ( "%s: grid size: %d x %d (%d tiles) + overview\n" , __func__ , grid_x , grid_y , grid_x * grid_y ); 1666 1667mtmd_image_preproc_out output ; 1668output . append ( hparams , stacked , true); 1669auto & entry = output . entries . back (); 1670entry . anyres . grid_x = grid_x ; 1671entry . anyres . grid_y = grid_y ; 1672entry . anyres . orig_nx = orig_size . width ; 1673entry . anyres . orig_ny = orig_size . height ; 1674return output ; 1675} 1676 1677// 1678// mtmd_image_preprocessor_muse_glimmer 1679// 1680 1681// Replicates transformers' get_aspect_ratio_preserving_size 1682static clip_image_size muse_glimmer_grid_size (int img_w , int img_h , int patch_hw , int max_tokens ) { 1683double i_nph = (double) img_h / patch_hw ; 1684double i_npw = (double) img_w / patch_hw ; 1685const double ratio = i_nph > 0.0 ? i_npw / i_nph : 1.0 ; 1686if ( i_nph * i_npw > (double) max_tokens ) { 1687i_nph = std:: sqrt ((double) max_tokens / ratio ); 1688i_npw = i_nph * ratio ; 1689} 1690const int hs [ 2 ] = { (int) std:: floor ( i_nph ), (int) std:: ceil ( i_nph ) }; 1691const int ws [ 2 ] = { (int) std:: floor ( i_npw ), (int) std:: ceil ( i_npw ) }; 1692const double target_ar = (double) img_h / (double) img_w ; 1693int best_nph = -1 ; 1694int best_npw = -1 ; 1695double best_d = 0.0 ; 1696for (int a = 0 ; a < 2 ; ++ a ) { 1697for (int b = 0 ; b < 2 ; ++ b ) { 1698const int nph = hs [ a ]; 1699const int npw = ws [ b ]; 1700if ( nph < 1 || npw < 1 || nph * npw > max_tokens ) { 1701continue ; 1702} 1703const double d = std:: fabs ((double) nph / (double) npw - target_ar ); 1704const int n_tokens = nph * npw ; 1705const int best_n_tokens = best_nph * best_npw ; 1706if ( best_nph < 0 || d < best_d || ( d == best_d && n_tokens > best_n_tokens )) { 1707best_nph = nph ; 1708best_npw = npw ; 1709best_d = d ; 1710} 1711} 1712} 1713if ( best_nph < 0 ) { // no candidate fit under the cap: round and clamp 1714best_nph = std:: max ( 1 , (int) std:: lround ( i_nph )); 1715best_npw = std:: max ( 1 , (int) std:: lround ( i_npw )); 1716} 1717return clip_image_size{ best_npw * patch_hw , best_nph * patch_hw }; 1718} 1719 1720mtmd_image_preproc_out mtmd_image_preprocessor_muse_glimmer:: preprocess ( const clip_image_u8 & img ) const { 1721const int patch_hw = hparams . patch_size * hparams . n_merge ; 1722const int patch_area = hparams . patch_size * hparams . patch_size * hparams . n_merge * hparams . n_merge ; 1723GGML_ASSERT ( patch_area > 0 && hparams . image_max_pixels > 0 ); 1724const int max_tokens = hparams . image_max_pixels / patch_area ; 1725 1726const clip_image_size original_size = img . get_size (); 1727const clip_image_size target_size = muse_glimmer_grid_size ( 1728original_size . width , original_size . height , patch_hw , max_tokens ); 1729 1730// PIL resizes directly to (target_w, target_h) -- a stretch, no padding. 1731clip_image_u8 resized_image ; 1732img_tool:: resize ( img , resized_image , target_size , hparams . image_resize_algo , PAD_NONE ); 1733 1734mtmd_image_preproc_out output ; 1735output . append ( hparams , resized_image , true); 1736return output ; 1737}