Skip to content

[Bug]: MiMo-V2.5 chat template is auto-detected as string, reordering multimodal content #53820

Description

@XuyangShen

Bug

The official XiaomiMiMo/MiMo-V2.5 chat template expects structured OpenAI content, but vLLM auto-detects it as string.

The template passes message.content into a macro:

{% macro render_content(message_content) %}
  {% for content in message_content %}
    ...
  {% endfor %}
{% endmacro %}

{{ render_content(message.content) }}

vLLM currently detects only loops directly over message.content, or loops over a variable literally named content. It therefore misses the macro parameter message_content:

vllm/vllm/renderers/hf.py

Lines 398 to 449 in 4af586e

def _iter_nodes_assign_content_item(root: jinja2.nodes.Node):
message_varnames = [
varname for _, varname in _iter_nodes_assign_messages_item(root)
]
# Search for {%- for content in message['content'] -%} loops
# or {%- for item in content -%} loops
for loop_ast in root.find_all(jinja2.nodes.For):
loop_iter = loop_ast.iter
loop_target = loop_ast.target
for varname in message_varnames:
if _is_var_or_elems_access(loop_iter, varname, "content"):
assert isinstance(loop_target, jinja2.nodes.Name)
yield loop_ast, loop_target.name
break
if isinstance(loop_iter, jinja2.nodes.Name) and loop_iter.name == "content":
assert isinstance(loop_target, jinja2.nodes.Name)
yield loop_ast, loop_target.name
def _try_extract_ast(chat_template: str) -> jinja2.nodes.Template | None:
import transformers.utils.chat_template_utils as hf_chat_utils
try:
jinja_compiled = hf_chat_utils._compile_jinja_template(chat_template)
return jinja_compiled.environment.parse(chat_template)
except Exception:
logger.exception("Error when compiling Jinja template")
return None
@lru_cache(maxsize=32)
def _detect_content_format(
chat_template: str,
*,
default: ChatTemplateContentFormat,
) -> ChatTemplateContentFormat:
jinja_ast = _try_extract_ast(chat_template)
if jinja_ast is None:
return default
try:
next(_iter_nodes_assign_content_item(jinja_ast))
except StopIteration:
return "string"
except Exception:
logger.exception("Error when parsing AST of Jinja template")
return default
else:
return "openai"

Impact

For OpenAI content ordered as:

text "A" -> image -> text "B"

vLLM's default string path (interleave_mm_strings=False) moves the image placeholder to the front:

<image-placeholder>\nA\nB

The official template should render:

A<image-placeholder>B

Using the following option works around the issue:

--chat-template-content-format openai

Expected fix

Auto detection should follow message.content through macro arguments instead of depending on the macro parameter name. Please also add a MiMo-V2.5 regression test using text -> image -> text, verifying that auto matches explicit openai.

Related: #49042 fixed a similar case for the Rust frontend when the macro parameter is literally named content; MiMo uses message_content.

Tested against vLLM main at 4af586e185b028acf08312a4dee381b5998a137e and MiMo-V2.5 revision 63651580ca774f8504f676040460aed3e1244ac1.

Metadata

Metadata

Assignees

No one assigned

    Labels

    multi-modalityRelated to multi-modality (#4194)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions