Skip to content

[Core][Multimodal] Skip redundant placeholder scan when token match succeeds - #52925

Merged
DarkLight1337 merged 4 commits into
vllm-project:mainfrom
Yiqin-17:main
Aug 20, 2026
Merged

DarkLight1337 merged 4 commits into
vllm-project:mainfrom
Yiqin-17:main

Conversation

@Yiqin-17

@Yiqin-17 Yiqin-17 commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Purpose

CLOSE #52924

_apply_prompt_updates first calls _apply_token_matches. If any token match fails, it falls back to _apply_text_matches. The current code always calls _find_mm_placeholders after matching, even when all token matches succeed. This call scans for placeholders again through the following path: _find_mm_placeholdersfind_mm_placeholders_iter_placeholdersprompt[start:end] == content_tokens.

The table below shows the placeholder scan time at different image resolutions with 2,500 input text tokens:

Function 480p 720p 1080p 3k
_apply_prompt_updates 5.126 ms 9.542 ms 23.728 ms 75.347 ms
_find_mm_placeholders 4.556 ms 8.950 ms 23.074 ms 74.241 ms
find_mm_placeholders 4.513 ms 8.908 ms 23.030 ms 74.198 ms
_iter_placeholders 4.328 ms 8.710 ms 22.781 ms 73.965 ms
slice compare 2.934 ms 7.150 ms 20.977 ms 71.419 ms
slice compare / total 57% 75% 88% 95%

Each function in the scan path takes almost as long as _apply_prompt_updates. This shows that placeholder scanning takes most of the total time. The slice compare row measures prompt[start_idx:end_idx_full] == content_tokens_full, which is the main bottleneck. As image resolution increases, this comparison grows from 2.934 ms to 71.419 ms and from 57% to 95% of the total _apply_prompt_updates time.

When all token matches succeed, token matching already knows each placeholder position. The code does not need to scan the prompt again. This PR collects placeholders during token matching and returns early when all matches succeed. It therefore skips _find_mm_placeholders. If any token match fails, the code still uses the existing text fallback path.

The following benchmarks compare _apply_prompt_updates before and after this change. The first benchmark fixes the image resolution and varies the input text length. The second fixes the input text length and varies the image resolution.

  1. Input text length comparison (fixed image resolutions: 720p and 3k)
input text tokens 720p 3k
baseline this PR speedup baseline this PR speedup
100.646 ms0.252 ms2.6×2.881 ms0.995 ms2.9×
500.917 ms0.268 ms3.4×3.468 ms1.004 ms3.5×
1001.226 ms0.275 ms4.5×5.480 ms1.020 ms5.4×
5003.455 ms0.342 ms10.1×18.694 ms1.101 ms17.0×
10005.517 ms0.424 ms13.0×33.501 ms1.194 ms28.1×
25009.525 ms0.685 ms13.9×75.249 ms1.479 ms50.9×
  1. Image resolution comparison (fixed input text length: 2,500 tokens)
resolution baseline this PR speedup
480p 5.126 ms 0.601 ms 8.5×
720p 9.542 ms 0.725 ms 13.2×
1080p 23.728 ms 0.944 ms 25.1×
3k 75.347 ms 1.712 ms 44.0×

As the input text grows, the speedup increases from 2.6× to 13.9× at 720p and from 2.9× to 50.9× at 3k. With 2,500 input text tokens, the optimized _apply_prompt_updates takes 0.601–1.712 ms across the tested image resolutions. This gives an 8.5×–44.0× speedup. Both benchmarks skip _find_mm_placeholders when all token matches succeed.

Behavior

Token-matching path: the code returns early only when every token match succeeds across all modalities (update_idx is not None). The unit tests show that the new path produces the same new_token_ids as the old path for general token-matching cases. _apply_token_matches_with_placeholders collects placeholders directly and removes empty modality lists.

Text fallback path: if any token match fails, the code still calls _apply_text_matches and _find_mm_placeholders. This PR does not change the fallback path.

For typical PromptReplacement updates, direct collection and rescanning return the same placeholder positions. For PromptInsertion, inserted tokens may match adjacent tokens in the original prompt. A rescan can then select an earlier range that includes an original token. Direct collection records the exact range inserted by the update. The issue below tracks this case.

Related work and performance

Related issue

Related to #52924.

This issue describes how find_mm_placeholders can return the wrong start position when tokens inserted by PromptInsertion overlap with adjacent tokens in the original prompt. This PR records the inserted token position directly. It does not treat an adjacent original token as part of the placeholder.

Related PR

Merged PR #51774 also improves multimodal prompt updates, but it targets a different bottleneck. This PR builds placeholders directly when all token matches succeed and skips the redundant _find_mm_placeholders scan. The results below measure this PR on top of #51774.

In the tables, #51774 is the baseline after that PR merged. #51774 + This PR adds this PR.

  1. 10,000 placeholders synthetic benchmark

The setup matches #51774. It uses 1,000,000 input tokens and 10,000 image placeholders. Each placeholder expands to 50 tokens, so the updated prompt contains 1,490,000 tokens. This PR reduces average latency by 45.0%.

Function Version Run 1 Run 2 Run 3 Average
_apply_prompt_updates #51774 466.451 ms 462.842 ms 462.278 ms 463.857 ms
_apply_prompt_updates #51774 + This PR 255.581 ms 255.501 ms 254.922 ms 255.335 ms
  1. 3k image with 2,500 input text tokens

We measured 64 requests with one 1728×3072 image and 2,500 input text tokens. Average latency drops from 3.432 ms to 1.409 ms. Maximum latency drops from 13.749 ms to 1.481 ms, an 89.2% reduction. The new maximum is only 5.1% above the average. The standard deviation drops from 2.587 ms to 0.024 ms, which shows more stable latency.

