[DSV4.1] Remove compressor-aware image sentinel token padding (#56554)
Signed-off-by: Isotr0py <Isotr0py@outlook.com>
Isotr0py committed
Sep 12, 2026 at 15:07 UTC
30118ba27d1d923bdd91f97d945528dcb4a862c1
4 files changed
+10
-132
vllm/models/deepseek_v4_1/amd/vl_model.py
-8
@@ -47,9 +47,7 @@ from ..common.mm_preprocess import (
47
IMAGE,
48
IMAGE_END,
49
IMAGE_NEW_LINE,
50
- IMAGE_PAD_ID,
50
IMAGE_PLACEHOLDER,
52
- IMAGE_SENTINEL_BASE_ID,
51
IMAGE_START,
52
DeepseekV4VLDummyInputsBuilder,
53
DeepseekV4VLMultiModalProcessor,
@@ -277,12 +275,6 @@ class DeepseekV41ForCausalLM(nn.Module, SupportsMultiModal, SupportsPP, Supports
275
) -> torch.Tensor:
276
from vllm.model_executor.models.utils import _merge_multimodal_embeddings
277
280
- # Compressor-alignment pads borrow a reserved id; embed them as the
281
- # plain image token (the checkpoint has no image_pad vector).
282
- # Branch-free: safe inside compiled/captured regions.
283
- input_ids = input_ids.masked_fill(
284
- input_ids == IMAGE_PAD_ID, IMAGE_SENTINEL_BASE_ID
285
- )
278
inputs_embeds = self.language_model.embed_input_ids(input_ids)
279
280
if multimodal_embeddings is None or len(multimodal_embeddings) == 0:
vllm/models/deepseek_v4_1/common/mm_preprocess.py
+10
-110
@@ -12,14 +12,9 @@ to ``[IMAGE_START] + ([IMAGE] * n_llm_w + [IMAGE_NEW_LINE]) * n_llm_h +
12
rows in reading order; the delimiters take the learned ``image_start`` /
13
``image_newline`` / ``image_end`` vectors.
14
15
-One vLLM-side deviation from the reference token stream: a leading
16
-compressor-alignment pad (``COMPRESS_PAD_TO - 1 - start % COMPRESS_PAD_TO``
17
-positions, so the span always starts at the same compressor phase) is
18
-prepended when the block is spliced into the final prompt. Pad positions
19
-borrow the reserved in-vocab token ``<|place_holder_mm_span_0436|>`` so
20
-they stay distinguishable from real span positions; they embed as the
21
-plain image token (v4.1 has no ``image_pad`` vector) and are routed and
22
-engram-deadened like image tokens.
15
+The token stream matches the reference exactly: no compressor-alignment
16
+pad is inserted (the reference pools image tokens across compressor-group
17
+boundaries freely).
18
"""
19
20
import math
@@ -30,7 +25,6 @@ import numpy as np
25
import torch
26
from PIL import Image, ImageOps
27
from transformers import BatchFeature
33
-from typing_extensions import assert_never
28
29
from vllm.config.multimodal import BaseDummyOptions, ImageDummyOptions
30
from vllm.inputs import MultiModalDataDict
@@ -44,42 +38,26 @@ from vllm.multimodal.processing import (
38
PromptUpdate,
39
PromptUpdateDetails,
40
)
47
-from vllm.multimodal.processing.processor import (
48
- MultiModalPromptUpdates,
49
- MultiModalPromptUpdatesApplyResult,
50
- PlaceholderFeaturesInfo,
51
- UpdateMode,
52
- _plan_prompt_updates,
53
-)
41
from vllm.transformers_utils.configs.deepseek_v41 import DeepseekV41Config
42
43
IMAGE_START, IMAGE, IMAGE_NEW_LINE, IMAGE_END = range(4)
57
-# Image spans are padded so they always start at the same compressor group
58
-# phase; v4.1 uses ratio-2 compressors (v4.0 used 4).
59
-COMPRESS_PAD_TO = 2
44
45
IMAGE_PLACEHOLDER = "<|deepseek_image|>"
46
47
# The checkpoint tokenizer's id for IMAGE_PLACEHOLDER (the config's
48
# ``image_token_id``). Every image-span position carries this id, exactly
49
# like the reference; the MoE router detects image tokens as the range
66
-# [IMAGE_SENTINEL_BASE_ID, IMAGE_SENTINEL_BASE_ID + 5), which also covers
67
-# IMAGE_PAD_ID below.
50
+# [IMAGE_SENTINEL_BASE_ID, IMAGE_SENTINEL_BASE_ID + 5).
51
IMAGE_SENTINEL_BASE_ID = 129264
69
-# Leading compressor-alignment pads borrow this reserved in-vocab token
70
-# (``<|place_holder_mm_span_0436|>``) so they can be told apart from real
71
-# span positions when the per-position role is decided from token ids.
72
-IMAGE_PAD_ID = 129265
73
-IMAGE_PAD_TOKEN_NAME = "<|place_holder_mm_span_0436|>"
52
53
54
def image_sentinel_mask(token_ids: torch.Tensor) -> torch.Tensor:
77
- """Boolean mask for image-span positions (span tokens and align pads)."""
78
- return (token_ids == IMAGE_SENTINEL_BASE_ID) | (token_ids == IMAGE_PAD_ID)
55
+ """Boolean mask for image-span positions."""
56
+ return token_ids == IMAGE_SENTINEL_BASE_ID
57
58
59
def validate_image_sentinel_ids(tokenizer) -> None:
82
- """Check the image/pad token ids against the tokenizer."""
60
+ """Check the image token id against the tokenizer."""
61
image_id = tokenizer.convert_tokens_to_ids(IMAGE_PLACEHOLDER)
62
if image_id != IMAGE_SENTINEL_BASE_ID:
63
raise ValueError(
@@ -88,13 +66,6 @@ def validate_image_sentinel_ids(tokenizer) -> None:
66
"image_token_id); the DeepSeek-V4.1 vision path keys image "
67
"routing and engram masking off this id."
68
)
91
- pad_id = tokenizer.convert_tokens_to_ids(IMAGE_PAD_TOKEN_NAME)
92
- if pad_id != IMAGE_PAD_ID:
93
- raise ValueError(
94
- f"Image pad token {IMAGE_PAD_TOKEN_NAME!r} has id {pad_id}, "
95
- f"expected {IMAGE_PAD_ID}; the DeepSeek-V4.1 vision path "
96
- "borrows this reserved id for compressor-alignment pads."
97
- )
69
70
71
def llm_grid(best_height, best_width, patch_size, downsample_ratio):
@@ -134,8 +105,7 @@ def safe_resize(
105
height, width, best_height, best_width, patch_size, downsample_ratio, max_n_token
106
):
107
"""Shrink the pixel size until the image costs at most max_n_token LLM
137
- tokens (minus the reservation for the compressor-alignment pad)."""
138
- max_n_token -= COMPRESS_PAD_TO - 1
108
+ tokens."""
109
n_llm_h, n_llm_w = llm_grid(best_height, best_width, patch_size, downsample_ratio)
110
if num_image_tokens(n_llm_h, n_llm_w) > max_n_token:
111
best_height, best_width = solve_resize_ratio(
@@ -293,13 +263,7 @@ class DeepseekV4VLProcessingInfo(BaseProcessingInfo):
263
seq_len: int,
264
mm_counts: Mapping[str, int],
265
) -> Mapping[str, int]:
296
- # ``safe_resize`` reserves COMPRESS_PAD_TO - 1 tokens of
297
- # vision_max_n_token for the compressor-alignment pad, so the full
298
- # image span is bounded by vision_max_n_token; the margin is kept
299
- # in case that reservation changes.
300
- return {
301
- "image": self.get_hf_config().vision_max_n_token + COMPRESS_PAD_TO - 1,
302
- }
266
+ return {"image": self.get_hf_config().vision_max_n_token}
267
268
def get_image_placeholder_token_id(self) -> int:
269
token_id = self.get_tokenizer().convert_tokens_to_ids(IMAGE_PLACEHOLDER)
@@ -314,7 +278,7 @@ class DeepseekV4VLProcessingInfo(BaseProcessingInfo):
278
# A square maximizes the ViT patch count (area) within the token
279
# budget; solve the budget-derived size directly to keep the dummy
280
# image small.
317
- budget = hf_config.vision_max_n_token - (COMPRESS_PAD_TO - 1)
281
+ budget = hf_config.vision_max_n_token
282
side = budget * patch_size * downsample_ratio
283
best_h, best_w = solve_resize_ratio(
284
side, side, patch_size, downsample_ratio, budget
@@ -397,67 +361,3 @@ class DeepseekV4VLMultiModalProcessor(
361
replacement=get_image_replacement,
362
),
363
]
400
-
401
- def _apply_token_matches_with_placeholders(
402
- self,
403
- token_ids: list[int],
404
- mm_prompt_updates: MultiModalPromptUpdates,
405
- ) -> tuple[
406
- list[int],
407
- MultiModalPromptUpdatesApplyResult,
408
- Mapping[str, list[PlaceholderFeaturesInfo]],
409
- ]:
410
- # Same as the base implementation, except that each image block gets
411
- # its compressor-alignment pad (``COMPRESS_PAD_TO - 1 - start %
412
- # COMPRESS_PAD_TO`` IMAGE_PAD_ID tokens) prepended while splicing:
413
- # the pad depends on the block's final position in the prompt, which
414
- # is unknown when the (cacheable) prompt updates are built. Pad
415
- # positions are not embed positions (their id differs from
416
- # image_token_id), so ``is_embed`` stays aligned with the pad-free
417
- # replacement content.
418
- matched_updates, result = _plan_prompt_updates(token_ids, mm_prompt_updates)
419
- placeholders: dict[str, list[PlaceholderFeaturesInfo]] = {
420
- modality: [] for modality in mm_prompt_updates
421
- }
422
-
423
- new_token_ids = list[int]()
424
- prev_end_idx = 0
425
- for matched_update in matched_updates:
426
- update = matched_update.update
427
- match = matched_update.match
428
-
429
- if update.mode == UpdateMode.INSERT:
430
- end_idx_to_insert = match.end_idx
431
- elif update.mode == UpdateMode.REPLACE:
432
- end_idx_to_insert = match.start_idx
433
- else:
434
- assert_never(update.mode)
435
-
436
- new_token_ids.extend(token_ids[prev_end_idx:end_idx_to_insert])
437
- start_idx = len(new_token_ids)
438
-
439
- tokens = list(update.content.full)
440
- if tokens and update.modality == "image":
441
- compress_pad = COMPRESS_PAD_TO - 1 - start_idx % COMPRESS_PAD_TO
442
- tokens = [IMAGE_PAD_ID] * compress_pad + tokens
443
-
444
- if tokens:
445
- content_is_embed = update.content.is_embed
446
- is_embed = (
447
- content_is_embed(tokens) if content_is_embed is not None else None
448
- )
449
- placeholders[update.modality].append(
450
- PlaceholderFeaturesInfo(
451
- modality=update.modality,
452
- item_idx=update.item_idx,
453
- start_idx=start_idx,
454
- tokens=tokens,
455
- is_embed=is_embed,
456
- )
457
- )
458
- new_token_ids.extend(tokens)
459
-
460
- prev_end_idx = match.end_idx
461
-
462
- new_token_ids.extend(token_ids[prev_end_idx:])
463
- return new_token_ids, result, placeholders
vllm/models/deepseek_v4_1/nvidia/vl_model.py
-8
@@ -47,9 +47,7 @@ from ..common.mm_preprocess import (
47
IMAGE,
48
IMAGE_END,
49
IMAGE_NEW_LINE,
50
- IMAGE_PAD_ID,
50
IMAGE_PLACEHOLDER,
52
- IMAGE_SENTINEL_BASE_ID,
51
IMAGE_START,
52
DeepseekV4VLDummyInputsBuilder,
53
DeepseekV4VLMultiModalProcessor,
@@ -277,12 +275,6 @@ class DeepseekV41ForCausalLM(nn.Module, SupportsMultiModal, SupportsPP, Supports
275
) -> torch.Tensor:
276
from vllm.model_executor.models.utils import _merge_multimodal_embeddings
277
280
- # Compressor-alignment pads borrow a reserved id; embed them as the
281
- # plain image token (the checkpoint has no image_pad vector).
282
- # Branch-free: safe inside compiled/captured regions.
283
- input_ids = input_ids.masked_fill(
284
- input_ids == IMAGE_PAD_ID, IMAGE_SENTINEL_BASE_ID
285
- )
278
inputs_embeds = self.language_model.embed_input_ids(input_ids)
279
280
if multimodal_embeddings is None or len(multimodal_embeddings) == 0:
vllm/transformers_utils/configs/deepseek_v41.py
-6
@@ -68,9 +68,3 @@ class DeepseekV41Config(PretrainedConfig):
68
# in-kernel, so mm-prefix ranges longer than sliding_window must be
69
# kept (they are only consumed by those kernels).
70
self.mm_prefix_clamp_sliding_window = vision_n_layers > 0
71
- # The visibility span covers the image block [IMAGE_START,
72
- # IMAGE_END]; the mm placeholder additionally carries a leading
73
- # compressor-alignment pad of ``COMPRESS_PAD_TO - 1 - offset %
74
- # COMPRESS_PAD_TO`` tokens (see common/mm_preprocess.py), with
75
- # COMPRESS_PAD_TO = 2 for v4.1's ratio-2 compressors.
76
- self.mm_prefix_span_leading_pad_modulus = 2 if vision_n_layers > 0 else 0