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

  1. Rebuild input_ids and image_token_counts from the same request so they stay in lockstep
  2. Assert placeholder_count == len(image_token_counts) before the call (as the bundled unit tests do)
  3. 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

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


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