sgl-project/sglang · error · ValueError

Expected {len(image_token_counts)} image placeholder(s), fou

Error message

Expected {len(image_token_counts)} image placeholder(s), found {len(parts) - 1}.

What it means

Raised by _expand_k3_image_prompt_text (the CPU HF-processor fallback path) when splitting the raw prompt text on the image token string does not yield exactly one segment boundary per expected image. The text path must mirror the token-id path, so counts must agree before expansion.

Source

Thrown at python/sglang/srt/multimodal/processors/kimi_k3.py:133

            )
        )
        output.extend([image_token_id] * image_token_counts[image_index])
        output.extend(_encode_k3_special_tokens(tokenizer, "<|media_end|>"))
        image_index += 1

    return torch.tensor(output, dtype=torch.long).unsqueeze(0)


def _expand_k3_image_prompt_text(
    input_text: str,
    image_token: str,
    image_token_counts: List[int],
    image_sizes: List[tuple[int, int]],
) -> str:
    """Render the K3 media framing for the CPU HF-processor fallback."""
    parts = input_text.split(image_token)
    if len(parts) - 1 != len(image_token_counts):
        raise ValueError(
            f"Expected {len(image_token_counts)} image placeholder(s), "
            f"found {len(parts) - 1}."
        )

    output = [parts[0]]
    for image_token_count, (width, height), suffix in zip(
        image_token_counts, image_sizes, parts[1:]
    ):
        output.extend(
            (
                f"<|media_begin|>image {width}x{height}<|media_content|>",
                image_token * image_token_count,
                "<|media_end|>",
                suffix,
            )
        )
    return "".join(output)

View on GitHub (pinned to 0132848349)

Solutions

  1. Ensure the prompt text embeds exactly one image_token string per image
  2. Confirm the image_token passed matches the model's configured placeholder token
  3. Avoid hand-building the media-framing text; use the model's chat template

Example fix

# before
text = "<image> describe" * 1  # wrong placeholder literal
expand(text, image_token="<|IMAGE_TOKEN|>", counts=[c1, c2])
# after
text = "<|IMAGE_TOKEN|><|IMAGE_TOKEN|> describe"
expand(text, image_token="<|IMAGE_TOKEN|>", counts=[c1, c2])
Defensive patterns

Strategy: validation

Validate before calling

def check_text(text, image_token, counts):
    n = text.split(image_token).__len__() - 1
    assert n == len(counts), f"{n} placeholders vs {len(counts)} images"

Prevention

When it happens

Trigger: Running the CPU fallback (_cpu_call path) where input_text.split(image_token) produces len(parts)-1 != len(image_token_counts), e.g. text uses a different placeholder string than mm_tokens.image_token, or extra/missing placeholders.

Common situations: Chat template renders a placeholder literal different from the configured image token; manually concatenated prompt text; version change of the image token string.

Related errors


AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28). Data as JSON: /api/errors/4562746d9d4e8613. Report an issue: GitHub.