{"record":{"id":"8f230a68ae50e1ad","repo":"sgl-project/sglang","slug":"expected-one-original-size-for-each-k3-image","errorCode":null,"errorMessage":"Expected one original size for each K3 image.","messagePattern":"Expected one original size for each K3 image\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/sglang/srt/multimodal/processors/kimi_k3.py","lineNumber":90,"sourceCode":"        return list(tokenizer.encode(text))\n\n\ndef _expand_k3_image_prompt_token_ids(\n    input_ids: Union[List[int], torch.Tensor],\n    image_token_id: int,\n    image_token_counts: List[int],\n    image_sizes: List[tuple[int, int]],\n    tokenizer,\n) -> torch.Tensor:\n    \"\"\"Expand K3 image placeholders into the checkpoint's media contract.\n\n    K3 requires each image feature span to be enclosed by its original uploaded\n    dimensions.  The chat template deliberately emits one ``media_pad`` per\n    image; after decode, insert the surrounding control tokens and expand that\n    one placeholder to the NaViT feature count.\n    \"\"\"\n    if len(image_token_counts) != len(image_sizes):\n        raise ValueError(\"Expected one original size for each K3 image.\")\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_count = np.count_nonzero(input_ids == image_token_id)\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    output = []\n    image_index = 0\n    for token_id in input_ids:\n        if token_id != image_token_id:\n            output.append(int(token_id))\n            continue","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/multimodal/processors/kimi_k3.py#L72-L108","documentation":"Kimi K3 expansion requires one original uploaded (width, height) per image so it can wrap each expanded feature span with the model's size-control tokens. If len(image_token_counts) != len(image_sizes), it cannot pair counts with sizes and raises ValueError.","triggerScenarios":"Calling _expand_k3_image_prompt_token_ids (via _prepare_input_ids, compose_request, or get_mm_data) with image_token_counts and image_sizes lists of different lengths.","commonSituations":"Passing original sizes only for a subset of images, reusing cached sizes after adding/removing an image, or a data loader that fails to record the source dimensions for downscaled/cached images.","solutions":["Supply one original [w,h] (or h,w per API) size entry per image, matching image_token_counts","Populate image_sizes from the actual uploaded files (PIL Image.size) at request build time","Add a pre-flight assert len(image_token_counts) == len(image_sizes)"],"exampleFix":"# before\nexpand(ids, image_token_counts=[c1, c2], image_sizes=[(1024, 768)])\n\n# after\nexpand(ids, image_token_counts=[c1, c2], image_sizes=[(1024, 768), (512, 512)])","handlingStrategy":"validation","validationCode":"assert len(image_token_counts) == len(image_sizes), (\n    f\"{len(image_token_counts)} counts vs {len(image_sizes)} sizes\"\n)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Capture original file dimensions at upload time and keep them attached to each image","Regenerate sizes whenever the image list changes","Treat counts and sizes as a paired per-image record"],"tags":["kimi","k3","multimodal","metadata-mismatch","validation"],"backgroundTag":"multimodal-metadata-mismatch","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}