4545 semantic_stage_config ,
4646 write_stage_manifest ,
4747)
48+ from modelopt .torch .puzzletron .orchestration .adapters .stage_compat import (
49+ stage_is_complete as artifacts_are_complete ,
50+ )
51+ from modelopt .torch .puzzletron .orchestration .adapters .stage_compat import (
52+ stage_output_patterns as canonical_stage_output_patterns ,
53+ )
4854from modelopt .torch .puzzletron .stages .graph import (
55+ StageStatus ,
4956 configured_parent_stage_ids ,
5057 distributed_stage_ids ,
5158 enabled_stage_ids ,
5259 required_stage_ids ,
5360 stage_ids ,
5461 stage_is_enabled ,
62+ stage_terminal_state ,
5563 topological_stage_ids ,
5664)
5765
6472PIPELINE_STAGE_ORDER = topological_stage_ids ()
6573REQUIRED_STAGES = frozenset (required_stage_ids ())
6674DISTRIBUTED_STAGES = frozenset (distributed_stage_ids ())
67- REQUIRED_OUTPUT_PATTERNS = {
68- "convert" : ("ckpts/teacher/config.json" ,),
69- "tokenize_data" : ("dataset_cache/*.tokens" , "dataset_cache/*.tokens.json" ),
70- "sort" : ("ckpts/sorted_teacher/config.json" ,),
71- "slicing_sanity" : (
72- "artifacts/width_slice_equivalence/manifest.json" ,
73- "artifacts/width_slice_equivalence/summary.json" ,
74- "artifacts/width_slice_equivalence/cases/**/*.json" ,
75- "artifacts/width_slice_equivalence/comparisons/*.safetensors" ,
76- ),
77- "depth_importance" : ("depth/iterative/trajectory.json" ,),
78- "build_library" : (
79- "replacement_library.json" ,
80- "candidate_library.json" ,
81- "subblock_stats.json" ,
82- ),
83- "vllm_stats" : ("artifacts/vllm_stats/summary.json" ,),
84- "replacement_scoring" : ("artifacts/replacement_scoring/summary.json" ,),
85- "mip" : ("mip/**/*.json" ,),
86- "zero_shot_evaluation" : ("artifacts/**/evaluation_summary.json" ,),
87- "aiperf" : ("artifacts/aiperf/**/*.json" ,),
88- "global_distillation_sanity" : ("artifacts/global_distillation_sanity/**/*.json" ,),
89- "global_distillation" : ("artifacts/global_distillation/**/*.json" ,),
90- }
9175
9276
9377def _register_faulthandler () -> None :
@@ -216,50 +200,17 @@ def _report_model_name(config: dict) -> str:
216200 )
217201
218202
219- def _manifest_is_complete (config : dict , stage : str ) -> bool :
203+ def _manifest_terminal_state (config : dict , stage : str ):
220204 puzzle_dir = Path (config .get ("puzzle_dir" ) or (config .get ("experiment" ) or {})["dir" ])
221205 path = puzzle_dir / "manifests" / f"{ stage } .json"
222206 try :
223207 payload = json .loads (path .read_text ())
224208 except (OSError , ValueError ):
225- return False
226- return payload .get ("status" ) in {"success" , "imported" }
227-
228-
229- def _runtime_stats_filename (config : dict ) -> str :
230- stats = config .get ("vllm_stats" ) or {}
231- return str (stats .get ("subblock_stats_filename" , "subblock_stats.json" ))
232-
233-
234- def _stage_output_patterns (config : dict , stage : str ) -> tuple [str , ...]:
235- if stage == "vllm_stats" :
236- return (_runtime_stats_filename (config ),)
237- if stage == "slicing_sanity" :
238- slicing_cfg = config .get ("slicing_sanity" ) or {}
239- if slicing_cfg .get ("backend" ) == "distributed_parent_sweep" :
240- return ("artifacts/slicing_sanity/summary.json" ,)
241- patterns = REQUIRED_OUTPUT_PATTERNS .get (stage , ())
242- if stage == "build_library" :
243- resolved = [
244- _runtime_stats_filename (config ) if pattern == "subblock_stats.json" else pattern
245- for pattern in patterns
246- ]
247- embedding = config .get ("embedding_pruning" ) or {}
248- if bool (embedding .get ("enabled" , False )):
249- resolved .append ("scenarios/width_scenarios.json" )
250- for configured_width in embedding .get ("widths" , ()):
251- scenario = f"scenarios/width-{ int (configured_width ):04d} /depth-00"
252- resolved .extend (
253- (
254- f"{ scenario } /scenario_manifest.json" ,
255- f"{ scenario } /replacement_library.json" ,
256- f"{ scenario } /candidate_library.json" ,
257- f"{ scenario } /{ _runtime_stats_filename (config )} " ,
258- f"{ scenario } /manifests/build_library.json" ,
259- )
260- )
261- return tuple (resolved )
262- return patterns
209+ return None
210+ state = stage_terminal_state (payload , expected_stage = stage )
211+ if state is None or not state .allows_completion (stage , config ):
212+ return None
213+ return state
263214
264215
265216def _resume_kwargs (config : dict , config_path : str | Path , stage : str ) -> dict :
@@ -280,7 +231,7 @@ def _resume_kwargs(config: dict, config_path: str | Path, stage: str) -> dict:
280231 "depth" : None ,
281232 "required_patterns" : (
282233 f"manifests/{ stage } .json" ,
283- * _stage_output_patterns (config , stage ),
234+ * canonical_stage_output_patterns (config , stage ),
284235 ),
285236 "upstream_markers" : upstream ,
286237 "stage_config" : semantic_stage_config (config , stage ),
@@ -289,19 +240,76 @@ def _resume_kwargs(config: dict, config_path: str | Path, stage: str) -> dict:
289240
290241
291242def _completion_is_valid (config : dict , config_path : str | Path , stage : str ) -> bool :
292- if not _manifest_is_complete (config , stage ):
243+ state = _manifest_terminal_state (config , stage )
244+ if state is None :
245+ return False
246+ if state .status is StageStatus .SKIPPED :
247+ return True
248+ if not artifacts_are_complete (config , stage ):
293249 return False
294250 kwargs = _resume_kwargs (config , config_path , stage )
295251 return check_marker (marker_path (kwargs ["root" ], stage , None , None ), ** kwargs )
296252
297253
298254def _mark_completion (config : dict , config_path : str | Path , stage : str ) -> None :
299- if not _manifest_is_complete (config , stage ):
300- raise RuntimeError (f"enabled stage { stage !r} did not write a successful manifest" )
255+ state = _manifest_terminal_state (config , stage )
256+ if state is None :
257+ raise RuntimeError (f"stage { stage !r} did not write an accepted terminal manifest" )
258+ if state .status is StageStatus .SKIPPED :
259+ return
260+ if not artifacts_are_complete (config , stage ):
261+ raise RuntimeError (f"stage { stage !r} failed canonical artifact validation" )
301262 kwargs = _resume_kwargs (config , config_path , stage )
302263 write_marker (kwargs ["root" ], stage , build_payload (** kwargs ))
303264
304265
266+ def _validate_worker_result (config : dict , result , * , expected_stage : str | None = None ) -> None :
267+ """Fail the worker unless its result, manifest, and required artifacts agree."""
268+
269+ expected_stage = expected_stage or result .stage
270+ if result .stage != expected_stage :
271+ raise RuntimeError (
272+ f"worker stage { expected_stage !r} returned result for stage { result .stage !r} "
273+ )
274+ puzzle_dir = Path (config .get ("puzzle_dir" ) or (config .get ("experiment" ) or {})["dir" ])
275+ expected_manifest_path = puzzle_dir / "manifests" / f"{ expected_stage } .json"
276+ if Path (result .manifest_path ).resolve () != expected_manifest_path .resolve ():
277+ raise RuntimeError (
278+ f"stage { expected_stage !r} returned manifest path { result .manifest_path !s} ; "
279+ f"expected { expected_manifest_path !s} "
280+ )
281+ try :
282+ payload = json .loads (expected_manifest_path .read_text ())
283+ except (OSError , ValueError ) as exc :
284+ raise RuntimeError (f"stage { expected_stage !r} wrote an unreadable manifest" ) from exc
285+ if payload .get ("stage" ) != expected_stage :
286+ raise RuntimeError (
287+ f"stage { expected_stage !r} manifest identifies stage { payload .get ('stage' )!r} "
288+ )
289+ state = stage_terminal_state (payload , expected_stage = expected_stage )
290+ if state is None or not state .allows_completion (expected_stage , config ):
291+ raise RuntimeError (f"stage { expected_stage !r} wrote an invalid terminal manifest" )
292+ if result .status != state .status .value :
293+ raise RuntimeError (
294+ f"stage { expected_stage !r} result status { result .status !r} disagrees with "
295+ f"manifest status { state .status .value !r} "
296+ )
297+ expected_reason = state .skip_reason .value if state .skip_reason is not None else None
298+ if result .skip_reason != expected_reason :
299+ raise RuntimeError (
300+ f"stage { expected_stage !r} result skip reason { result .skip_reason !r} disagrees with "
301+ f"manifest skip reason { expected_reason !r} "
302+ )
303+ if not state .produced_artifacts :
304+ return
305+ if not artifacts_are_complete (config , expected_stage ):
306+ expected = canonical_stage_output_patterns (config , expected_stage )
307+ raise RuntimeError (
308+ f"stage { expected_stage !r} failed canonical artifact validation; expected: "
309+ + (", " .join (expected ) or "stage-specific outputs" )
310+ )
311+
312+
305313def run_pipeline (
306314 * ,
307315 config_path : str | Path ,
@@ -369,7 +377,9 @@ def _run_worker(args: argparse.Namespace) -> None:
369377 )
370378 gpus_per_node = int (args .gpus_per_node or (cfg .get ("execution" ) or {}).get ("gpus_per_node" , 8 ))
371379 composite_only = {"replacement_scoring" , "mip" }
372- if args .worker_stage == "tokenize_data" :
380+ if not _stage_enabled (cfg , args .worker_stage ):
381+ result = mtpz .stage_runner .run_stage (cfg , args .worker_stage , handlers = {})
382+ elif args .worker_stage == "tokenize_data" :
373383 if __package__ :
374384 from .tokenize_data import tokenize_data_stage
375385 else :
@@ -405,13 +415,15 @@ def _run_worker(args: argparse.Namespace) -> None:
405415 )
406416 outputs ["base_manifest" ] = str (result .manifest_path )
407417 result = _complete_composite_stage (cfg , args .worker_stage , outputs )
418+ if int (os .environ .get ("RANK" , "0" )) == 0 :
419+ if result .status == "failed" :
420+ refresh_campaign_report (cfg )
421+ _validate_worker_result (cfg , result , expected_stage = args .worker_stage )
408422 refresh_campaign_report (cfg )
409423 mtpz .tools .mprint (
410424 f"Puzzletron stage { result .stage !r} finished with status { result .status } : "
411425 f"{ result .manifest_path } "
412426 )
413- if result .status not in {"success" , "skipped" }:
414- raise RuntimeError (f"stage { result .stage !r} finished with status { result .status !r} " )
415427
416428
417429def _complete_composite_stage (config : dict , stage : str , outputs : dict ):
0 commit comments