From 059238268b1b4998c41ab069b1dc354aa6736b4d Mon Sep 17 00:00:00 2001 From: Maosheng Liao Date: Wed, 17 Jun 2026 08:13:34 -0700 Subject: [PATCH] fix(mot): resolve shipped model-config JSONs relative to the package root MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _MoTConfigBase.from_json_file open()'d its json_file argument verbatim. The shipped config defaults (configs/base/defaults/vlm.py) pass repo-root-relative paths like "cosmos_framework/model/vfm/vlm/qwen3_vl/configs/Qwen3-VL-8B-Instruct.json", so model construction only worked when the process CWD was the framework repo root. Launching cosmos_framework.scripts.train from any other directory (e.g. a cookbook folder) raised FileNotFoundError. Resolve a relative json_file that does not exist against the CWD against the installed package root (parent of the cosmos_framework package — the repo root for editable installs, site-packages for wheels; the JSONs ship there in both). Absolute paths and CWD-relative paths that exist are returned unchanged, so behavior is unchanged when launched from the repo root. Mirrors the package-root resolution the inference path already does via chdir, without mutating global process state. Add unified_mot_test.py covering the helper and from_json_file from a foreign CWD. Co-Authored-By: Claude Opus 4.8 (1M context) --- cosmos_framework/model/vfm/mot/unified_mot.py | 34 +++++++++- .../model/vfm/mot/unified_mot_test.py | 68 +++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 cosmos_framework/model/vfm/mot/unified_mot_test.py diff --git a/cosmos_framework/model/vfm/mot/unified_mot.py b/cosmos_framework/model/vfm/mot/unified_mot.py index 4ead628..2444c66 100644 --- a/cosmos_framework/model/vfm/mot/unified_mot.py +++ b/cosmos_framework/model/vfm/mot/unified_mot.py @@ -5,6 +5,7 @@ import time from collections.abc import Mapping from dataclasses import dataclass +from pathlib import Path from typing import Any import torch @@ -139,6 +140,29 @@ def is_moe(self) -> bool: # MoT wrapper configs — one per architecture family # ----------------------------------------------------------------------------- +# Package root = parent of the top-level ``cosmos_framework`` package directory, +# i.e. the framework repo root in an editable install or site-packages for a +# wheel. The shipped model-config JSONs live under ``cosmos_framework/model/...`` +# beneath it. +_PACKAGE_ROOT = Path(__file__).resolve().parents[4] + + +def _resolve_packaged_config_path(json_file: str) -> str: + """Resolve a model-config JSON path so it loads regardless of CWD. + + Absolute paths and paths that already exist relative to the CWD are returned + unchanged (preserving existing behavior when launched from the repo root). + A relative path that does not exist against the CWD — e.g. the shipped + ``"cosmos_framework/model/.../X.json"`` defaults — is resolved against the + installed package root. If that candidate is missing too, the original path + is returned so ``open()`` raises the familiar ``FileNotFoundError``. + """ + path = Path(json_file) + if path.is_absolute() or path.exists(): + return json_file + candidate = _PACKAGE_ROOT / json_file + return str(candidate) if candidate.exists() else json_file + class _MoTConfigBase(object): """Shared MoT wrapper logic for all three architecture families. @@ -365,8 +389,16 @@ def from_json_file(cls, json_file: str) -> "_MoTConfigBase": fields (when present) are surfaced lazily via :pyattr:`vision_config` and by HF downstream consumers reading the dict directly. + + ``json_file`` may be absolute, or relative. The shipped config + defaults reference these JSONs by a repo-root-relative path (e.g. + ``"cosmos_framework/model/vfm/vlm/qwen3_vl/configs/X.json"``), which + only resolves when the process CWD is the framework repo root. To keep + ``cosmos_framework.scripts.*`` runnable from any working directory, a + relative path that does not exist against the CWD is resolved against + the installed package root. """ - with open(json_file, encoding="utf-8") as reader: + with open(_resolve_packaged_config_path(json_file), encoding="utf-8") as reader: config_dict = json.load(reader) return cls(config_dict=config_dict) diff --git a/cosmos_framework/model/vfm/mot/unified_mot_test.py b/cosmos_framework/model/vfm/mot/unified_mot_test.py new file mode 100644 index 0000000..1d24662 --- /dev/null +++ b/cosmos_framework/model/vfm/mot/unified_mot_test.py @@ -0,0 +1,68 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: OpenMDW-1.1 + +"""Regression tests for package-relative model-config resolution. + +The shipped config defaults (cosmos_framework/configs/base/defaults/vlm.py) +reference the per-architecture JSONs by a repo-root-relative path, e.g. +``"cosmos_framework/model/vfm/vlm/qwen3_vl/configs/Qwen3-VL-8B-Instruct.json"``. +``_MoTConfigBase.from_json_file`` used to ``open()`` that string verbatim, so it +only resolved when the process CWD was the framework repo root. Launching +``cosmos_framework.scripts.train`` from any other directory (e.g. a cookbook +folder) raised ``FileNotFoundError`` during model construction. These tests pin +the package-root fallback that fixes it. +""" + +from pathlib import Path + +import cosmos_framework +from cosmos_framework.model.vfm.mot.unified_mot import ( + Qwen3VLMoTConfig, + _PACKAGE_ROOT, + _resolve_packaged_config_path, +) + +# A config JSON that ships inside the package, named exactly as the config +# defaults reference it (relative to the package root). +_SHIPPED_REL = "cosmos_framework/model/vfm/vlm/qwen3_vl/configs/Qwen3-VL-8B-Instruct.json" + + +def test_package_root_contains_cosmos_framework(): + # _PACKAGE_ROOT must be the directory that *contains* the cosmos_framework + # package, so that "/cosmos_framework/..." resolves in both editable + # and wheel installs. + assert (_PACKAGE_ROOT / "cosmos_framework").is_dir() + assert Path(cosmos_framework.__file__).resolve().parent == _PACKAGE_ROOT / "cosmos_framework" + + +def test_resolve_absolute_path_passes_through(tmp_path): + abs_path = tmp_path / "cfg.json" + abs_path.write_text("{}") + assert _resolve_packaged_config_path(str(abs_path)) == str(abs_path) + + +def test_resolve_existing_relative_path_passes_through(tmp_path, monkeypatch): + # A relative path that exists against the CWD is returned unchanged — the + # package-root fallback must not shadow a real working-directory file. + monkeypatch.chdir(tmp_path) + (tmp_path / "local.json").write_text("{}") + assert _resolve_packaged_config_path("local.json") == "local.json" + + +def test_resolve_shipped_config_from_foreign_cwd(tmp_path, monkeypatch): + # The actual regression: from a directory that is not the repo root, the + # shipped repo-root-relative path still resolves to the packaged file. + monkeypatch.chdir(tmp_path) + resolved = Path(_resolve_packaged_config_path(_SHIPPED_REL)) + assert resolved.is_absolute() + assert resolved.is_file() + assert resolved == _PACKAGE_ROOT / _SHIPPED_REL + + +def test_from_json_file_loads_shipped_config_from_foreign_cwd(tmp_path, monkeypatch): + # End-to-end: from_json_file (the call made during model construction) loads + # the shipped config from a foreign CWD instead of raising FileNotFoundError. + monkeypatch.chdir(tmp_path) + config = Qwen3VLMoTConfig.from_json_file(_SHIPPED_REL) + assert isinstance(config.config_dict, dict) + assert config.config_dict.get("model_type") # sanity: a real Qwen3-VL config