Repository navigation
[Bugfix][Frontend] Align DeepSeek V4.1 images with rendered content - #60792
Conversation
Co-authored-by: Codex <noreply@openai.com> Signed-off-by: ahmed xijiaat <52128022+xijiaat@users.noreply.github.com>
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
3 open findings
What changed in this PR
Fixes DeepSeek V4.1 multimodal rendering so image attachments remain aligned with the final encoded prompt after tool-result reordering and thinking/developer-content dropping.
Changes:
- Track per-image source indices during message normalization and propagate them through V4.1 encoding/merging.
- Accumulate the final rendered image order during encoding and apply it to both
multi_modal_data["image"]andmulti_modal_uuids["image"]. - Register a dedicated
DeepseekV41Rendererand add regression tests for tool-result reorder + dropped developer-only images.
| File | Description |
|---|---|
| vllm/tokenizers/deepseek_v41_encoding.py | Propagates and collects _image_indices during preprocessing/rendering to compute a final image_order. |
| vllm/tokenizers/deepseek_v41.py | Assigns source image indices during normalization and threads image_order through apply_chat_template. |
| vllm/renderers/registry.py | Routes deepseek_v41 to the new DeepseekV41Renderer. |
| vllm/renderers/deepseek_v4.py | Adds prompt-building step that reorders/prunes image mm payloads based on image_order; introduces DeepseekV41Renderer. |
| tests/tokenizers_/test_deepseek_v41.py | Adds sync+async regression coverage for tool image reordering and dropped developer-only images. |
🧠 Review effort: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Hi @chaunceyjiang, when you have a chance, would you mind taking a look at this fix? The PR description includes the reproduction and CPU/H20 validation results, and I've replied to the automated review comments with the relevant source references. Please let me know if any additional changes or tests would be helpful. I understand reviews take time and appreciate your help. Thank you! |
thanks for the reveiw!!! |
chaunceyjiang
left a comment
There was a problem hiding this comment.
Could you paste some actual test results into the PR description?
|
Thanks @chaunceyjiang. I've added the actual CPU and full-model H20 output to the PR description: the four baseline regression failures, the patched The reversed-image case changes from |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Robert Shaw <robertgshaw2@gmail.com>
|
Merged Before: on current After: the import is now No other changes. |
|
✅ @xijiaat, CI is now available for this PR.
|
|
/ci run |
|
✅ Triggered Buildkite CI #95629 for commit |
|
What this fixes: the assistant calls two tools, [{"role": "assistant", "tool_calls": [call("red"), call("blue")]},
{"role": "tool", "tool_call_id": "blue", "content": [text("blue"), image("blue")]},
{"role": "tool", "tool_call_id": "red", "content": [text("red"), image("red")]}]The encoder sorts tool results into call order, so the prompt reads Broken (main): images are passed in message order, Fixed (this PR): images follow the prompt order, Verified with |
|
The official DeepSeek encoder had this bug too and fixed it on 2026-10-16 ( The approach differs: the reference encoder collects the images itself and returns them in prompt order, while vLLM collects them earlier, so this PR records each image's index and reorders afterwards. |
Co-authored-by: Codex <noreply@openai.com> Signed-off-by: ahmed xijiaat <52128022+xijiaat@users.noreply.github.com>
|
/ci run |
|
✅ Triggered Buildkite CI #95647 for commit |
|
Thanks @robertgshaw2-redhat for merging main, fixing the test import, validating the regression, and kicking off CI. The explanation and comparison with the official encoder were really helpful. I appreciate your time and help getting this fix reviewed! |
…llm-project#60792) Signed-off-by: ahmed xijiaat <52128022+xijiaat@users.noreply.github.com> Signed-off-by: Robert Shaw <robertgshaw2@gmail.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Robert Shaw <robertgshaw2@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>


Overview
Fix Python DeepSeek V4.1 rendering when tool results reorder image placeholders or thinking mode drops historical developer content. Carry source image indices through the native encoder and apply the resulting order to both image data and UUIDs.
This follows the source-order approach of #56366, which fixed the Rust frontend only.
Claims
Validation
The following results are from the tests run on 2026-10-09, not a new rerun. The four production files tested then are unchanged at the current PR head.
Two regression cases, each exercised through sync and async rendering; the existing V4/V4.1 tokenizer suites are also included.
Run in a CPU-only container against the source checkout, with model/processor metadata stubbed and real message parsing, image tracking and native encoding:
Actual CPU output: baseline failures and patched result
Verbatim excerpts from
regression-baseline.log:From
regression-patched.log:Full pre-commit checks passed on all five changed files, including mypy and test CI tethering.
GPU validation passed on the real DeepSeek-V4.1-Flash checkpoint (
DeepseekV41ForCausalLM), using 8x H20-3e, TP=8, eager execution and temperature=0. The GPU runtime was vLLM 0.31.0 / PyTorch 2.13.0+cu130 with this PR's four production files mounted unchanged; their SHA-256 hashes were verified against commitf5622b6. This was not a full build of main.The same loaded model compared the original media handling (the base renderer without an image-order accumulator) and the patched V4.1 renderer, using generated 560x560 red and blue images:
A=red, B=blueA=blue, B=red(wrong image assignment)A=red, B=blue; output token IDs match the ordered baselineAssertionError: Failed to apply prompt replacement for mm_items['image'][0]OKActual full-model GPU output, including generated text and token IDs
The in-container validation command was
.venv/bin/python /work/gpu_validate.py. These are verbatim result lines fromgpu.log, not reconstructed from the summary table:The log subsequently contains shutdown warnings about force-killing the remaining EngineCore and one leaked shared-memory object. The pass marker covers the inference assertions above, not a warning-free shutdown.
These checks verify the two rendering regressions, not performance or general model accuracy.
Details
The encoder already decides which content survives and in what order it renders. Image indices now follow those same merge/sort/drop operations; the renderer does not duplicate those policies or scan text for image markers. The V4.1 renderer applies the order to media and UUIDs together. Existing prompt fixtures and rendering parity checks retain the encoded text.
Duplicate checks covered open PRs, the related Rust PR's changed files and discussion, and V4.1 frontend searches. #56366 changes Rust files only; #60522 only moves Python imports and does not fix this behavior. No matching Python fix was found.
Pull Request Checklist
/pr-checklistskill. (Mandatory for agents, optional for humans).