Prhub

#56460 [httpx migration] Import httpx from huggingface_hub

原始 PR 作者 Wauplin 合并时间 2026-09-11 23:29 文件变更 7 提交数 1 评论 7 代码增减 +8 / -6

执行摘要

httpx 导入改走 huggingface_hub 再导出,为 2.x 迁移铺路

上游 huggingface_hub 正在把底层 HTTP 库从 httpx 迁移到新包 httpx2(pydantic 维护,PyPI 上自 2.0.0 起为独立包),直接 import httpx 的下游库会在 2.x 发布时失效。作者 Wauplin 在 PR body 中明示自己是 huggingface_hub 维护者,并写道:"Direct httpx imports will break when huggingface_hub migrates to httpx2. Import through huggingface_hub.utils instead, using the compatibility export shipped in huggingface_hub 1.31.0. Goal is to make vLLM compatible with both huggingface_hub 1.x and 2.x."。关联 issue huggingface/huggingface_hub#4802 给出了分步迁移计划:先在 1.31.0 的 minor 版本中把 httpx 作为 huggingface_hub.utils 的子模块暴露,再让 transformers、diffusers、vLLM 等下游改用该再导出,最后才发 2.0.0 并让下游锁到 >=1.30.0;<3.0。本 PR 是「下游适配」这一步在 vLLM 的落地。

本 PR 代码层面机械简单,不必逐行精读;但有三点值得关注:一是 requirements/common.txt 下限与四份 test 锁的一致性(这是导入改写能否工作的前提);二是 vllm/logger.py 中「双 logger 压制」的写法,作为依赖换名时的通用适配范式可复用;三是 mooncake 连接器的再导出导入,它点出了「等上游 major 迁移时下游如何零成本兼容」的模式。后续建议:1)在 GPU CI 上确认 tests/v1/kv_connector/unit/test_mooncake_connector.py 通过;2)按 issue 4802 计划为 huggingface_hub 补 <3.0 上界;3)合并后 grep 全仓源码路径,确认生产代码中不再有直接 import httpx(tests/examples 例外)。

讨论亮点
  • 讨论几乎不含技术交锋,集中在流程与验证边界上。
  • 符号可达性由作者自证:PR body 的 AI 使用声明里写明 "Verified against huggingface_hub==1.31.0 that AsyncClient, ConnectError and HTTPStatusError — the only symbols mooncake_connector.py uses — are all reachable from huggingface_hub.utils.httpx",同时坦白 "I did not run tests/v1/kv_connector/unit/test_mooncake_connector.py locally (no GPU environment on hand)"。reviewer 未要求补跑,直接依赖 CI。
  • DCO 流程问题:hmellor 提出 "@Wauplin please fix DCO",Wauplin 回复 "DCO fixed, sorry",随后 hmellor 二次 /ci run 触发 Buildkite CI #88331,最终给出 APPROVED。
  • fork PR 无法自动审查:claude[bot] 提示 "This pull request is from a fork — automated review is disabled",因此本 PR 的 review_comments_count 为 0,没有任何 inline 讨论,全部结论来自 maintainer 人工批准与 CI 结果。

实现拆解

