From 55b51a97bb2061b0cddc41346d472dbd0e9b2866 Mon Sep 17 00:00:00 2001 From: Philipp Schaad Date: Tue, 17 Sep 2024 14:52:57 +0200 Subject: [PATCH 1/5] Give an option to deepcopy, preserving GUIDs for SDFGs --- dace/sdfg/analysis/cutout.py | 2 +- dace/sdfg/state.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/dace/sdfg/analysis/cutout.py b/dace/sdfg/analysis/cutout.py index 50272167bb..9af24186aa 100644 --- a/dace/sdfg/analysis/cutout.py +++ b/dace/sdfg/analysis/cutout.py @@ -176,7 +176,7 @@ def singlestate_cutout(cls, to run the graph separately. In addition, all transient data containers that may contain data when the cutout is executed are made global, as well as any transient data containers which are written to inside the cutout but may be read after the cutout. - + :param state: The SDFG state in which the subgraph resides. :param nodes: The nodes in the subgraph to cut out. :param make_copy: If True, deep-copies every SDFG element in the copy. Otherwise, original references are kept. diff --git a/dace/sdfg/state.py b/dace/sdfg/state.py index e8a8161747..b0d172a41e 100644 --- a/dace/sdfg/state.py +++ b/dace/sdfg/state.py @@ -1169,6 +1169,12 @@ def __str__(self): def __repr__(self) -> str: return f'ControlFlowBlock ({self.label})' + def clone(self, keep_guid = False) -> 'ControlFlowBlock': + new_block = copy.deepcopy(self) + if keep_guid: + new_block.guid = self.guid + return new_block + def __deepcopy__(self, memo): cls = self.__class__ result = cls.__new__(cls) From 853ef9013171b90f77595afb1ba806b2a42e88e0 Mon Sep 17 00:00:00 2001 From: Philipp Schaad Date: Thu, 3 Oct 2024 13:52:30 +0200 Subject: [PATCH 2/5] More cloning --- dace/memlet.py | 6 ++++++ dace/sdfg/analysis/cutout.py | 37 ++++++++++++++++++++++++++++-------- dace/sdfg/nodes.py | 16 ++++++++++++++++ dace/sdfg/sdfg.py | 16 ++++++++++++++++ 4 files changed, 67 insertions(+), 8 deletions(-) diff --git a/dace/memlet.py b/dace/memlet.py index d50c6c77f7..3d88635f75 100644 --- a/dace/memlet.py +++ b/dace/memlet.py @@ -197,6 +197,12 @@ def from_json(json_obj, context=None): ret._state = context['sdfg_state'] return ret + def clone(self, keep_guid = False) -> 'Memlet': + new_memlet = dcpy(self) + if keep_guid: + new_memlet.guid = self.guid + return new_memlet + def __deepcopy__(self, memo): node = object.__new__(Memlet) diff --git a/dace/sdfg/analysis/cutout.py b/dace/sdfg/analysis/cutout.py index 9af24186aa..1da7294721 100644 --- a/dace/sdfg/analysis/cutout.py +++ b/dace/sdfg/analysis/cutout.py @@ -13,7 +13,7 @@ from dace.sdfg import nodes as nd, SDFG, SDFGState, utils as sdutil, InterstateEdge from dace.memlet import Memlet from dace.sdfg.graph import Edge, MultiConnectorEdge -from dace.sdfg.state import StateSubgraphView, SubgraphView +from dace.sdfg.state import ControlFlowBlock, StateSubgraphView, SubgraphView from dace.transformation.transformation import (MultiStateTransformation, PatternTransformation, SubgraphTransformation, @@ -118,7 +118,7 @@ def from_json(cls, json_obj, context=None): def from_transformation( cls, sdfg: SDFG, transformation: Union[PatternTransformation, SubgraphTransformation], make_side_effects_global = True, use_alibi_nodes: bool = True, reduce_input_config = True, - symbols_map: Optional[Dict[str, Any]] = None + symbols_map: Optional[Dict[str, Any]] = None, preserve_guids: bool = False ) -> Union['SDFGCutout', SDFG]: """ Create a cutout from a transformation's set of affected graph elements. @@ -130,6 +130,9 @@ def from_transformation( :param reduce_input_config: Whether to reduce the input configuration where possible in singlestate cutouts. :param symbols_map: A mapping of symbols to values to use for the cutout. Optional, only used when reducing the input configuration. + :param preserve_guids: If True, ensures that the GUIDs of graph elements contained in the cutout remain + identical to the ones in their original graph. If False, new GUIDs will be generated. + False by default. :return: The cutout. """ affected_nodes = _transformation_determine_affected_nodes(sdfg, transformation) @@ -150,11 +153,12 @@ def from_transformation( state = target_sdfg.node(transformation.state_id) cutout = cls.singlestate_cutout(state, *affected_nodes, make_side_effects_global=make_side_effects_global, use_alibi_nodes=use_alibi_nodes, reduce_input_config=reduce_input_config, - symbols_map=symbols_map) + symbols_map=symbols_map, preserve_guids=preserve_guids) cutout.translate_transformation_into(transformation) return cutout elif isinstance(transformation, MultiStateTransformation): - cutout = cls.multistate_cutout(*affected_nodes, make_side_effects_global=make_side_effects_global) + cutout = cls.multistate_cutout(*affected_nodes, make_side_effects_global=make_side_effects_global, + preserve_guids=preserve_guids) # If the cutout is an SDFG, there's no need to translate the transformation. if isinstance(cutout, SDFGCutout): cutout.translate_transformation_into(transformation) @@ -169,7 +173,8 @@ def singlestate_cutout(cls, make_side_effects_global: bool = True, use_alibi_nodes: bool = True, reduce_input_config: bool = False, - symbols_map: Optional[Dict[str, Any]] = None) -> 'SDFGCutout': + symbols_map: Optional[Dict[str, Any]] = None, + preserve_guids: bool = False) -> 'SDFGCutout': """ Cut out a subgraph of a state from an SDFG to run separately for localized testing or optimization. The subgraph defined by the list of nodes will be extended to include access nodes of data containers necessary @@ -188,17 +193,26 @@ def singlestate_cutout(cls, :param reduce_input_config: Whether to reduce the input configuration where possible in singlestate cutouts. :param symbols_map: A mapping of symbols to values to use for the cutout. Optional, only used when reducing the input configuration. + :param preserve_guids: If True, ensures that the GUIDs of graph elements contained in the cutout remain + identical to the ones in their original graph. If False, new GUIDs will be generated. + False by default - if make_copy is False, this has no effect by extension. :return: The created SDFGCutout. """ if reduce_input_config: nodes = _reduce_in_configuration(state, nodes, use_alibi_nodes, symbols_map) - create_element = copy.deepcopy if make_copy else (lambda x: x) + + def clone_f(x: Union[Memlet, InterstateEdge, nd.Node, ControlFlowBlock]): + return x.clone(keep_guid=preserve_guids) + + create_element = clone_f if make_copy else (lambda x: x) sdfg = state.parent subgraph: StateSubgraphView = StateSubgraphView(state, nodes) subgraph = _extend_subgraph_with_access_nodes(state, subgraph, use_alibi_nodes) # Make a new SDFG with the included constants, used symbols, and data containers. cutout = SDFGCutout(sdfg.name + '_cutout', sdfg.constants_prop) + if preserve_guids: + cutout.guid = sdfg.guid cutout._base_sdfg = sdfg defined_syms = subgraph.defined_symbols() freesyms = subgraph.free_symbols @@ -218,6 +232,8 @@ def singlestate_cutout(cls, # Add a single state with the extended subgraph new_state = cutout.add_state(state.label, is_start_state=True) + if preserve_guids: + new_state.guid = state.guid in_translation = dict() out_translation = dict() for e in sg_edges: @@ -321,7 +337,8 @@ def singlestate_cutout(cls, @classmethod def multistate_cutout(cls, *states: SDFGState, - make_side_effects_global: bool = True) -> Union['SDFGCutout', SDFG]: + make_side_effects_global: bool = True, + preserve_guids: bool = False) -> Union['SDFGCutout', SDFG]: """ Cut out a multi-state subgraph from an SDFG to run separately for localized testing or optimization. @@ -336,9 +353,13 @@ def multistate_cutout(cls, :param make_side_effects_global: If True, all transient data containers which are read inside the cutout but may be written to _before_ the cutout, or any data containers which are written to inside the cutout but may be read _after_ the cutout, are made global. + :param preserve_guids: If True, ensures that the GUIDs of graph elements contained in the cutout remain + identical to the ones in their original graph. If False, new GUIDs will be generated. + False by default - if make_copy is False, this has no effect by extension. :return: The created SDFGCutout or the original SDFG where no smaller cutout could be obtained. """ - create_element = copy.deepcopy + def create_element(x: Union[ControlFlowBlock, InterstateEdge]) -> Union[ControlFlowBlock, InterstateEdge]: + return x.clone(keep_guid=preserve_guids) # Check that all states are inside the same SDFG. sdfg = list(states)[0].parent diff --git a/dace/sdfg/nodes.py b/dace/sdfg/nodes.py index 409d30c57a..7a12c6520f 100644 --- a/dace/sdfg/nodes.py +++ b/dace/sdfg/nodes.py @@ -55,6 +55,22 @@ def __str__(self): else: return type(self).__name__ + def clone(self, keep_guid = False) -> 'Node': + new_elem = dcpy(self) + if keep_guid: + new_elem.guid = self.guid + return new_elem + + def __deepcopy__(self, memo): + cls = self.__class__ + result = cls.__new__(cls) + memo[id(self)] = result + for k, v in self.__dict__.items(): + if k == 'guid': # Skip ID + continue + setattr(result, k, dcpy(v, memo)) + return result + def validate(self, sdfg, state): pass diff --git a/dace/sdfg/sdfg.py b/dace/sdfg/sdfg.py index 84d7189ebd..d51092a985 100644 --- a/dace/sdfg/sdfg.py +++ b/dace/sdfg/sdfg.py @@ -205,6 +205,22 @@ def __setattr__(self, name: str, value: Any) -> None: super().__setattr__('_uncond', None) return super().__setattr__(name, value) + def clone(self, keep_guid = False) -> 'InterstateEdge': + new_elem = copy.deepcopy(self) + if keep_guid: + new_elem.guid = self.guid + return new_elem + + def __deepcopy__(self, memo): + cls = self.__class__ + result = cls.__new__(cls) + memo[id(self)] = result + for k, v in self.__dict__.items(): + if k == 'guid': # Skip ID + continue + setattr(result, k, copy.deepcopy(v, memo)) + return result + @staticmethod def _convert_assignment(assignment) -> str: if isinstance(assignment, ast.AST): From 698e093fcf44a104ca9aa0567cc199f89d7b7372 Mon Sep 17 00:00:00 2001 From: Philipp Schaad Date: Thu, 31 Oct 2024 15:53:12 +0100 Subject: [PATCH 3/5] Fix cutout extraction for memlets accessing struct members --- dace/sdfg/analysis/cutout.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/dace/sdfg/analysis/cutout.py b/dace/sdfg/analysis/cutout.py index 1da7294721..2759080c63 100644 --- a/dace/sdfg/analysis/cutout.py +++ b/dace/sdfg/analysis/cutout.py @@ -227,8 +227,19 @@ def clone_f(x: Union[Memlet, InterstateEdge, nd.Node, ControlFlowBlock]): memlet = edge.data if memlet.data in cutout.arrays: continue - new_desc = sdfg.arrays[memlet.data].clone() - cutout.add_datadesc(memlet.data, new_desc) + dataname = memlet.data + if '.' in dataname: + # This is an access to a struct memeber, which typically happens for the memlets between an access node + # pointing to a struct (or view thereof), and a view pointing to the member. Assert that this is indeed + # the case (i.e., only one '.' is found in the name of the data being accessed), and if so, clone the + # struct (or struct view) data descriptor instad. + parts = dataname.split('.') + if len(parts) == 2: + dataname = parts[0] + else: + raise RuntimeError('Attempting to add invalid multi-nested data ' + memlet.data + ' to a cutout') + new_desc = sdfg.arrays[dataname].clone() + cutout.add_datadesc(dataname, new_desc) # Add a single state with the extended subgraph new_state = cutout.add_state(state.label, is_start_state=True) From a399fbad848819a1007e05959cd1a87bd4ccb34a Mon Sep 17 00:00:00 2001 From: Philipp Schaad Date: Mon, 4 Nov 2024 10:23:26 +0100 Subject: [PATCH 4/5] Remove clone functions --- dace/memlet.py | 6 ------ dace/sdfg/analysis/cutout.py | 10 ++++++++-- dace/sdfg/nodes.py | 6 ------ dace/sdfg/state.py | 6 ------ 4 files changed, 8 insertions(+), 20 deletions(-) diff --git a/dace/memlet.py b/dace/memlet.py index fade026165..f78da3a6b7 100644 --- a/dace/memlet.py +++ b/dace/memlet.py @@ -199,12 +199,6 @@ def from_json(json_obj, context=None): ret._state = context['sdfg_state'] return ret - def clone(self, keep_guid = False) -> 'Memlet': - new_memlet = dcpy(self) - if keep_guid: - new_memlet.guid = self.guid - return new_memlet - def __deepcopy__(self, memo): node = object.__new__(Memlet) diff --git a/dace/sdfg/analysis/cutout.py b/dace/sdfg/analysis/cutout.py index 5ad01d8058..ec95157989 100644 --- a/dace/sdfg/analysis/cutout.py +++ b/dace/sdfg/analysis/cutout.py @@ -202,7 +202,10 @@ def singlestate_cutout(cls, nodes = _reduce_in_configuration(state, nodes, use_alibi_nodes, symbols_map) def clone_f(x: Union[Memlet, InterstateEdge, nd.Node, ControlFlowBlock]): - return x.clone(keep_guid=preserve_guids) + ret = copy.deepcopy(x) + if preserve_guids: + ret.guid = x.guid + return ret create_element = clone_f if make_copy else (lambda x: x) sdfg = state.parent @@ -374,7 +377,10 @@ def multistate_cutout(cls, :return: The created SDFGCutout or the original SDFG where no smaller cutout could be obtained. """ def create_element(x: Union[ControlFlowBlock, InterstateEdge]) -> Union[ControlFlowBlock, InterstateEdge]: - return x.clone(keep_guid=preserve_guids) + ret = copy.deepcopy(x) + if preserve_guids: + ret.guid = x.guid + return ret # Check that all states are inside the same SDFG. sdfg = list(states)[0].parent diff --git a/dace/sdfg/nodes.py b/dace/sdfg/nodes.py index 21596e1f2d..d29b1a22e4 100644 --- a/dace/sdfg/nodes.py +++ b/dace/sdfg/nodes.py @@ -55,12 +55,6 @@ def __str__(self): else: return type(self).__name__ - def clone(self, keep_guid = False) -> 'Node': - new_elem = dcpy(self) - if keep_guid: - new_elem.guid = self.guid - return new_elem - def __deepcopy__(self, memo): cls = self.__class__ result = cls.__new__(cls) diff --git a/dace/sdfg/state.py b/dace/sdfg/state.py index e44e8ea87e..b982dfd718 100644 --- a/dace/sdfg/state.py +++ b/dace/sdfg/state.py @@ -1248,12 +1248,6 @@ def __str__(self): def __repr__(self) -> str: return f'ControlFlowBlock ({self.label})' - def clone(self, keep_guid = False) -> 'ControlFlowBlock': - new_block = copy.deepcopy(self) - if keep_guid: - new_block.guid = self.guid - return new_block - def __deepcopy__(self, memo): cls = self.__class__ result = cls.__new__(cls) From fefe8a5b051e5162c66c4dee4806649eb4685003 Mon Sep 17 00:00:00 2001 From: Philipp Schaad Date: Mon, 4 Nov 2024 10:24:02 +0100 Subject: [PATCH 5/5] Removed clone functions --- dace/sdfg/sdfg.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/dace/sdfg/sdfg.py b/dace/sdfg/sdfg.py index be45917ad6..95c2c9318e 100644 --- a/dace/sdfg/sdfg.py +++ b/dace/sdfg/sdfg.py @@ -205,12 +205,6 @@ def __setattr__(self, name: str, value: Any) -> None: super().__setattr__('_uncond', None) return super().__setattr__(name, value) - def clone(self, keep_guid = False) -> 'InterstateEdge': - new_elem = copy.deepcopy(self) - if keep_guid: - new_elem.guid = self.guid - return new_elem - def __deepcopy__(self, memo): cls = self.__class__ result = cls.__new__(cls)