diff --git a/tests/diffusion/attention/test_attention_config.py b/tests/diffusion/attention/test_attention_config.py index 4a053b1bf..0b975f27c 100644 --- a/tests/diffusion/attention/test_attention_config.py +++ b/tests/diffusion/attention/test_attention_config.py @@ -42,6 +42,14 @@ from vllm_omni.diffusion.data import ( pytestmark = [pytest.mark.core_model, pytest.mark.diffusion, pytest.mark.cpu] +@pytest.fixture(autouse=True) +def _clear_inherited_attention_backend(monkeypatch): + # Weekly CPU exports DIFFUSION_ATTENTION_BACKEND; OmniDiffusionConfig + # treats that as an explicit default so auto-backend cases break. + # Tests that setenv the same variable still work: this fixture runs first. + monkeypatch.delenv("DIFFUSION_ATTENTION_BACKEND", raising=False) + + class TestAttentionSpec: def test_construct_no_skip_softmax(self): spec = AttentionSpec(backend="FLASH_ATTN") @@ -416,13 +424,6 @@ class TestBuildAttentionConfig: class TestOmniDiffusionConfigAttentionParsing: """Test OmniDiffusionConfig attention shorthand and structured config.""" - @pytest.fixture(autouse=True) - def _clear_diffusion_attention_backend_env(self, monkeypatch): - # OmniDiffusionConfig.__post_init__ applies DIFFUSION_ATTENTION_BACKEND via - # build_attention_config(); clear it so these tests assert config defaults, - # not whatever the process inherited from CI / sibling tests. - monkeypatch.delenv("DIFFUSION_ATTENTION_BACKEND", raising=False) - def test_diffusion_attention_backend_sets_default(self): config = OmniDiffusionConfig.from_kwargs(diffusion_attention_backend="SAGE_ATTN") assert isinstance(config.diffusion_attention_config, AttentionConfig) diff --git a/tests/diffusion/model_loader/test_hub_prefetch.py b/tests/diffusion/model_loader/test_hub_prefetch.py index 94170edcc..b17189539 100644 --- a/tests/diffusion/model_loader/test_hub_prefetch.py +++ b/tests/diffusion/model_loader/test_hub_prefetch.py @@ -3,21 +3,21 @@ import contextlib -import huggingface_hub import pytest +from tests.helpers.mock import patch_hf_snapshot_download from vllm_omni.diffusion.model_loader import hub_prefetch pytestmark = [pytest.mark.core_model, pytest.mark.diffusion, pytest.mark.cpu] -def test_prefetch_subfolders_propagates_revision(monkeypatch): +def test_prefetch_subfolders_propagates_revision(monkeypatch, tmp_path): calls = [] - def fake_snapshot_download(self, **kwargs): + def fake_snapshot_download(**kwargs): calls.append(kwargs) - monkeypatch.setattr(huggingface_hub.HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) monkeypatch.setattr(hub_prefetch, "_repo_prefetch_lock", lambda _model: contextlib.nullcontext()) hub_prefetch.prefetch_subfolders( diff --git a/tests/diffusion/models/hunyuan_image3/test_hunyuan_image3_it2i_multi_image.py b/tests/diffusion/models/hunyuan_image3/test_hunyuan_image3_it2i_multi_image.py index 1e0fd1590..14a854a7a 100644 --- a/tests/diffusion/models/hunyuan_image3/test_hunyuan_image3_it2i_multi_image.py +++ b/tests/diffusion/models/hunyuan_image3/test_hunyuan_image3_it2i_multi_image.py @@ -1,5 +1,5 @@ # SPDX-License-Identifier: Apache-2.0 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project +# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project """Multi-image input regression for HunyuanImage3 IT2I prompt construction. The official HunyuanImage-3.0-Instruct supports up to 3 reference images @@ -41,7 +41,7 @@ pytestmark = [pytest.mark.core_model, pytest.mark.cpu] class FakeTokenizer: - """Recording fake tokenizer mirroring the one in test_prompt_utils. + """Recording fake tokenizer mirroring the one in test_hunyuan_image3_prompt_utils. Special token ids: `<|startoftext|>`=1, ``=2, ``=3, ``=4. encode() returns one id per character starting at diff --git a/tests/diffusion/models/hunyuan_image3/test_prompt_utils.py b/tests/diffusion/models/hunyuan_image3/test_hunyuan_image3_prompt_utils.py similarity index 99% rename from tests/diffusion/models/hunyuan_image3/test_prompt_utils.py rename to tests/diffusion/models/hunyuan_image3/test_hunyuan_image3_prompt_utils.py index 7a467e359..a2be63fd2 100644 --- a/tests/diffusion/models/hunyuan_image3/test_prompt_utils.py +++ b/tests/diffusion/models/hunyuan_image3/test_hunyuan_image3_prompt_utils.py @@ -1,5 +1,5 @@ # SPDX-License-Identifier: Apache-2.0 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project +# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project from __future__ import annotations import ast diff --git a/tests/diffusion/models/magi2/test_pipeline_magi2.py b/tests/diffusion/models/magi2/test_pipeline_magi2.py index 2cda7e66e..4e6057457 100644 --- a/tests/diffusion/models/magi2/test_pipeline_magi2.py +++ b/tests/diffusion/models/magi2/test_pipeline_magi2.py @@ -171,7 +171,7 @@ def test_huggingface_url_resolves_pinned_snapshot(tmp_path, monkeypatch): else pytest.fail("unexpected snapshot request") ) monkeypatch.setattr( - "vllm.transformers_utils.repo_utils.hf_api", + "vllm_omni.transformers_utils.repo_utils.hf_api", lambda: api, ) assert _resolve_checkpoint_root( diff --git a/tests/diffusion/models/minimax_h3/test_minimax_h3_quantization_quality.py b/tests/diffusion/models/minimax_h3/test_minimax_h3_quantization_quality.py index 3beda5acc..dd54f00ae 100644 --- a/tests/diffusion/models/minimax_h3/test_minimax_h3_quantization_quality.py +++ b/tests/diffusion/models/minimax_h3/test_minimax_h3_quantization_quality.py @@ -16,6 +16,7 @@ from tests.diffusion.quantization.test_quantization_quality import ( _maybe_save_output, ) from tests.helpers.mark import hardware_marks +from tests.helpers.mock import patch_hf_snapshot_download _MINIMAX_H3_REPO = "MiniMaxAI/MiniMax-H3" _MINIMAX_H3_REVISION = "48d93ede732756e404a3b1b2f3b3a9b5a22f6cfc" @@ -258,13 +259,13 @@ def test_resolve_fl2va_model_ref(tmp_path, monkeypatch): fl2va_root.mkdir() (fl2va_root / "model_index.json").write_text("{}", encoding="utf-8") - def fake_snapshot_download(self, *, repo_id, revision, allow_patterns): + def fake_snapshot_download(*, repo_id, revision, allow_patterns): assert repo_id == _MINIMAX_H3_REPO assert revision == _MINIMAX_H3_REVISION assert allow_patterns == ["FL2VA/**"] return str(tmp_path) - monkeypatch.setattr("huggingface_hub.HfApi.snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) assert _resolve_fl2va_model_ref() == str(fl2va_root) diff --git a/tests/e2e/accuracy/ltx/test_ltx25_official_similarity.py b/tests/e2e/accuracy/ltx/test_ltx25_official_similarity.py index c8ce29c37..ba39fb204 100644 --- a/tests/e2e/accuracy/ltx/test_ltx25_official_similarity.py +++ b/tests/e2e/accuracy/ltx/test_ltx25_official_similarity.py @@ -32,7 +32,6 @@ from huggingface_hub import hf_hub_download, snapshot_download from torchmetrics.image import PeakSignalNoiseRatio, StructuralSimilarityIndexMeasure from tests.e2e.accuracy.helpers import reset_artifact_dir -from tests.helpers import skip_if_gated_repo_inaccessible from tests.helpers.mark import hardware_test OFFICIAL_REPOSITORY = "https://github.com/Lightricks/LTX-2.git" @@ -303,32 +302,6 @@ def _resolve_ltx25_image() -> Path: ) -def _require_ltx25_model_access() -> None: - configured_omni_model = os.environ.get("VLLM_TEST_LTX25_MODEL") - if not (configured_omni_model and Path(configured_omni_model).exists()): - omni_model = configured_omni_model or LTX25_OMNI_MODEL_ID - omni_revision = os.environ.get("VLLM_TEST_LTX25_MODEL_REVISION") - if omni_revision is None and omni_model == LTX25_OMNI_MODEL_ID: - omni_revision = LTX25_OMNI_MODEL_REVISION - skip_if_gated_repo_inaccessible( - omni_model, - revision=omni_revision, - filename="model_index.json", - ) - - configured_official_model = os.environ.get("VLLM_TEST_LTX25_OFFICIAL_MODEL") - if not (configured_official_model and Path(configured_official_model).exists()): - official_model = configured_official_model or LTX25_OFFICIAL_MODEL_ID - official_revision = os.environ.get("VLLM_TEST_LTX25_OFFICIAL_MODEL_REVISION") - if official_revision is None and official_model == LTX25_OFFICIAL_MODEL_ID: - official_revision = LTX25_OFFICIAL_MODEL_REVISION - skip_if_gated_repo_inaccessible( - official_model, - revision=official_revision, - filename=LTX25_OFFICIAL_COMMON_FILES["text_encoder"], - ) - - def _ltx25_request(image: Path | None = None) -> dict[str, object]: request: dict[str, object] = { "prompt": LTX25_PROMPT, @@ -475,7 +448,6 @@ def _assert_strict_similarity( @pytest.mark.parametrize("task", ["t2v", "i2v"]) def test_ltx25_distilled_two_stage_matches_official(accuracy_artifact_root: Path, task: str) -> None: """Compare Omni with official canonical DiffVAE and matching connector weights.""" - _require_ltx25_model_access() artifact_parent = accuracy_artifact_root / "ltx_official" output_root = reset_artifact_dir(artifact_parent / f"ltx2_5_distilled_{task}") official_root, official_revision = _ltx25_official_source(artifact_parent) @@ -600,7 +572,6 @@ def test_ltx25_distilled_two_stage_matches_official(accuracy_artifact_root: Path @pytest.mark.parametrize("pipeline_mode", ["full_one_stage", "full_two_stage"]) def test_ltx25_full_matches_official(accuracy_artifact_root: Path, task: str, pipeline_mode: str) -> None: """Compare raw official Full/SFT and DiffVAE weights with Omni.""" - _require_ltx25_model_access() artifact_parent = accuracy_artifact_root / "ltx_official" output_root = reset_artifact_dir(artifact_parent / f"ltx2_5_{pipeline_mode}_{task}") model_class_name = { diff --git a/tests/e2e/offline_inference/test_hunyuanimage3.py b/tests/e2e/offline_inference/test_hunyuanimage3.py index 21bfe9b39..ab1bb5701 100644 --- a/tests/e2e/offline_inference/test_hunyuanimage3.py +++ b/tests/e2e/offline_inference/test_hunyuanimage3.py @@ -558,7 +558,7 @@ def test_shared_script_ar_path_reaches_generation( # safe against MODEL_NAME's base checkpoint missing the Instruct-only # / tokens. Wiring it here exercises the # real production path (get_ar_tokenizer_validator via the registry), - # not just the isolated fake-tokenizer coverage in test_prompt_utils.py + # not just the isolated fake-tokenizer coverage in test_hunyuan_image3_prompt_utils.py # / test_shared_script_ar_integration.py. validate_tokenizer=get_ar_tokenizer_validator(model_class_name), ) diff --git a/tests/e2e/online_serving/test_ltx25.py b/tests/e2e/online_serving/test_ltx25.py index 4975abcc0..400210a7d 100644 --- a/tests/e2e/online_serving/test_ltx25.py +++ b/tests/e2e/online_serving/test_ltx25.py @@ -7,7 +7,6 @@ import os import pytest -from tests.helpers import skip_if_gated_repo_inaccessible from tests.helpers.mark import hardware_marks from tests.helpers.media import generate_synthetic_image from tests.helpers.runtime import OmniServer, OmniServerParams, OpenAIClientHandler @@ -24,16 +23,6 @@ pytestmark = [pytest.mark.diffusion, pytest.mark.slow] SINGLE_CARD_MARKS = hardware_marks(res={"cuda": "H100"}) -@pytest.fixture(scope="module", autouse=True) -def require_ltx25_model_access() -> None: - if not os.path.isdir(MODEL): - skip_if_gated_repo_inaccessible( - MODEL, - revision=MODEL_REVISION or None, - filename="model_index.json", - ) - - def _server(model_class_name: str) -> OmniServerParams: return OmniServerParams( model=MODEL, diff --git a/tests/e2e/online_serving/test_stable_audio_online_expansion.py b/tests/e2e/online_serving/test_stable_audio_online_expansion.py index fbd6bdbfd..beb51f7c9 100644 --- a/tests/e2e/online_serving/test_stable_audio_online_expansion.py +++ b/tests/e2e/online_serving/test_stable_audio_online_expansion.py @@ -1,5 +1,5 @@ # SPDX-License-Identifier: Apache-2.0 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project +# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project """ E2E online serving test for Stable Audio Open text-to-audio diffusion. @@ -11,7 +11,6 @@ import os import pytest -from tests.helpers import skip_if_gated_repo_inaccessible from tests.helpers.mark import hardware_marks from tests.helpers.runtime import OmniServer, OmniServerParams, OpenAIClientHandler @@ -32,17 +31,10 @@ def _stable_audio_server_cases(model: str): ] -@pytest.fixture -def _require_stable_audio_access() -> None: - """Skip cleanly (before the server boots) if the gated checkpoint is inaccessible.""" - skip_if_gated_repo_inaccessible(STABLE_AUDIO_TEST_MODEL) - - @pytest.mark.slow @pytest.mark.diffusion @pytest.mark.parametrize("omni_server", _stable_audio_server_cases(STABLE_AUDIO_TEST_MODEL), indirect=True) def test_stable_audio_t2a_online( - _require_stable_audio_access: None, omni_server: OmniServer, openai_client: OpenAIClientHandler, ) -> None: diff --git a/tests/engine/test_arg_utils.py b/tests/engine/test_arg_utils.py index 3b0192a79..142affab7 100644 --- a/tests/engine/test_arg_utils.py +++ b/tests/engine/test_arg_utils.py @@ -22,6 +22,7 @@ from pydantic import ValidationError from transformers import PretrainedConfig, Qwen3OmniMoeConfig from vllm.engine.arg_utils import EngineArgs +from tests.helpers.mock import patch_hf_snapshot_download from vllm_omni.config.model import OmniModelConfig from vllm_omni.engine.arg_utils import OmniEngineArgs from vllm_omni.engine.stage_init_utils import build_engine_args_dict @@ -296,7 +297,7 @@ def test_remote_tokenizer_subfolder_download_does_not_report_failure(tmp_path, m baseline_config = Mock() warning = mocker.patch("vllm_omni.engine.arg_utils.logger.warning") - monkeypatch.setattr("huggingface_hub.HfApi.snapshot_download", lambda *args, **kwargs: str(tmp_path)) + patch_hf_snapshot_download(monkeypatch, lambda *args, **kwargs: str(tmp_path), hf_home=tmp_path) monkeypatch.setattr(OmniEngineArgs, "_patch_empty_hf_config", lambda *args, **kwargs: None) monkeypatch.setattr(EngineArgs, "create_model_config", lambda _self: baseline_config) monkeypatch.setattr( diff --git a/tests/engine/test_async_omni_engine_stage_init.py b/tests/engine/test_async_omni_engine_stage_init.py index ec897bd7d..a8255f81a 100644 --- a/tests/engine/test_async_omni_engine_stage_init.py +++ b/tests/engine/test_async_omni_engine_stage_init.py @@ -14,6 +14,7 @@ import pytest from omegaconf import OmegaConf from vllm.v1.engine.utils import EngineZmqAddresses +from tests.helpers.mock import patch_hf_snapshot_download from vllm_omni.config.omni_config import OmniStageRuntimeConfig from vllm_omni.diffusion.data import AttentionConfig from vllm_omni.engine import omni_engine_base as async_omni_engine_module @@ -1566,22 +1567,20 @@ def test_build_engine_args_pulls_stage_subdirs_missing_from_cached_snapshot(monk never materialized. Stage init must fetch exactly those subfolders instead of joining onto a path that exists nowhere. """ - from huggingface_hub import HfApi - from vllm_omni.engine.stage_init_utils import build_engine_args_dict snapshot = tmp_path / "snapshots" / "deadbeef" _make_snapshot(snapshot, ["tokenizer"]) calls = [] - def fake_snapshot_download(self, repo_id, **kwargs): + def fake_snapshot_download(repo_id, **kwargs): calls.append(kwargs) if kwargs.get("local_files_only"): return str(snapshot) _make_snapshot(snapshot, ["language_model"]) return str(snapshot) - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) stage_cfg = types.SimpleNamespace( stage_id=0, @@ -1601,18 +1600,16 @@ def test_build_engine_args_pulls_stage_subdirs_missing_from_cached_snapshot(monk def test_build_engine_args_skips_hub_call_when_cached_snapshot_is_complete(monkeypatch, tmp_path): """A warm cache stays offline-friendly: no outgoing Hub request.""" - from huggingface_hub import HfApi - from vllm_omni.engine.stage_init_utils import build_engine_args_dict snapshot = tmp_path / "snapshots" / "deadbeef" _make_snapshot(snapshot, ["language_model", "tokenizer"]) - def fake_snapshot_download(self, repo_id, **kwargs): + def fake_snapshot_download(repo_id, **kwargs): assert kwargs.get("local_files_only"), "warm cache must not reach the Hub" return str(snapshot) - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) stage_cfg = types.SimpleNamespace( stage_id=0, @@ -1633,8 +1630,6 @@ def test_build_engine_args_redownloads_a_partial_subdir(monkeypatch, tmp_path): that local path leaves vLLM's loader without any fallback for the missing weights. """ - from huggingface_hub import HfApi - from vllm_omni.engine.stage_init_utils import build_engine_args_dict snapshot = tmp_path / "snapshots" / "deadbeef" @@ -1644,14 +1639,14 @@ def test_build_engine_args_redownloads_a_partial_subdir(monkeypatch, tmp_path): _make_snapshot(snapshot, ["tokenizer"]) calls = [] - def fake_snapshot_download(self, repo_id, **kwargs): + def fake_snapshot_download(repo_id, **kwargs): calls.append(kwargs) if kwargs.get("local_files_only"): return str(snapshot) _make_snapshot(snapshot, ["language_model"]) return str(snapshot) - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) stage_cfg = types.SimpleNamespace( stage_id=0, @@ -1672,8 +1667,6 @@ def test_build_engine_args_redownloads_when_index_lists_missing_shards(monkeypat The index downloads early; accepting it as proof of weights converts the Hub ID into a local path vLLM cannot fetch the remaining shards for. """ - from huggingface_hub import HfApi - from vllm_omni.engine.stage_init_utils import build_engine_args_dict snapshot = tmp_path / "snapshots" / "deadbeef" @@ -1693,13 +1686,13 @@ def test_build_engine_args_redownloads_when_index_lists_missing_shards(monkeypat _make_snapshot(snapshot, ["tokenizer"]) calls = [] - def fake_snapshot_download(self, repo_id, **kwargs): + def fake_snapshot_download(repo_id, **kwargs): calls.append(kwargs) if not kwargs.get("local_files_only"): (partial / "model-00002-of-00002.safetensors").write_text("x") return str(snapshot) - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) stage_cfg = types.SimpleNamespace( stage_id=0, @@ -1716,8 +1709,6 @@ def test_build_engine_args_redownloads_when_index_lists_missing_shards(monkeypat def test_build_engine_args_redownloads_shards_without_their_index(monkeypatch, tmp_path): """Shard-named weights always ship an index; one without it is partial.""" - from huggingface_hub import HfApi - from vllm_omni.engine.stage_init_utils import build_engine_args_dict snapshot = tmp_path / "snapshots" / "deadbeef" @@ -1727,7 +1718,7 @@ def test_build_engine_args_redownloads_shards_without_their_index(monkeypatch, t _make_snapshot(snapshot, ["tokenizer"]) calls = [] - def fake_snapshot_download(self, repo_id, **kwargs): + def fake_snapshot_download(repo_id, **kwargs): calls.append(kwargs) if not kwargs.get("local_files_only"): shards = {f"w{i}.weight": f"model-0000{i}-of-00004.safetensors" for i in range(1, 5)} @@ -1736,7 +1727,7 @@ def test_build_engine_args_redownloads_shards_without_their_index(monkeypatch, t (partial / shard).write_text("x") return str(snapshot) - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) stage_cfg = types.SimpleNamespace( stage_id=0, @@ -1752,8 +1743,6 @@ def test_build_engine_args_redownloads_shards_without_their_index(monkeypatch, t def test_build_engine_args_redownloads_a_tokenizer_folder_without_vocabulary(monkeypatch, tmp_path): """Templates and configs download first; alone they are not a tokenizer.""" - from huggingface_hub import HfApi - from vllm_omni.engine.stage_init_utils import build_engine_args_dict snapshot = tmp_path / "snapshots" / "deadbeef" @@ -1763,13 +1752,13 @@ def test_build_engine_args_redownloads_a_tokenizer_folder_without_vocabulary(mon (partial / "chat_template.jinja").write_text("{{ messages }}") calls = [] - def fake_snapshot_download(self, repo_id, **kwargs): + def fake_snapshot_download(repo_id, **kwargs): calls.append(kwargs) if not kwargs.get("local_files_only"): _make_snapshot(snapshot, ["tokenizer"]) return str(snapshot) - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) stage_cfg = types.SimpleNamespace( stage_id=0, @@ -1787,19 +1776,17 @@ def test_build_engine_args_redownloads_a_tokenizer_folder_without_vocabulary(mon def test_build_engine_args_forwards_revision_and_download_dir(monkeypatch, tmp_path): """revision/download_dir must shape snapshot selection (they cannot be corrected downstream once the repo ID is a local path).""" - from huggingface_hub import HfApi - from vllm_omni.engine.stage_init_utils import build_engine_args_dict snapshot = tmp_path / "snapshots" / "pinned" _make_snapshot(snapshot, ["language_model", "tokenizer"]) seen = [] - def fake_snapshot_download(self, repo_id, **kwargs): + def fake_snapshot_download(repo_id, **kwargs): seen.append(kwargs) return str(snapshot) - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) stage_cfg = types.SimpleNamespace( stage_id=0, @@ -1824,8 +1811,6 @@ def test_build_engine_args_forwards_revision_and_download_dir(monkeypatch, tmp_p def test_build_engine_args_resolves_tokenizer_revision_separately(monkeypatch, tmp_path): """A tokenizer pinned to a different revision resolves against its own snapshot.""" - from huggingface_hub import HfApi - from vllm_omni.engine.stage_init_utils import build_engine_args_dict model_snapshot = tmp_path / "snapshots" / "model-rev" @@ -1833,12 +1818,12 @@ def test_build_engine_args_resolves_tokenizer_revision_separately(monkeypatch, t tokenizer_snapshot = tmp_path / "snapshots" / "tok-rev" _make_snapshot(tokenizer_snapshot, ["tokenizer"]) - def fake_snapshot_download(self, repo_id, **kwargs): + def fake_snapshot_download(repo_id, **kwargs): if kwargs.get("revision") == "tok-rev": return str(tokenizer_snapshot) return str(model_snapshot) - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) stage_cfg = types.SimpleNamespace( stage_id=0, @@ -1861,8 +1846,6 @@ def test_build_engine_args_resolves_tokenizer_revision_separately(monkeypatch, t def test_build_engine_args_resolves_root_tokenizer_revision_separately(monkeypatch, tmp_path): """An empty tokenizer_subdir targets the snapshot root and still honors its own revision.""" - from huggingface_hub import HfApi - from vllm_omni.engine.stage_init_utils import build_engine_args_dict model_snapshot = tmp_path / "snapshots" / "model-rev" @@ -1875,12 +1858,12 @@ def test_build_engine_args_resolves_root_tokenizer_revision_separately(monkeypat tokenizer_snapshot.mkdir(parents=True, exist_ok=True) (tokenizer_snapshot / _SUBDIR_ARTIFACT["tokenizer"]).write_text("x") - def fake_snapshot_download(self, repo_id, **kwargs): + def fake_snapshot_download(repo_id, **kwargs): if kwargs.get("revision") == "tok-rev": return str(tokenizer_snapshot) return str(model_snapshot) - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) stage_cfg = types.SimpleNamespace( stage_id=0, @@ -1902,19 +1885,17 @@ def test_build_engine_args_resolves_root_tokenizer_revision_separately(monkeypat def test_build_engine_args_fails_closed_when_subdir_cannot_be_downloaded(monkeypatch, tmp_path): """An undownloadable subfolder raises here instead of reaching HuggingFace.""" - from huggingface_hub import HfApi - from vllm_omni.engine.stage_init_utils import build_engine_args_dict snapshot = tmp_path / "snapshots" / "deadbeef" _make_snapshot(snapshot, ["tokenizer"]) - def fake_snapshot_download(self, repo_id, **kwargs): + def fake_snapshot_download(repo_id, **kwargs): if kwargs.get("local_files_only"): return str(snapshot) raise OSError("offline") - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) stage_cfg = types.SimpleNamespace( stage_id=0, @@ -1927,16 +1908,14 @@ def test_build_engine_args_fails_closed_when_subdir_cannot_be_downloaded(monkeyp build_engine_args_dict(stage_cfg, "MiniMaxAI/MiniMax-Music3") -def test_build_engine_args_fails_closed_on_cold_cache_instead_of_joining_repo_id(monkeypatch): +def test_build_engine_args_fails_closed_on_cold_cache_instead_of_joining_repo_id(monkeypatch, tmp_path): """With nothing cached, the stage must not join a subdir onto the repo id.""" - from huggingface_hub import HfApi - from vllm_omni.engine.stage_init_utils import build_engine_args_dict - def fake_snapshot_download(self, repo_id, **kwargs): + def fake_snapshot_download(repo_id, **kwargs): raise OSError("offline") - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) stage_cfg = types.SimpleNamespace( stage_id=0, diff --git a/tests/helpers/__init__.py b/tests/helpers/__init__.py index 9120e183f..af7977906 100644 --- a/tests/helpers/__init__.py +++ b/tests/helpers/__init__.py @@ -3,49 +3,10 @@ """Shared, importable test helper utilities. -Submodules (``assertions``, ``clean``, ``client``, ``media``, ``runtime``, …) are imported +Submodules (``assertions``, ``clean``, ``client``, ``media``, ``mock``, ``runtime``, …) are imported explicitly by callers. Unit tests for these helpers live under ``tests/``. Avoid star-importing everything here: that ran before refactor only inside the old monolithic ``conftest``; a greedy ``__init__`` changes import order and can affect in-process Omni (``OmniRunner`` / offline e2e) vs subprocess-based ``OmniServer`` tests. """ - -from __future__ import annotations - - -def skip_if_gated_repo_inaccessible( - repo_id: str, - *, - revision: str | None = None, - filename: str = "config.json", -) -> None: - """Skip the test if a gated HuggingFace repo is not accessible. - - Tries to download ``filename`` via ``hf_hub_download``, which performs an - actual file-access check (unlike ``HfApi().model_info()`` - that only checks metadata). If the token has metadata access but not - file-download access, ``hf_hub_download`` will raise ``GatedRepoError`` - and we skip cleanly. - """ - try: - from huggingface_hub.errors import GatedRepoError, RepositoryNotFoundError - - from vllm_omni.transformers_utils.repo_utils import hf_api - except Exception: - return - try: - hf_api().hf_hub_download(repo_id=repo_id, filename=filename, revision=revision) - except GatedRepoError as exc: - import pytest - - pytest.skip( - f"Skipping: gated HF repo {repo_id!r} inaccessible to the current " - f"HF_TOKEN ({exc}). See docs/contributing/ci/hf_credentials.md." - ) - except RepositoryNotFoundError as exc: - import pytest - - pytest.skip(f"Skipping: HF repo {repo_id!r} not found ({exc}).") - except Exception: - return diff --git a/tests/helpers/mock.py b/tests/helpers/mock.py new file mode 100644 index 000000000..47ce8f79d --- /dev/null +++ b/tests/helpers/mock.py @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project +"""Shared pytest mocks for unit tests. + +Add reusable ``monkeypatch`` helpers here instead of a new module per target. +Callers import explicitly: ``from tests.helpers.mock import ...``. +""" + +from __future__ import annotations + +from collections.abc import Callable +from os import PathLike +from pathlib import Path +from typing import Any + + +def patch_hf_snapshot_download( + monkeypatch: Any, + fake: Callable[..., Any], + *, + hf_home: str | PathLike[str] | None = None, +) -> None: + """Replace ``snapshot_download`` on the cached ``hf_api()`` singleton. + + Production code calls ``hf_api().snapshot_download``. Patching + ``huggingface_hub.HfApi.snapshot_download`` misses that instance, so + weekly CPU jobs talk to the real Hub / ``HF_HOME`` cache. + + ``fake`` is installed on the instance, so it is called without ``self``. + + Pass ``hf_home`` (typically pytest ``tmp_path``) to isolate the Hub cache. + ``repo_utils`` imports ``huggingface_hub`` before tests can ``setenv``, so + ``constants.HF_HUB_CACHE`` is already frozen from the process environment + (weekly: ``/fsx/hf_cache``). Downloads without ``cache_dir`` use that + constant, not ``HF_HOME``. Patch both the env and the imported constants + so a missed instance mock cannot read the builder cache. + """ + from huggingface_hub import constants as hub_constants + + from vllm_omni.transformers_utils import repo_utils + + monkeypatch.setattr(repo_utils.hf_api(), "snapshot_download", fake) + if hf_home is not None: + hf_home_str = str(hf_home) + hub_cache = str(Path(hf_home_str) / "hub") + monkeypatch.setenv("HF_HOME", hf_home_str) + monkeypatch.setenv("HF_HUB_CACHE", hub_cache) + monkeypatch.setattr(hub_constants, "HF_HOME", hf_home_str) + monkeypatch.setattr(hub_constants, "default_cache_path", hub_cache) + monkeypatch.setattr(hub_constants, "HF_HUB_CACHE", hub_cache) + monkeypatch.setattr(hub_constants, "HUGGINGFACE_HUB_CACHE", hub_cache) diff --git a/tests/helpers/tests/test_mock.py b/tests/helpers/tests/test_mock.py new file mode 100644 index 000000000..f30187395 --- /dev/null +++ b/tests/helpers/tests/test_mock.py @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project + +import os +from pathlib import Path + +import pytest + +from tests.helpers.mock import patch_hf_snapshot_download + +pytestmark = [pytest.mark.core_model, pytest.mark.cpu] + + +def test_patch_hf_snapshot_download_binds_instance_without_self(monkeypatch): + from vllm_omni.transformers_utils.repo_utils import hf_api + + calls: list[tuple[tuple[object, ...], dict[str, object]]] = [] + + def fake(*args, **kwargs): + calls.append((args, kwargs)) + return "ok" + + patch_hf_snapshot_download(monkeypatch, fake) + assert hf_api().snapshot_download("org/model", revision="abc") == "ok" + assert calls == [(("org/model",), {"revision": "abc"})] + + +def test_patch_hf_snapshot_download_sets_hf_home(monkeypatch, tmp_path): + hf_home = tmp_path / "hf-home" + patch_hf_snapshot_download(monkeypatch, lambda *args, **kwargs: "ok", hf_home=hf_home) + assert os.environ["HF_HOME"] == str(hf_home) + from huggingface_hub import constants + + assert constants.HF_HOME == str(hf_home) + assert constants.HF_HUB_CACHE == str(hf_home / "hub") + assert constants.HUGGINGFACE_HUB_CACHE == str(hf_home / "hub") + assert constants.default_cache_path == str(hf_home / "hub") + + +def test_patch_hf_snapshot_download_leaves_hf_home_when_omitted(monkeypatch): + from huggingface_hub import constants + + monkeypatch.setenv("HF_HOME", "/original-hf-home") + original_cache = constants.HF_HUB_CACHE + original_default = constants.default_cache_path + patch_hf_snapshot_download(monkeypatch, lambda *args, **kwargs: "ok") + assert os.environ["HF_HOME"] == "/original-hf-home" + assert constants.HF_HUB_CACHE == original_cache + assert constants.default_cache_path == original_default + + +def test_patch_hf_snapshot_download_overrides_preimported_hub_cache(monkeypatch, tmp_path): + """Hub is imported before the helper; ``HF_HOME`` env alone does not move the cache. + + ``repo_utils`` / ``huggingface_hub`` compute ``constants.HF_HUB_CACHE`` at + import time. Weekly CPU already imported them with ``HF_HOME=/fsx/hf_cache``. + A download that bypasses the instance mock uses that constant, not the env. + """ + from huggingface_hub import constants, snapshot_download + from huggingface_hub.errors import LocalEntryNotFoundError + + from vllm_omni.transformers_utils.repo_utils import hf_api + + hf_api() + + poisoned_home = tmp_path / "fsx-hf-cache" + poisoned_cache = poisoned_home / "hub" + repo_dir = poisoned_cache / "models--org--model" + (repo_dir / "refs").mkdir(parents=True) + (repo_dir / "refs" / "main").write_text("deadbeef", encoding="utf-8") + snapshot = repo_dir / "snapshots" / "deadbeef" + snapshot.mkdir(parents=True) + (snapshot / "config.json").write_text("{}", encoding="utf-8") + + monkeypatch.setenv("HF_HOME", str(poisoned_home)) + monkeypatch.setattr(constants, "HF_HOME", str(poisoned_home)) + monkeypatch.setattr(constants, "HF_HUB_CACHE", str(poisoned_cache)) + monkeypatch.setattr(constants, "HUGGINGFACE_HUB_CACHE", str(poisoned_cache)) + + found = snapshot_download("org/model", local_files_only=True) + assert Path(found).resolve() == snapshot.resolve() + + isolated = tmp_path / "isolated-hf-home" + patch_hf_snapshot_download(monkeypatch, lambda *args, **kwargs: "mocked", hf_home=isolated) + + assert os.environ["HF_HOME"] == str(isolated) + assert os.environ["HF_HUB_CACHE"] == str(isolated / "hub") + assert constants.HF_HOME == str(isolated) + assert constants.HF_HUB_CACHE == str(isolated / "hub") + assert constants.HUGGINGFACE_HUB_CACHE == str(isolated / "hub") + assert constants.default_cache_path == str(isolated / "hub") + with pytest.raises(LocalEntryNotFoundError): + snapshot_download("org/model", local_files_only=True) diff --git a/tests/model_executor/models/fish_speech/test_prompt_utils.py b/tests/model_executor/models/fish_speech/test_fish_speech_prompt_utils.py similarity index 100% rename from tests/model_executor/models/fish_speech/test_prompt_utils.py rename to tests/model_executor/models/fish_speech/test_fish_speech_prompt_utils.py diff --git a/tests/model_executor/models/indextts2/test_prompt_utils.py b/tests/model_executor/models/indextts2/test_indextts2_prompt_utils.py similarity index 98% rename from tests/model_executor/models/indextts2/test_prompt_utils.py rename to tests/model_executor/models/indextts2/test_indextts2_prompt_utils.py index fe73e69a5..29c86567f 100644 --- a/tests/model_executor/models/indextts2/test_prompt_utils.py +++ b/tests/model_executor/models/indextts2/test_indextts2_prompt_utils.py @@ -1,5 +1,5 @@ # SPDX-License-Identifier: Apache-2.0 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project +# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project import importlib.util from pathlib import Path diff --git a/tests/model_executor/models/minicpmo_4_5/test_code2wav_model_dir.py b/tests/model_executor/models/minicpmo_4_5/test_code2wav_model_dir.py index 8d5aeade6..fced5ae46 100644 --- a/tests/model_executor/models/minicpmo_4_5/test_code2wav_model_dir.py +++ b/tests/model_executor/models/minicpmo_4_5/test_code2wav_model_dir.py @@ -12,9 +12,9 @@ from __future__ import annotations from types import SimpleNamespace -import huggingface_hub import pytest +from tests.helpers.mock import patch_hf_snapshot_download from vllm_omni.model_executor.models.minicpmo_4_5.minicpmo_4_5_code2wav import ( MiniCPMO45Code2Wav, _resolve_model_dir, @@ -23,28 +23,28 @@ from vllm_omni.model_executor.models.minicpmo_4_5.minicpmo_4_5_code2wav import ( pytestmark = [pytest.mark.core_model, pytest.mark.cpu] -def _no_hub(monkeypatch): +def _no_hub(monkeypatch, tmp_path): def _fail(*args, **kwargs): # pragma: no cover - must not be reached raise AssertionError("snapshot_download must not be called here") - monkeypatch.setattr(huggingface_hub.HfApi, "snapshot_download", _fail) + patch_hf_snapshot_download(monkeypatch, _fail, hf_home=tmp_path) def test_local_directory_is_returned_unchanged(tmp_path, monkeypatch): - _no_hub(monkeypatch) + _no_hub(monkeypatch, tmp_path) assert _resolve_model_dir(str(tmp_path)) == str(tmp_path) def test_repo_id_resolves_via_snapshot_download(tmp_path, monkeypatch): calls = {} - def _fake_snapshot_download(self, model_ref, revision=None, allow_patterns=None): + def _fake_snapshot_download(model_ref, revision=None, allow_patterns=None): calls["model_ref"] = model_ref calls["revision"] = revision calls["allow_patterns"] = allow_patterns return str(tmp_path / "snapshot") - monkeypatch.setattr(huggingface_hub.HfApi, "snapshot_download", _fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, _fake_snapshot_download, hf_home=tmp_path) resolved = _resolve_model_dir("openbmb/MiniCPM-o-4_5", revision="abc123") assert resolved == str(tmp_path / "snapshot") assert calls["model_ref"] == "openbmb/MiniCPM-o-4_5" @@ -52,18 +52,18 @@ def test_repo_id_resolves_via_snapshot_download(tmp_path, monkeypatch): assert calls["allow_patterns"] == ["assets/*"] -def test_snapshot_download_failure_propagates(monkeypatch): +def test_snapshot_download_failure_propagates(monkeypatch, tmp_path): def _raise(*args, **kwargs): raise FileNotFoundError("offline and not cached") - monkeypatch.setattr(huggingface_hub.HfApi, "snapshot_download", _raise) + patch_hf_snapshot_download(monkeypatch, _raise, hf_home=tmp_path) with pytest.raises(FileNotFoundError): _resolve_model_dir("openbmb/MiniCPM-o-4_5") -def test_init_with_fake_path_does_not_resolve(monkeypatch): +def test_init_with_fake_path_does_not_resolve(monkeypatch, tmp_path): """Mirrors the CPU-test construction: fake model path, no ``revision``.""" - _no_hub(monkeypatch) + _no_hub(monkeypatch, tmp_path) config = SimpleNamespace( model_config=SimpleNamespace( model="/fake/model", @@ -75,8 +75,8 @@ def test_init_with_fake_path_does_not_resolve(monkeypatch): assert model._default_prompt_wav == "/fake/model/assets/HT_ref_audio.wav" -def test_default_prompt_wav_follows_resolved_model_path(monkeypatch): - _no_hub(monkeypatch) +def test_default_prompt_wav_follows_resolved_model_path(monkeypatch, tmp_path): + _no_hub(monkeypatch, tmp_path) config = SimpleNamespace( model_config=SimpleNamespace( model="openbmb/MiniCPM-o-4_5", diff --git a/tests/model_executor/models/test_minimax_music3_repo_root.py b/tests/model_executor/models/test_minimax_music3_repo_root.py index 182635ab7..6cac9d559 100644 --- a/tests/model_executor/models/test_minimax_music3_repo_root.py +++ b/tests/model_executor/models/test_minimax_music3_repo_root.py @@ -12,6 +12,7 @@ from __future__ import annotations import pytest +from tests.helpers.mock import patch_hf_snapshot_download from vllm_omni.model_executor.models.minimax_music3.weights import ( _COMPONENT_WEIGHT_NAME, _REQUIRED_COMPONENTS, @@ -47,16 +48,14 @@ def test_resolve_repo_root_walks_up_from_a_model_subdir(tmp_path): def test_resolve_repo_root_resolves_a_hub_id_to_a_local_snapshot(monkeypatch, tmp_path): """A hub id must resolve through the cache, not against the working directory.""" - from huggingface_hub import HfApi - root = _make_root(tmp_path / "snapshots" / "deadbeef") seen = [] - def fake_snapshot_download(self, repo_id, **kwargs): + def fake_snapshot_download(repo_id, **kwargs): seen.append(kwargs) return str(root) - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) assert resolve_repo_root("MiniMaxAI/MiniMax-Music3") == root # Cache-first, and only the component folders. @@ -66,18 +65,16 @@ def test_resolve_repo_root_resolves_a_hub_id_to_a_local_snapshot(monkeypatch, tm def test_resolve_repo_root_falls_back_to_the_hub_when_the_cache_is_incomplete(monkeypatch, tmp_path): - from huggingface_hub import HfApi - root = _make_root(tmp_path / "snapshots" / "deadbeef") calls = [] - def fake_snapshot_download(self, repo_id, **kwargs): + def fake_snapshot_download(repo_id, **kwargs): calls.append(kwargs) if kwargs.get("local_files_only"): raise OSError("incomplete snapshot") return str(root) - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) assert resolve_repo_root("MiniMaxAI/MiniMax-Music3") == root assert len(calls) == 2 @@ -89,19 +86,17 @@ def test_resolve_repo_root_retries_online_when_the_local_lookup_returns_a_partia A returned partial root must fall through to the online call all the same, or startup fails without ever consulting the Hub. """ - from huggingface_hub import HfApi - snapshot = tmp_path / "models--MiniMaxAI--MiniMax-Music3" / "snapshots" / "deadbeef" (snapshot / "language_model").mkdir(parents=True) calls = [] - def fake_snapshot_download(self, repo_id, **kwargs): + def fake_snapshot_download(repo_id, **kwargs): calls.append(kwargs) if not kwargs.get("local_files_only"): _make_root(snapshot) return str(snapshot) - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) assert resolve_repo_root("MiniMaxAI/MiniMax-Music3") == snapshot assert [bool(call.get("local_files_only")) for call in calls] == [True, False] @@ -109,32 +104,28 @@ def test_resolve_repo_root_retries_online_when_the_local_lookup_returns_a_partia def test_resolve_repo_root_redownloads_weightless_marker_dirs(monkeypatch, tmp_path): """Marker folders without their component weights are an interrupted download.""" - from huggingface_hub import HfApi - snapshot = tmp_path / "models--MiniMaxAI--MiniMax-Music3" / "snapshots" / "deadbeef" for marker in _ROOT_MARKERS: (snapshot / marker).mkdir(parents=True) calls = [] - def fake_snapshot_download(self, repo_id, **kwargs): + def fake_snapshot_download(repo_id, **kwargs): calls.append(kwargs) if not kwargs.get("local_files_only"): _make_root(snapshot) return str(snapshot) - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) assert resolve_repo_root("MiniMaxAI/MiniMax-Music3") == snapshot assert any(not call.get("local_files_only") for call in calls) -def test_resolve_repo_root_reports_the_original_reference_when_unresolvable(monkeypatch): - from huggingface_hub import HfApi - - def fake_snapshot_download(self, repo_id, **kwargs): +def test_resolve_repo_root_reports_the_original_reference_when_unresolvable(monkeypatch, tmp_path): + def fake_snapshot_download(repo_id, **kwargs): raise OSError("offline") - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) with pytest.raises(FileNotFoundError, match="MiniMaxAI/MiniMax-Music3"): resolve_repo_root("MiniMaxAI/MiniMax-Music3") @@ -147,19 +138,17 @@ def test_resolve_repo_root_downloads_components_for_a_cold_cache_model_subdir(mo the marker walk fails although the repo id is recoverable from the cache layout and one snapshot_download away from working. """ - from huggingface_hub import HfApi - snapshot = tmp_path / "models--MiniMaxAI--MiniMax-Music3" / "snapshots" / "deadbeef" (snapshot / "language_model").mkdir(parents=True) (snapshot / "tokenizer").mkdir() seen = [] - def fake_snapshot_download(self, repo_id, **kwargs): + def fake_snapshot_download(repo_id, **kwargs): seen.append(repo_id) _make_root(snapshot) return str(snapshot) - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) assert resolve_repo_root(str(snapshot / "language_model")) == snapshot assert seen == ["MiniMaxAI/MiniMax-Music3"] @@ -167,12 +156,11 @@ def test_resolve_repo_root_downloads_components_for_a_cold_cache_model_subdir(mo def test_resolve_repo_root_does_not_guess_a_repo_id_for_plain_directories(monkeypatch, tmp_path): """A directory outside the HF cache has no recoverable repo id.""" - from huggingface_hub import HfApi - def fake_snapshot_download(self, repo_id, **kwargs): + def fake_snapshot_download(repo_id, **kwargs): raise AssertionError("must not reach the Hub for a plain directory") - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) plain = tmp_path / "language_model" plain.mkdir() @@ -187,8 +175,6 @@ def test_resolve_repo_root_rejects_a_snapshot_missing_condition_encoder(monkeypa acoustic stage would still fail, so completeness must cover every required component rather than the identification markers alone. """ - from huggingface_hub import HfApi - snapshot = tmp_path / "models--MiniMaxAI--MiniMax-Music3" / "snapshots" / "deadbeef" for marker in _ROOT_MARKERS: folder = snapshot / marker @@ -196,13 +182,13 @@ def test_resolve_repo_root_rejects_a_snapshot_missing_condition_encoder(monkeypa (folder / _COMPONENT_WEIGHT_NAME).write_bytes(b"x") repaired = [] - def fake_snapshot_download(self, repo_id, **kwargs): + def fake_snapshot_download(repo_id, **kwargs): if not kwargs.get("local_files_only"): repaired.append(repo_id) _make_root(snapshot) return str(snapshot) - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) assert resolve_repo_root(str(snapshot)) == snapshot assert repaired == ["MiniMaxAI/MiniMax-Music3"] @@ -210,8 +196,6 @@ def test_resolve_repo_root_rejects_a_snapshot_missing_condition_encoder(monkeypa def test_resolve_repo_root_rejects_a_partial_shard_run(monkeypatch, tmp_path): """One shard of a multi-shard component satisfies a bare glob but cannot load.""" - from huggingface_hub import HfApi - stem = _COMPONENT_WEIGHT_NAME.removesuffix(".safetensors") snapshot = tmp_path / "models--MiniMaxAI--MiniMax-Music3" / "snapshots" / "deadbeef" for component in _REQUIRED_COMPONENTS: @@ -223,14 +207,14 @@ def test_resolve_repo_root_rejects_a_partial_shard_run(monkeypatch, tmp_path): (partial / f"{stem}-00001-of-00003.safetensors").write_bytes(b"x") repaired = [] - def fake_snapshot_download(self, repo_id, **kwargs): + def fake_snapshot_download(repo_id, **kwargs): if not kwargs.get("local_files_only"): repaired.append(repo_id) for index in (2, 3): (partial / f"{stem}-0000{index}-of-00003.safetensors").write_bytes(b"x") return str(snapshot) - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) assert resolve_repo_root(str(snapshot)) == snapshot assert repaired == ["MiniMaxAI/MiniMax-Music3"] @@ -242,17 +226,15 @@ def test_resolve_repo_root_threads_revision_and_download_dir(monkeypatch, tmp_pa Resolving components against the default branch while the AR backbone is pinned elsewhere would combine weights from two different commits. """ - from huggingface_hub import HfApi - snapshot = tmp_path / "snapshot" calls = [] - def fake_snapshot_download(self, repo_id, **kwargs): + def fake_snapshot_download(repo_id, **kwargs): calls.append(kwargs) _make_root(snapshot) return str(snapshot) - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) assert resolve_repo_root("MiniMaxAI/MiniMax-Music3", revision="abc123", download_dir="/tmp/hub-cache") == snapshot assert calls @@ -266,18 +248,16 @@ def test_resolve_repo_root_reuses_the_snapshot_revision_from_the_cache_path(monk Re-resolving the repo id alone would fetch the default branch rather than the snapshot the caller is already sitting in. """ - from huggingface_hub import HfApi - snapshot = tmp_path / "models--MiniMaxAI--MiniMax-Music3" / "snapshots" / "cafebabe" (snapshot / "language_model").mkdir(parents=True) calls = [] - def fake_snapshot_download(self, repo_id, **kwargs): + def fake_snapshot_download(repo_id, **kwargs): calls.append(kwargs) _make_root(snapshot) return str(snapshot) - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) assert resolve_repo_root(str(snapshot / "language_model")) == snapshot assert calls @@ -292,8 +272,6 @@ def test_resolve_repo_root_accepts_shard_names_the_loader_can_read(monkeypatch, loadable. Judging it incomplete would force a needless re-download and, with no network, fail on weights that are present and readable. """ - from huggingface_hub import HfApi - stem = _COMPONENT_WEIGHT_NAME.removesuffix(".safetensors") root = tmp_path / "snapshot" for component in _REQUIRED_COMPONENTS: @@ -303,18 +281,16 @@ def test_resolve_repo_root_accepts_shard_names_the_loader_can_read(monkeypatch, (unsharded / _COMPONENT_WEIGHT_NAME).unlink() (unsharded / f"{stem}-fp16.safetensors").write_bytes(b"x") - def fake_snapshot_download(self, repo_id, **kwargs): + def fake_snapshot_download(repo_id, **kwargs): raise AssertionError("must not reach the Hub for readable weights") - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) assert resolve_repo_root(str(root)) == root def test_resolve_repo_root_still_rejects_a_partial_run_beside_an_extra_file(monkeypatch, tmp_path): """An unparsable extra must not mask a genuinely short ``-of-`` run.""" - from huggingface_hub import HfApi - stem = _COMPONENT_WEIGHT_NAME.removesuffix(".safetensors") root = tmp_path / "snapshot" for component in _REQUIRED_COMPONENTS: @@ -326,13 +302,13 @@ def test_resolve_repo_root_still_rejects_a_partial_run_beside_an_extra_file(monk (partial / f"{stem}-extra.safetensors").write_bytes(b"x") repaired = [] - def fake_snapshot_download(self, repo_id, **kwargs): + def fake_snapshot_download(repo_id, **kwargs): repaired.append(repo_id) for index in (2, 3): (partial / f"{stem}-0000{index}-of-00003.safetensors").write_bytes(b"x") return str(root) - monkeypatch.setattr(HfApi, "snapshot_download", fake_snapshot_download) + patch_hf_snapshot_download(monkeypatch, fake_snapshot_download, hf_home=tmp_path) with pytest.raises(FileNotFoundError, match="components are incomplete"): resolve_repo_root(str(root)) diff --git a/vllm_omni/diffusion/model_loader/diffusers_loader.py b/vllm_omni/diffusion/model_loader/diffusers_loader.py index c47eab7f7..7cbff273c 100644 --- a/vllm_omni/diffusion/model_loader/diffusers_loader.py +++ b/vllm_omni/diffusion/model_loader/diffusers_loader.py @@ -25,7 +25,6 @@ from vllm.model_executor.model_loader.weight_utils import ( pt_weights_iterator, safetensors_weights_iterator, ) -from vllm.transformers_utils.repo_utils import hf_api from vllm.utils.import_utils import resolve_obj_by_qualname from vllm.utils.torch_utils import set_default_torch_dtype @@ -54,6 +53,7 @@ from vllm_omni.diffusion.offloader.module_collector import ModuleDiscovery from vllm_omni.diffusion.offloader.offload_plan import get_offload_plan from vllm_omni.diffusion.registry import initialize_model from vllm_omni.model_executor.model_loader.weight_utils import download_weights_from_hf_specific +from vllm_omni.transformers_utils.repo_utils import hf_api # download_gguf was removed from upstream vLLM (commit 6635279d8). diff --git a/vllm_omni/diffusion/models/magi2/pipeline_magi2.py b/vllm_omni/diffusion/models/magi2/pipeline_magi2.py index 910e0d074..52b95fa4c 100644 --- a/vllm_omni/diffusion/models/magi2/pipeline_magi2.py +++ b/vllm_omni/diffusion/models/magi2/pipeline_magi2.py @@ -225,7 +225,7 @@ def _resolve_checkpoint_root(model: str, revision: str | None) -> str: "MAGI-2 expects a local checkpoint directory or the official " f"model ID {MAGI2_MODEL_ID!r}; got {model!r}." ) - from vllm.transformers_utils.repo_utils import hf_api + from vllm_omni.transformers_utils.repo_utils import hf_api pinned_revision = revision or MAGI2_MODEL_REVISION logger.warning( diff --git a/vllm_omni/model_executor/models/minimax_music3/weights.py b/vllm_omni/model_executor/models/minimax_music3/weights.py index b019ff461..912395a81 100644 --- a/vllm_omni/model_executor/models/minimax_music3/weights.py +++ b/vllm_omni/model_executor/models/minimax_music3/weights.py @@ -119,7 +119,7 @@ def _download_component_snapshot( components against the default branch and the default cache while the AR backbone was pinned elsewhere would silently mix weights from two commits. """ - from vllm.transformers_utils.repo_utils import hf_api + from vllm_omni.transformers_utils.repo_utils import hf_api kwargs: dict[str, object] = {"allow_patterns": list(_COMPONENT_PATTERNS)} if revision is not None: