Skip to content

[Bugfix][Multimodal] Fix DeepSeek-OCR zero-sum patch guard in _encode… - #64091

Merged
DarkLight1337 merged 5 commits into
vllm-project:mainfrom
nrenvo:fix/deepseek-ocr-zero-sum-patches
Oct 28, 2026
Merged

DarkLight1337 merged 5 commits into
vllm-project:mainfrom
nrenvo:fix/deepseek-ocr-zero-sum-patches

Conversation

@nrenvo

@nrenvo nrenvo commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

Fixes #64075.

_encode_local_features in deepseek_ocr.py and deepseek_ocr2.py used torch.sum(patches).item() == 0 to detect "no crop patches", so a tiled image whose normalized pixels cancel to zero (e.g. half-white/half-black 1536x768) wrongly returned None, dropped local features, and crashed EngineCore with a placeholder/embedding shape mismatch. Replace the guard with patches.numel() == 0; #59417 fixed the equivalent check in _parse_and_validate_image_input but left this one as "not engine-fatal", which #64075 disproves.

New test class TestDeepseekOCREncodeLocalFeaturesGuard fails on the base commit (2 failed) and passes with the fix (13 passed in tests/models/multimodal/processing/test_deepseek_ocr.py); ruff check and ruff format are clean.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@mergify mergify Bot added deepseek Related to DeepSeek models multi-modality Related to multi-modality (#4194) bug Something isn't working labels Oct 27, 2026
…_local_features

Fixes vllm-project#64075.

_encode_local_features in both deepseek_ocr.py and deepseek_ocr2.py
guarded against empty patches with:

    if torch.sum(patches).item() == 0:
        return None

This is wrong when a tiled image produces crop patches whose normalized
pixel values cancel to zero (e.g. a half-white/half-black 1536x768 image
after Normalize(0.5, 0.5)). The guard returned None even though patches
was non-empty (numel > 0), making _pixel_values_to_embedding build the
combined embedding from global features only. That embedding was shorter
than the placeholder count expected by _merge_multimodal_embeddings:

    RuntimeError: shape mismatch: value tensor of shape [257, 1280]
    cannot be broadcast to indexing result of shape [545, 1280]

The engine then raised a fatal error and shut down.

PR vllm-project#59417 fixed the equivalent guard in _parse_and_validate_image_input
(the pixel_values layer) but explicitly left _encode_local_features
unchanged, noting it "is not engine-fatal". Issue vllm-project#64075 shows it is.

Fix: replace the sum-based guard with patches.numel() == 0, which is
the correct test for "no crop patches were produced" (non-tiled image).

Tested on base commit (before fix):
  test_ocr2_zero_sum_patches_enters_model_body FAILED
  test_ocr1_zero_sum_patches_enters_model_body FAILED

With fix:
  4/4 new tests PASSED, 7/7 related tests PASSED

Signed-off-by: nrenvo <nrenvo@gmail.com>
@nrenvo
nrenvo force-pushed the fix/deepseek-ocr-zero-sum-patches branch from c295992 to 91c7398 Compare October 27, 2026 16:34
@noone-zhhh

Copy link
Copy Markdown
Contributor

Thanks for working on this!
I had already investigated the root cause in #64075 and mentioned that I was planning to submit a PR to address it.
Since I've been working on a fix as well, would you be willing to close this PR and let me submit mine?
Appreciate for your understanding!

@nrenvo

nrenvo commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Hey, thanks for the note. I already have a fix up with regression tests for both DeepSeek-OCR and OCR-2, validated against the base commit, so I'd prefer to keep this PR open and let the maintainers decide. Happy to compare approaches if that helps.

@noone-zhhh

Copy link
Copy Markdown
Contributor

Thanks, I understand you’d like to keep this PR open. The current unit tests cover the zero-sum guard, but they don’t exercise the end-to-end inference path reported in #64075. My commit adds a vllm_runner regression test for that path.

I ran the tests on an RTX 5070 Ti and observed:

Image Before fix After fix
Normal OCR photo Passed Passed
Constructed black-and-white image EngineCore crashed Passed

I’d be happy to contribute the end-to-end test if it would help strengthen this PR. As a minor style suggestion, the commit message is too long maybe.

@nrenvo

nrenvo commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, fair point — the unit tests cover the guard, but you're right that they don't exercise the full inference path. Happy to include your e2e test. Could you put the test on top of 91c7398 and open a cross-fork PR against nrenvo:fix/deepseek-ocr-zero-sum-patches with just the test file? I'll merge it and keep your authorship. If that's a hassle, paste the test file here and I'll apply it with a Co-authored-by. And thanks for the commit message note.

@noone-zhhh

Copy link
Copy Markdown
Contributor

FYI @nrenvo

Details
import os

import pytest
import torch
from PIL import Image

from vllm.transformers_utils.processors.deepseek_ocr import (
    ImageTransform,
    dynamic_preprocess,
)


@pytest.mark.core_model
@pytest.mark.parametrize(
    ("model_id", "image_size", "model_env", "load_format"),
    [
        pytest.param(
            "deepseek-ai/DeepSeek-OCR",
            640,
            "DEEPSEEK_OCR_TEST_MODEL",
            "dummy",
            id="ocr1",
        ),
        pytest.param(
            "deepseek-ai/DeepSeek-OCR-2",
            768,
            "DEEPSEEK_OCR2_TEST_MODEL",
            "auto",
            id="ocr2",
        ),
    ],
)
def test_deepseek_ocr_balanced_tiled_image(
    vllm_runner, model_id, image_size, model_env, load_format
):
    image = Image.new("RGB", (1536, 768), "white")
    image.paste("black", (768, 0, 1536, 768))

    crops, crop_ratio = dynamic_preprocess(image, image_size=image_size)
    patches = torch.stack([ImageTransform()(crop) for crop in crops])
    assert crop_ratio == (2, 1)
    assert patches.numel() > 0
    assert patches.sum().item() == 0

    model = os.environ.get(model_env, model_id)
    with vllm_runner(
        model,
        max_model_len=2048,
        gpu_memory_utilization=0.8,
        load_format=load_format,
    ) as vllm_model:
        outputs = vllm_model.generate_greedy(
            ["<image>\nFree OCR."], max_tokens=8, images=[[image]]
        )

    assert len(outputs) == 1
    assert len(outputs[0][0]) > 0

@nrenvo

nrenvo commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

Added your test as a separate commit with your authorship — thanks for putting it together.

assert torch.equal(image_input.data, pixel_values)


class TestDeepseekOCREncodeLocalFeaturesGuard:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move this to generation directory

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, moved TestDeepseekOCREncodeLocalFeaturesGuard to generation/test_deepseek_ocr_guard.py!

@@ -0,0 +1,60 @@
# SPDX-License-Identifier: Apache-2.0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's save CI cost and only include the processing tests

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, dropped the vllm_runner e2e test — the guard tests cover the fix without the extra CI cost.

@nrenvo
nrenvo force-pushed the fix/deepseek-ocr-zero-sum-patches branch from d3e1c56 to ce39a8b Compare October 28, 2026 07:09
Move TestDeepseekOCREncodeLocalFeaturesGuard from
tests/models/multimodal/processing/test_deepseek_ocr.py to
tests/models/multimodal/generation/test_deepseek_ocr_guard.py per
reviewer feedback; remove the heavier vllm_runner e2e test to save CI cost.

Signed-off-by: nrenvo <nrenvo@gmail.com>
@nrenvo
nrenvo force-pushed the fix/deepseek-ocr-zero-sum-patches branch from ce39a8b to e74ecc9 Compare October 28, 2026 07:14

@DarkLight1337 DarkLight1337 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@DarkLight1337
DarkLight1337 enabled auto-merge (squash) October 28, 2026 07:15
@DarkLight1337

Copy link
Copy Markdown
Member

/ci run

@github-actions github-actions Bot added the ready ONLY add when PR is ready to merge/full CI is needed label Oct 28, 2026
@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #97898 for commit eaad20df1ff9.

ℹ️ This PR is 78 commits behind upstream main. Merged upstream main into this branch as eaad20df1ff9 before starting CI. Pull the branch before pushing more changes.

@mergify

mergify Bot commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

Hi @nrenvo, the pre-commit checks have failed. Please run:

uv pip install pre-commit>=4.5.1
pre-commit install
pre-commit run --all-files

Then, commit the changes and push to your branch.

For future commits, pre-commit will run automatically on changed files before each commit.

Move TestDeepseekOCREncodeLocalFeaturesGuard from
tests/models/multimodal/processing/test_deepseek_ocr.py to
tests/models/multimodal/generation/test_deepseek_ocr_guard.py per
reviewer feedback; remove the heavier vllm_runner e2e test to save CI cost.

Signed-off-by: nrenvo <nrenvo@gmail.com>
auto-merge was automatically disabled October 28, 2026 07:34

Head branch was pushed to by a user without write access

@DarkLight1337

Copy link
Copy Markdown
Member

/ci run

@DarkLight1337
DarkLight1337 enabled auto-merge (squash) October 28, 2026 07:36
@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #97902 for commit 7a6f22c3e7f6.

ℹ️ This PR is 2 commits behind upstream main. Merged upstream main into this branch as 7a6f22c3e7f6 before starting CI. Pull the branch before pushing more changes.

@DarkLight1337
DarkLight1337 merged commit 76c4ad2 into vllm-project:main Oct 28, 2026
112 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working deepseek Related to DeepSeek models multi-modality Related to multi-modality (#4194) ready ONLY add when PR is ready to merge/full CI is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: DeepSeek-OCR/DeepSeek-OCR-2 crashes on a zero-sum tiled image

4 participants