Prhub

#56554 [DSV4.1] Remove compressor-aware image sentinel token padding

原始 PR 作者 Isotr0py 合并时间 2026-09-12 15:07 文件变更 4 提交数 1 评论 0 代码增减 +10 / -132

执行摘要

移除 DSV4.1 误移植的 compressor 对齐 padding,token 流对齐 HF 参考

PR body 明确指出 Image sentinel padding only lives in deepseek-v4-vision-exp, not deepseek-v4.1,并以官方仓库 image_processor.py 的具体行号作为证据(v4-vision-exp 的 L135-L155 与 v4.1 的 L140-L173)。vLLM 此前从 v4-vision-exp 移植了 compressor-alignment padding 到 v4.1,这属于跨模型版本的误共享:v4.1 参考实现允许图片 token 自由跨越 compressor 组边界,不做任何相位对齐。保留该 pad 会让 vLLM 产出的图片 token 流与官方预处理不一致,进而影响路由、engram 掩码等依赖图片 sentinel 位置的下游逻辑。

值得精读。该 PR 是“参考实现比对驱动删除”的小而清晰案例:核心看点是 _apply_token_matches_with_placeholders 覆写的删除动机——pad 依赖拼接位置导致 prompt updates 无法缓存,删除后不仅 token 流对齐官方,还顺带恢复基类默认路径。对多模态模型接入团队有参考价值:跨模型版本移植预处理逻辑前应逐行比对官方 image_processor.py。建议后续补充一条针对 DSV4.1 图片 token 流 bit-match 的回归测试,并核查 mm_prefix_span_leading_pad_modulus 是否还有其他读取方。

讨论亮点

该 PR 没有实质 review 讨论线程。claude[bot] 自动评论指出 This pull request is from a fork — automated review is disabled,即 fork PR 未触发自动评审;维护者 ywang96 直接 APPROVE 且未留文字评论。PR body 中对 HF 官方 image_processor.py 行号(v4-vision-exp L135-L155 与 v4.1 L140-L173)的引用是唯一设计论据,表明这是一次基于参考实现比对的“删除误移植逻辑”决策。未解决的疑虑主要是测试证据缺口:无配套测试、Test Plan 为 TODO,以及 mm_prefix_span_leading_pad_modulus 属性删除后没有显式说明是否还有其他消费方。

实现拆解

  1. 常量与掩码精简(common/mm_preprocess.py,+10/-110):删除 COMPRESS_PAD_TO = 2、IMAGE_PAD_ID = 129265、IMAGE_PAD_TOKEN_NAME 三个常量;image_sentinel_mask 从双 id 比较退化为 token_ids == IMAGE_SENTINEL_BASE_ID 单 id 比较;validate_image_sentinel_ids 删除对 pad token 的 id 校验。这保证后续以 sentinel 掩码为基准的逻辑(MoE router 图片 token 识别、engram 掩码)不再感知 pad。

  2. splice 行为恢复基类:删除自定义 _apply_token_matches_with_placeholders 覆写(约 67 行)。原覆写在图片块拼接时按 COMPRESS_PAD_TO - 1 - start_idx % COMPRESS_PAD_TO 前插 pad,且因 pad 依赖块的最终位置而无法缓存 prompt updates;删除后恢复基类 _plan_prompt_updates 默认拼接,PlaceholderFeaturesInfo 的 token 位置与无 pad 内容天然对齐。

  3. 预算与 token 上限恢复:safe_resize 不再预留 COMPRESS_PAD_TO - 1 个 token;get_mm_max_tokens_per_item 直接返回 vision_max_n_token 而非加 pad 余量;get_image_size_with_most_features 的预算同样恢复为 vision_max_n_token。最大允许图片 token 数回落为配置值,预算临界处的图片尺寸选择与官方一致。

  4. 模型侧清理(amd/vl_model.py、nvidia/vl_model.py,各 -8):两文件同步删除 IMAGE_PAD_ID、IMAGE_SENTINEL_BASE_ID 导入,以及 embed_input_ids 中把 IMAGE_PAD_ID 通过 masked_fill 映射为 IMAGE_SENTINEL_BASE_ID 的兜底逻辑。该兜底原先用于让 pad 位置以普通图片 token 嵌入(v4.1 无 image_pad 向量);pad 不复存在后 input_ids 直接送入 embed_input_ids。

  5. 配置属性清理(vllm/transformers_utils/configs/deepseek_v41.py,-6):删除 mm_prefix_span_leading_pad_modulus = 2 属性。该属性描述 mm-prefix 可见区间前导 pad 的周期;删除后 is_mm_prefix_lm 与 mm_prefix_clamp_sliding_window 继续保留,mm-prefix 管道行为不受影响。

  6. 测试与配套:本次没有测试文件变更,PR 正文 Test Plan 与 Test Result 均为 TODO;无配置模板、部署或文档配套改动。回归保障依赖现有 DSV4.1 相关 CI 用例与维护者人工核对。

