|
10 | 10 | import copy |
11 | 11 | from itertools import chain |
12 | 12 | from looseversion import LooseVersion |
| 13 | +import shutil |
13 | 14 |
|
14 | 15 | import torch |
15 | 16 | from thunder.core.pytree import tree_flatten |
@@ -1394,3 +1395,111 @@ def save_failing_repros( |
1394 | 1395 | report.write_repro( |
1395 | 1396 | repros_folder, compile_fn, extra_comment_str=comment, check_consistency=check_consistency |
1396 | 1397 | ) |
| 1398 | + |
| 1399 | + |
| 1400 | +def create_folder(folder_path: str | PathLike, force_overwrite: bool = False): |
| 1401 | + folder_path = Path(folder_path) |
| 1402 | + |
| 1403 | + if folder_path.exists(): |
| 1404 | + if not folder_path.is_dir(): |
| 1405 | + raise RuntimeError(f"{folder_path} exists and is not a directory.") |
| 1406 | + |
| 1407 | + if force_overwrite: |
| 1408 | + shutil.rmtree(folder_path) |
| 1409 | + else: |
| 1410 | + raise RuntimeError(f"Folder {folder_path} already exists. Use force_overwrite=True to overwrite.") |
| 1411 | + |
| 1412 | + folder_path.mkdir(parents=True, exist_ok=False) |
| 1413 | + |
| 1414 | + |
| 1415 | +def save_thunderfx_repros( |
| 1416 | + fn: Callable, |
| 1417 | + folder_path: str | PathLike, |
| 1418 | + *, |
| 1419 | + use_benchmark: bool = False, |
| 1420 | + check_runnability: bool = False, |
| 1421 | + save_fusion: bool = False, |
| 1422 | + save_trace: bool = False, |
| 1423 | + stream: TextIO = sys.stdout, |
| 1424 | + force_overwrite: bool = False, |
| 1425 | + **compile_kwargs, |
| 1426 | +): |
| 1427 | + """ |
| 1428 | + Saves reproduction scripts for ThunderFX subgraphs. |
| 1429 | +
|
| 1430 | + This function: |
| 1431 | + 1. Creates a folder structure to organize the repros |
| 1432 | + . |
| 1433 | + └── graph0 |
| 1434 | + ├── fusion_reports |
| 1435 | + │ ├── graph0_thunder_0_nvFusion0_forward_repro_nvfuser.py |
| 1436 | + │ ├── graph0_thunder_0_nvFusion1_forward_repro_nvfuser.py |
| 1437 | + │ ├── graph0_thunder_0_nvFusion2_backward_repro_nvfuser.py |
| 1438 | + ├── graph0_thunder_0_bwd_trace.py |
| 1439 | + ├── graph0_thunder_0_fwd_trace.py |
| 1440 | + └── graph0_thunder_0.py |
| 1441 | +
|
| 1442 | + 2. For each Thunder FX graph and its subgraphs: |
| 1443 | + - Checks runnability if requested |
| 1444 | + - Saves benchmark or repro scripts |
| 1445 | + - Saves trace information if requested |
| 1446 | + - Saves nvFusion repros if requested |
| 1447 | +
|
| 1448 | + Args: |
| 1449 | + fn: The callable to analyze |
| 1450 | + folder_path: Path to save repros to |
| 1451 | + use_benchmark: If True, saves benchmark scripts instead of repros |
| 1452 | + check_runnability: If True, checks if graphs can run with Thunder |
| 1453 | + save_fusion: If True, saves nvFusion repros |
| 1454 | + save_trace: If True, saves trace information |
| 1455 | + stream: Stream to write output log informationto |
| 1456 | + force_overwrite: If True, overwrites existing folder at folder_path |
| 1457 | + **compile_kwargs: Keyword arguments for Thunder and torch.compile |
| 1458 | +
|
| 1459 | + Returns: |
| 1460 | + A wrapped function that saves repros when called with inputs |
| 1461 | + """ |
| 1462 | + from thunder.dynamo.utils import get_torch_compile_kwargs |
| 1463 | + |
| 1464 | + folder_path = Path(folder_path) |
| 1465 | + create_folder(folder_path, force_overwrite) |
| 1466 | + torch_compile_kwargs = get_torch_compile_kwargs(**compile_kwargs) |
| 1467 | + thunder_jit_kwargs = {k: v for k, v in compile_kwargs.items() if k not in torch_compile_kwargs} |
| 1468 | + thunderjit = ThunderCompileSpecification(**thunder_jit_kwargs) |
| 1469 | + |
| 1470 | + def inner_fn(*args, **kwargs): |
| 1471 | + thunder_fxgraph_reports = get_thunder_fxgraph_reports(fn, stream=stream, **compile_kwargs)(*args, **kwargs) |
| 1472 | + for thunder_fxgraph_report in thunder_fxgraph_reports: |
| 1473 | + graph_folder = folder_path / thunder_fxgraph_report.graph_name |
| 1474 | + graph_folder.mkdir(exist_ok=True, parents=True) |
| 1475 | + for split_report in thunder_fxgraph_report.subgraph_reports: |
| 1476 | + if check_runnability or save_trace or save_fusion: |
| 1477 | + try: |
| 1478 | + split_report.create_fusion_reports() |
| 1479 | + except Exception as e: |
| 1480 | + stream.write(f"Failed to run the {split_report.graph_name} using Thunder with exception: {e}\n") |
| 1481 | + split_report.write_repro( |
| 1482 | + graph_folder, thunderjit, file_name=f"failed_{split_report.graph_name}.py" |
| 1483 | + ) |
| 1484 | + continue |
| 1485 | + else: |
| 1486 | + stream.write(f"Successfully ran the {split_report.graph_name} using Thunder\n") |
| 1487 | + if use_benchmark: |
| 1488 | + split_report.write_benchmark(graph_folder, thunderjit, WallTime) |
| 1489 | + else: |
| 1490 | + split_report.write_repro(graph_folder, thunderjit) |
| 1491 | + if save_trace: |
| 1492 | + with open(graph_folder / f"{split_report.graph_name}_fwd_trace.py", "w") as f: |
| 1493 | + f.write(str(split_report.fwd_trc)) |
| 1494 | + with open(graph_folder / f"{split_report.graph_name}_bwd_trace.py", "w") as f: |
| 1495 | + f.write(str(split_report.bwd_trc)) |
| 1496 | + if save_fusion: |
| 1497 | + fusion_folder = graph_folder / "fusion_reports" |
| 1498 | + fusion_folder.mkdir(exist_ok=True, parents=True) |
| 1499 | + for fusion_report in split_report.fusion_reports: |
| 1500 | + if use_benchmark: |
| 1501 | + fusion_report.write_nvfuser_benchmark(fusion_folder, WallTime) |
| 1502 | + else: |
| 1503 | + fusion_report.write_nvfuser_repro(fusion_folder) |
| 1504 | + |
| 1505 | + return inner_fn |
0 commit comments