{"record":{"id":"0488affc02212deb","repo":"sgl-project/sglang","slug":"inklingmultimodalprocessor-n-img-ph-image-place","errorCode":null,"errorMessage":"InklingMultimodalProcessor: {n_img_ph} image placeholder token(s) in input_ids but {len(image_data)} image(s) provided; counts must match.","messagePattern":"InklingMultimodalProcessor: (.+?) image placeholder token\\(s\\) in input_ids but (.+?) image\\(s\\) provided; counts must match\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/sglang/srt/multimodal/processors/inkling.py","lineNumber":210,"sourceCode":"        ``image_data`` / ``audio_data`` in encounter order.\n        \"\"\"\n        image_data = image_data or []\n        audio_data = audio_data or []\n\n        # One placeholder per media item (expanded below); a count mismatch (incl. a\n        # None token id absent from config) must fail loudly, not drop media silently.\n        n_img_ph = (\n            sum(1 for t in input_ids if t == self.IMAGE_TOKEN_ID)\n            if self.IMAGE_TOKEN_ID is not None\n            else 0\n        )\n        n_aud_ph = (\n            sum(1 for t in input_ids if t == self.AUDIO_TOKEN_ID)\n            if self.AUDIO_TOKEN_ID is not None\n            else 0\n        )\n        if n_img_ph != len(image_data):\n            raise ValueError(\n                f\"InklingMultimodalProcessor: {n_img_ph} image placeholder token(s) in \"\n                f\"input_ids but {len(image_data)} image(s) provided; counts must match.\"\n            )\n        if n_aud_ph != len(audio_data):\n            raise ValueError(\n                f\"InklingMultimodalProcessor: {n_aud_ph} audio placeholder token(s) in \"\n                f\"input_ids but {len(audio_data)} audio(s) provided; counts must match.\"\n            )\n\n        img_feat = (\n            self.inkling_processor.process_images(image_data) if image_data else None\n        )\n        aud_feat = (\n            self.inkling_processor.process_audios(audio_data) if audio_data else None\n        )\n\n        # Rust processor returns content_hashes; original processor does not.\n        img_hashes = img_feat.get(\"content_hashes\") if img_feat else None","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/multimodal/processors/inkling.py#L192-L228","documentation":"During Inkling request assembly, the number of image placeholder tokens found in input_ids must equal len(image_data). A mismatch means the chat template rendered a different number of <image> placeholders than the images supplied — typically caused by sending input_ids (pre-tokenized) that were rendered with the wrong template or wrong placeholder token id.","triggerScenarios":"Passing request_obj.input_ids tokenized with a template that emits 0 or 2 image tokens while image_data has 1 image; using a placeholder token id that differs from InklingProcessor's IMAGE_TOKEN_ID so the counting pass finds none; image list containing None entries after a filter step.","commonSituations":"Offline batch pipelines pre-tokenizing prompts with the base tokenizer instead of the processor's chat template; template version drift after model upgrade; duplicated or dropped images during data loading.","solutions":["Regenerate input_ids with the Inkling chat template so exactly one image placeholder per provided image is emitted","Verify the placeholder token id used when rendering matches processor.IMAGE_TOKEN_ID","Assert counts before submit: len([t for t in input_ids if t==IMAGE_TOKEN_ID]) == len(image_data)"],"exampleFix":"# before\nreq.input_ids = base_tokenizer(text)['input_ids']  # no image placeholder\n# after\nreq.input_ids = inkling_processor.apply_chat_template(msgs_with_image_tokens)['input_ids']  # 1 placeholder per image","handlingStrategy":"validation","validationCode":"n_ph = sum(1 for t in input_ids if t == processor.IMAGE_TOKEN_ID)\nassert n_ph == len(image_data or []), f'{n_ph} placeholders vs {len(image_data)} images'","typeGuard":"def placeholders_match(input_ids: list[int], token_id: int, data: list) -> bool:\n    return sum(1 for t in input_ids if t == token_id) == len(data or [])","tryCatchPattern":"try:\n    out = await processor.process_mm_data_async(None, im, request_obj)\nexcept ValueError as e:\n    if 'placeholder' in str(e):\n        request_obj.input_ids = re-render_with_template(msgs, n_images=len(im))\n        out = await processor.process_mm_data_async(None, im, request_obj)\n    else: raise","preventionTips":["Always render input_ids through the model's chat template","Unit-test template rendering with N images before batch runs"],"tags":["multimodal","inkling","placeholder-mismatch","input-validation"],"backgroundTag":"placeholder-count-mismatch","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}