文件 模块 状态 重要度
vllm/models/deepseek_v4_1/common/mm_preprocess.py 多模态预处理 modified 7.93
vllm/models/deepseek_v4_1/amd/vl_model.py 视觉模型 modified 5.48
vllm/models/deepseek_v4_1/nvidia/vl_model.py 视觉模型 modified 5.48
vllm/transformers_utils/configs/deepseek_v41.py 模型配置 modified 5.35

关键符号

_apply_token_matches_with_placeholders image_sentinel_mask validate_image_sentinel_ids safe_resize get_mm_max_tokens_per_item get_image_size_with_most_features embed_input_ids

关键源码片段

vllm/models/deepseek_v4_1/common/mm_preprocess.py data-contract

核心文件:删除 COMPRESS_PAD_TO、IMAGE_PAD_ID 常量、IMAGE_PAD_ID 掩码及自定义 _apply_token_matches_with_placeholders 覆写,图片 token 流与 HF 参考逐位对齐(+10/-110)。

# DeepSeek-V4.1 视觉输入的 sentinel 相关常量与校验(head 状态)。
# 移除 compressor-alignment padding 后,token 流与官方 image_processor.py
# 完全一致:官方参考实现允许图片 token 跨 compressor 组边界自由拼接,
# 不做相位对齐;对齐 pad 是 deepseek-v4-vision-exp 才有的逻辑。
​
IMAGE_START, IMAGE, IMAGE_NEW_LINE, IMAGE_END = range(4)
​
IMAGE_PLACEHOLDER = "<|deepseek_image|>"
​
# checkpoint tokenizer 中 IMAGE_PLACEHOLDER 的 id(即配置里的 image_token_id)。
# 每个 image-span 位置都携带该 id,MoE router 以区间
# [IMAGE_SENTINEL_BASE_ID, IMAGE_SENTINEL_BASE_ID + 5) 识别图片 token。
# 原实现曾额外引入 IMAGE_PAD_ID = 129265(<|place_holder_mm_span_0436|>)
# 作为对齐 pad 的占位 id,本 PR 已删除。
IMAGE_SENTINEL_BASE_ID = 129264
​
​
def image_sentinel_mask(token_ids: torch.Tensor) -> torch.Tensor:
    """返回 image-span 位置的布尔掩码。"""
    # 不再需要叠加 IMAGE_PAD_ID 判断,掩码退化为单 id 比较,
    # 路由与 engram 掩码路径的语义随之简化。
    return token_ids == IMAGE_SENTINEL_BASE_ID
​
​
def validate_image_sentinel_ids(tokenizer) -> None:
    """校验图片 token id 与 tokenizer 是否一致。"""
    # 原先还会校验 IMAGE_PAD_TOKEN_NAME 对应的 id,
    # padding 移除后该项校验一并删除,只保留图片占位符校验。
    image_id = tokenizer.convert_tokens_to_ids(IMAGE_PLACEHOLDER)
    if image_id != IMAGE_SENTINEL_BASE_ID:
        raise ValueError(
            f"Image placeholder {IMAGE_PLACEHOLDER!r} has id {image_id}, "
            f"expected {IMAGE_SENTINEL_BASE_ID} (the config's "
            "image_token_id); the DeepSeek-V4.1 vision path keys image "
            "routing and engram masking off this id."
        )
​
​
def safe_resize(
    height, width, best_height, best_width, patch_size, downsample_ratio, max_n_token
):
    """收缩图片尺寸,直到图片 LLM token 消耗不超过 max_n_token。"""
    # 原先会先扣除 COMPRESS_PAD_TO - 1 个预留 token 再比较,
    # 现已移除,预算判定与官方参考完全一致。
    n_llm_h, n_llm_w = llm_grid(best_height, best_width, patch_size, downsample_ratio)
    if num_image_tokens(n_llm_h, n_llm_w) > max_n_token:
        best_height, best_width = solve_resize_ratio(
            height, width, patch_size, downsample_ratio, max_n_token
        )
        n_llm_h, n_llm_w = llm_grid(
            best_height, best_width, patch_size, downsample_ratio
        )
        assert num_image_tokens(n_llm_h, n_llm_w) <= max_n_token
    return n_llm_h, n_llm_w, best_height, best_width
​
# 原 _apply_token_matches_with_placeholders 覆写(已整体删除)会在拼接图片块时
# 前插 COMPRESS_PAD_TO - 1 - start_idx % COMPRESS_PAD_TO 个 IMAGE_PAD_ID;
# 因 v4.1 官方 image_processor.py 无此 pad,现恢复基类 _plan_prompt_updates 的
# 默认拼接,PlaceholderFeaturesInfo 的 token 位置与无 pad 内容天然对齐。
vllm/models/deepseek_v4_1/amd/vl_model.py data-contract

