Skip to content

Commit 48fd752

Browse files
author
Yifan Shen
committed
dump op tests in more rigorous format
1 parent a68f1ad commit 48fd752

1 file changed

Lines changed: 49 additions & 19 deletions

File tree

tests/utils.py

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -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,43 @@ 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 coreai_torch surfaces it as void16 (``|V2``).
422+
run-all-tests.py's process_numpy_file consults ``_dtype_<key>`` overrides to
423+
recover the MPS-side type — without it, void16 raises in numpy_to_mps_type.
424+
"""
425+
io_numpy[key] = arr
426+
if arr.dtype.str == "|V2":
427+
io_numpy[f"_dtype_{key}"] = np.array("bf16")
428+
429+
430+
def _dump_optest_artifacts(
431+
coreai_program: Any,
432+
inputs: dict[str, NDArray],
433+
rt_outputs: dict[str, NDArray],
434+
dump_path: Path,
435+
) -> None:
436+
"""Write a `<testname>.aimodel` + `<testname>_test_data.npz` pair.
437+
438+
Format: aimodel prefix == npz prefix == dump_path
439+
leaf name; npz holds an ``op_name`` scalar plus ``input_<n>`` /
440+
``output_<n>`` keys. ``coreai-run`` consumes ``.aimodel`` directly (its
441+
``main.mlirb`` is bitwise identical to what ``_save_bytecode`` used to
442+
produce, but ``save_asset`` is the public API).
443+
"""
444+
testname = dump_path.name
445+
coreai_program.save_asset(dump_path / f"{testname}.aimodel")
446+
447+
io_numpy: dict[str, np.ndarray] = {"op_name": np.array("main")}
448+
for name, arr in inputs.items():
449+
_add_npz_entry(io_numpy, f"input_{name}", arr.numpy())
450+
for name, arr in rt_outputs.items():
451+
_add_npz_entry(io_numpy, f"output_{name}", arr.numpy())
452+
np.savez(dump_path / f"{testname}_test_data.npz", **io_numpy)
453+
454+
428455
async def _run_with_model(
429456
model: torch.nn.Module,
430457
rt_func: Any,
@@ -437,6 +464,7 @@ async def _run_with_model(
437464
rtol: float,
438465
atol: float,
439466
metal_inputs: bool = False,
467+
coreai_program: Any = None,
440468
dump_path: Path | None = None,
441469
) -> None:
442470
"""Path A: stateful, multi-call, name-based matching."""
@@ -478,6 +506,7 @@ def compare(
478506
num_calls=num_calls,
479507
produce_torch_out=produce_torch_out,
480508
compare=compare,
509+
coreai_program=coreai_program,
481510
dump_path=dump_path,
482511
)
483512

@@ -490,6 +519,7 @@ async def _run_with_program(
490519
rtol: float,
491520
atol: float,
492521
metal_inputs: bool = False,
522+
coreai_program: Any = None,
493523
dump_path: Path | None = None,
494524
) -> None:
495525
"""Path B: pre-converted program, single call, sorted-key matching."""
@@ -519,6 +549,7 @@ def compare(
519549
num_calls=1,
520550
produce_torch_out=produce_torch_out,
521551
compare=compare,
552+
coreai_program=coreai_program,
522553
dump_path=dump_path,
523554
)
524555

@@ -592,9 +623,6 @@ async def validate_numerical_output(**kwargs: Any) -> None:
592623
if dump_optests_enabled():
593624
dump_path = _optest_dump_path(get_current_test_id())
594625
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)
598626

599627
with TemporaryDirectory() as temp_directory:
600628
aimodel_path = Path(temp_directory) / "model.aimodel"
@@ -616,6 +644,7 @@ async def validate_numerical_output(**kwargs: Any) -> None:
616644
rtol=rtol,
617645
atol=atol,
618646
metal_inputs=metal_inputs,
647+
coreai_program=coreai_program,
619648
dump_path=dump_path,
620649
)
621650
else:
@@ -626,6 +655,7 @@ async def validate_numerical_output(**kwargs: Any) -> None:
626655
rtol=rtol,
627656
atol=atol,
628657
metal_inputs=metal_inputs,
658+
coreai_program=coreai_program,
629659
dump_path=dump_path,
630660
)
631661

0 commit comments

Comments
 (0)