实现拆解

  1. 归一化 httpx 导入源(运行时路径唯一改动):vllm/distributed/kv_transfer/kv_connector/v1/mooncake/mooncake_connector.py 删除第三方的 import httpx,改为在同组末尾追加 from huggingface_hub.utils import httpx(huggingface_hub 属第三方包,isort 排序后落在 zmq.asyncio 之后)。该文件只使用 AsyncClient、ConnectError、HTTPStatusError 三个符号,作者已核对它们在 huggingface_hub==1.31.0 下均可从再导出取得。影响面:这是全 PR 唯一改变运行时依赖来源的代码,也是 vLLM 生产路径中唯一被改动的直接 httpx 依赖。
  2. 日志压制同步覆盖 httpx2:vllm/logger.py 模块末尾、_configure_vllm_root_logger() 之后的模块级逻辑中,envs.VLLM_LOGGING_LEVEL == "INFO" 分支除原有的 logging.getLogger("httpx").setLevel(logging.WARNING) 外新增 logging.getLogger("httpx2").setLevel(logging.WARNING),并补注释说明 httpx2 是 huggingface_hub 2.x 将切换到的后继包。这么做的原因是 httpx2 以自己的 logger 名字输出日志,若不同步压制,迁移完成后 INFO 级网络日志会重新刷屏。该段是导入期执行一次的模块级代码,不进入任何函数,也不改变既有 httpx 压制语义。
  3. 依赖下限提升与锁文件重生成:requirements/common.txt 把 huggingface_hub >= 1.28.0 改为 >= 1.31.0,并追加注释 # Required for the huggingface_hub.utils.httpx re-export,为步骤 1 的导入方式提供版本前提。四个平台测试锁 requirements/test/{cpu,cuda,rocm,xpu}.txt 由 pre-commit 中的 pip-compile 钩子重新生成,huggingface-hub==1.28.0 统一变为 ==1.31.0,各文件仅 1 行变化。
  4. 测试与示例保持原生 httpx:按作者说明,tests/ 与 examples/ 继续直接 import httpx,因为 httpx 仍是 requirements/test/*.in 中的显式测试依赖;锁文件里也依然保留 httpx==0.27.2(schemathesis)与 httpx2==2.12.0(mcp、openai)两条独立记录,说明生产路径与测试路径的 httpx 来源在 hub 2.x 之后会分叉。
  5. 验证与测试配套:作者运行了 pre-commit run --files <changed files>(含重新生成四份锁文件的 pip-compile 钩子),并在 huggingface_hub==1.31.0 下人工核对三个符号可达;明确未在本地执行 tests/v1/kv_connector/unit/test_mooncake_connector.py(无 GPU 环境),交由 CI 覆盖。本 PR 未新增或修改任何测试文件,属于典型的「依赖接线 + 现网 CI 兜底」形态。
文件 模块 状态 重要度
vllm/distributed/kv_transfer/kv_connector/v1/mooncake/mooncake_connector.py KV 连接器 modified 5.29
vllm/logger.py 日志配置 modified 4.72
requirements/common.txt 依赖清单 modified 2.52
requirements/test/cpu.txt 测试依赖 modified 1.89

关键源码片段

vllm/distributed/kv_transfer/kv_connector/v1/mooncake/mooncake_connector.py dependency-wiring

生产路径上唯一被改动的 httpx 使用点:把 import httpx 换成 huggingface_hub.utils 的再导出,是本次兼容性改造的实质代码;该文件只依赖 AsyncClient、ConnectError、HTTPStatusError 三个符号,决定了改动的风险边界。

# vllm/distributed/kv_transfer/kv_connector/v1/mooncake/mooncake_connector.py
import asyncio
import logging
import threading
import time
from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass
from enum import IntEnum
from typing import TYPE_CHECKING, Any
​
import msgspec
import numpy as np
import torch
import zmq
import zmq.asyncio
# 关键改动:不再直接 import httpx,改用 huggingface_hub 的再导出。
# 1.31.0 起 huggingface_hub.utils 暴露 httpx 子模块;
# 等到 2.x 真正切到 httpx2 时,该再导出仍指向「当前生效的那个包」,
# 因此 vLLM 自身不需要区分 httpx 与 httpx2。
# 本文件只用到 AsyncClient、ConnectError、HTTPStatusError 三个符号,
# 作者已确认它们在 1.31.0 下都能从 huggingface_hub.utils.httpx 取到。
from huggingface_hub.utils import httpx
​
from vllm import envs
from vllm.config import VllmConfig
from vllm.distributed.kv_transfer.kv_connector.utils import (
    EngineId,
    TransferTopology,
    get_current_attn_backends,
)
# …… 其余 vllm 内部导入与后续连接器逻辑均未改动
vllm/logger.py core-logic

新增对 httpx2 logger 的级别压制,是同一个「上游换库」问题的配套处理:httpx2 以自己的 logger 名字输出日志,若不压制,迁移完成后 INFO 级网络日志会重新刷屏。

# vllm/logger.py —— 模块末尾的模块级初始化区(不属于任何函数)
# root logger 在模块导入时初始化一次,GIL 保证线程安全。
_configure_vllm_root_logger()
​
# Transformers 通过 httpx 访问 Hugging Face Hub,httpx 的日志相当啰嗦,
# 所以当 vLLM 的日志级别是 INFO 时,把它压到 WARNING。
# httpx2 是 huggingface_hub 在 2.x 中切换到的后继包,
# 并且用自己的 logger 名字输出日志,因此两个名字都要压制,
# 否则迁移完成后 httpx2 的 INFO 级日志会重新刷屏。
if envs.VLLM_LOGGING_LEVEL == "INFO":
    logging.getLogger("httpx").setLevel(logging.WARNING)
    logging.getLogger("httpx2").setLevel(logging.WARNING)
​
logger = init_logger(__name__)

评论区精华

DCO 签署缺失与 CI 触发流程 other

hmellor 要求 "@Wauplin please fix DCO",Wauplin 回复 "DCO fixed, sorry",期间 hmellor 两次执行 `/ci run`,分别触发 Buildkite CI #88330 和 #88331。

结论:已解决:DCO 在最终 commit e94c1ed 中带上 Signed-off-by,CI 重跑后 hmellor 给出 APPROVED。 · 已解决

fork PR 自动审查被禁用,缺少 inline review 测试

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.",且 review_comments_count 为 0。

结论:未补做自动审查,只由 maintainer hmellor 人工批准 + CI 覆盖;对于这种 8 增 6 删的依赖接线改动,人工 review 成本可接受。 · 已解决

再导出符号可达性与测试缺口 设计

作者在 AI 使用声明中说明 "Verified against `huggingface_hub==1.31.0` that `AsyncClient`, `ConnectError` and `HTTPStatusError` ... are all reachable from `huggingface_hub.utils.httpx`",并声明未在本地跑 `tests/v1/kv_connector/unit/test_mooncake_connector.py`(无 GPU)。

结论:reviewer 未提出异议,接受「静态核对符号 + CI 兜底」的验证方式;选择 1.31.0 作为下限(再导出首次落地的版本)。 · 已解决

日志压制是否要一并覆盖 httpx2 设计

作者主动在 vllm/logger.py 的 INFO 分支追加 logging.getLogger("httpx2").setLevel(logging.WARNING),并注释说明 httpx2 是 huggingface_hub 2.x 的后继包、有自己的 logger 名字。

结论:未被质疑并随 PR 合并,属于为换库提前清理日志噪音的防御性改动。 · 已解决

风险与影响

  • 依赖下限提升的解析风险:requirements/common.txt 抬到 huggingface_hub >= 1.31.0 后,锁定在 1.28–1.30 的内网镜像、离线 wheel 缓存或冻结环境在升级 vLLM 时可能出现依赖解析冲突。本 PR 只同步了 common.txt 与四个 test 锁,其他 requirements 变体或镜像构建脚本是否仍引用旧下限,需要在合并后另行核对(本 PR 未涉及)。
  • mooncake 路径仅静态验证:作者未在本地跑 tests/v1/kv_connector/unit/test_mooncake_connector.py。在 hub 1.x 下再导出与原生 httpx 是同一模块对象,异常捕获语义完全等价;但一旦 hub 2.x 把 huggingface_hub.utils.httpx 指向 httpx2,except httpx.ConnectError 这类捕获与仍使用原生 httpx 的第三方组件将不再是同一异常类型,跨库混用错误处理需要届时的整体验证,而不是本 PR 能覆盖的。
  • 缺少上界约束:issue 4802 计划中下游应使用 huggingface_hub>=1.30.0;<3.0,本 PR 只抬下限、未加 <3.0,未来 3.x 的破坏性变更仍会被解析进来。
  • 无新增测试覆盖:改动依赖现有 mooncake 单测与 CI 兜底,若 CI 中该套件未被触发,回归会以「启动即 ImportError/异常类型不匹配」的形式在 KV connector 用户处暴露,而非在单测阶段。
  • 日志压制副作用(极低):在 VLLM_LOGGING_LEVEL == "INFO" 时会把 httpx2 强制压到 WARNING,用户调试网络问题需显式调整日志级别;与既有 httpx 行为对称,不构成回归。
  • 对用户与部署:无需任何配置变更,行为等价;唯一受影响的是使用 mooncake KV connector 的 P/D 分离部署(kv-connector 标签相关),其 HTTP 客户端来源从 httpx 变为 huggingface_hub 再导出,在 1.x 下仍是同一个包。升级 vLLM 时依赖解析器会拉到 huggingface_hub >= 1.31.0。
  • 对生态与依赖链:vLLM 成为 huggingface_hub/httpx2 迁移中较早完成适配的下游之一(与 openai-python SDK 的迁移并列),降低 HF 2.0 发布时整个生态的摩擦;测试锁中 httpx 与 httpx2 并存,说明测试/示例路径未来会与生产路径使用不同的 HTTP 客户端库,需要在 2.0 落地时再次审视。
  • 对团队与流程:改动面 7 个文件、8 增 6 删、1 个 commit,review 成本极低;但它是长线迁移的第一步,后续需要跟随 huggingface_hub 的 major 发布节奏做联动升级与上界收敛。
依赖下限提升 缺少测试改动 KV 连接器仅静态验证 缺少版本上界 无 inline review

关联 Issue

#4802 [httpx] Let's migrate to httpx2!

完整报告

参与讨论