diff --git a/modelopt/torch/opt/config_loader.py b/modelopt/torch/opt/config_loader.py index 80864523e52..d3a263b48ac 100644 --- a/modelopt/torch/opt/config_loader.py +++ b/modelopt/torch/opt/config_loader.py @@ -193,7 +193,7 @@ def _load_raw_config_with_schema(config_file: str | Path | Traversable) -> _RawC schema = _parse_modelopt_schema(text, config_path) docs = list(yaml.safe_load_all(text)) - if len(docs) == 0 or docs[0] is None: + if len(docs) == 0 or (len(docs) == 1 and docs[0] is None): return _RawConfig({}, schema=schema, path=config_path) if len(docs) == 1: _raw = docs[0] @@ -201,6 +201,10 @@ def _load_raw_config_with_schema(config_file: str | Path | Traversable) -> _RawC # Multi-document: first doc is imports/metadata, second is content. # Merge the imports into the content for downstream resolution. header, content = docs[0], docs[1] + if header is None: + # An explicit null/empty first document is an empty header, not an + # empty file — the second document still carries the real body. + header = {} if not isinstance(header, dict): raise ValueError( f"Config file {config_path}: first YAML document must be a mapping, " diff --git a/tests/unit/recipe/test_loader.py b/tests/unit/recipe/test_loader.py index 3aaacaa3e0e..0d5eab37c80 100644 --- a/tests/unit/recipe/test_loader.py +++ b/tests/unit/recipe/test_loader.py @@ -1611,6 +1611,38 @@ def test_load_config_multi_doc_null_content(tmp_path): assert data == {"key": "value"} +def test_load_config_multi_doc_null_header_list_body(tmp_path): + """Multi-document YAML with a null first doc and a list body keeps the list.""" + cfg_file = tmp_path / "null_header_list.yaml" + cfg_file.write_text("null\n---\n- item1\n- item2\n") + data = load_config(cfg_file) + assert data == ["item1", "item2"] + + +def test_load_config_multi_doc_empty_header_list_body(tmp_path): + """Multi-document YAML with an empty explicit first doc and a list body keeps the list.""" + cfg_file = tmp_path / "empty_header_list.yaml" + cfg_file.write_text("---\n---\n- item1\n") + data = load_config(cfg_file) + assert data == ["item1"] + + +def test_load_config_multi_doc_null_header_dict_body(tmp_path): + """Multi-document YAML with a null first doc and a dict body keeps the dict.""" + cfg_file = tmp_path / "null_header_dict.yaml" + cfg_file.write_text("~\n---\nkey: value\n") + data = load_config(cfg_file) + assert data == {"key": "value"} + + +def test_load_config_lone_null_doc(tmp_path): + """A single explicit null document still loads as an empty dict.""" + cfg_file = tmp_path / "lone_null.yaml" + cfg_file.write_text("null\n") + data = load_config(cfg_file) + assert data == {} + + def test_load_config_multi_doc_first_not_dict_raises(tmp_path): """Multi-document YAML with non-dict first document raises ValueError.""" cfg_file = tmp_path / "bad_multi.yaml"