{"record":{"id":"4562746d9d4e8613","repo":"sgl-project/sglang","slug":"expected-len-image-token-counts-image-placehold-456274","errorCode":null,"errorMessage":"Expected {len(image_token_counts)} image placeholder(s), found {len(parts) - 1}.","messagePattern":"Expected (.+?) image placeholder\\(s\\), found (.+?)\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/sglang/srt/multimodal/processors/kimi_k3.py","lineNumber":133,"sourceCode":"            )\n        )\n        output.extend([image_token_id] * image_token_counts[image_index])\n        output.extend(_encode_k3_special_tokens(tokenizer, \"<|media_end|>\"))\n        image_index += 1\n\n    return torch.tensor(output, dtype=torch.long).unsqueeze(0)\n\n\ndef _expand_k3_image_prompt_text(\n    input_text: str,\n    image_token: str,\n    image_token_counts: List[int],\n    image_sizes: List[tuple[int, int]],\n) -> str:\n    \"\"\"Render the K3 media framing for the CPU HF-processor fallback.\"\"\"\n    parts = input_text.split(image_token)\n    if len(parts) - 1 != len(image_token_counts):\n        raise ValueError(\n            f\"Expected {len(image_token_counts)} image placeholder(s), \"\n            f\"found {len(parts) - 1}.\"\n        )\n\n    output = [parts[0]]\n    for image_token_count, (width, height), suffix in zip(\n        image_token_counts, image_sizes, parts[1:]\n    ):\n        output.extend(\n            (\n                f\"<|media_begin|>image {width}x{height}<|media_content|>\",\n                image_token * image_token_count,\n                \"<|media_end|>\",\n                suffix,\n            )\n        )\n    return \"\".join(output)\n","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/multimodal/processors/kimi_k3.py#L115-L151","documentation":"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.","triggerScenarios":"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.","commonSituations":"Chat template renders a placeholder literal different from the configured image token; manually concatenated prompt text; version change of the image token string.","solutions":["Ensure the prompt text embeds exactly one image_token string per image","Confirm the image_token passed matches the model's configured placeholder token","Avoid hand-building the media-framing text; use the model's chat template"],"exampleFix":"# before\ntext = \"<image> describe\" * 1  # wrong placeholder literal\nexpand(text, image_token=\"<|IMAGE_TOKEN|>\", counts=[c1, c2])\n# after\ntext = \"<|IMAGE_TOKEN|><|IMAGE_TOKEN|> describe\"\nexpand(text, image_token=\"<|IMAGE_TOKEN|>\", counts=[c1, c2])","handlingStrategy":"validation","validationCode":"def check_text(text, image_token, counts):\n    n = text.split(image_token).__len__() - 1\n    assert n == len(counts), f\"{n} placeholders vs {len(counts)} images\"","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use the model chat template to render media framing","Keep the image_token string in sync with model config"],"tags":["multimodal","kimi-k3","cpu-fallback","string-split","validation"],"backgroundTag":"multimodal-placeholder-count-mismatch","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}