hiyouga/LlamaFactory · error · ValueError
{kind} placeholder count ({seen}) != number of {kind} blocks
Error message
{kind} placeholder count ({seen}) != number of {kind} blocks ({count}); media must be provided via image_url/video_url content blocks. What it means
After rendering a multimodal conversation, the renderer counts placeholder tokens (e.g. the image/video/audio expansion token from the processor) in the final text and compares them to the number of media content blocks. A mismatch means media was referenced in text without matching image_url/video_url/audio_url blocks, or vice versa, which would desync token ids from pixel/feature tensors downstream.
Source
Thrown at src/llamafactory/v1/core/rendering/format.py:167
def _check_placeholder_counts(
processor: "Processor", full_text: str, n_images: int, n_videos: int, n_audios: int = 0
) -> None:
"""Guard: every media placeholder in the rendered text must originate from a media block."""
tokenizer = get_tokenizer(processor)
for attr, count, kind in (
("image_token_id", n_images, "image"),
("video_token_id", n_videos, "video"),
("audio_token_id", n_audios, "audio"),
):
tid = getattr(processor, attr, None)
if tid is None:
tid = getattr(tokenizer, attr, None)
if tid is None:
continue
placeholder = tokenizer.convert_ids_to_tokens(tid)
seen = full_text.count(placeholder)
if seen != count:
raise ValueError(
f"{kind} placeholder count ({seen}) != number of {kind} blocks ({count}); "
"media must be provided via image_url/video_url content blocks."
)
View on GitHub (pinned to f28afaf635)
Solutions
- Remove hand-written placeholder tokens from the text; supply media exclusively through {'type': 'image_url'|'video_url'|'audio_url', 'value': ...} content blocks
- Ensure every media block you pass actually reaches the renderer (no earlier filtering of empty/None values)
- If you intentionally inject placeholders, make the counts match exactly — one placeholder run per media block
Example fix
# before
text = "Describe <|vision_start|><|image_pad|><|vision_end|>"
content = [{"type": "text", "value": text}, {"type": "image_url", "value": img}]
# after
content = [{"type": "text", "value": "Describe this image."}, {"type": "image_url", "value": img}] Defensive patterns
Strategy: validation
Validate before calling
def media_blocks_match_text(blocks: list[dict], text: str, placeholder: str) -> bool:
n_media = sum(1 for b in blocks if b["type"] in ("image_url", "video_url", "audio_url"))
return text.count(placeholder) == n_media Prevention
- Never paste placeholder tokens into text fields; use content blocks only
- Regression-test dataset converters with a known (blocks, text) pair
When it happens
Trigger: Sample text contains literal placeholder tokens (e.g. '<|vision_start|><|image_pad|><|vision_end|>') pasted as plain text while media is supplied via blocks (count too high), or media blocks are present but the chat template did not emit the placeholder (count too low, e.g. a template that skips images in earlier turns).
Common situations: Multimodal SFT datasets that pre-expand image markers into the text column; using a chat template that renders history differently from the final turn; missing 'value' keys so a media block was dropped earlier.
Related errors
- The number of images does not match the number of {IMAGE_PLA
- tool_call value is not valid JSON: {content['value']!r}
- `image_max_pixels` cannot be smaller than `image_min_pixels`
- tool_call must be a JSON object with 'name' and 'arguments'
- Unsupported dummy media modality: {modality!r} (expected ima
AI-assisted analysis of hiyouga/LlamaFactory@f28afaf635 (2026-08-14).
Data as JSON: /api/errors/aa1ad26f304b3f4f.
Report an issue: GitHub.