Prhub

#41359 Bump Transformers version to 5.10.4

原始 PR 作者 hmellor 合并时间 2026-07-07 20:13 文件变更 51 提交数 44 评论 12 代码增减 +271 / -234

执行摘要

升级 Transformers 依赖至 5.10.4 并修复多模块兼容问题

根据 PR 描述,需要将一系列上游修复(如 huggingface/transformers#46456 等)合入,以解决因 Transformers 版本变更导致的多个测试失败,包括 Entrypoints Integration、Pooling、Multi-Modal Models 等。具体问题涉及 tie_word_embeddings 属性、缺失的 fetch_images/fetch_audio 方法、scope_prefix 引入等。

值得精读,尤其是 tokenizer 适配层(vllm/tokenizers/hf.pyvllm/tokenizers/mistral.py)和多模态处理器(vllm/model_executor/models/qwen3_vl.py)的修改,展示了如何在不破坏向后兼容的前提下应对上游 API 重构。

讨论亮点

依赖安全性:depthfirst-app[bot] 指出 requirements/test/cuda.in 中指向可变 git 分支 v5.10-release 存在供应链风险(强制推送会静默改变安装代码)。作者 hmellor 回应这是临时措施,会在正式发布前改为具体版本。

合并要求:Isotr0py 要求先运行完整 CI 并更新至 transformers==5.10.3 再合并。最终标题显示升级至 5.10.4。

实现拆解

  1. 依赖版本更新:修改 requirements/test/cuda.inrequirements/test/rocm.inrequirements/test/xpu.inrequirements/test/nightly-torch.in 等文件,将 Transformers 版本从 5.5.3 提升至 5.10.4(中间经历临时 git 分支引用后锁定正式版本)。

  2. Tokenizer 适配:在 vllm/tokenizers/hf.py 中将类型别名 HfTokenizerPreTrainedTokenizer | PreTrainedTokenizerFast 改为 PythonBackend | TokenizersBackendmaybe_make_thread_pool 的类型检查同步更新。在 get_cached_tokenizer 中为 CachedTokenizer 新增 is_fast 属性(安全获取,默认 True)以及 convert_ids_to_tokensconvert_tokens_to_string 方法,当底层为 MistralCommonBackend 时委托给新增的 tekken 适配函数。

  3. 新增 Mistral Tekken 支持:在 vllm/tokenizers/mistral.py 中新增 mistral_common_tekkenizer 辅助函数提取底层 Tekkenizer,以及 tekken_convert_ids_to_tokens(处理 byte-fallback 乱码)和 tekken_convert_tokens_to_string。在 MistralTokenizer.convert_tokens_to_string 中原有手写逻辑被替换为对 tekken_convert_tokens_to_string 的调用。

  4. 多模态处理器调整:在 vllm/model_executor/models/qwen3_vl.py 中,_replace_video_token_placeholders 支持可变长度的 target 序列(从硬编码 3-token 改为通用匹配);新增 _expands_only_video_token 静态方法检测上游版本行为。MiniCPM-V 4.6 处理器新增 _recompute_cached_prompt_update 以处理 <image_id> 索引重写。多个处理器(如 InternVL、Step3VL、Isaac)继承新的 mixin 基类以适配上游处理器基类变更。

  5. 测试与临时补丁清理:删除 tests/models/multimodal/generation/vlm_utils/model_utils.pyqianfan_ocr_hf_model_kwargsqianfan_ocr_patch_hf_runner 函数,因为相关修复已在上游 Transformers 中实现。更新 token 计数测试以匹配新的 tokenizer 行为。

文件 模块 状态 重要度
vllm/tokenizers/hf.py 分词器 modified 7.86
vllm/tokenizers/mistral.py 分词器 modified 7.83
vllm/model_executor/models/qwen3_vl.py 多模态模型 modified 7.64
tests/models/multimodal/generation/vlm_utils/model_utils.py 测试工具 modified 7.63
vllm/model_executor/models/minicpmv4_6.py 多模态模型 modified 7.32
requirements/test/cuda.in 依赖配置 modified 6.48

关键符号

maybe_make_thread_pool get_cached_tokenizer mistral_common_tekkenizer tekken_convert_ids_to_tokens tekken_convert_tokens_to_string _expands_only_video_token _replace_video_token_placeholders _recompute_cached_prompt_update speech_tokenizer get_speech_tokenizer

关键源码片段

vllm/tokenizers/hf.py dependency-wiring

核心 tokenizer 适配层:`HfTokenizer` 类型别名从 `PreTrainedTokenizerFast` 改为 `TokenizersBackend`,`maybe_make_thread_pool` 类型检查同步更新;`CachedTokenizer` 新增 `is_fast` 属性和 `convert_ids_to_tokens`/`convert_tokens_to_string` 方法以支持 MistralCommonBackend。

    class CachedTokenizer(tokenizer.__class__): # 继承原 tokenizer 类以保留所有原始方法
        @property
        def all_special_ids(self) -> list[int]:
            return tokenizer_all_special_ids
​
        @property
        def all_special_tokens(self) -> list[str]:
            return tokenizer_all_special_tokens
​
        @property
        def max_token_id(self) -> int:
            return max_token_id
​
        @property
        def max_chars_per_token(self) -> int:
            return max_chars_per_token
​
        @property
        def is_fast(self) -> bool:
            # MistralCommonBackend 不实现 is_fast,所以安全获取并默认 True
            return tokenizer_is_fast
​
        def convert_ids_to_tokens(self, ids, skip_special_tokens: bool = False):
            # 如果底层是 Mistral Tekken,使用 byte-fallback 感知的转换(返回可能含 bytes 的列表)
            if mistral_tekkenizer is not None:
                from vllm.tokenizers.mistral import tekken_convert_ids_to_tokens
                return tekken_convert_ids_to_tokens(mistral_tekkenizer, ids)
            return super().convert_ids_to_tokens(ids, skip_special_tokens=skip_special_tokens)
​
        def convert_tokens_to_string(self, tokens: list[str]) -> str:
            if mistral_tekkenizer is not None:
                from vllm.tokenizers.mistral import tekken_convert_tokens_to_string
                return tekken_convert_tokens_to_string(mistral_tekkenizer, tokens)
            return super().convert_tokens_to_string(tokens)
vllm/tokenizers/mistral.py core-logic

新增 Mistral Tekken 适配函数:`mistral_common_tekkenizer` 提取底层 `Tekkenizer`,`tekken_convert_ids_to_tokens` 和 `tekken_convert_tokens_to_string` 处理 byte-fallback 转换。原 `MistralTokenizer.convert_tokens_to_string` 被简化。

def mistral_common_tekkenizer(tokenizer: object) -> "Tekkenizer | None":
    """获取 MistralCommonBackend 底层的 `Tekkenizer`,如果存在的话。"""
    mistral = getattr(tokenizer, "tokenizer", None)
    instruct = getattr(mistral, "instruct_tokenizer", None)
    tekken = getattr(instruct, "tokenizer", None)
    return tekken if isinstance(tekken, Tekkenizer) else None
​
​
def tekken_convert_ids_to_tokens(
    tokenizer: "Tekkenizer", ids: Sequence[int]
) -> list[str | bytes]:
    """
    将 IDs 转换为 tokens。对于 byte-fallback 替换导致的乱码(包含 �),
    改用 `id_to_byte_piece` 返回原始 bytes,避免信息丢失。
    """
    tokens: list[str | bytes] = [tokenizer.id_to_piece(i) for i in ids]
    if any("�" in t for t in tokens):
        # 遇到替换字符(�)时,对非特殊 token 使用 id_to_byte_piece
        tokens = [
            tokenizer.id_to_byte_piece(i, SpecialTokenPolicy.KEEP)
            if i >= tokenizer.num_special_tokens
            else tokenizer.decode([i], SpecialTokenPolicy.KEEP)
            for i in ids
        ]
    return tokens
​
​
def tekken_convert_tokens_to_string(
    tokenizer: "Tekkenizer", tokens: Sequence[str | bytes]
) -> str:
    """将 `tekken_convert_ids_to_tokens` 返回的 token 列表重新组装为字符串。"""
    if any(isinstance(t, bytes) for t in tokens):
        # 如果有 bytes 元素,需要 id 来回 decode
        ids = [_tekken_token_to_id(tokenizer, t) for t in tokens]
        return tokenizer.decode(ids, SpecialTokenPolicy.KEEP)
    return "".join(cast(Sequence[str], tokens))

评论区精华

使用可变 git 分支作为依赖的供应链风险 安全

depthfirst-app[bot] 指出指向 v5.10-release 分支而非固定 commit 存在风险;作者回应是临时措施。

结论:作者确认临时使用,正式发布时改为具体版本。 · 已解决

合并前需运行完整 CI 并更新版本 other

Isotr0py 要求先运行完整 CI 并更新至 transformers==5.10.3 再合并。

结论:最终 PR 标题显示升级至 5.10.4 且已合并,说明要求已满足。 · 已解决

风险与影响

核心路径变更:vllm/tokenizers/hf.pyHfTokenizer 类型别名变更影响整个 tokenizer 使用链;maybe_make_thread_pool 的类型判断从 PreTrainedTokenizerFast 改为 TokenizersBackend,可能导致其他第三方 tokenizer 后端未被正确识别。

多模态处理:Qwen3VL 等模型的 placeholder 展开逻辑依赖上游版本行为,如果版本判断有误可能导致视频 token 展开错误或断言失败。

测试覆盖:虽改动大量测试,但 qianfan_ocr 临时补丁被直接删除,对应测试是否充分需验证。

依赖版本跃升:Transformers 从 5.5.3 到 5.10.4 跨越多个主版本,可能存在未发现的 API 不兼容。

用户:无直接功能变化,但修复了部分模型在最新 Transformers 下的兼容性问题,提升稳定性。
系统:Transformers 版本大幅跳跃(5.5.3 -> 5.10.4),未来升级需关注类似 API 变更。
团队:合并后需确保 CI 全绿,后续维护需跟进 Transformers 版本演进。

核心路径变更 缺少测试覆盖 依赖版本跃升

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论