@@ -3265,9 +3265,78 @@ def from_pretrained(
32653265 )
32663266
32673267
3268- class MTMDChatHandler ( Llava15ChatHandler ) :
3268+ class MTMDChatHandler :
32693269 DEFAULT_SYSTEM_MESSAGE = None
32703270
3271+ def __init__ (self , clip_model_path : str , verbose : bool = True ):
3272+ import llama_cpp .mtmd_cpp as mtmd_cpp
3273+
3274+ self .clip_model_path = clip_model_path
3275+ self .verbose = verbose
3276+ self ._mtmd_cpp = mtmd_cpp
3277+ self ._exit_stack = ExitStack ()
3278+ self .mtmd_ctx : Optional [mtmd_cpp .mtmd_context_p ] = None
3279+
3280+ if not os .path .exists (clip_model_path ):
3281+ raise ValueError (f"Clip model path does not exist: { clip_model_path } " )
3282+
3283+ def _init_mtmd_context (self , llama_model : llama .Llama ):
3284+ if self .mtmd_ctx is not None :
3285+ return
3286+
3287+ with suppress_stdout_stderr (disable = self .verbose ):
3288+ ctx_params = self ._mtmd_cpp .mtmd_context_params_default ()
3289+ ctx_params .use_gpu = True # TODO: Make this configurable
3290+ ctx_params .print_timings = self .verbose
3291+ ctx_params .n_threads = llama_model .n_threads
3292+ ctx_params .flash_attn_type = (
3293+ llama_cpp .LLAMA_FLASH_ATTN_TYPE_ENABLED
3294+ if (
3295+ llama_model .context_params .flash_attn_type
3296+ == llama_cpp .LLAMA_FLASH_ATTN_TYPE_ENABLED
3297+ )
3298+ else llama_cpp .LLAMA_FLASH_ATTN_TYPE_DISABLED
3299+ )
3300+
3301+ self .mtmd_ctx = self ._mtmd_cpp .mtmd_init_from_file (
3302+ self .clip_model_path .encode (), llama_model .model , ctx_params
3303+ )
3304+
3305+ if self .mtmd_ctx is None :
3306+ raise ValueError (
3307+ f"Failed to load mtmd context from: { self .clip_model_path } "
3308+ )
3309+
3310+ if not self ._mtmd_cpp .mtmd_support_vision (self .mtmd_ctx ):
3311+ raise ValueError ("Vision is not supported by this model" )
3312+
3313+ def mtmd_free ():
3314+ with suppress_stdout_stderr (disable = self .verbose ):
3315+ if self .mtmd_ctx is not None :
3316+ self ._mtmd_cpp .mtmd_free (self .mtmd_ctx )
3317+ self .mtmd_ctx = None
3318+
3319+ self ._exit_stack .callback (mtmd_free )
3320+
3321+ def load_image (self , image_url : str ) -> bytes :
3322+ return self ._load_image (image_url )
3323+
3324+ def _create_bitmap_from_bytes (self , image_bytes : bytes ):
3325+ if self .mtmd_ctx is None :
3326+ raise ValueError ("mtmd context not initialized" )
3327+
3328+ with suppress_stdout_stderr (disable = self .verbose ):
3329+ bitmap = self ._mtmd_cpp .mtmd_helper_bitmap_init_from_buf (
3330+ self .mtmd_ctx ,
3331+ (ctypes .c_uint8 * len (image_bytes )).from_buffer (bytearray (image_bytes )),
3332+ len (image_bytes ),
3333+ )
3334+
3335+ if bitmap is None :
3336+ raise ValueError ("Failed to create bitmap from image bytes" )
3337+
3338+ return bitmap
3339+
32713340 def _get_chat_template (self , llama_model : llama .Llama ) -> str :
32723341 chat_template = llama_model .metadata .get ("tokenizer.chat_template" )
32733342 if not isinstance (chat_template , str ) or chat_template == "" :
@@ -3590,6 +3659,121 @@ def raise_exception(message: str):
35903659 )
35913660 return _convert_completion_to_chat (completion_or_chunks , stream = stream )
35923661
3662+ @staticmethod
3663+ def _load_image (image_url : str ) -> bytes :
3664+ if image_url .startswith ("data:" ):
3665+ import base64
3666+
3667+ image_bytes = base64 .b64decode (image_url .split ("," )[1 ])
3668+ return image_bytes
3669+ else :
3670+ import urllib .request
3671+
3672+ with urllib .request .urlopen (image_url ) as f :
3673+ image_bytes = f .read ()
3674+ return image_bytes
3675+
3676+ @staticmethod
3677+ def get_image_urls (messages : List [llama_types .ChatCompletionRequestMessage ]):
3678+ image_urls : List [str ] = []
3679+ for message in messages :
3680+ if message ["role" ] == "user" :
3681+ if message ["content" ] is None :
3682+ continue
3683+ for content in message ["content" ]:
3684+ if isinstance (content , dict ) and "type" in content :
3685+ if content ["type" ] == "image_url" :
3686+ if (
3687+ isinstance (content ["image_url" ], dict )
3688+ and "url" in content ["image_url" ]
3689+ ):
3690+ image_urls .append (content ["image_url" ]["url" ])
3691+ else :
3692+ image_urls .append (content ["image_url" ])
3693+ return image_urls
3694+
3695+ @classmethod
3696+ def from_pretrained (
3697+ cls ,
3698+ repo_id : str ,
3699+ filename : Optional [str ],
3700+ local_dir : Optional [Union [str , os .PathLike [str ]]] = None ,
3701+ local_dir_use_symlinks : Union [bool , Literal ["auto" ]] = "auto" ,
3702+ cache_dir : Optional [Union [str , os .PathLike [str ]]] = None ,
3703+ ** kwargs : Any ,
3704+ ) -> "MTMDChatHandler" :
3705+ import fnmatch
3706+ from pathlib import Path
3707+
3708+ try :
3709+ from huggingface_hub import hf_hub_download , HfFileSystem # type: ignore
3710+ from huggingface_hub .utils import validate_repo_id # type: ignore
3711+ except ImportError :
3712+ raise ImportError (
3713+ "Llama.from_pretrained requires the huggingface-hub package. "
3714+ "You can install it with `pip install huggingface-hub`."
3715+ )
3716+
3717+ validate_repo_id (repo_id )
3718+
3719+ hffs = HfFileSystem ()
3720+
3721+ files = [
3722+ file ["name" ] if isinstance (file , dict ) else file
3723+ for file in hffs .ls (repo_id ) # type: ignore
3724+ ]
3725+
3726+ file_list : List [str ] = []
3727+ for file in files :
3728+ rel_path = Path (file ).relative_to (repo_id )
3729+ file_list .append (str (rel_path ))
3730+
3731+ matching_files = [file for file in file_list if fnmatch .fnmatch (file , filename )] # type: ignore
3732+
3733+ if len (matching_files ) == 0 :
3734+ raise ValueError (
3735+ f"No file found in { repo_id } that match { filename } \n \n "
3736+ f"Available Files:\n { json .dumps (file_list )} "
3737+ )
3738+
3739+ if len (matching_files ) > 1 :
3740+ raise ValueError (
3741+ f"Multiple files found in { repo_id } matching { filename } \n \n "
3742+ f"Available Files:\n { json .dumps (files )} "
3743+ )
3744+
3745+ (matching_file ,) = matching_files
3746+
3747+ subfolder = str (Path (matching_file ).parent )
3748+ filename = Path (matching_file ).name
3749+
3750+ hf_hub_download (
3751+ repo_id = repo_id ,
3752+ filename = filename ,
3753+ subfolder = subfolder ,
3754+ local_dir = cast (Union [str , Path , None ], local_dir ),
3755+ local_dir_use_symlinks = local_dir_use_symlinks ,
3756+ cache_dir = cast (Union [str , Path , None ], cache_dir ),
3757+ )
3758+
3759+ if local_dir is None :
3760+ model_path = hf_hub_download (
3761+ repo_id = repo_id ,
3762+ filename = filename ,
3763+ subfolder = subfolder ,
3764+ local_dir = local_dir ,
3765+ local_dir_use_symlinks = local_dir_use_symlinks ,
3766+ cache_dir = cast (Union [str , Path , None ], cache_dir ),
3767+ local_files_only = True ,
3768+ )
3769+ else :
3770+ model_path = os .path .join (local_dir , filename )
3771+
3772+ return cls (
3773+ clip_model_path = model_path ,
3774+ ** kwargs ,
3775+ )
3776+
35933777
35943778class Gemma4ChatHandler (MTMDChatHandler ):
35953779 pass
0 commit comments