Spaces:
Running on L40S
Running on L40S
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| # SPDX-License-Identifier: OpenMDW-1.1 | |
| """Inference/training script test fixtures. | |
| Used by 'tests/scripts_test.py'. | |
| """ | |
| import os | |
| import re | |
| import shutil | |
| import subprocess | |
| import warnings | |
| from dataclasses import dataclass | |
| from functools import cached_property | |
| from pathlib import Path | |
| from typing import Any, Callable | |
| import numpy as np | |
| import pydantic | |
| import pytest | |
| from cosmos_framework.inference.common.args import MEDIA_EXTENSIONS, ResolvedFilePath | |
| from cosmos_framework.inference.common.init import get_free_port | |
| from cosmos_framework.inference.fixtures.args import Level, NumGpus | |
| from cosmos_framework.utils.checkpoint_db import HF_VERSION | |
| from cosmos_framework.utils.easy_io import easy_io | |
| INPUT_DIR = Path("inputs").absolute() | |
| OUTPUT_DIR = Path("outputs").absolute() | |
| class ScriptConfig(pydantic.BaseModel): | |
| model_config = pydantic.ConfigDict(extra="forbid") | |
| name: str = "" | |
| """Test name.""" | |
| script: ResolvedFilePath | |
| """Script path.""" | |
| use_tmp_input_dir: bool = False | |
| """If set, use a per-test temp directory for INPUT_DIR.""" | |
| levels: tuple[Level, ...] = (0,) | |
| """Test levels.""" | |
| gpus: tuple[NumGpus, NumGpus, NumGpus] = (0, 1, 1) | |
| """Number of GPUs for each level.""" | |
| marks: tuple[pytest.MarkDecorator | pytest.Mark, ...] = () | |
| """Additional pytest marks.""" | |
| golden_psnr: pydantic.PositiveFloat = 14.0 | |
| """Golden comparison PSNR threshold in dB.""" | |
| get_env: Callable[["ScriptRunner", "ScriptConfig"], dict[str, str]] | None = None | |
| """Function to get environment variables.""" | |
| before_script: Callable[["ScriptRunner", "ScriptConfig"], None] | None = None | |
| """Function to run before the script.""" | |
| after_script: Callable[["ScriptRunner", "ScriptConfig"], None] | None = None | |
| """Function to run after the script.""" | |
| def validate_name(cls, data: Any) -> Any: | |
| if not isinstance(data, dict): | |
| return data | |
| if not data.get("name"): | |
| script_path: Path = data["script"] | |
| data["name"] = script_path.name.replace(".sh", "") | |
| return data | |
| def get_marks(self, level: int) -> list[pytest.MarkDecorator | pytest.Mark]: | |
| marks = list(self.marks) | |
| if level not in self.levels: | |
| marks.append(pytest.mark.manual) | |
| marks.append(pytest.mark.gpus(self.gpus[level])) | |
| return marks | |
| class ScriptRunner: | |
| request: pytest.FixtureRequest | |
| tmp_path_factory: pytest.TempPathFactory | |
| tmp_path: Path | |
| level: int = 0 | |
| def output_name(self) -> str: | |
| test_name = self.request.node.name | |
| if "[" in test_name and "]" in test_name: | |
| base_part, param_part = test_name.split("[", 1) | |
| param_part = param_part.rstrip("]").replace("/", "_").replace("-", "_") | |
| sanitized_name = f"{base_part}_{param_part}" | |
| else: | |
| sanitized_name = test_name.replace("/", "_").replace("-", "_") | |
| return sanitized_name | |
| def input_dir(self) -> Path: | |
| return INPUT_DIR | |
| def tmp_input_dir(self) -> Path: | |
| return self.tmp_path / "inputs" | |
| def output_dir(self) -> Path: | |
| return OUTPUT_DIR / "pytest" / self.output_name | |
| def golden_dir(self) -> Path: | |
| return INPUT_DIR / "outputs/pytest" / self.output_name | |
| def _get_env( | |
| self, | |
| cfg: ScriptConfig, | |
| *, | |
| torchrun_args: list[str] | None = None, | |
| inference_args: list[str] | None = None, | |
| train_args: list[str] | None = None, | |
| train_overrides: list[str] | None = None, | |
| ) -> dict[str, str]: | |
| if torchrun_args is None: | |
| torchrun_args = [] | |
| if inference_args is None: | |
| inference_args = [] | |
| if train_args is None: | |
| train_args = [] | |
| if train_overrides is None: | |
| train_overrides = [] | |
| num_gpus = os.environ["NUM_GPUS"] | |
| master_port = get_free_port() | |
| env = dict(os.environ) | |
| # Ensure reproducibility | |
| env = {k: v for k, v in os.environ.items() if not k.startswith("COSMOS_")} | |
| env |= { | |
| "COSMOS_INTERNAL": "0", | |
| # Disable S3 checkpoints | |
| "IMAGINAIRE_CACHE_DIR": "/invalid", | |
| "INPUT_DIR": f"{self.tmp_input_dir if cfg.use_tmp_input_dir else self.input_dir}", | |
| "OUTPUT_DIR": f"{self.output_dir}", | |
| "TMP_DIR": f"{self.tmp_path}/tmp", | |
| "MASTER_PORT": str(master_port), | |
| "HF_VERSION": HF_VERSION, | |
| "TORCHRUN_ARGS": " ".join( | |
| [ | |
| f"--nproc_per_node={num_gpus}", | |
| f"--master_port={master_port}", | |
| *torchrun_args, | |
| ] | |
| ), | |
| "INFERENCE_ARGS": " ".join( | |
| [ | |
| "--seed=0", | |
| "--debug", | |
| *inference_args, | |
| ] | |
| ), | |
| "TRAIN_ARGS": " ".join( | |
| [ | |
| *train_args, | |
| ] | |
| ), | |
| "TRAIN_OVERRIDES": " ".join( | |
| [ | |
| "job.wandb_mode=disabled", | |
| f"model.config.parallelism.data_parallel_shard_degree={num_gpus}", | |
| "model.config.parallelism.context_parallel_shard_degree=1", | |
| "model.config.parallelism.cfg_parallel_shard_degree=1", | |
| *train_overrides, | |
| ] | |
| ), | |
| } | |
| if cfg.get_env is not None: | |
| env |= cfg.get_env(self, cfg) | |
| return env | |
| def get_env(self, cfg: ScriptConfig, level: int) -> dict[str, str]: | |
| match level: | |
| case 0: | |
| return self._get_env(cfg) | {"COSMOS_SMOKE": "1"} | |
| case 1: | |
| return self._get_env( | |
| cfg, | |
| inference_args=[ | |
| "--no-guardrails", | |
| ], | |
| train_overrides=[ | |
| "trainer.max_iter=5", | |
| ], | |
| ) | |
| case 2: | |
| return self._get_env( | |
| cfg, | |
| inference_args=[ | |
| "--guardrails", | |
| ], | |
| train_overrides=[ | |
| "trainer.max_iter=20", | |
| ], | |
| ) | |
| case _: | |
| assert False, "unreachable" | |
| def run(self, cfg: ScriptConfig, level: int): | |
| object.__setattr__(self, "level", level) # frozen dataclass, but level is set per call | |
| shutil.rmtree(self.output_dir, ignore_errors=True) | |
| if cfg.before_script is not None: | |
| cfg.before_script(self, cfg) | |
| subprocess.check_call( | |
| ["bash", "-euxo", "pipefail", str(cfg.script)], | |
| cwd=self.request.config.rootpath, | |
| env=self.get_env(cfg, level), | |
| ) | |
| if cfg.after_script is not None: | |
| cfg.after_script(self, cfg) | |
| if False: | |
| _check_golden_dir(self.output_dir, self.golden_dir, min_psnr=cfg.golden_psnr) | |
| def script_test(configs: list[ScriptConfig]) -> Callable[[type], type]: | |
| names = set() | |
| for cfg in configs: | |
| if cfg.name in names: | |
| raise ValueError(f"Duplicate script name: {cfg.name}") | |
| names.add(cfg.name) | |
| def decorator(cls: type) -> type: | |
| def script_runner( | |
| self, request: pytest.FixtureRequest, tmp_path_factory: pytest.TempPathFactory, tmp_path: Path | |
| ) -> ScriptRunner: | |
| return ScriptRunner(request=request, tmp_path_factory=tmp_path_factory, tmp_path=tmp_path) | |
| setattr(cls, "script_runner", script_runner) | |
| def test_level_0(self, cfg: ScriptConfig, script_runner: ScriptRunner): | |
| script_runner.run(cfg, 0) | |
| setattr(cls, "test_level_0", test_level_0) | |
| def test_level_1(self, cfg: ScriptConfig, script_runner: ScriptRunner): | |
| script_runner.run(cfg, 1) | |
| setattr(cls, "test_level_1", test_level_1) | |
| def test_level_2(self, cfg: ScriptConfig, script_runner: ScriptRunner): | |
| script_runner.run(cfg, 2) | |
| setattr(cls, "test_level_2", test_level_2) | |
| return cls | |
| return decorator | |
| def _extract_bash_commands(md_file: Path) -> list[str]: | |
| content = md_file.read_text() | |
| pattern = r"```(bash|shell)([^\n]*)\n(.*?)```" | |
| matches = re.findall(pattern, content, re.DOTALL) | |
| scripts = [] | |
| for lang, attrs, block_content in matches: | |
| if "exclude=true" in attrs.lower(): | |
| continue | |
| lines = [] | |
| for line in block_content.strip().split("\n"): | |
| if line.strip() and not line.strip().startswith("#"): | |
| line = line.split("#")[0].rstrip() | |
| # Replace --nproc_per_node with dynamic NUM_GPUS value | |
| line = re.sub(r"--nproc_per_node=\d+", "--nproc_per_node=$NUM_GPUS", line) | |
| line = re.sub(r"--master_port=\d+", "--master_port=$MASTER_PORT", line) | |
| if line: | |
| lines.append(line) | |
| if lines: | |
| script = "\n".join(lines) | |
| scripts.append(script) | |
| return scripts | |
| def _array_to_float(array: np.ndarray) -> np.ndarray: | |
| if np.issubdtype(array.dtype, np.floating): | |
| assert np.min(array) >= 0.0 and np.max(array) <= 1.0 | |
| return array | |
| if array.dtype == np.uint8: | |
| return array / 255.0 | |
| raise NotImplementedError(f"Unsupported dtype: {array.dtype}") | |
| def _compute_psnr(array1: np.ndarray, array2: np.ndarray) -> float: | |
| """Compare PSNR between two arrays.""" | |
| array1 = _array_to_float(array1) | |
| array2 = _array_to_float(array2) | |
| overall_mse = ((array1 - array2) ** 2).mean() | |
| return 10 * np.log10(1.0 / overall_mse) if overall_mse > 0 else float("inf") | |
| def _check_golden_file(output_path: Path, golden_path: Path, /, min_psnr: float) -> None: | |
| output_array, _output_meta = easy_io.load(output_path) | |
| assert isinstance(output_array, np.ndarray) | |
| golden_array, _golden_meta = easy_io.load(golden_path) | |
| assert isinstance(golden_array, np.ndarray) | |
| psnr = _compute_psnr(output_array, golden_array) | |
| if psnr < min_psnr: | |
| warnings.warn( | |
| f"FAIL: Golden PSNR {psnr:.2f} dB is less than minimum {min_psnr:.2f} dB for file '{output_path}'" | |
| ) | |
| else: | |
| print(f"PASS: Golden PSNR {psnr:.2f} dB is greater than minimum {min_psnr:.2f} dB for file '{output_path}'") | |
| def _check_golden_dir(output_dir: Path, golden_dir: Path, /, min_psnr: float) -> None: | |
| if not golden_dir.exists(): | |
| warnings.warn(f"Golden directory '{golden_dir}' does not exist") | |
| return | |
| for dirpath, _dirnames, filenames in os.walk(golden_dir): | |
| for filename in filenames: | |
| golden_path = Path(dirpath) / filename | |
| output_path = output_dir / golden_path.relative_to(golden_dir) | |
| if output_path.suffix not in MEDIA_EXTENSIONS: | |
| continue | |
| if not output_path.exists(): | |
| warnings.warn(f"File '{output_path}' missing in output directory") | |
| continue | |
| _check_golden_file(output_path, golden_path, min_psnr=min_psnr) | |