Function Version Requests Average Maximum Std
_apply_prompt_updates #51774 64 3.432 ms 13.749 ms 2.587 ms
_apply_prompt_updates #51774 + This PR 64 1.409 ms 1.481 ms 0.024 ms

Test Plan

This PR adds test_apply_token_matches_with_placeholders. The test reuses the five tokenized cases from test_find_update_tokens and covers:

  • PromptInsertion and PromptReplacement;
  • multimodal item counts of 0, 1, and 2;
  • matching new_token_ids against manually constructed expected output when token matching succeeds;
  • matching directly collected PlaceholderFeaturesInfo fields (modality, item_idx, start_idx, and tokens) against manually constructed expected output;
  • keeping failed token matches on the existing text fallback path.

We also keep the refactored test_find_update_tokens to verify that existing token-update results do not change.
We run test_apply_matches_many_shared_targets_scales_linearly to verify that the new helper still uses the linearly scaling prompt-update planner.

pytest tests/multimodal/test_processing.py::test_apply_token_matches_with_placeholders \
    tests/multimodal/test_processing.py::test_find_update_tokens \
    tests/multimodal/test_processing.py::test_apply_matches_many_shared_targets_scales_linearly \
    -q

Test Result

11 passed, 2 warnings in 6.30s

@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 the multi-modality Related to multi-modality (#4194) label Aug 19, 2026
@github-actions

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to the vLLM project.

💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in #pr-reviews, coordinate on features in #feat- channels, or join special interest groups in #sig- channels.

PRs do not trigger a full CI run by default. Reviewers with write access and configured trusted contributors can comment /ci run for upstream CI or /amd-ci run for AMD CI only whenever CI signals are needed.

Once the PR is approved or has the ready label, the PR author can also use the corresponding /ci run, /ci retry, and /ci cancel commands, or their /amd-ci variants. New commits do not start upstream CI automatically.

If you have any questions, please reach out to us on Slack at https://slack.vllm.ai.

Agent Guidelines

IMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban.

🚀

Comment thread vllm/multimodal/processing/processor.py Outdated
@DarkLight1337

Copy link
Copy Markdown
Member

@claude review

@DarkLight1337 DarkLight1337 added the verified Run pre-commit for new contributors without triggering other tests label Aug 20, 2026

@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.

Automated review found no bugs. The design concern raised earlier in this thread (the early-return path bypassing processor-specific overrides of _apply_token_matches/_find_mm_placeholders) has been addressed in the latest commit: _apply_token_matches_with_placeholders is now an overridable instance method, and Gemma3/Gemma3n implement their own override that calls their custom _apply_token_matches and _find_mm_placeholders, so the fast path no longer silently skips their newline-merging logic. Because this touches core, performance-critical multimodal prompt-matching logic used by all models and changes the override contract subclasses rely on, a maintainer look at the final design is still worthwhile before merge.

Extended reasoning...

Overview

This PR optimizes _apply_prompt_updates in vllm/multimodal/processing/processor.py to skip the redundant _find_mm_placeholders rescan when all token matches succeed, by collecting placeholders directly during token matching via a new _apply_token_matches_with_placeholders helper. It also updates gemma3_mm.py and gemma3n_mm.py to override this new method (since they already override _apply_token_matches/_find_mm_placeholders for newline-token handling), and adds substantial new parametrized tests in tests/multimodal/test_processing.py.

Security risks

None identified. This is internal prompt/token processing logic with no user-facing input parsing changes, auth, or external data handling.

Level of scrutiny

This warrants above-average scrutiny: it changes a core code path (_apply_prompt_updates) exercised by every multimodal model on every request, and it changes the override contract for subclasses (previously _apply_token_matches/_find_mm_placeholders; now also _apply_token_matches_with_placeholders). A subtle mismatch here could silently produce wrong placeholder positions for some model, which is hard to catch without targeted testing.

Other factors

The PR went through several rounds of maintainer feedback (DarkLight1337) about exactly this override-bypass concern, and the current commit implements the maintainer's own suggested fix (explicit overridable method with Gemma3/Gemma3n implementations) rather than the previous ad hoc runtime-type-check workaround. Verified that Gemma3 and Gemma3n are the only two processors in the repo overriding _apply_token_matches/_find_mm_placeholders, and both now have a corresponding _apply_token_matches_with_placeholders override, so no other processor is silently bypassed. Test coverage was expanded with a new test_apply_token_matches_with_placeholders test plus reuse of existing tokenized cases; I could not execute the test suite in this environment (no venv installed) so I relied on static review of the logic and diff.

@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.

LGTM, thanks for your patience!

@DarkLight1337
DarkLight1337 enabled auto-merge (squash) August 20, 2026 12:40
@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 Aug 20, 2026
@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #84828 for commit 32d7e2979763.

@DarkLight1337
DarkLight1337 merged commit cb09dd7 into vllm-project:main Aug 20, 2026
102 of 106 checks passed
zufangzhu pushed a commit to zufangzhu/vllm that referenced this pull request Aug 24, 2026
…ucceeds (vllm-project#52925)

Co-authored-by: shenyiqin <shenyiqin1@huawei.com>
Signed-off-by: Zhu, Zufang <zufang.zhu@intel.com>
am-cohere pushed a commit to am-cohere/vllm that referenced this pull request Sep 1, 2026
…ucceeds (vllm-project#52925)

Co-authored-by: shenyiqin <shenyiqin1@huawei.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

multi-modality Related to multi-modality (#4194) ready ONLY add when PR is ready to merge/full CI is needed verified Run pre-commit for new contributors without triggering other tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: find_mm_placeholders may return an incorrect position for PromptInsertion

2 participants