{"record":{"id":"71f00dbb5f84fa88","repo":"sgl-project/sglang","slug":"expected-len-image-token-counts-image-placehold","errorCode":null,"errorMessage":"Expected {len(image_token_counts)} image placeholder token(s), found {placeholder_count}.","messagePattern":"Expected (.+?) image placeholder token\\(s\\), found (.+?)\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/sglang/srt/multimodal/processors/kimi_k25.py","lineNumber":101,"sourceCode":"def _expand_image_token_ids(\n    input_ids: Union[List[int], torch.Tensor],\n    image_token_id: int,\n    image_token_counts: List[int],\n) -> torch.Tensor:\n    \"\"\"Expand one placeholder per image without tokenizing the media string again.\n\n    Same rebuild as ``BaseMultimodalProcessor._expand_input_ids``, but staying in\n    the array domain skips a list round trip on the way to the output tensor.\n    test_kimi_k25.py pins the two together.\n    \"\"\"\n    if isinstance(input_ids, torch.Tensor):\n        input_ids = input_ids.detach().flatten().cpu().numpy()\n    input_ids = np.asarray(input_ids, dtype=np.int64)\n\n    placeholder_mask = input_ids == image_token_id\n    placeholder_count = np.count_nonzero(placeholder_mask)\n    if placeholder_count != len(image_token_counts):\n        raise ValueError(\n            f\"Expected {len(image_token_counts)} image placeholder token(s), \"\n            f\"found {placeholder_count}.\"\n        )\n\n    repeats = np.ones(input_ids.shape, dtype=np.int64)\n    repeats[placeholder_mask] = image_token_counts\n    return torch.from_numpy(np.repeat(input_ids, repeats)).unsqueeze(0)\n\n\ndef _pil_to_cuda_chw(image: Image.Image) -> torch.Tensor:\n    \"\"\"Convert PIL Image to (C, H, W) uint8 CUDA tensor.\"\"\"\n    arr = np.asarray(image.convert(\"RGB\"))\n    return torch.from_numpy(arr).permute(2, 0, 1).cuda()\n\n\ndef _ensure_chw_rgb(image: torch.Tensor) -> torch.Tensor:\n    \"\"\"Coerce an already-decoded (C, H, W) image tensor to 3-channel RGB.\n","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/multimodal/processors/kimi_k25.py#L83-L119","documentation":"Kimi K2.5's _expand_image_token_ids requires the count of image_token_id tokens in input_ids to exactly equal len(image_token_counts). A mismatch (either direction) aborts expansion because repeats cannot be assigned one-to-one to placeholders.","triggerScenarios":"Calling _prepare_input_ids / _cpu_call (or the processor API that uses them) with input_ids whose image placeholder count differs from the number of per-image token counts derived from grids.","commonSituations":"Retokenization drift adding/removing a placeholder token, reusing cached input_ids after changing the image list, or prompts assembled with a different placeholder token id than the one counted.","solutions":["Rebuild input_ids and image_token_counts from the same request so they stay in lockstep","Assert placeholder_count == len(image_token_counts) before the call (as the bundled unit tests do)","Verify the tokenizer/model use the same image_token_id constant as the processor"],"exampleFix":"// before\nids = tokenizer.encode(f\"{IMG} two images here\")  # 1 placeholder\nexpand(ids, image_token_counts=[c1, c2])  # 2 counts -> raises\n\n// after\nids = tokenizer.encode(f\"{IMG} {IMG} two images here\")\nexpand(ids, image_token_counts=[c1, c2])","handlingStrategy":"validation","validationCode":"import numpy as np\ncount = int(np.count_nonzero(np.asarray(input_ids) == image_token_id))\nassert count == len(image_token_counts), (count, len(image_token_counts))","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Derive input_ids and token counts from the same request build","Avoid caching tokenized prompts across image-list changes","Mirror the repo's unit-test style pre-asserts in your code"],"tags":["kimi","k25","multimodal","placeholder-count","tokenization"],"backgroundTag":"multimodal-placeholder-mismatch","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}