@@ -386,6 +386,7 @@ async def _execute_and_compare(
386386 num_calls : int ,
387387 produce_torch_out : Any ,
388388 compare : Any ,
389+ coreai_program : Any = None ,
389390 dump_path : Path | None = None ,
390391) -> None :
391392 """The single place we invoke the Core AI runtime.
@@ -394,28 +395,17 @@ async def _execute_and_compare(
394395 two callbacks let path A (stateful, recompute per call) and path B
395396 (fixed expected output) share this loop without ad-hoc branching.
396397 """
398+ # Stateful tests can't round-trip for now,
399+ # which has no `--input` JSON path for state — so skip the dump entirely.
400+ should_dump = dump_optests_enabled () and dump_path is not None and not state
397401 try :
398402 for call_idx in range (num_calls ):
399- io_numpy = {}
400- if call_idx == 0 and dump_optests_enabled ():
401- assert dump_path is not None
402- for name , arr in state .items ():
403- io_numpy [f"initial_state_{ name } " ] = arr .numpy ()
404-
405403 torch_out = produce_torch_out (call_idx )
406404 rt_outputs = await rt_func (inputs = inputs , state = state )
407405 compare (rt_outputs , torch_out , call_idx )
408406
409- if call_idx == 0 and dump_optests_enabled ():
410- assert dump_path is not None
411- for name , arr in inputs .items ():
412- io_numpy [name ] = arr .numpy ()
413- for name , arr in state .items ():
414- io_numpy [f"final_state_{ name } " ] = arr .numpy ()
415- for name , arr in rt_outputs .items ():
416- io_numpy [name ] = arr .numpy ()
417-
418- np .savez (dump_path / "test_data.npz" , ** io_numpy )
407+ if call_idx == 0 and should_dump :
408+ _dump_optest_artifacts (coreai_program , inputs , rt_outputs , dump_path )
419409
420410 except Exception :
421411 # Wipe bytecode and reference IO if the test failed as
@@ -425,6 +415,39 @@ async def _execute_and_compare(
425415 raise
426416
427417
418+ def _add_npz_entry (io_numpy : dict [str , np .ndarray ], key : str , arr : np .ndarray ) -> None :
419+ """Add an array to the npz dict, emitting a bf16 dtype override if needed.
420+
421+ NumPy has no native bf16, so surfaces it as void16 (``|V2``).
422+ """
423+ io_numpy [key ] = arr
424+ if arr .dtype .str == "|V2" :
425+ io_numpy [f"_dtype_{ key } " ] = np .array ("bf16" )
426+
427+
428+ def _dump_optest_artifacts (
429+ coreai_program : Any ,
430+ inputs : dict [str , NDArray ],
431+ rt_outputs : dict [str , NDArray ],
432+ dump_path : Path ,
433+ ) -> None :
434+ """Write a `<testname>.aimodel` + `<testname>_test_data.npz` pair.
435+
436+ Format: aimodel prefix == npz prefix == dump_path
437+ leaf name; npz holds an ``op_name`` scalar plus ``input_<n>`` /
438+ ``output_<n>`` keys.
439+ """
440+ testname = dump_path .name
441+ coreai_program .save_asset (dump_path / f"{ testname } .aimodel" )
442+
443+ io_numpy : dict [str , np .ndarray ] = {"op_name" : np .array ("main" )}
444+ for name , arr in inputs .items ():
445+ _add_npz_entry (io_numpy , f"input_{ name } " , arr .numpy ())
446+ for name , arr in rt_outputs .items ():
447+ _add_npz_entry (io_numpy , f"output_{ name } " , arr .numpy ())
448+ np .savez (dump_path / f"{ testname } _test_data.npz" , ** io_numpy )
449+
450+
428451async def _run_with_model (
429452 model : torch .nn .Module ,
430453 rt_func : Any ,
@@ -437,6 +460,7 @@ async def _run_with_model(
437460 rtol : float ,
438461 atol : float ,
439462 metal_inputs : bool = False ,
463+ coreai_program : Any = None ,
440464 dump_path : Path | None = None ,
441465) -> None :
442466 """Path A: stateful, multi-call, name-based matching."""
@@ -478,6 +502,7 @@ def compare(
478502 num_calls = num_calls ,
479503 produce_torch_out = produce_torch_out ,
480504 compare = compare ,
505+ coreai_program = coreai_program ,
481506 dump_path = dump_path ,
482507 )
483508
@@ -490,6 +515,7 @@ async def _run_with_program(
490515 rtol : float ,
491516 atol : float ,
492517 metal_inputs : bool = False ,
518+ coreai_program : Any = None ,
493519 dump_path : Path | None = None ,
494520) -> None :
495521 """Path B: pre-converted program, single call, sorted-key matching."""
@@ -519,6 +545,7 @@ def compare(
519545 num_calls = 1 ,
520546 produce_torch_out = produce_torch_out ,
521547 compare = compare ,
548+ coreai_program = coreai_program ,
522549 dump_path = dump_path ,
523550 )
524551
@@ -592,9 +619,6 @@ async def validate_numerical_output(**kwargs: Any) -> None:
592619 if dump_optests_enabled ():
593620 dump_path = _optest_dump_path (get_current_test_id ())
594621 dump_path .mkdir (parents = True , exist_ok = True )
595- model_path = dump_path / "main.AICode.bc"
596- model_path .unlink (missing_ok = True )
597- coreai_program ._save_bytecode (model_path )
598622
599623 with TemporaryDirectory () as temp_directory :
600624 aimodel_path = Path (temp_directory ) / "model.aimodel"
@@ -616,6 +640,7 @@ async def validate_numerical_output(**kwargs: Any) -> None:
616640 rtol = rtol ,
617641 atol = atol ,
618642 metal_inputs = metal_inputs ,
643+ coreai_program = coreai_program ,
619644 dump_path = dump_path ,
620645 )
621646 else :
@@ -626,6 +651,7 @@ async def validate_numerical_output(**kwargs: Any) -> None:
626651 rtol = rtol ,
627652 atol = atol ,
628653 metal_inputs = metal_inputs ,
654+ coreai_program = coreai_program ,
629655 dump_path = dump_path ,
630656 )
631657
0 commit comments