Skip to content

Commit c974531

Browse files
committed
feat(mtmd): enhance generic chat template support
- Enhance GenericMTMDChatHandler to better support model-provided chat templates. - Allow the generic handler to accept an optional named chat template, load it from the model at call time via llama_model_chat_template(), fall back to the model's default chat template, and finally use the built-in MTMD CHAT_FORMAT when no model template is available. - Also expand the generic media placeholder list for common multimodal templates and document the handler as a template-driven MTMD implementation. This prepares the generic path for a later render-driven placeholder replacement pass. Signed-off-by: JamePeng <jame_peng@sina.com>
1 parent d84b0c2 commit c974531

2 files changed

Lines changed: 110 additions & 10 deletions

File tree

llama_cpp/llama.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ def __init__(
174174
log_filters: Optional[Sequence[str]] = None,
175175
log_filters_case_sensitive: bool = True,
176176
# Extra Params
177+
chat_template_name: Optional[str] = None,
177178
chat_handler_kwargs: Dict[str, Any] = {},
178179
**kwargs, # type: ignore
179180
):
@@ -721,6 +722,7 @@ def __init__(
721722
chat_format = self.metadata.get("tokenizer.chat_template", None),
722723
mmproj_path = mmproj_path,
723724
verbose = self.verbose,
725+
chat_template_name=chat_template_name,
724726
**chat_handler_kwargs
725727
)
726728

llama_cpp/llama_multimodal.py

Lines changed: 108 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
11931195
class 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

Comments
 (0)