AMD 视觉路径同步清理:移除 IMAGE_PAD_ID 导入与 embed_input_ids 中的 masked_fill 兜底(-8 行)。

def embed_input_ids(
    self,
    input_ids: torch.Tensor,
    multimodal_embeddings: MultiModalEmbeddings | None = None,
    *,
    is_multimodal: torch.Tensor | None = None,
) -> torch.Tensor:
    from vllm.model_executor.models.utils import _merge_multimodal_embeddings
​
    # v4.1 参考实现不插入 compressor-alignment pad,token 流与 HF 完全一致,
    # 因此不再需要把 IMAGE_PAD_ID 掩码成 IMAGE_SENTINEL_BASE_ID 的兜底;
    # 原先的 masked_fill 是 Branch-free 的编译区安全写法,现已整体移除。
    inputs_embeds = self.language_model.embed_input_ids(input_ids)
​
    if multimodal_embeddings is None or len(multimodal_embeddings) == 0:
        return inputs_embeds
​
    assert is_multimodal is not None
    return _merge_multimodal_embeddings(
        inputs_embeds=inputs_embeds,
        multimodal_embeddings=multimodal_embeddings,
        is_multimodal=is_multimodal,
    )
vllm/transformers_utils/configs/deepseek_v41.py core-logic

配置类删除 mm_prefix_span_leading_pad_modulus 属性,mm-prefix 可见区间不再描述前导 pad 周期(-6 行)。

# DeepseekV41Config.__init__ 尾部(head 状态)。
# mm-prefix 相关属性中,原实现额外设置了
# mm_prefix_span_leading_pad_modulus = 2 用于描述 compressor-alignment pad
# 的周期;padding 移除后该属性不复存在,其余 mm-prefix 属性保留。
vision_n_layers = vision_config.get("num_hidden_layers", 0)
self.vision_n_layers = vision_n_layers
# 视觉变体需要 mm-prefix 管道(原子 image-span prefill + 稀疏 SWA 扩窗),
# 基础架构配置转换器读取该配置级属性。
self.is_mm_prefix_lm = vision_n_layers > 0
# 稀疏 SWA 索引内核会在 image-span 内原地扩窗,因此超过 sliding_window
# 的 mm-prefix 区间必须保留(仅这些内核消费它们)。
self.mm_prefix_clamp_sliding_window = vision_n_layers > 0
# mm_prefix_span_leading_pad_modulus 已删除:v4.1 参考实现不插入前导 pad。

评论区精华

fork PR 自动评审被禁用 other

claude[bot] 自动评论:This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

结论:未触发人工 bot 评审,无实际技术讨论产生。 · closed

维护者批准与测试证据缺失 测试

PR body 中 Test Plan 与 Test Result 均为 TODO,且无测试文件变更、无 review 评论;ywang96 直接 APPROVE 并合并。

结论:维护者基于 HF 参考代码行号证据直接批准;测试证据缺口未被讨论,遗留回归风险。 · 已解决

风险与影响

1) 行为回归:移除 pad 后图片 token 流与上一版 vLLM 行为不同,每个图片块最多少 1 个 pad token;若模型实际依赖相位对齐将影响生成质量——依据 HF 参考代码判断官方不做 pad,方向风险较低但未被自动化测试显式验证。
2) 配置属性兼容性:mm_prefix_span_leading_pad_modulus 被删除,若 vllm/v1 的 mm-prefix 或 SWA 相关模块直接读取该属性,将触发 AttributeError;本 PR 未改动其他消费者,存在隐含耦合风险。
3) 测试缺失:image_sentinel_mask 与 validate_image_sentinel_ids 语义变化(掩码范围缩小)会影响 MoE router 图片 token 识别与 engram 掩码路径,缺少对应回归测试。
4) 双路径一致性:AMD 与 NVIDIA 两份 vl_model.py 逻辑需保持同步,本次同步删除,后续单侧修改容易漂移。

对用户:使用 DeepSeek-V4.1 视觉能力的输出 token 序列与旧版 vLLM 不同(更贴近官方参考),图片 token 计数与生成长度可能变化,属预期内的保真度修正。对系统:影响 DSV4.1 的 mm-prefix 拼接、图片 sentinel 掩码、MoE 路由与 engram 掩码链路,同时覆盖 AMD 与 NVIDIA 两条硬件路径。对团队:消除了 v4-vision-exp 逻辑被误共享到 v4.1 的隐患,为后续 DSV4.1 视觉能力迭代提供正确基线;但没有配套测试,后续改动需自行补回归。整体影响集中在单模型家族(DeepSeek-V4.1),程度中等。

token 流行为变更 无配套测试 配置属性删除需核查消费者 AMD/NVIDIA 双路径同步改动

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论