diff --git a/tests/conftest.py b/tests/conftest.py index db097fbc455..2c5a907e557 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -31,7 +31,7 @@ import pytest import torch import torch.nn as nn import torch.nn.functional as F -from huggingface_hub import snapshot_download +from vllm.transformers_utils.repo_utils import hf_api from PIL import Image from transformers import ( AutoConfig, @@ -1496,7 +1496,7 @@ _dummy_gemma2_embedding_path = os.path.join(temp_dir, "dummy_gemma2_embedding") def dummy_opt_path(): json_path = os.path.join(_dummy_opt_path, "config.json") if not os.path.exists(_dummy_opt_path): - snapshot_download( + hf_api().snapshot_download( repo_id="facebook/opt-125m", local_dir=_dummy_opt_path, ignore_patterns=["*.bin", "*.bin.index.json", "*.pt", "*.h5", "*.msgpack"], @@ -1514,7 +1514,7 @@ def dummy_opt_path(): def dummy_llava_path(): json_path = os.path.join(_dummy_llava_path, "config.json") if not os.path.exists(_dummy_llava_path): - snapshot_download( + hf_api().snapshot_download( repo_id="llava-hf/llava-1.5-7b-hf", local_dir=_dummy_llava_path, ignore_patterns=[ @@ -1539,7 +1539,7 @@ def dummy_llava_path(): def dummy_gemma2_embedding_path(): json_path = os.path.join(_dummy_gemma2_embedding_path, "config.json") if not os.path.exists(_dummy_gemma2_embedding_path): - snapshot_download( + hf_api().snapshot_download( repo_id="BAAI/bge-multilingual-gemma2", local_dir=_dummy_gemma2_embedding_path, ignore_patterns=[ diff --git a/tests/distributed/test_rocm_quick_reduce.py b/tests/distributed/test_rocm_quick_reduce.py index 06933fb0af2..a7e91f236a2 100644 --- a/tests/distributed/test_rocm_quick_reduce.py +++ b/tests/distributed/test_rocm_quick_reduce.py @@ -15,12 +15,12 @@ from unittest.mock import patch import pytest import torch import torch.distributed as dist -from huggingface_hub import snapshot_download import vllm.envs as envs from vllm import LLM, SamplingParams from vllm.distributed import cleanup_dist_env_and_memory from vllm.platforms import current_platform +from vllm.transformers_utils.repo_utils import hf_api from vllm.utils.network_utils import get_open_port pytestmark = pytest.mark.skipif( @@ -210,11 +210,11 @@ def _log_prompt_summaries() -> None: @lru_cache(maxsize=1) def _get_model_path() -> str: try: - path = snapshot_download(repo_id=MODEL_NAME, local_files_only=True) + path = hf_api().snapshot_download(repo_id=MODEL_NAME, local_files_only=True) _log(f"using cached model snapshot: {path}") return path except Exception: - path = snapshot_download(repo_id=MODEL_NAME) + path = hf_api().snapshot_download(repo_id=MODEL_NAME) _log(f"downloaded model snapshot: {path}") return path diff --git a/tests/entrypoints/conftest.py b/tests/entrypoints/conftest.py index c2e9a1de318..27383f87ea7 100644 --- a/tests/entrypoints/conftest.py +++ b/tests/entrypoints/conftest.py @@ -190,30 +190,32 @@ number: "1" | "2" @pytest.fixture(scope="session") def qwen3_lora_files(): """Download Qwen3 LoRA files once per test session.""" - from huggingface_hub import snapshot_download + from vllm.transformers_utils.repo_utils import hf_api - return snapshot_download(repo_id="charent/self_cognition_Alice") + return hf_api().snapshot_download(repo_id="charent/self_cognition_Alice") @pytest.fixture(scope="session") def qwen3_meowing_lora_files(): """Download Qwen3 LoRA files once per test session.""" - from huggingface_hub import snapshot_download + from vllm.transformers_utils.repo_utils import hf_api - return snapshot_download(repo_id="Jackmin108/Qwen3-0.6B-Meow-LoRA") + return hf_api().snapshot_download(repo_id="Jackmin108/Qwen3-0.6B-Meow-LoRA") @pytest.fixture(scope="session") def qwen3_woofing_lora_files(): """Download Qwen3 LoRA files once per test session.""" - from huggingface_hub import snapshot_download + from vllm.transformers_utils.repo_utils import hf_api - return snapshot_download(repo_id="Jackmin108/Qwen3-0.6B-Woof-LoRA") + return hf_api().snapshot_download(repo_id="Jackmin108/Qwen3-0.6B-Woof-LoRA") @pytest.fixture(scope="session") def opt125_lora_files() -> str: """Download opt-125m LoRA files once per test session.""" - from huggingface_hub import snapshot_download + from vllm.transformers_utils.repo_utils import hf_api - return snapshot_download(repo_id="peft-internal-testing/opt-125m-dummy-lora") + return hf_api().snapshot_download( + repo_id="peft-internal-testing/opt-125m-dummy-lora" + ) diff --git a/tests/entrypoints/multimodal/openai/chat_completion/test_chat_completion_with_mixed_audio_embeds.py b/tests/entrypoints/multimodal/openai/chat_completion/test_chat_completion_with_mixed_audio_embeds.py index a417f860f61..aac68334bf9 100644 --- a/tests/entrypoints/multimodal/openai/chat_completion/test_chat_completion_with_mixed_audio_embeds.py +++ b/tests/entrypoints/multimodal/openai/chat_completion/test_chat_completion_with_mixed_audio_embeds.py @@ -12,10 +12,10 @@ import pytest_asyncio import safetensors import torch import torch.nn as nn -from huggingface_hub import hf_hub_download from transformers import AutoConfig, AutoTokenizer from tests.utils import RemoteOpenAIServer +from vllm.transformers_utils.repo_utils import hf_api from vllm.utils.serial_utils import tensor2base64 from vllm.utils.torch_utils import is_torch_equal_or_newer @@ -126,11 +126,13 @@ def qwen2audio_aligned_content_and_embeds_b64() -> tuple[str, str]: content = "Describe this audio." tokenizer = AutoTokenizer.from_pretrained(QWEN2AUDIO_MODEL, trust_remote_code=True) - index_path = hf_hub_download(QWEN2AUDIO_MODEL, "model.safetensors.index.json") + index_path = hf_api().hf_hub_download( + QWEN2AUDIO_MODEL, "model.safetensors.index.json" + ) with open(index_path) as f: weight_map = json.load(f)["weight_map"] embed_key = next(k for k in weight_map if k.endswith("embed_tokens.weight")) - shard_path = hf_hub_download(QWEN2AUDIO_MODEL, weight_map[embed_key]) + shard_path = hf_api().hf_hub_download(QWEN2AUDIO_MODEL, weight_map[embed_key]) with safetensors.safe_open(shard_path, framework="pt", device="cpu") as f: embed_weight = f.get_tensor(embed_key) embed_layer = nn.Embedding.from_pretrained(embed_weight.to(QWEN2AUDIO_DTYPE)) diff --git a/tests/entrypoints/multimodal/openai/chat_completion/test_chat_completion_with_mixed_image_embeds.py b/tests/entrypoints/multimodal/openai/chat_completion/test_chat_completion_with_mixed_image_embeds.py index dbbed3c4712..c6ce165cf39 100644 --- a/tests/entrypoints/multimodal/openai/chat_completion/test_chat_completion_with_mixed_image_embeds.py +++ b/tests/entrypoints/multimodal/openai/chat_completion/test_chat_completion_with_mixed_image_embeds.py @@ -13,12 +13,12 @@ import pytest_asyncio import safetensors import torch import torch.nn as nn -from huggingface_hub import hf_hub_download from transformers import AutoTokenizer from tests.utils import RemoteOpenAIServer from vllm.assets.image import ImageAsset from vllm.multimodal.utils import encode_image_url +from vllm.transformers_utils.repo_utils import hf_api from vllm.utils.serial_utils import tensor2base64 MODEL_NAME = "Qwen/Qwen2-VL-2B-Instruct" @@ -84,11 +84,11 @@ def aligned_content_and_embeds_b64() -> tuple[str, str]: content = "Describe this image." tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True) - index_path = hf_hub_download(MODEL_NAME, "model.safetensors.index.json") + index_path = hf_api().hf_hub_download(MODEL_NAME, "model.safetensors.index.json") with open(index_path) as f: weight_map = json.load(f)["weight_map"] embed_key = next(k for k in weight_map if k.endswith("embed_tokens.weight")) - shard_path = hf_hub_download(MODEL_NAME, weight_map[embed_key]) + shard_path = hf_api().hf_hub_download(MODEL_NAME, weight_map[embed_key]) with safetensors.safe_open(shard_path, framework="pt", device="cpu") as f: embed_weight = f.get_tensor(embed_key) embed_layer = nn.Embedding.from_pretrained(embed_weight.to(MODEL_DTYPE)) diff --git a/tests/entrypoints/multimodal/openai/chat_completion/test_default_mm_loras.py b/tests/entrypoints/multimodal/openai/chat_completion/test_default_mm_loras.py index e285c8d3139..eca23d0a48a 100644 --- a/tests/entrypoints/multimodal/openai/chat_completion/test_default_mm_loras.py +++ b/tests/entrypoints/multimodal/openai/chat_completion/test_default_mm_loras.py @@ -6,17 +6,19 @@ import os import openai # use the official client for correctness check import pytest import pytest_asyncio -from huggingface_hub import snapshot_download from tests.conftest import AudioTestAssets from tests.utils import RemoteOpenAIServer +from vllm.transformers_utils.repo_utils import hf_api # NOTE - the tests in this module are currently analogous to test_chat, but are # separated to avoid OOM killing due to module-scoped servers, since we # need a multimodal model for these tests. # Contains a modality specific lora alongside the base model -MULTIMODAL_MODEL_NAME = snapshot_download("microsoft/Phi-4-multimodal-instruct") +MULTIMODAL_MODEL_NAME = hf_api().snapshot_download( + "microsoft/Phi-4-multimodal-instruct" +) AUDIO_LORA_PATH = os.path.join(MULTIMODAL_MODEL_NAME, "speech-lora") ACTIVE_MM_LORA_RESPONSE = "Spoken text: The first words I spoke in the original chronograph, a little piece of practical poetry. Mary had a little lamb, it slept with quite a snow, and everywhere that Mary went, the lamb was sure to go." # noqa: E501 diff --git a/tests/entrypoints/openai/chat_completion/test_chat.py b/tests/entrypoints/openai/chat_completion/test_chat.py index dbfb48f2351..32c72f1ef93 100644 --- a/tests/entrypoints/openai/chat_completion/test_chat.py +++ b/tests/entrypoints/openai/chat_completion/test_chat.py @@ -27,9 +27,9 @@ MODEL_NAME = "HuggingFaceH4/zephyr-7b-beta" @pytest.fixture(scope="module") def zephyr_lora_files(): """Download zephyr LoRA files once per test session.""" - from huggingface_hub import snapshot_download + from vllm.transformers_utils.repo_utils import hf_api - return snapshot_download(repo_id="typeof/zephyr-7b-beta-lora") + return hf_api().snapshot_download(repo_id="typeof/zephyr-7b-beta-lora") @pytest.fixture(scope="module") diff --git a/tests/entrypoints/pooling/scoring/util.py b/tests/entrypoints/pooling/scoring/util.py index 8aab9cd1069..dbf4b3dc3ac 100644 --- a/tests/entrypoints/pooling/scoring/util.py +++ b/tests/entrypoints/pooling/scoring/util.py @@ -6,7 +6,6 @@ from io import BytesIO import pybase64 as base64 import torch import torch.nn.functional as F -from huggingface_hub import hf_hub_download from PIL import Image from safetensors.torch import load_file from transformers import AutoModel, AutoTokenizer @@ -18,6 +17,7 @@ from vllm.entrypoints.chat_utils import ( ) from vllm.entrypoints.pooling.scoring.typing import ScoreMultiModalParam from vllm.entrypoints.pooling.scoring.utils import compute_maxsim_score +from vllm.transformers_utils.repo_utils import hf_api class ColBERTScoringHfRunner(torch.nn.Module): @@ -38,7 +38,7 @@ class ColBERTScoringHfRunner(torch.nn.Module): ).to(self.device) self.model.eval() - path = hf_hub_download(model_name, filename="model.safetensors") + path = hf_api().hf_hub_download(model_name, filename="model.safetensors") weights = load_file(path) self.linear_weight = weights[linear_weights_key].to(self.device).float() diff --git a/tests/entrypoints/serve/sagemaker/conftest.py b/tests/entrypoints/serve/sagemaker/conftest.py index d36c20ccd9a..0c76d25b423 100644 --- a/tests/entrypoints/serve/sagemaker/conftest.py +++ b/tests/entrypoints/serve/sagemaker/conftest.py @@ -21,9 +21,9 @@ HEADER_SAGEMAKER_NEW_SESSION_ID = "X-Amzn-SageMaker-New-Session-Id" @pytest.fixture(scope="session") def smollm2_lora_files(): """Download LoRA files once per test session.""" - from huggingface_hub import snapshot_download + from vllm.transformers_utils.repo_utils import hf_api - return snapshot_download(repo_id=LORA_ADAPTER_NAME_SMOLLM) + return hf_api().snapshot_download(repo_id=LORA_ADAPTER_NAME_SMOLLM) @pytest.fixture(scope="module") diff --git a/tests/entrypoints/tool_parsers/test_hermes_tool_parser.py b/tests/entrypoints/tool_parsers/test_hermes_tool_parser.py index 5d769c0fd88..f9a7bef2533 100644 --- a/tests/entrypoints/tool_parsers/test_hermes_tool_parser.py +++ b/tests/entrypoints/tool_parsers/test_hermes_tool_parser.py @@ -6,13 +6,13 @@ import json import openai import pytest import pytest_asyncio -from huggingface_hub import snapshot_download from typing_extensions import TypedDict from tests.utils import RemoteOpenAIServer from vllm.tool_parsers.abstract_tool_parser import ToolParser from vllm.tool_parsers.granite4_tool_parser import Granite4ToolParser from vllm.tool_parsers.hermes_tool_parser import Hermes2ProToolParser +from vllm.transformers_utils.repo_utils import hf_api LORA_MODEL = "minpeter/LoRA-Llama-3.2-1B-tool-vllm-ci" @@ -91,7 +91,7 @@ def server_config(request): config = CONFIGS[request.param] # download model and tokenizer using transformers - snapshot_download(config["model"]) + hf_api().snapshot_download(config["model"]) yield CONFIGS[request.param] diff --git a/tests/kernels/quantization/test_nvfp4_emulation.py b/tests/kernels/quantization/test_nvfp4_emulation.py index d2056fe01eb..eb057cebd91 100644 --- a/tests/kernels/quantization/test_nvfp4_emulation.py +++ b/tests/kernels/quantization/test_nvfp4_emulation.py @@ -2,7 +2,6 @@ # SPDX-FileCopyrightText: Copyright contributors to the vLLM project from typing import cast -import huggingface_hub import pytest import torch from safetensors import safe_open @@ -33,6 +32,7 @@ from vllm.model_executor.layers.quantization.utils.quant_utils import ( kNvfp4Static, ) from vllm.platforms import current_platform +from vllm.transformers_utils.repo_utils import hf_api from vllm.triton_utils import triton if current_platform.is_rocm(): @@ -56,7 +56,7 @@ MOE_MODEL_CONFIGS = { @pytest.fixture(scope="module") def loaded_model_files(): return { - model_id: huggingface_hub.snapshot_download( + model_id: hf_api().snapshot_download( repo_id=model_id, allow_patterns=config["shards"] ) for model_id, config in MOE_MODEL_CONFIGS.items() diff --git a/tests/lora/conftest.py b/tests/lora/conftest.py index dea54ed21ae..899b3129d62 100644 --- a/tests/lora/conftest.py +++ b/tests/lora/conftest.py @@ -9,7 +9,6 @@ from unittest.mock import MagicMock import pytest import torch import torch.nn as nn -from huggingface_hub import snapshot_download from vllm.distributed import ( cleanup_dist_env_and_memory, @@ -25,6 +24,7 @@ from vllm.model_executor.layers.logits_processor import LogitsProcessor from vllm.model_executor.layers.vocab_parallel_embedding import ParallelLMHead from vllm.model_executor.models.interfaces import SupportsLoRA from vllm.platforms import current_platform +from vllm.transformers_utils.repo_utils import hf_api @pytest.fixture() @@ -170,43 +170,43 @@ def dummy_model_gate_up(default_vllm_config) -> nn.Module: def mixtral_lora_files(): # Note: this module has incorrect adapter_config.json to test # https://github.com/vllm-project/vllm/pull/5909/files. - return snapshot_download(repo_id="SangBinCho/mixtral-lora") + return hf_api().snapshot_download(repo_id="SangBinCho/mixtral-lora") @pytest.fixture(scope="session") def chatglm3_lora_files(): - return snapshot_download(repo_id="jeeejeee/chatglm3-text2sql-spider") + return hf_api().snapshot_download(repo_id="jeeejeee/chatglm3-text2sql-spider") @pytest.fixture(scope="session") def baichuan_lora_files(): - return snapshot_download(repo_id="jeeejeee/baichuan7b-text2sql-spider") + return hf_api().snapshot_download(repo_id="jeeejeee/baichuan7b-text2sql-spider") @pytest.fixture(scope="session") def baichuan_zero_lora_files(): # all the lora_B weights are initialized to zero. - return snapshot_download(repo_id="jeeejeee/baichuan7b-zero-init") + return hf_api().snapshot_download(repo_id="jeeejeee/baichuan7b-zero-init") @pytest.fixture(scope="session") def baichuan_regex_lora_files(): - return snapshot_download(repo_id="jeeejeee/baichuan-7b-lora-zero-regex") + return hf_api().snapshot_download(repo_id="jeeejeee/baichuan-7b-lora-zero-regex") @pytest.fixture(scope="session") def ilama_lora_files(): - return snapshot_download(repo_id="jeeejeee/ilama-text2sql-spider") + return hf_api().snapshot_download(repo_id="jeeejeee/ilama-text2sql-spider") @pytest.fixture(scope="session") def minicpmv_lora_files(): - return snapshot_download(repo_id="jeeejeee/minicpmv25-lora-pokemon") + return hf_api().snapshot_download(repo_id="jeeejeee/minicpmv25-lora-pokemon") @pytest.fixture(scope="session") def qwen2vl_lora_files(): - return snapshot_download(repo_id="jeeejeee/qwen2-vl-lora-pokemon") + return hf_api().snapshot_download(repo_id="jeeejeee/qwen2-vl-lora-pokemon") @pytest.fixture(scope="session") @@ -217,74 +217,84 @@ def qwen25vl_base_huggingface_id(): @pytest.fixture(scope="session") def qwen25vl_lora_files(): - return snapshot_download(repo_id="jeeejeee/qwen25-vl-lora-pokemon") + return hf_api().snapshot_download(repo_id="jeeejeee/qwen25-vl-lora-pokemon") @pytest.fixture(scope="session") def qwen2vl_language_lora_files(): - return snapshot_download(repo_id="prashanth058/qwen2vl-flickr-lora-language") + return hf_api().snapshot_download( + repo_id="prashanth058/qwen2vl-flickr-lora-language" + ) @pytest.fixture(scope="session") def qwen2vl_vision_tower_connector_lora_files(): - return snapshot_download(repo_id="prashanth058/qwen2vl-flickr-lora-tower-connector") + return hf_api().snapshot_download( + repo_id="prashanth058/qwen2vl-flickr-lora-tower-connector" + ) @pytest.fixture(scope="session") def qwen2vl_vision_tower_lora_files(): - return snapshot_download(repo_id="prashanth058/qwen2vl-flickr-lora-tower") + return hf_api().snapshot_download(repo_id="prashanth058/qwen2vl-flickr-lora-tower") @pytest.fixture(scope="session") def qwen25vl_vision_lora_files(): - return snapshot_download(repo_id="EpochEcho/qwen2.5-3b-vl-lora-vision-connector") + return hf_api().snapshot_download( + repo_id="EpochEcho/qwen2.5-3b-vl-lora-vision-connector" + ) @pytest.fixture(scope="session") def qwen3vl_vision_lora_files(): - return snapshot_download(repo_id="EpochEcho/qwen3-4b-vl-lora-vision-connector") + return hf_api().snapshot_download( + repo_id="EpochEcho/qwen3-4b-vl-lora-vision-connector" + ) @pytest.fixture(scope="session") def qwen3_meowing_lora_files(): """Download Qwen3 Meow LoRA files once per test session.""" - return snapshot_download(repo_id="Jackmin108/Qwen3-0.6B-Meow-LoRA") + return hf_api().snapshot_download(repo_id="Jackmin108/Qwen3-0.6B-Meow-LoRA") @pytest.fixture(scope="session") def qwen3_woofing_lora_files(): """Download Qwen3 Woof LoRA files once per test session.""" - return snapshot_download(repo_id="Jackmin108/Qwen3-0.6B-Woof-LoRA") + return hf_api().snapshot_download(repo_id="Jackmin108/Qwen3-0.6B-Woof-LoRA") @pytest.fixture(scope="session") def tinyllama_lora_files(): - return snapshot_download(repo_id="jashing/tinyllama-colorist-lora") + return hf_api().snapshot_download(repo_id="jashing/tinyllama-colorist-lora") @pytest.fixture(scope="session") def deepseekv2_lora_files(): - return snapshot_download(repo_id="wuchen01/DeepSeek-V2-Lite-Chat-All-LoRA") + return hf_api().snapshot_download(repo_id="wuchen01/DeepSeek-V2-Lite-Chat-All-LoRA") @pytest.fixture(scope="session") def gptoss20b_lora_files(): - return snapshot_download(repo_id="jeeejeee/gpt-oss-20b-lora-adapter-text2sql") + return hf_api().snapshot_download( + repo_id="jeeejeee/gpt-oss-20b-lora-adapter-text2sql" + ) @pytest.fixture(scope="session") def qwen3moe_lora_files(): - return snapshot_download(repo_id="jeeejeee/qwen3-moe-text2sql-spider") + return hf_api().snapshot_download(repo_id="jeeejeee/qwen3-moe-text2sql-spider") @pytest.fixture(scope="session") def olmoe_lora_files(): - return snapshot_download(repo_id="jeeejeee/olmoe-instruct-text2sql-spider") + return hf_api().snapshot_download(repo_id="jeeejeee/olmoe-instruct-text2sql-spider") @pytest.fixture(scope="session") def qwen3_lora_files(): - return snapshot_download(repo_id="charent/self_cognition_Alice") + return hf_api().snapshot_download(repo_id="charent/self_cognition_Alice") @pytest.fixture(scope="session") @@ -295,32 +305,40 @@ def llama32_lora_huggingface_id(): @pytest.fixture(scope="session") def llama32_lora_files(llama32_lora_huggingface_id): - return snapshot_download(repo_id=llama32_lora_huggingface_id) + return hf_api().snapshot_download(repo_id=llama32_lora_huggingface_id) @pytest.fixture(scope="session") def whisper_lora_files(): - return snapshot_download(repo_id="chengyili2005/whisper-small-mandarin-lora") + return hf_api().snapshot_download( + repo_id="chengyili2005/whisper-small-mandarin-lora" + ) @pytest.fixture(scope="session") def qwen35_text_lora_files(): - return snapshot_download(repo_id="jeeejeee/qwen35-4b-text-only-sql-lora") + return hf_api().snapshot_download(repo_id="jeeejeee/qwen35-4b-text-only-sql-lora") @pytest.fixture(scope="session") def qwen35_vl_lora_files(): - return snapshot_download(repo_id="jeeejeee/qwen35-4b-all-linear-pokemon-lora") + return hf_api().snapshot_download( + repo_id="jeeejeee/qwen35-4b-all-linear-pokemon-lora" + ) @pytest.fixture(scope="session") def qwen36_moe_2d_lora_files(): - return snapshot_download(repo_id="jeeejeee/qwen36-35ba3b-2d-weights-poken-lora") + return hf_api().snapshot_download( + repo_id="jeeejeee/qwen36-35ba3b-2d-weights-poken-lora" + ) @pytest.fixture(scope="session") def qwen36_moe_3d_lora_files(): - return snapshot_download(repo_id="jeeejeee/qwen36-35ba3b-moe-all-linear-poken-lora") + return hf_api().snapshot_download( + repo_id="jeeejeee/qwen36-35ba3b-moe-all-linear-poken-lora" + ) @pytest.fixture diff --git a/tests/lora/test_default_mm_loras.py b/tests/lora/test_default_mm_loras.py index 19c910e2453..5e3c762f79b 100644 --- a/tests/lora/test_default_mm_loras.py +++ b/tests/lora/test_default_mm_loras.py @@ -8,15 +8,15 @@ import os import unittest.mock as mock import pytest -from huggingface_hub import snapshot_download from vllm.lora.request import LoRARequest from vllm.platforms import current_platform +from vllm.transformers_utils.repo_utils import hf_api from ..conftest import AudioTestAssets, VllmRunner from ..utils import create_new_process_for_each_test -MODEL_PATH = snapshot_download("microsoft/Phi-4-multimodal-instruct") +MODEL_PATH = hf_api().snapshot_download("microsoft/Phi-4-multimodal-instruct") AUDIO_LORA_PATH = os.path.join(MODEL_PATH, "speech-lora") IMAGE_LORA_PATH = os.path.join(MODEL_PATH, "vision-lora") diff --git a/tests/model_executor/model_loader/runai_streamer_loader/test_runai_model_streamer_s3.py b/tests/model_executor/model_loader/runai_streamer_loader/test_runai_model_streamer_s3.py index d60c9ba64cb..d7abc80edbf 100644 --- a/tests/model_executor/model_loader/runai_streamer_loader/test_runai_model_streamer_s3.py +++ b/tests/model_executor/model_loader/runai_streamer_loader/test_runai_model_streamer_s3.py @@ -3,10 +3,10 @@ from pathlib import Path -from huggingface_hub import snapshot_download from runai_model_streamer.safetensors_streamer.streamer_mock import StreamerPatcher from vllm.engine.arg_utils import EngineArgs +from vllm.transformers_utils.repo_utils import hf_api from .conftest import RunaiDummyExecutor @@ -25,7 +25,7 @@ def test_runai_model_loader_download_files_s3_mocked_with_patch( # Download model from HF mock_model_dir = f"{tmp_path}/gpt2" - snapshot_download(repo_id=test_model, local_dir=mock_model_dir) + hf_api().snapshot_download(repo_id=test_model, local_dir=mock_model_dir) monkeypatch.setattr( "vllm.transformers_utils.runai_utils.runai_list_safetensors", diff --git a/tests/model_executor/model_loader/test_sharded_state_loader.py b/tests/model_executor/model_loader/test_sharded_state_loader.py index a0b5a2a4aec..48672d4314d 100644 --- a/tests/model_executor/model_loader/test_sharded_state_loader.py +++ b/tests/model_executor/model_loader/test_sharded_state_loader.py @@ -9,11 +9,11 @@ from tempfile import TemporaryDirectory import pytest import torch -from huggingface_hub import snapshot_download from vllm import LLM, SamplingParams from vllm.model_executor.model_loader import ShardedStateLoader from vllm.platforms import current_platform +from vllm.transformers_utils.repo_utils import hf_api prompts = [ "Hello, my name is", @@ -52,7 +52,7 @@ def test_filter_subtensors(): @pytest.fixture(scope="module") def llama_3p2_1b_files(): - input_dir = snapshot_download( + input_dir = hf_api().snapshot_download( "meta-llama/Llama-3.2-1B-Instruct", ignore_patterns=["*.bin*", "original/*"] ) diff --git a/tests/models/language/pooling/test_colbert.py b/tests/models/language/pooling/test_colbert.py index 6c82ad8a9ca..810d7064d75 100644 --- a/tests/models/language/pooling/test_colbert.py +++ b/tests/models/language/pooling/test_colbert.py @@ -126,10 +126,11 @@ def _load_hf_model(model_name: str, hf_spec: dict, device: torch.device): def _load_projection_weight(model_name: str, hf_spec: dict, device: torch.device): """Download and return the ColBERT linear projection weight.""" - from huggingface_hub import hf_hub_download from safetensors.torch import load_file - path = hf_hub_download(model_name, filename=hf_spec["weights_file"]) + from vllm.transformers_utils.repo_utils import hf_api + + path = hf_api().hf_hub_download(model_name, filename=hf_spec["weights_file"]) weights = load_file(path) return weights[hf_spec["weights_key"]].to(device) diff --git a/tests/models/multimodal/generation/test_phi4mm.py b/tests/models/multimodal/generation/test_phi4mm.py index 1a4fb35a28a..5ab75e145ae 100644 --- a/tests/models/multimodal/generation/test_phi4mm.py +++ b/tests/models/multimodal/generation/test_phi4mm.py @@ -6,7 +6,6 @@ from collections.abc import Sequence import pytest import regex as re -from huggingface_hub import snapshot_download from transformers import AutoTokenizer from vllm.assets.image import ImageAsset @@ -14,6 +13,7 @@ from vllm.logprobs import SampleLogprobs from vllm.lora.request import LoRARequest from vllm.multimodal.image import convert_image_mode, rescale_image_size from vllm.multimodal.media.audio import load_audio +from vllm.transformers_utils.repo_utils import hf_api from ....conftest import ( IMAGE_ASSETS, @@ -35,7 +35,7 @@ HF_MULTIIMAGE_IMAGE_PROMPT = ( "<|user|>\n<|image_1|>\n<|image_2|>\nDescribe these images.<|end|>\n<|assistant|>\n" # noqa: E501 ) -model_path = snapshot_download("microsoft/Phi-4-multimodal-instruct") +model_path = hf_api().snapshot_download("microsoft/Phi-4-multimodal-instruct") # Since the vision-lora and speech-lora co-exist with the base model, # we have to manually specify the path of the lora weights. vision_lora_path = os.path.join(model_path, "vision-lora") diff --git a/tests/models/multimodal/pooling/test_intern_vit.py b/tests/models/multimodal/pooling/test_intern_vit.py index d7b67b8bdb6..72d2a7018bf 100644 --- a/tests/models/multimodal/pooling/test_intern_vit.py +++ b/tests/models/multimodal/pooling/test_intern_vit.py @@ -3,11 +3,11 @@ import pytest import torch import torch.nn as nn -from huggingface_hub import snapshot_download from transformers import AutoConfig, AutoModel, CLIPImageProcessor from vllm.distributed import cleanup_dist_env_and_memory from vllm.platforms import current_platform +from vllm.transformers_utils.repo_utils import hf_api from vllm.utils.torch_utils import STR_DTYPE_TO_TORCH_DTYPE from ....conftest import ImageTestAssets @@ -31,7 +31,7 @@ def run_intern_vit_test( *, dtype: str, ): - model = snapshot_download(model_id, allow_patterns=DOWNLOAD_PATTERN) + model = hf_api().snapshot_download(model_id, allow_patterns=DOWNLOAD_PATTERN) torch_dtype = STR_DTYPE_TO_TORCH_DTYPE[dtype] img_processor = CLIPImageProcessor.from_pretrained(model) diff --git a/tests/models/multimodal/pooling/test_radio.py b/tests/models/multimodal/pooling/test_radio.py index fcab077fbba..7c039ab8b9c 100644 --- a/tests/models/multimodal/pooling/test_radio.py +++ b/tests/models/multimodal/pooling/test_radio.py @@ -3,13 +3,13 @@ import pytest import torch import torch.nn as nn -from huggingface_hub import snapshot_download from transformers import AutoConfig, AutoModel, CLIPImageProcessor from vllm.distributed import cleanup_dist_env_and_memory from vllm.model_executor.models.radio import RadioModel from vllm.platforms import current_platform from vllm.transformers_utils.configs.radio import RadioConfig +from vllm.transformers_utils.repo_utils import hf_api from vllm.utils.torch_utils import STR_DTYPE_TO_TORCH_DTYPE from ....conftest import ImageTestAssets @@ -28,7 +28,7 @@ def run_radio_test( *, dtype: str, ): - model = snapshot_download(model_id, allow_patterns=DOWNLOAD_PATTERN) + model = hf_api().snapshot_download(model_id, allow_patterns=DOWNLOAD_PATTERN) torch_dtype = STR_DTYPE_TO_TORCH_DTYPE[dtype] img_processor = CLIPImageProcessor.from_pretrained(model) diff --git a/tests/models/quantization/test_gpt_oss.py b/tests/models/quantization/test_gpt_oss.py index 783f1773d21..f7a7b98c3ea 100644 --- a/tests/models/quantization/test_gpt_oss.py +++ b/tests/models/quantization/test_gpt_oss.py @@ -22,6 +22,7 @@ import pytest from packaging import version from vllm.platforms import current_platform +from vllm.transformers_utils.repo_utils import hf_api if current_platform.is_rocm(): from vllm.platforms.rocm import on_gfx950 @@ -47,7 +48,7 @@ QUARK_MXFP4_AVAILABLE = importlib.util.find_spec("quark") is not None and versio def has_huggingface_access(repo): try: - huggingface_hub.list_repo_refs(repo) + hf_api().list_repo_refs(repo) return True except huggingface_hub.errors.RepositoryNotFoundError: return False diff --git a/tests/plugins_tests/gguf/test_gguf_plugin_multimodal.py b/tests/plugins_tests/gguf/test_gguf_plugin_multimodal.py index cc7a021e981..56930a7d0bc 100644 --- a/tests/plugins_tests/gguf/test_gguf_plugin_multimodal.py +++ b/tests/plugins_tests/gguf/test_gguf_plugin_multimodal.py @@ -8,12 +8,12 @@ os.environ["TOKENIZERS_PARALLELISM"] = "true" from typing import Any, NamedTuple import pytest -from huggingface_hub import hf_hub_download from pytest import MarkDecorator from transformers import AutoModelForImageTextToText from vllm.assets.image import ImageAsset from vllm.multimodal.image import rescale_image_size +from vllm.transformers_utils.repo_utils import hf_api from vllm.utils.torch_utils import set_default_torch_num_threads from ...conftest import IMAGE_ASSETS, HfRunner, VllmRunner @@ -33,8 +33,8 @@ class GGUFMMTestConfig(NamedTuple): @property def gguf_model(self): - hf_hub_download(self.gguf_repo, filename=self.gguf_mmproj) - return hf_hub_download(self.gguf_repo, filename=self.gguf_backbone) + hf_api().hf_hub_download(self.gguf_repo, filename=self.gguf_mmproj) + return hf_api().hf_hub_download(self.gguf_repo, filename=self.gguf_backbone) # Common prompts aligned with test_common.py "gemma3" entry format diff --git a/tests/plugins_tests/lora_resolvers/test_filesystem_resolver.py b/tests/plugins_tests/lora_resolvers/test_filesystem_resolver.py index d4adf6f84cf..4ea8d8605c1 100644 --- a/tests/plugins_tests/lora_resolvers/test_filesystem_resolver.py +++ b/tests/plugins_tests/lora_resolvers/test_filesystem_resolver.py @@ -4,9 +4,9 @@ import os import shutil import pytest -from huggingface_hub import snapshot_download from vllm.plugins.lora_resolvers.filesystem_resolver import FilesystemResolver +from vllm.transformers_utils.repo_utils import hf_api MODEL_NAME = "Qwen/Qwen3-0.6B" LORA_NAME = "charent/self_cognition_Alice" @@ -22,12 +22,12 @@ def adapter_cache(request, tmpdir_factory): @pytest.fixture(scope="module") def qwen3_lora_files(): - return snapshot_download(repo_id=LORA_NAME) + return hf_api().snapshot_download(repo_id=LORA_NAME) @pytest.fixture(scope="module") def pa_files(): - return snapshot_download(repo_id=PA_NAME) + return hf_api().snapshot_download(repo_id=PA_NAME) @pytest.mark.asyncio diff --git a/tests/quantization/test_modelopt.py b/tests/quantization/test_modelopt.py index d2541dcc910..c8f8a429e37 100644 --- a/tests/quantization/test_modelopt.py +++ b/tests/quantization/test_modelopt.py @@ -42,12 +42,12 @@ def _skip(msg: str) -> NoReturn: def _snapshot_download_or_skip(model_id: str) -> str: try: - from huggingface_hub import snapshot_download + from vllm.transformers_utils.repo_utils import hf_api except Exception as e: # pragma: no cover _skip(f"huggingface_hub is required to download {model_id}: {e}") try: - return snapshot_download( + return hf_api().snapshot_download( repo_id=model_id, repo_type="model", # These checkpoints are already small; download full repo for simplicity. diff --git a/tests/quantization/test_quark.py b/tests/quantization/test_quark.py index 1d531fcfbfe..b31b8580e34 100644 --- a/tests/quantization/test_quark.py +++ b/tests/quantization/test_quark.py @@ -30,6 +30,7 @@ from vllm.model_executor.layers.quantization.utils.quant_utils import ( is_layer_skipped, ) from vllm.platforms import current_platform +from vllm.transformers_utils.repo_utils import hf_api if current_platform.is_rocm(): from vllm.platforms.rocm import on_gfx942, on_gfx950 @@ -59,7 +60,7 @@ if QUARK_MXFP4_AVAILABLE: from quark.torch.quantization.config.config import FP4PerGroupSpec try: - huggingface_hub.list_repo_refs( + hf_api().list_repo_refs( "amd/Llama-3.3-70B-Instruct-WMXFP4-AMXFP4-KVFP8-Scale-UINT8-SQ" ) HF_HUB_AMD_ORG_ACCESS = True diff --git a/tests/tool_use/conftest.py b/tests/tool_use/conftest.py index ff9cdeeb737..909e12ddc82 100644 --- a/tests/tool_use/conftest.py +++ b/tests/tool_use/conftest.py @@ -3,10 +3,10 @@ import pytest import pytest_asyncio -from huggingface_hub import snapshot_download from tests.utils import RemoteOpenAIServer from vllm.platforms import current_platform +from vllm.transformers_utils.repo_utils import hf_api from .utils import ARGS, CONFIGS, ServerConfig @@ -47,7 +47,7 @@ def server_config(request): ) # download model and tokenizer using transformers - snapshot_download(config["model"]) + hf_api().snapshot_download(config["model"]) yield CONFIGS[request.param] diff --git a/tests/tool_use/mistral/conftest.py b/tests/tool_use/mistral/conftest.py index 9b0a6eb27fc..984b3a51416 100644 --- a/tests/tool_use/mistral/conftest.py +++ b/tests/tool_use/mistral/conftest.py @@ -3,10 +3,10 @@ import pytest import pytest_asyncio -from huggingface_hub import snapshot_download from tests.utils import RemoteOpenAIServer from vllm.platforms import current_platform +from vllm.transformers_utils.repo_utils import hf_api from .utils import ARGS, CONFIGS, ServerConfig @@ -22,7 +22,7 @@ def server_config(request): ) # download model and tokenizer using transformers - snapshot_download(config["model"]) + hf_api().snapshot_download(config["model"]) yield CONFIGS[request.param] diff --git a/tests/utils.py b/tests/utils.py index 645e988dbb8..90f12d444e2 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -33,7 +33,6 @@ import pytest import requests import torch import torch.nn.functional as F -from huggingface_hub import hf_hub_download from huggingface_hub.constants import HF_HUB_OFFLINE from openai.types.completion import Completion from typing_extensions import ParamSpec @@ -57,6 +56,7 @@ from vllm.model_executor.layers.quantization.utils.quant_utils import ( from vllm.model_executor.model_loader import get_model_loader from vllm.platforms import current_platform from vllm.tokenizers import get_tokenizer +from vllm.transformers_utils.repo_utils import hf_api from vllm.utils.argparse_utils import FlexibleArgumentParser from vllm.utils.mem_constants import GB_bytes from vllm.utils.network_utils import get_open_port @@ -77,7 +77,7 @@ def prewarm_hf_cache(assets: list[tuple[str, str]]) -> None: return for repo_id, filename in assets: try: - hf_hub_download(repo_id=repo_id, filename=filename) + hf_api().hf_hub_download(repo_id=repo_id, filename=filename) except Exception as e: logger.warning( "Failed to prefetch %s/%s: %r. Tests depending on this asset may fail.", diff --git a/tools/pre_commit/check_forbidden_imports.py b/tools/pre_commit/check_forbidden_imports.py index a2fc173f035..a788cecc6ce 100644 --- a/tools/pre_commit/check_forbidden_imports.py +++ b/tools/pre_commit/check_forbidden_imports.py @@ -6,6 +6,13 @@ from dataclasses import dataclass, field import regex as re +# Hub entry points that must go through the vLLM-tagged repo_utils helpers. +_HF_NAMES = ( + r"HfApi|HfFileSystem|hf_hub_download|snapshot_download" + r"|list_repo_files|file_exists|try_to_load_from_cache" + r"|list_repo_refs|repo_exists" +) + @dataclass class ForbiddenImport: @@ -13,6 +20,7 @@ class ForbiddenImport: tip: str allowed_pattern: re.Pattern = re.compile(r"^$") # matches nothing by default allowed_files: set[str] = field(default_factory=set) + allowed_dirs: set[str] = field(default_factory=set) CHECK_IMPORTS = { @@ -83,6 +91,23 @@ CHECK_IMPORTS = { ), allowed_files={"vllm/triton_utils/importing.py"}, ), + "huggingface_hub repo API": ForbiddenImport( + # Catch `from huggingface_hub import `, including parenthesized, + # multi-line imports. + pattern=( + r"^\s*from\s+huggingface_hub\s+import\s*\([^)]*\b(?:" + _HF_NAMES + r")\b" + r"|" + r"^\s*from\s+huggingface_hub\s+import\b[^\n]*\b(?:" + _HF_NAMES + r")\b" + ), + tip=( + "Use the shared, vLLM-tagged helpers from " + "vllm.transformers_utils.repo_utils (e.g. hf_api(), hf_fs(), " + "list_repo_files, file_exists) instead of calling " + "huggingface_hub directly." + ), + allowed_files={"vllm/transformers_utils/repo_utils.py"}, + allowed_dirs={"examples/"}, + ), } @@ -95,11 +120,18 @@ def check_file(path: str) -> int: # Skip files that are allowed for this import if path in forbidden_import.allowed_files: continue + # Skip directories that are allowed for this import + if any(path.startswith(prefix) for prefix in forbidden_import.allowed_dirs): + continue # Search for forbidden imports for match in re.finditer(forbidden_import.pattern, content, re.MULTILINE): # Check if it's allowed if forbidden_import.allowed_pattern.match(match.group()): continue + # Skip matches inside a comment + line_start = content.rfind("\n", 0, match.start()) + 1 + if "#" in content[line_start : match.start()]: + continue # Calculate line number from match position line_num = content[: match.start() + 1].count("\n") + 1 print( @@ -119,7 +151,10 @@ def main(): def test_regex(): - test_cases = [ + def matches(rule: str, content: str) -> bool: + return bool(re.search(CHECK_IMPORTS[rule].pattern, content, re.MULTILINE)) + + pickle_cases = [ # Should match ("import pickle", True), ("import cloudpickle", True), @@ -139,11 +174,47 @@ def test_regex(): ("print('import pickle')", False), ("import pickleas as asdf", False), ] - for i, (line, should_match) in enumerate(test_cases): - result = bool(CHECK_IMPORTS["pickle/cloudpickle"].pattern.match(line)) + for i, (content, should_match) in enumerate(pickle_cases): + result = matches("pickle/cloudpickle", content) assert result == should_match, ( - f"Test case {i} failed: '{line}' (expected {should_match}, got {result})" + f"pickle case {i} failed: {content!r} " + f"(expected {should_match}, got {result})" ) + + hf_cases = [ + # Should match + ("from huggingface_hub import snapshot_download", True), + ("from huggingface_hub import hf_hub_download", True), + ("from huggingface_hub import HfApi", True), + ("from huggingface_hub import HfFileSystem", True), + ("from huggingface_hub import list_repo_files", True), + ("from huggingface_hub import try_to_load_from_cache", True), + (" from huggingface_hub import snapshot_download", True), + ("from huggingface_hub import PyTorchModelHubMixin, hf_hub_download", True), + ("from huggingface_hub import (snapshot_download)", True), + # Parenthesized multi-line import must not bypass the hook + ("from huggingface_hub import (\n snapshot_download,\n)", True), + ( + "from huggingface_hub import (\n PyTorchModelHubMixin,\n HfApi,\n)", + True, + ), + # Should not match + ("import huggingface_hub", False), + ("import huggingface_hub as hf", False), + ("from huggingface_hub import PyTorchModelHubMixin", False), + ("from huggingface_hub.constants import HF_HUB_CACHE", False), + ("from huggingface_hub.utils import EntryNotFoundError", False), + ("from vllm.transformers_utils.repo_utils import hf_api", False), + ("from huggingface_hub import (\n PyTorchModelHubMixin,\n)", False), + ("# from huggingface_hub import snapshot_download", False), + ] + for i, (content, should_match) in enumerate(hf_cases): + result = matches("huggingface_hub repo API", content) + assert result == should_match, ( + f"huggingface_hub case {i} failed: {content!r} " + f"(expected {should_match}, got {result})" + ) + print("All regex tests passed.") diff --git a/vllm/model_executor/models/llava_onevision2.py b/vllm/model_executor/models/llava_onevision2.py index 552e2a9e8f8..e17398cc8b7 100644 --- a/vllm/model_executor/models/llava_onevision2.py +++ b/vllm/model_executor/models/llava_onevision2.py @@ -24,8 +24,6 @@ from __future__ import annotations import hashlib import importlib -import json -import os from collections.abc import Callable, Iterable, Mapping, Sequence from functools import lru_cache from typing import ( @@ -39,7 +37,6 @@ import regex as re import torch import torch.nn as nn import torch.nn.functional as F -from huggingface_hub import hf_hub_download from PIL import Image from transformers import AutoProcessor, AutoTokenizer, BatchFeature from transformers.dynamic_module_utils import get_class_from_dynamic_module @@ -108,6 +105,7 @@ from vllm.multimodal.video import ( ) from vllm.sequence import IntermediateTensors from vllm.transformers_utils.processor import _merge_mm_kwargs +from vllm.transformers_utils.repo_utils import get_hf_file_to_dict from vllm.transformers_utils.utils import convert_model_repo_to_path from vllm.utils.tensor_schema import TensorSchema, TensorShape @@ -165,13 +163,10 @@ def _load_ov2_processor( # codec video backend keeps its configured defaults. codec_config: dict = {} try: - config_file = os.path.join(path, "preprocessor_config.json") - if not os.path.isfile(config_file): - config_file = hf_hub_download( - path, "preprocessor_config.json", revision=revision - ) - with open(config_file, encoding="utf-8") as f: - codec_config = json.load(f).get("codec", {}) or {} + preprocessor_config = get_hf_file_to_dict( + "preprocessor_config.json", path, revision + ) + codec_config = (preprocessor_config or {}).get("codec", {}) or {} except Exception: logger.debug("OV2: no codec defaults found in preprocessor_config.json")