2222from pathlib import Path
2323from typing import Any
2424
25- from ._config_loader import BUILTIN_RECIPES_LIB , load_config
25+ from ._config_loader import BUILTIN_RECIPES_LIB , _load_raw_config , _resolve_imports , load_config
2626from .config import ModelOptPTQRecipe , ModelOptRecipeBase , RecipeType
2727
2828__all__ = ["load_config" , "load_recipe" ]
2929
3030
31- _IMPORT_KEY = "$import"
32-
33-
34- def _resolve_imports (
35- data : dict [str , Any ], _loading : frozenset [str ] | None = None
36- ) -> dict [str , Any ]:
37- """Resolve the ``imports`` section and ``$import`` references in a recipe.
38-
39- An ``imports`` block is a dict mapping short names to config file paths::
40-
41- imports:
42- fp8: configs/numerics/fp8
43- nvfp4: configs/numerics/nvfp4_dynamic
44-
45- References use the explicit ``$import`` marker so they are never confused
46- with literal string values::
47-
48- quant_cfg:
49- - $import: base_disable_all # entire entry replaced (or list spliced)
50- - quantizer_name: '*weight_quantizer'
51- cfg:
52- $import: fp8 # cfg value replaced
53-
54- Resolution is **recursive**: an imported snippet may itself contain an
55- ``imports`` section. Circular imports are detected and raise ``ValueError``.
56- """
57- imports_dict = data .pop ("imports" , None )
58- if not imports_dict :
59- return data
60-
61- if not isinstance (imports_dict , dict ):
62- raise ValueError (
63- f"'imports' must be a dict mapping names to config paths, got: { type (imports_dict ).__name__ } "
64- )
65-
66- if _loading is None :
67- _loading = frozenset ()
68-
69- # Build name → config mapping (recursively resolve nested imports)
70- import_map : dict [str , Any ] = {}
71- for name , config_path in imports_dict .items ():
72- if not config_path :
73- raise ValueError (f"Import { name !r} has an empty config path." )
74- if config_path in _loading :
75- raise ValueError (
76- f"Circular import detected: { config_path !r} is already being loaded. "
77- f"Import chain: { sorted (_loading )} "
78- )
79- snippet = load_config (config_path )
80- if isinstance (snippet , dict ) and "imports" in snippet :
81- snippet = _resolve_imports (snippet , _loading | {config_path })
82- # Unwrap _list_content (multi-document YAML: imports + list content)
83- if isinstance (snippet , dict ) and "_list_content" in snippet :
84- snippet = snippet ["_list_content" ]
85- import_map [name ] = snippet
86-
87- def _lookup (ref_name : str , context : str ) -> Any :
88- if ref_name not in import_map :
89- raise ValueError (
90- f"Unknown $import reference { ref_name !r} in { context } . "
91- f"Available imports: { list (import_map .keys ())} "
92- )
93- return import_map [ref_name ]
94-
95- def _resolve_list (entries : list [Any ]) -> list [Any ]:
96- """Resolve $import markers in a list of quant_cfg-style entries."""
97- resolved : list [Any ] = []
98- for entry in entries :
99- if isinstance (entry , dict ) and _IMPORT_KEY in entry :
100- # {$import: name} → splice imported list
101- if len (entry ) > 1 :
102- raise ValueError (
103- f"$import must be the only key in the dict, got extra keys: "
104- f"{ sorted (k for k in entry if k != _IMPORT_KEY )} "
105- )
106- imported = _lookup (entry [_IMPORT_KEY ], "list entry" )
107- if not isinstance (imported , list ):
108- raise ValueError (
109- f"$import { entry [_IMPORT_KEY ]!r} in list must resolve to a "
110- f"list, got { type (imported ).__name__ } ."
111- )
112- resolved .extend (imported )
113- elif (
114- isinstance (entry , dict )
115- and isinstance (entry .get ("cfg" ), dict )
116- and _IMPORT_KEY in entry ["cfg" ]
117- ):
118- # cfg: {$import: name_or_list, ...inline} → import then override
119- #
120- # Precedence (lowest → highest):
121- # 1. Imports in list order (later imports override earlier)
122- # 2. Inline keys (override all imports)
123- ref = entry ["cfg" ].pop (_IMPORT_KEY )
124- inline_keys = dict (entry ["cfg" ])
125- ref_names = ref if isinstance (ref , list ) else [ref ]
126-
127- merged : dict [str , Any ] = {}
128- for name in ref_names :
129- snippet = _lookup (name , f"cfg of { entry } " )
130- if not isinstance (snippet , dict ):
131- raise ValueError (
132- f"$import { name !r} in cfg must resolve to a dict, "
133- f"got { type (snippet ).__name__ } ."
134- )
135- merged .update (snippet )
136-
137- merged .update (inline_keys )
138- entry ["cfg" ] = merged
139- resolved .append (entry )
140- else :
141- resolved .append (entry )
142- return resolved
143-
144- # Resolve $import references in quant_cfg entries
145- quantize = data .get ("quantize" )
146- if isinstance (quantize , dict ):
147- quant_cfg = quantize .get ("quant_cfg" )
148- if isinstance (quant_cfg , list ):
149- quantize ["quant_cfg" ] = _resolve_list (quant_cfg )
150-
151- # Resolve $import references in _list_content (multi-document snippets)
152- if "_list_content" in data :
153- data ["_list_content" ] = _resolve_list (data ["_list_content" ])
154-
155- return data
156-
157-
15831def _resolve_recipe_path (recipe_path : str | Path | Traversable ) -> Path | Traversable :
15932 """Resolve a recipe path, checking the built-in library first then the filesystem.
16033
@@ -214,7 +87,7 @@ def _load_recipe_from_file(recipe_file: Path | Traversable) -> ModelOptRecipeBas
21487 The file must contain a ``metadata`` section with at least ``recipe_type``,
21588 plus a ``quant_cfg`` mapping and an optional ``algorithm`` for PTQ recipes.
21689 """
217- raw = load_config (recipe_file )
90+ raw = _load_raw_config (recipe_file )
21891 assert isinstance (raw , dict ), f"Recipe file { recipe_file } must be a YAML mapping."
21992 data = _resolve_imports (raw )
22093
@@ -247,7 +120,7 @@ def _load_recipe_from_dir(recipe_dir: Path | Traversable) -> ModelOptRecipeBase:
247120 f"Cannot find a recipe descriptor in { recipe_dir } . Looked for: recipe.yml, recipe.yaml"
248121 )
249122
250- recipe_data = load_config (recipe_file )
123+ recipe_data = _load_raw_config (recipe_file )
251124 assert isinstance (recipe_data , dict ), f"Recipe file { recipe_file } must be a YAML mapping."
252125 metadata = recipe_data .get ("metadata" , {})
253126 recipe_type = metadata .get ("recipe_type" )
@@ -266,7 +139,7 @@ def _load_recipe_from_dir(recipe_dir: Path | Traversable) -> ModelOptRecipeBase:
266139 f"Cannot find quantize in { recipe_dir } . Looked for: quantize.yml, quantize.yaml"
267140 )
268141 # Resolve imports: imports are in recipe.yml, quantize data is separate
269- quantize_data = load_config (quantize_file )
142+ quantize_data = _load_raw_config (quantize_file )
270143 assert isinstance (quantize_data , dict ), f"{ quantize_file } must be a YAML mapping."
271144 combined : dict [str , Any ] = {"quantize" : quantize_data }
272145 imports = recipe_data .get ("imports" )
0 commit comments