Skip to content

Commit f035671

Browse files
authored
[https://nvbugs/6958731][fix] Use the tokenizer chat template when the processor has none (#20880)
Signed-off-by: chenfeiz0326 <203214996+chenfeiz0326@users.noreply.github.com> Co-authored-by: chenfeiz0326 <203214996+chenfeiz0326@users.noreply.github.com>
1 parent 1526744 commit f035671

2 files changed

Lines changed: 52 additions & 5 deletions

File tree

‎tensorrt_llm/inputs/utils.py‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -644,11 +644,15 @@ def resolve_hf_chat_template(
644644
return templates[chat_template]
645645
return chat_template
646646

647-
# 2. If tool is not provided, use the processor's default chat template
648-
if not tools and processor and hasattr(processor, 'chat_template'):
649-
return processor.chat_template
650-
651-
# 3. If tool is provided, use the tool
647+
# 2. Without tools, prefer the processor's chat template when it has one.
648+
# A processor without one (some keep the template on their tokenizer
649+
# only) falls through to the tokenizer below.
650+
if not tools and processor:
651+
processor_template = getattr(processor, "chat_template", None)
652+
if processor_template is not None:
653+
return processor_template
654+
655+
# 3. Use the tokenizer's chat template, which accounts for the tools
652656
try:
653657
return tokenizer.get_chat_template(chat_template, tools=tools)
654658
except Exception:

‎tests/unittest/inputs/test_chat_template_dispatch.py‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,49 @@ def get_chat_template(self, *_args, **_kwargs):
460460
)
461461

462462

463+
class TestProcessorWithoutChatTemplate:
464+
"""A processor that has no chat template must not hide the tokenizer's."""
465+
466+
@staticmethod
467+
def _tokenizer(template):
468+
class JinjaTokenizer:
469+
chat_template = template
470+
471+
def get_chat_template(self, chat_template=None, tools=None):
472+
return self.chat_template
473+
474+
def apply_chat_template(self, **kwargs):
475+
# Return the template it was asked to render, so the result names the winner.
476+
return kwargs["chat_template"]
477+
478+
return JinjaTokenizer()
479+
480+
@staticmethod
481+
def _render(tokenizer, processor):
482+
return apply_chat_template(
483+
model_type="test_string_model",
484+
tokenizer=tokenizer,
485+
processor=processor,
486+
conversation=[ConversationMessage(role="user", content="hello", media=[])],
487+
add_generation_prompt=True,
488+
mm_placeholder_counts=[{}],
489+
)
490+
491+
def test_falls_back_to_tokenizer_template(self):
492+
class Processor:
493+
chat_template = None
494+
495+
tokenizer = self._tokenizer("{{ 'tokenizer template' }}")
496+
assert self._render(tokenizer, Processor()) == "{{ 'tokenizer template' }}"
497+
498+
def test_processor_template_is_still_preferred(self):
499+
class Processor:
500+
chat_template = "{{ 'processor template' }}"
501+
502+
tokenizer = self._tokenizer("{{ 'tokenizer template' }}")
503+
assert self._render(tokenizer, Processor()) == "{{ 'processor template' }}"
504+
505+
463506
class TestServingChatTemplateGather:
464507
"""Cover the asyncio.gather integration in the serving chat-template paths."""
465508

0 commit comments

Comments
 (0)