From 9f9bb334718f14f452b8ce8b6cf9fe10025059ff Mon Sep 17 00:00:00 2001 From: Kyle Romero Date: Wed, 6 May 2026 21:08:39 +0000 Subject: [PATCH 1/7] initial qairtpipeline pass implementation --- olive/olive_config.json | 9 + olive/passes/qairt/pipeline.py | 121 +++++++++++ test/passes/qairt/test_pipeline_pass.py | 262 ++++++++++++++++++++++++ 3 files changed, 392 insertions(+) create mode 100644 olive/passes/qairt/pipeline.py create mode 100644 test/passes/qairt/test_pipeline_pass.py diff --git a/olive/olive_config.json b/olive/olive_config.json index 97ca9bd02..9857292ba 100644 --- a/olive/olive_config.json +++ b/olive/olive_config.json @@ -511,6 +511,15 @@ "supported_quantization_encodings": [ ], "extra_dependencies": [ "qairt-dev" ] }, + "QairtPipelinePass": { + "module_path": "olive.passes.qairt.pipeline.QairtPipelinePass", + "supported_providers": [ "QNNExecutionProvider" ], + "supported_accelerators": [ "npu" ], + "supported_precisions": [ "*" ], + "supported_algorithms": [ ], + "supported_quantization_encodings": [ ], + "extra_dependencies": [ "qairt-dev" ] + }, "QairtPreparation": { "module_path": "olive.passes.qairt.preparation.QairtPreparation", "supported_providers": [ "QNNExecutionProvider" ], diff --git a/olive/passes/qairt/pipeline.py b/olive/passes/qairt/pipeline.py new file mode 100644 index 000000000..5d1fd31f3 --- /dev/null +++ b/olive/passes/qairt/pipeline.py @@ -0,0 +1,121 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: MIT +# -------------------------------------------------------------------------- + +import logging +from pathlib import Path + +from olive.common.config_utils import ParamCategory +from olive.hardware.accelerator import AcceleratorSpec +from olive.model import HfModelHandler, QairtModelHandler +from olive.passes import Pass +from olive.passes.pass_config import BasePassConfig, PassConfigParam +from olive.passes.qairt.utils import QairtLogLevel + +logger = logging.getLogger(__name__) + + +class QairtPipelinePass(Pass): + """Run a QairtPipeline from a YAML recipe on a HuggingFace model. + + Executes the full LLMPipeline workflow (model loading, quantization, compilation) + defined by the recipe and exports the result as a QairtModelHandler. This pass + is intended to replace the QairtPreparation -> QairtGenAIBuilder workflow. + + The input HfModelHandler is the authoritative source for the model identity. + If the recipe also specifies model_id_or_path and it differs from the handler's + path, an error is raised. If the recipe omits model_id_or_path, the handler's + path is used. + """ + + @classmethod + def _default_config(cls, accelerator_spec: AcceleratorSpec) -> dict[str, PassConfigParam]: + return { + "recipe": PassConfigParam( + type_=str, + required=True, + category=ParamCategory.PATH, + description="Path to the YAML recipe file that defines the LLM pipeline stages " + "(model loading, quantization, genai_builder, etc.).", + ), + "cache_dir": PassConfigParam( + type_=str, + required=False, + default_value=None, + description="Directory for pipeline intermediate artifacts. " + "Overrides the recipe's cache_dir field when set.", + ), + "log_level": PassConfigParam( + type_=QairtLogLevel, + required=False, + default_value=None, + description="Log level for underlying QAIRT pipeline components. " + "Valid values: DEBUG, INFO, WARN, ERROR. " + "Overrides the recipe's log_level field when set.", + ), + } + + @classmethod + def validate_config( + cls, + config: type[BasePassConfig], + accelerator_spec: AcceleratorSpec, + ) -> bool: + try: + import qairt # noqa: F401 # pylint: disable=unused-import + except ImportError as exc: + raise ImportError( + "Failed to import QAIRT SDK - please install olive-ai[qairt] to use QAIRT passes. " + "If already installed, please run `qairt-vm -i` for help troubleshooting issues." + ) from exc + + return True + + def _run_for_config( + self, + model: HfModelHandler, + config: type[BasePassConfig], + output_model_path: str, + ) -> QairtModelHandler: + try: + import qairt # noqa: F401 # pylint: disable=unused-import + from qairt.experimental.pipeline.torch.common.recipe import Recipe + from qairt.experimental.pipeline.torch.llm.pipeline import LLMPipeline + except ImportError as exc: + raise ImportError( + "Failed to import QAIRT Pipeline API - please install olive-ai[qairt] to use QAIRT passes. " + "If already installed, please run `qairt-vm -i` for help troubleshooting issues." + ) from exc + + if not isinstance(model, HfModelHandler): + raise ValueError( + f"QairtPipelinePass requires HfModelHandler as input, got {type(model).__name__}" + ) + + recipe_path = Path(config.recipe).resolve() + if not recipe_path.exists(): + raise ValueError(f"Recipe file not found at: {recipe_path}") + + recipe_data = dict(Recipe.from_file(recipe_path)) + + recipe_model_id = recipe_data.get("model_id_or_path") + if recipe_model_id and recipe_model_id != model.model_path: + raise ValueError( + f"Conflict between recipe model_id_or_path '{recipe_model_id}' and input model " + f"path '{model.model_path}'. Remove model_id_or_path from the recipe or ensure " + "it matches the input model path." + ) + + if config.cache_dir: + recipe_data["cache_dir"] = config.cache_dir + if config.log_level: + recipe_data["log_level"] = config.log_level + + pipe = LLMPipeline.from_pretrained(model.model_path, recipe=recipe_data) + pipe.construct() + + Path(output_model_path).mkdir(parents=True, exist_ok=True) + pipe.export(output_model_path) + + return QairtModelHandler(model_path=output_model_path) diff --git a/test/passes/qairt/test_pipeline_pass.py b/test/passes/qairt/test_pipeline_pass.py new file mode 100644 index 000000000..1276f48db --- /dev/null +++ b/test/passes/qairt/test_pipeline_pass.py @@ -0,0 +1,262 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: MIT +# -------------------------------------------------------------------------- +# pylint: disable=protected-access + +import builtins +from unittest.mock import MagicMock, patch + +import pytest +import yaml + +from olive.model import QairtModelHandler +from olive.passes.olive_pass import create_pass_from_dict +from olive.passes.qairt.pipeline import QairtPipelinePass + + +@pytest.fixture(name="mock_pipeline_modules") +def mock_pipeline_modules_fixture(): + """Mock qairt and the LLMPipeline API.""" + mock_qairt = MagicMock() + mock_recipe_cls = MagicMock() + mock_pipeline_cls = MagicMock() + + mock_pipeline = MagicMock() + mock_pipeline_cls.from_pretrained.return_value = mock_pipeline + + with ( + patch.dict("sys.modules", {"qairt": mock_qairt}), + patch( + "qairt.experimental.pipeline.torch.common.recipe.Recipe", + mock_recipe_cls, + create=True, + ), + patch( + "qairt.experimental.pipeline.torch.llm.pipeline.LLMPipeline", + mock_pipeline_cls, + create=True, + ), + patch.dict( + "sys.modules", + { + "qairt.experimental.pipeline.torch.common.recipe": MagicMock(Recipe=mock_recipe_cls), + "qairt.experimental.pipeline.torch.llm.pipeline": MagicMock(LLMPipeline=mock_pipeline_cls), + }, + ), + ): + yield { + "qairt": mock_qairt, + "Recipe": mock_recipe_cls, + "LLMPipeline": mock_pipeline_cls, + "pipeline": mock_pipeline, + } + + +@pytest.fixture(name="recipe_file") +def recipe_file_fixture(tmp_path): + """Write a minimal YAML recipe file with no model_id_or_path.""" + recipe = { + "cache_dir": "./pipeline_cache", + "backend": "HTP", + "stages": { + "model_loader": {}, + "quantization": {"recipe_name": "lpbq_seqmse"}, + }, + } + path = tmp_path / "recipe.yaml" + path.write_text(yaml.dump(recipe)) + return path + + +@pytest.fixture(name="recipe_file_with_model_id") +def recipe_file_with_model_id_fixture(tmp_path): + """Write a minimal YAML recipe that includes model_id_or_path.""" + recipe = { + "model_id_or_path": "meta-llama/Llama-3.2-3B-Instruct", + "cache_dir": "./pipeline_cache", + "backend": "HTP", + "stages": {"model_loader": {}}, + } + path = tmp_path / "recipe_with_model.yaml" + path.write_text(yaml.dump(recipe)) + return path + + +def test_pipeline_pass_default_config(mock_accelerator_spec): + """Test that the default config has the expected parameters.""" + config = QairtPipelinePass._default_config(mock_accelerator_spec) + + assert "recipe" in config + assert config["recipe"].required is True + assert "cache_dir" in config + assert config["cache_dir"].default_value is None + assert "log_level" in config + assert config["log_level"].default_value is None + + +def test_pipeline_pass_success(tmp_path, mock_hf_model, recipe_file, mock_pipeline_modules): + """Test successful pass execution with no model_id_or_path in recipe.""" + output_path = tmp_path / "output" + + mock_pipeline_modules["Recipe"].from_file.return_value = { + "cache_dir": "./pipeline_cache", + "backend": "HTP", + "stages": {}, + } + + pipeline_pass = create_pass_from_dict( + QairtPipelinePass, + {"recipe": str(recipe_file)}, + disable_search=True, + ) + + result = pipeline_pass.run(mock_hf_model, str(output_path)) + + assert isinstance(result, QairtModelHandler) + assert result.model_path == str(output_path) + mock_pipeline_modules["LLMPipeline"].from_pretrained.assert_called_once_with( + mock_hf_model.model_path, + recipe={"cache_dir": "./pipeline_cache", "backend": "HTP", "stages": {}, "model_id_or_path": mock_hf_model.model_path}, + ) + mock_pipeline_modules["pipeline"].construct.assert_called_once() + mock_pipeline_modules["pipeline"].export.assert_called_once_with(str(output_path)) + + +def test_pipeline_pass_recipe_model_id_matches_handler( + tmp_path, mock_hf_model, recipe_file_with_model_id, mock_pipeline_modules +): + """Test that no error is raised when recipe model_id_or_path matches the handler path.""" + output_path = tmp_path / "output" + + mock_pipeline_modules["Recipe"].from_file.return_value = { + "model_id_or_path": mock_hf_model.model_path, + "stages": {}, + } + + pipeline_pass = create_pass_from_dict( + QairtPipelinePass, + {"recipe": str(recipe_file_with_model_id)}, + disable_search=True, + ) + + result = pipeline_pass.run(mock_hf_model, str(output_path)) + assert isinstance(result, QairtModelHandler) + + +def test_pipeline_pass_recipe_model_id_conflict_raises( + tmp_path, mock_hf_model, recipe_file_with_model_id, mock_pipeline_modules +): + """Test that a ValueError is raised when recipe model_id_or_path conflicts with handler path.""" + output_path = tmp_path / "output" + + mock_pipeline_modules["Recipe"].from_file.return_value = { + "model_id_or_path": "meta-llama/Llama-3.2-3B-Instruct", + "stages": {}, + } + + pipeline_pass = create_pass_from_dict( + QairtPipelinePass, + {"recipe": str(recipe_file_with_model_id)}, + disable_search=True, + ) + + with pytest.raises(ValueError, match="Conflict between recipe model_id_or_path"): + pipeline_pass.run(mock_hf_model, str(output_path)) + + +def test_pipeline_pass_cache_dir_override(tmp_path, mock_hf_model, recipe_file, mock_pipeline_modules): + """Test that Olive-level cache_dir overrides the recipe's cache_dir.""" + output_path = tmp_path / "output" + + mock_pipeline_modules["Recipe"].from_file.return_value = { + "cache_dir": "./recipe_cache", + "stages": {}, + } + + pipeline_pass = create_pass_from_dict( + QairtPipelinePass, + {"recipe": str(recipe_file), "cache_dir": "/custom/cache"}, + disable_search=True, + ) + + pipeline_pass.run(mock_hf_model, str(output_path)) + + call_kwargs = mock_pipeline_modules["LLMPipeline"].from_pretrained.call_args + recipe_arg = call_kwargs.kwargs["recipe"] + assert recipe_arg["cache_dir"] == "/custom/cache" + + +def test_pipeline_pass_log_level_override(tmp_path, mock_hf_model, recipe_file, mock_pipeline_modules): + """Test that Olive-level log_level overrides the recipe's log_level.""" + output_path = tmp_path / "output" + + mock_pipeline_modules["Recipe"].from_file.return_value = { + "log_level": "warn", + "stages": {}, + } + + pipeline_pass = create_pass_from_dict( + QairtPipelinePass, + {"recipe": str(recipe_file), "log_level": "DEBUG"}, + disable_search=True, + ) + + pipeline_pass.run(mock_hf_model, str(output_path)) + + call_kwargs = mock_pipeline_modules["LLMPipeline"].from_pretrained.call_args + recipe_arg = call_kwargs.kwargs["recipe"] + assert recipe_arg["log_level"] == "DEBUG" + + +def test_pipeline_pass_invalid_input_model(tmp_path, mock_qairt_prepared_model, recipe_file, mock_pipeline_modules): + """Test that ValueError is raised when input is not HfModelHandler.""" + output_path = tmp_path / "output" + + mock_pipeline_modules["Recipe"].from_file.return_value = {"stages": {}} + + pipeline_pass = create_pass_from_dict( + QairtPipelinePass, + {"recipe": str(recipe_file)}, + disable_search=True, + ) + + with pytest.raises(ValueError, match="QairtPipelinePass requires HfModelHandler"): + pipeline_pass.run(mock_qairt_prepared_model, str(output_path)) + + +def test_pipeline_pass_missing_recipe_file(tmp_path, mock_hf_model, mock_pipeline_modules): + """Test that ValueError is raised when recipe file does not exist.""" + output_path = tmp_path / "output" + + mock_pipeline_modules["Recipe"].from_file.return_value = {"stages": {}} + + pipeline_pass = create_pass_from_dict( + QairtPipelinePass, + {"recipe": str(tmp_path / "nonexistent_recipe.yaml")}, + disable_search=True, + ) + + with pytest.raises(ValueError, match="Recipe file not found"): + pipeline_pass.run(mock_hf_model, str(output_path)) + + +def test_pipeline_pass_import_error(tmp_path, mock_hf_model, recipe_file): + """Test that ImportError is raised if qairt cannot be imported.""" + + def import_side_effect(name, *args, **kwargs): + if "qairt" in name: + raise ImportError("Mock import error") + return original_import(name, *args, **kwargs) + + original_import = builtins.__import__ + + with patch("builtins.__import__", side_effect=import_side_effect): + pipeline_pass = create_pass_from_dict( + QairtPipelinePass, + {"recipe": str(recipe_file)}, + disable_search=True, + ) + + with pytest.raises(ImportError, match="Failed to import QAIRT Pipeline API"): + pipeline_pass.run(mock_hf_model, str(tmp_path / "output")) From a0a2f00919e2b74c983e32bdc72ce45ca81db532 Mon Sep 17 00:00:00 2001 From: Kyle Romero Date: Thu, 14 May 2026 21:56:22 +0000 Subject: [PATCH 2/7] Copy HF config files to output dir for QairtEncapsulation QairtEncapsulation needs config.json and generation_config.json to generate genai_config.json. Copy them from the source HF model if not already present. --- olive/passes/qairt/pipeline.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/olive/passes/qairt/pipeline.py b/olive/passes/qairt/pipeline.py index 5d1fd31f3..8bc07e91a 100644 --- a/olive/passes/qairt/pipeline.py +++ b/olive/passes/qairt/pipeline.py @@ -4,6 +4,7 @@ # -------------------------------------------------------------------------- import logging +import shutil from pathlib import Path from olive.common.config_utils import ParamCategory @@ -118,4 +119,12 @@ def _run_for_config( Path(output_model_path).mkdir(parents=True, exist_ok=True) pipe.export(output_model_path) + # QairtEncapsulation needs config.json and generation_config.json to generate + # genai_config.json. Copy them from the source HF model if not already present. + for fname in ("config.json", "generation_config.json"): + src = Path(model.model_path) / fname + dst = Path(output_model_path) / fname + if src.exists() and not dst.exists(): + shutil.copy2(src, dst) + return QairtModelHandler(model_path=output_model_path) From 922c53bf11066e560b51fd865e9ce93622b5ca55 Mon Sep 17 00:00:00 2001 From: Kyle Romero Date: Thu, 14 May 2026 15:18:37 -0700 Subject: [PATCH 3/7] Fix None checks for config overrides and correct test assertion --- olive/passes/qairt/pipeline.py | 4 ++-- test/passes/qairt/test_pipeline_pass.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/olive/passes/qairt/pipeline.py b/olive/passes/qairt/pipeline.py index 8bc07e91a..3e7c2a3d1 100644 --- a/olive/passes/qairt/pipeline.py +++ b/olive/passes/qairt/pipeline.py @@ -108,9 +108,9 @@ def _run_for_config( "it matches the input model path." ) - if config.cache_dir: + if config.cache_dir is not None: recipe_data["cache_dir"] = config.cache_dir - if config.log_level: + if config.log_level is not None: recipe_data["log_level"] = config.log_level pipe = LLMPipeline.from_pretrained(model.model_path, recipe=recipe_data) diff --git a/test/passes/qairt/test_pipeline_pass.py b/test/passes/qairt/test_pipeline_pass.py index 1276f48db..23f38b268 100644 --- a/test/passes/qairt/test_pipeline_pass.py +++ b/test/passes/qairt/test_pipeline_pass.py @@ -117,7 +117,7 @@ def test_pipeline_pass_success(tmp_path, mock_hf_model, recipe_file, mock_pipeli assert result.model_path == str(output_path) mock_pipeline_modules["LLMPipeline"].from_pretrained.assert_called_once_with( mock_hf_model.model_path, - recipe={"cache_dir": "./pipeline_cache", "backend": "HTP", "stages": {}, "model_id_or_path": mock_hf_model.model_path}, + recipe={"cache_dir": "./pipeline_cache", "backend": "HTP", "stages": {}}, ) mock_pipeline_modules["pipeline"].construct.assert_called_once() mock_pipeline_modules["pipeline"].export.assert_called_once_with(str(output_path)) From eb1be3b6f10f70282bf5b5a6d5a88e41b46980e6 Mon Sep 17 00:00:00 2001 From: Kyle Romero Date: Thu, 14 May 2026 15:25:36 -0700 Subject: [PATCH 4/7] Apply ruff formatting to pipeline.py --- olive/passes/qairt/pipeline.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/olive/passes/qairt/pipeline.py b/olive/passes/qairt/pipeline.py index 3e7c2a3d1..b76805bc8 100644 --- a/olive/passes/qairt/pipeline.py +++ b/olive/passes/qairt/pipeline.py @@ -90,9 +90,7 @@ def _run_for_config( ) from exc if not isinstance(model, HfModelHandler): - raise ValueError( - f"QairtPipelinePass requires HfModelHandler as input, got {type(model).__name__}" - ) + raise ValueError(f"QairtPipelinePass requires HfModelHandler as input, got {type(model).__name__}") recipe_path = Path(config.recipe).resolve() if not recipe_path.exists(): From a50d398a06102464e520dda1547c015d134ba531 Mon Sep 17 00:00:00 2001 From: Kyle Romero Date: Fri, 15 May 2026 16:28:43 +0000 Subject: [PATCH 5/7] Fix config file copy when model_path is a HuggingFace repo ID Use snapshot_download(local_files_only=True) to resolve the local HF cache path before copying config.json and generation_config.json. Fixes the case where model.model_path is a HuggingFace repo ID rather than a local directory. --- olive/passes/qairt/pipeline.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/olive/passes/qairt/pipeline.py b/olive/passes/qairt/pipeline.py index b76805bc8..34b93b556 100644 --- a/olive/passes/qairt/pipeline.py +++ b/olive/passes/qairt/pipeline.py @@ -118,9 +118,21 @@ def _run_for_config( pipe.export(output_model_path) # QairtEncapsulation needs config.json and generation_config.json to generate - # genai_config.json. Copy them from the source HF model if not already present. + # genai_config.json. Resolve the local HF cache path (model.model_path may be a + # HuggingFace repo ID rather than a local directory) and copy if not already present. + try: + from huggingface_hub import snapshot_download + + local_model_path = snapshot_download( + model.model_path, + local_files_only=True, + ignore_patterns=["*.pt", "*.bin", "*.safetensors"], + ) + except Exception: + local_model_path = model.model_path + for fname in ("config.json", "generation_config.json"): - src = Path(model.model_path) / fname + src = Path(local_model_path) / fname dst = Path(output_model_path) / fname if src.exists() and not dst.exists(): shutil.copy2(src, dst) From b69668d8074c03c6527d9be8288ec25b18f9b699 Mon Sep 17 00:00:00 2001 From: Kyle Romero Date: Tue, 19 May 2026 18:40:09 +0000 Subject: [PATCH 6/7] Copy chat_template files to model root for QairtEncapsulation The pipeline export writes chat_template.jinja and tokenizer_config.json into a chat_template/ subdirectory, but QairtEncapsulation expects them as flat files in the model root. Mirror the existing config.json and generation_config.json copy step so encapsulation can resolve both files without changes on the consumer side. --- olive/passes/qairt/pipeline.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/olive/passes/qairt/pipeline.py b/olive/passes/qairt/pipeline.py index 34b93b556..347807dc2 100644 --- a/olive/passes/qairt/pipeline.py +++ b/olive/passes/qairt/pipeline.py @@ -137,4 +137,13 @@ def _run_for_config( if src.exists() and not dst.exists(): shutil.copy2(src, dst) + # The pipeline exports chat_template files into a chat_template/ subdirectory. + # QairtEncapsulation expects these as flat files in the model root. + chat_template_dir = Path(output_model_path) / "chat_template" + for fname in ("chat_template.jinja", "tokenizer_config.json"): + src = chat_template_dir / fname + dst = Path(output_model_path) / fname + if src.exists() and not dst.exists(): + shutil.copy2(src, dst) + return QairtModelHandler(model_path=output_model_path) From 4f899675cc23a00351c5ca485b8736bd1e2e790d Mon Sep 17 00:00:00 2001 From: Kyle Romero Date: Tue, 19 May 2026 14:11:49 -0700 Subject: [PATCH 7/7] Address self-review: log_level description, bare except warning, test fixture clarity - Fix log_level description to match QairtLogLevel enum (WARNING/TRACE, not WARN) - Add logger.warning() to bare except in HF cache resolution block - Add comment to validate_config noting sub-module limitation - Simplify recipe fixtures to empty files with clarifying comments - Remove unused yaml import from test file --- olive/passes/qairt/pipeline.py | 12 ++++++++++-- test/passes/qairt/test_pipeline_pass.py | 25 ++++++------------------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/olive/passes/qairt/pipeline.py b/olive/passes/qairt/pipeline.py index 347807dc2..ca630774e 100644 --- a/olive/passes/qairt/pipeline.py +++ b/olive/passes/qairt/pipeline.py @@ -52,7 +52,7 @@ def _default_config(cls, accelerator_spec: AcceleratorSpec) -> dict[str, PassCon required=False, default_value=None, description="Log level for underlying QAIRT pipeline components. " - "Valid values: DEBUG, INFO, WARN, ERROR. " + "Valid values: DEBUG, INFO, WARNING, ERROR, TRACE. " "Overrides the recipe's log_level field when set.", ), } @@ -63,6 +63,9 @@ def validate_config( config: type[BasePassConfig], accelerator_spec: AcceleratorSpec, ) -> bool: + # Only validates the top-level qairt import. The qairt.experimental.pipeline.* + # sub-modules are not checked here; if they are absent (e.g. older SDK), the + # error surfaces in _run_for_config instead. try: import qairt # noqa: F401 # pylint: disable=unused-import except ImportError as exc: @@ -128,7 +131,12 @@ def _run_for_config( local_files_only=True, ignore_patterns=["*.pt", "*.bin", "*.safetensors"], ) - except Exception: + except Exception as e: + logger.warning( + "Failed to resolve local HF cache for '%s': %s. File copy will be skipped.", + model.model_path, + e, + ) local_model_path = model.model_path for fname in ("config.json", "generation_config.json"): diff --git a/test/passes/qairt/test_pipeline_pass.py b/test/passes/qairt/test_pipeline_pass.py index 23f38b268..09ab4c096 100644 --- a/test/passes/qairt/test_pipeline_pass.py +++ b/test/passes/qairt/test_pipeline_pass.py @@ -8,7 +8,6 @@ from unittest.mock import MagicMock, patch import pytest -import yaml from olive.model import QairtModelHandler from olive.passes.olive_pass import create_pass_from_dict @@ -55,31 +54,19 @@ def mock_pipeline_modules_fixture(): @pytest.fixture(name="recipe_file") def recipe_file_fixture(tmp_path): - """Write a minimal YAML recipe file with no model_id_or_path.""" - recipe = { - "cache_dir": "./pipeline_cache", - "backend": "HTP", - "stages": { - "model_loader": {}, - "quantization": {"recipe_name": "lpbq_seqmse"}, - }, - } + # Content is irrelevant — Recipe.from_file is mocked in every test that uses this fixture. + # The file must exist so that the recipe_path.exists() guard in _run_for_config passes. path = tmp_path / "recipe.yaml" - path.write_text(yaml.dump(recipe)) + path.write_text("") return path @pytest.fixture(name="recipe_file_with_model_id") def recipe_file_with_model_id_fixture(tmp_path): - """Write a minimal YAML recipe that includes model_id_or_path.""" - recipe = { - "model_id_or_path": "meta-llama/Llama-3.2-3B-Instruct", - "cache_dir": "./pipeline_cache", - "backend": "HTP", - "stages": {"model_loader": {}}, - } + # Content is irrelevant — Recipe.from_file is mocked in every test that uses this fixture. + # The file must exist so that the recipe_path.exists() guard in _run_for_config passes. path = tmp_path / "recipe_with_model.yaml" - path.write_text(yaml.dump(recipe)) + path.write_text("") return path