@@ -91,6 +91,8 @@ class MTMDChatHandler:
9191 "{% endif %}"
9292 )
9393
94+ KNOWN_MEDIA_TAGS : List [str ] = []
95+
9496 def __init__ (
9597 self ,
9698 mmproj_path : Optional [str ] = None ,
@@ -1189,41 +1191,137 @@ def from_pretrained(
11891191 ** kwargs ,
11901192 )
11911193
1192- # Experiments are not recommended for this purpose at this time .
1194+ # Generic template-driven MTMD handler .
11931195class GenericMTMDChatHandler (MTMDChatHandler ):
1196+ """
1197+ Generic MTMD chat handler backed by the model-provided chat template.
1198+
1199+ This handler is intentionally template-driven. It renders the model's
1200+ tokenizer.chat_template first, then normalizes rendered media URLs or
1201+ placeholder tokens into MTMD media markers before tokenization.
1202+
1203+ It is designed for model templates that emit media placeholders such as
1204+ <|image_pad|>, <|image|>, <image>, [IMG], or Kimi-style <|media_pad|>.
1205+ Model-specific handlers may still be preferable when a model requires
1206+ special stop tokens, generation flags, or non-standard template arguments.
1207+ """
1208+
11941209 KNOWN_MEDIA_TAGS = [
1210+ # Pad placeholders inside model-specific wrappers.
11951211 "<|image_pad|>" ,
11961212 "<|audio_pad|>" ,
11971213 "<|video_pad|>" ,
1214+
1215+ # Direct placeholders inside Gemma/Llama/GLM-style wrappers.
11981216 "<|image|>" ,
11991217 "<|audio|>" ,
12001218 "<|video|>" ,
1201- "[IMG]"
1219+
1220+ # LLaVA / LFM / Mistral-style placeholders.
1221+ "<image>" ,
1222+ "<audio>" ,
1223+ "<video>" ,
1224+ "[IMG]" ,
1225+
1226+ # Kimi-style placeholders.
1227+ "<|media_pad|>" ,
1228+ "<|kimi_k25_video_placeholder|>" ,
12021229 ]
12031230
12041231 def __init__ (
12051232 self ,
1206- chat_format : str ,
1233+ chat_format : Optional [ str ] ,
12071234 mmproj_path : str ,
12081235 verbose : bool = True ,
1236+ chat_template_name : Optional [str ] = None ,
12091237 ** kwargs
12101238 ) -> None :
1211-
12121239 self .chat_format = chat_format
1213- if self .chat_format is None :
1214- raise ValueError ("Failed to get model chat template automatically." )
1215-
1240+ self .chat_template_name = chat_template_name
12161241 self .verbose = verbose
1217- if self .verbose :
1218- print (f"Got chat template from model:\n ```jinja\n { self .chat_format } \n ```" , flush = True )
1242+
1243+ if self .verbose and self .chat_format is not None :
1244+ print (
1245+ f"{ self .__class__ .__name__ } .__init__: using provided chat template:\n "
1246+ f"```jinja\n { self .chat_format } \n ```" ,
1247+ file = sys .stderr ,
1248+ )
12191249
12201250 super ().__init__ (mmproj_path = mmproj_path , verbose = verbose , ** kwargs )
12211251
1252+ def _resolve_chat_format (self , llama : llama_core .Llama ) -> str :
1253+ # Highest priority: use the template explicitly provided by the caller.
1254+ if self .chat_format is not None :
1255+ return self .chat_format
1256+
1257+ chat_format = None
1258+
1259+ # The Llama instance is only available at call time, so query llama.cpp here
1260+ # for either the requested named template or the model's default template.
1261+ try :
1262+ name = (
1263+ self .chat_template_name .encode ("utf-8" )
1264+ if self .chat_template_name is not None
1265+ else None
1266+ )
1267+ chat_format = llama ._model .model_chat_template (name )
1268+ except Exception as exc :
1269+ if self .verbose :
1270+ print (
1271+ f"{ self .log_prefix } : failed to load chat template"
1272+ f"{ f' { self .chat_template_name !r} ' if self .chat_template_name else '' } "
1273+ f"from llama model: { exc } " ,
1274+ file = sys .stderr ,
1275+ )
1276+
1277+ # If a named template is unavailable, try the default model template.
1278+ if chat_format is None and self .chat_template_name is not None :
1279+ try :
1280+ chat_format = llama ._model .model_chat_template (None )
1281+ if self .verbose and chat_format is not None :
1282+ print (
1283+ f"{ self .log_prefix } : chat template { self .chat_template_name !r} "
1284+ "not found; using default model chat template." ,
1285+ file = sys .stderr ,
1286+ )
1287+ except Exception as exc :
1288+ if self .verbose :
1289+ print (
1290+ f"{ self .log_prefix } : failed to load default model chat template: { exc } " ,
1291+ file = sys .stderr ,
1292+ )
1293+
1294+ # Last resort: use the generic built-in MTMD template.
1295+ if chat_format is None :
1296+ chat_format = self .CHAT_FORMAT
1297+ if self .verbose :
1298+ print (
1299+ f"{ self .log_prefix } : no model chat template found; "
1300+ "using MTMDChatHandler built-in CHAT_FORMAT." ,
1301+ file = sys .stderr ,
1302+ )
1303+
1304+ self .chat_format = chat_format
1305+ return chat_format
1306+
12221307 def __call__ (self , ** kwargs ):
1308+ llama = kwargs ["llama" ]
1309+
1310+ self ._resolve_chat_format (llama )
1311+
1312+ if self .chat_format is None :
1313+ raise ValueError (
1314+ f"{ self .log_prefix } : failed to resolve a chat template. "
1315+ "`chat_format` must be a Jinja chat template string. You may pass it "
1316+ "directly, read it from a chat_template.jinja file, set a valid "
1317+ "`chat_template_name` for a named template stored in the model, or use "
1318+ "a model that provides tokenizer.chat_template metadata."
1319+ )
1320+
12231321 self ._chat_format_parser_tags = [tag for tag in self .KNOWN_MEDIA_TAGS if tag in self .chat_format ]
12241322
12251323 if self .verbose :
1226- print (f"{ self .log_prefix } - Start processing" )
1324+ print (f"{ self .log_prefix } - Start processing" , file = sys . stderr )
12271325
12281326 # Use parent implementation
12291327 return super ().__call__ (** kwargs )
0 commit comments