Recursively apply algo to graph_module and its submodules for control flow. Partitions specs by device type and device idx, and runs the memory planning algorithm independently per device, then merges results into separate buffers. This ensures device memory and CPU memory are neve
(
algo: Callable[..., list[int]],
graph_module: torch.fx.GraphModule,
alignment: int,
graph_signature: Optional[ExportGraphSignature] = None,
alloc_graph_input: bool = True,
alloc_graph_output: bool = True,
alloc_mutable_buffers: bool = True,
enable_non_cpu_memory_planning: bool = False,
)
| 1409 | |
| 1410 | |
| 1411 | def apply_algo( |
| 1412 | algo: Callable[..., list[int]], |
| 1413 | graph_module: torch.fx.GraphModule, |
| 1414 | alignment: int, |
| 1415 | graph_signature: Optional[ExportGraphSignature] = None, |
| 1416 | alloc_graph_input: bool = True, |
| 1417 | alloc_graph_output: bool = True, |
| 1418 | alloc_mutable_buffers: bool = True, |
| 1419 | enable_non_cpu_memory_planning: bool = False, |
| 1420 | ) -> list[int]: |
| 1421 | """ |
| 1422 | Recursively apply algo to graph_module and its submodules for control flow. |
| 1423 | |
| 1424 | Partitions specs by device type and device idx, and runs the memory planning |
| 1425 | algorithm independently per device, then merges results into separate buffers. |
| 1426 | This ensures device memory and CPU memory are never mixed. |
| 1427 | |
| 1428 | When enable_non_cpu_memory_planning is False (default), all specs are planned |
| 1429 | into a single CPU memory pool regardless of their device attribute. This |
| 1430 | preserves the legacy behavior. Set to True to enable per-device partitioning. |
| 1431 | |
| 1432 | Algo implementation should handle one of two meta entries for submodules: |
| 1433 | 1. input_mem_buffer_sizes: List of int offset bytes. Memory allocated by |
| 1434 | `algo` should start at the offset specified by this list; |
| 1435 | OR |
| 1436 | 2. non_const_buffer_sizes: List of bufsizes for planned memory in submodule. |
| 1437 | `algo` should reserve the space specified by this list for the lifetime |
| 1438 | of the submodule node (e.g. cond, while, map). |
| 1439 | |
| 1440 | TODO: Missing optimizations: |
| 1441 | 1. To handle maps, we set `alloc_graph_input=True`, which allocates |
| 1442 | appropriate space for mapped arg but ends up allocating extra space for |
| 1443 | `operand` arg. The memory for operands is unused. |
| 1444 | """ |
| 1445 | # Extract the nodes and their lifespans from the graph_module |
| 1446 | _ = update_all_tensors_lifetime(graph_module, graph_signature) |
| 1447 | |
| 1448 | # Collect specs into an ordered list so we can iterate multiple times and |
| 1449 | # partition by device. Order matters: order-sensitive algorithms (e.g. |
| 1450 | # greedy with bisect.insort) rely on insertion order for stable tie-breaking, |
| 1451 | # and `collect_specs_from_nodes` already deduplicates via its `dedup` flag. |
| 1452 | all_specs: list[TensorSpec] = list( |
| 1453 | collect_specs_from_nodes( |
| 1454 | graph_module.graph.nodes, |
| 1455 | graph_signature, |
| 1456 | do_assertion=False, |
| 1457 | ignore_graph_input=not alloc_graph_input, |
| 1458 | ignore_graph_output=not alloc_graph_output, |
| 1459 | ignore_mutable_buffers=not alloc_mutable_buffers, |
| 1460 | ) |
| 1461 | ) |
| 1462 | |
| 1463 | # Get temporary specs for submodules to set aside space during execution |
| 1464 | # of submodules. |
| 1465 | # NOTE: submodule_bufsizes are currently applied only to the CPU partition. |
| 1466 | # This assumes all control-flow submodule tensors (cond/while/map) live in |
| 1467 | # CPU memory. Today this is safe because on-device tensors only appear as |
| 1468 | # delegate blob I/O, which never lives inside control-flow submodules. |