sgl-project/sglang · error · ValueError
Expected {len(image_token_counts)} image placeholder token(s
Error message
Expected {len(image_token_counts)} image placeholder token(s), found {placeholder_count}. What it means
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.
Source
Thrown at python/sglang/srt/multimodal/processors/kimi_k25.py:101
def _expand_image_token_ids(
input_ids: Union[List[int], torch.Tensor],
image_token_id: int,
image_token_counts: List[int],
) -> torch.Tensor:
"""Expand one placeholder per image without tokenizing the media string again.
Same rebuild as ``BaseMultimodalProcessor._expand_input_ids``, but staying in
the array domain skips a list round trip on the way to the output tensor.
test_kimi_k25.py pins the two together.
"""
if isinstance(input_ids, torch.Tensor):
input_ids = input_ids.detach().flatten().cpu().numpy()
input_ids = np.asarray(input_ids, dtype=np.int64)
placeholder_mask = input_ids == image_token_id
placeholder_count = np.count_nonzero(placeholder_mask)
if placeholder_count != len(image_token_counts):
raise ValueError(
f"Expected {len(image_token_counts)} image placeholder token(s), "
f"found {placeholder_count}."
)
repeats = np.ones(input_ids.shape, dtype=np.int64)
repeats[placeholder_mask] = image_token_counts
return torch.from_numpy(np.repeat(input_ids, repeats)).unsqueeze(0)
def _pil_to_cuda_chw(image: Image.Image) -> torch.Tensor:
"""Convert PIL Image to (C, H, W) uint8 CUDA tensor."""
arr = np.asarray(image.convert("RGB"))
return torch.from_numpy(arr).permute(2, 0, 1).cuda()
def _ensure_chw_rgb(image: torch.Tensor) -> torch.Tensor:
"""Coerce an already-decoded (C, H, W) image tensor to 3-channel RGB.
View on GitHub (pinned to 0132848349)
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
Example fix
// before
ids = tokenizer.encode(f"{IMG} two images here") # 1 placeholder
expand(ids, image_token_counts=[c1, c2]) # 2 counts -> raises
// after
ids = tokenizer.encode(f"{IMG} {IMG} two images here")
expand(ids, image_token_counts=[c1, c2]) Defensive patterns
Strategy: validation
Validate before calling
import numpy as np count = int(np.count_nonzero(np.asarray(input_ids) == image_token_id)) assert count == len(image_token_counts), (count, len(image_token_counts))
Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Kimi image placeholders must map one-to-one to image data: e
- The number of image placeholders exceeds img_grid_thw entrie
- The number of image placeholders does not match img_grid_thw
- Kimi GPU preprocessing expects raw uint8 pixels, got {image.
- Kimi image placeholders must map one-to-one to image data: e
AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28).
Data as JSON: /api/errors/71f00dbb5f84fa88.
Report an issue: GitHub.