From 915471ef62fe627d3fa82c597a54c8421a8b9740 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Sun, 25 Apr 2021 21:54:59 -0500 Subject: [PATCH 01/59] add constrain_loop_nesting to imported functions --- loopy/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/loopy/__init__.py b/loopy/__init__.py index 6672a7c12..ed11600f1 100644 --- a/loopy/__init__.py +++ b/loopy/__init__.py @@ -67,7 +67,8 @@ from loopy.version import VERSION, MOST_RECENT_LANGUAGE_VERSION from loopy.transform.iname import ( - set_loop_priority, prioritize_loops, untag_inames, + set_loop_priority, prioritize_loops, constrain_loop_nesting, + untag_inames, split_iname, chunk_iname, join_inames, tag_inames, duplicate_inames, rename_iname, remove_unused_inames, split_reduction_inward, split_reduction_outward, @@ -187,7 +188,8 @@ # {{{ transforms - "set_loop_priority", "prioritize_loops", "untag_inames", + "set_loop_priority", "prioritize_loops", "constrain_loop_nesting", + "untag_inames", "split_iname", "chunk_iname", "join_inames", "tag_inames", "duplicate_inames", "rename_iname", "remove_unused_inames", From 1e68d02a2420f74e49ab8f7221bd7829073e7813 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Sun, 25 Apr 2021 21:55:29 -0500 Subject: [PATCH 02/59] add use_loop_nest_constraints option --- loopy/options.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/loopy/options.py b/loopy/options.py index 3742cb27b..895e655f7 100644 --- a/loopy/options.py +++ b/loopy/options.py @@ -172,6 +172,8 @@ class Options(ImmutableRecord): If equal to ``"no_check"``, then no check is performed. """ + # TODO document use_loop_nest_constraints + _legacy_options_map = { "cl_build_options": ("build_options", None), "write_cl": ("write_code", None), @@ -232,6 +234,8 @@ def __init__( False), check_dep_resolution=kwargs.get("check_dep_resolution", True), use_dependencies_v2=kwargs.get("use_dependencies_v2", False), + use_loop_nest_constraints=kwargs.get( + "use_loop_nest_constraints", False), enforce_variable_access_ordered=kwargs.get( "enforce_variable_access_ordered", True), From 7d7fc0216d4d994f79a990ed8f01f7b6f6c7fe95 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Sun, 25 Apr 2021 21:56:08 -0500 Subject: [PATCH 03/59] add loop_nest_constraint attribute to kernel --- loopy/kernel/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/loopy/kernel/__init__.py b/loopy/kernel/__init__.py index 9b022936b..96c609af3 100644 --- a/loopy/kernel/__init__.py +++ b/loopy/kernel/__init__.py @@ -252,6 +252,7 @@ class LoopKernel(ImmutableRecordWithoutPickling, Taggable): .. automethod:: tagged .. automethod:: without_tags """ + # TODO document loop_nest_constraints attribute # {{{ constructor @@ -272,6 +273,7 @@ def __init__(self, domains, instructions, args=None, iname_slab_increments=None, loop_priority=frozenset(), + loop_nest_constraints=None, silenced_warnings=None, applied_iname_rewrites=None, @@ -417,6 +419,7 @@ def __init__(self, domains, instructions, args=None, assumptions=assumptions, iname_slab_increments=iname_slab_increments, loop_priority=loop_priority, + loop_nest_constraints=loop_nest_constraints, silenced_warnings=silenced_warnings, temporary_variables=temporary_variables, local_sizes=local_sizes, @@ -1550,6 +1553,7 @@ def __setstate__(self, state): "substitutions", "iname_slab_increments", "loop_priority", + "loop_nest_constraints", "silenced_warnings", "options", "state", From 7acf1aab980b262df2998265f4884b8d7fb4fffc Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Sun, 25 Apr 2021 22:00:22 -0500 Subject: [PATCH 04/59] copy in AND UPDATE functions for creating and checking new loop nest constraints from old branch (https://gitlab.tiker.net/jdsteve2/loopy/iname-sets-in-loop-priorities): UnexpandedInameSet, LoopNestConstraints, process_loop_nest_specification, constrain_loop_nesting, update_must_nest_graph, _expand_iname_sets_in_tuple, check_must_not_nest, check_must_not_nest_against_must_nest_graph --- loopy/transform/iname.py | 585 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 585 insertions(+) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index 8758284ef..673754a38 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -28,6 +28,7 @@ RuleAwareIdentityMapper, RuleAwareSubstitutionMapper, SubstitutionRuleMappingContext) from loopy.diagnostic import LoopyError +from pytools import Record __doc__ = """ @@ -112,6 +113,590 @@ def prioritize_loops(kernel, loop_priority): # }}} +# {{{ loop nest constraints + +# {{{ classes to house loop nest constraints + +# {{{ UnexpandedInameSet + +class UnexpandedInameSet(Record): + def __init__(self, inames, complement=False): + Record.__init__( + self, + inames=inames, + complement=complement, + ) + + def contains(self, iname): + return (iname not in self.inames if self.complement + else iname in self.inames) + + def contains_all(self, iname_set): + return (not (iname_set & self.inames) if self.complement + else iname_set.issubset(self.inames)) + + def get_inames_represented(self, iname_universe=None): + """Return the set of inames represented by the UnexpandedInameSet + """ + if self.complement: + if not iname_universe: + raise ValueError( + "Cannot expand UnexpandedInameSet %s without " + "iname_universe." % (self)) + return iname_universe-self.inames + else: + return self.inames.copy() + + def __lt__(self, other): + # TODO is this function really necessary? If so, what should it return? + return self.__hash__() < other.__hash__() + + def __hash__(self): + return hash(repr(self)) + + def update_persistent_hash(self, key_hash, key_builder): + """Custom hash computation function for use with + :class:`pytools.persistent_dict.PersistentDict`. + """ + + key_builder.rec(key_hash, self.inames) + key_builder.rec(key_hash, self.complement) + + def __str__(self): + return "%s{%s}" % ("~" if self.complement else "", + ",".join(i for i in sorted(self.inames))) + +# }}} + + +# {{{ LoopNestConstraints + +class LoopNestConstraints(Record): + def __init__(self, must_nest=None, must_not_nest=None, + must_nest_graph=None): + Record.__init__( + self, + must_nest=must_nest, + must_not_nest=must_not_nest, + must_nest_graph=must_nest_graph, + ) + + def __hash__(self): + return hash(repr(self)) + + def update_persistent_hash(self, key_hash, key_builder): + """Custom hash computation function for use with + :class:`pytools.persistent_dict.PersistentDict`. + """ + + key_builder.rec(key_hash, self.must_nest) + key_builder.rec(key_hash, self.must_not_nest) + key_builder.rec(key_hash, self.must_nest_graph) + + def __str__(self): + return "LoopNestConstraints(\n" \ + " must_nest = " + str(self.must_nest) + "\n" \ + " must_not_nest = " + str(self.must_not_nest) + "\n" \ + " must_nest_graph = " + str(self.must_nest_graph) + "\n" \ + ")" + +# }}} + +# }}} + + +# {{{ initial loop nest constraint creation + +# {{{ process_loop_nest_specification + +def process_loop_nest_specification( + nesting, + max_tuple_size=None, + complement_sets_allowed=True, + ): + # make sure user-supplied nesting conforms to rules + # convert string representations of nestings to tuple of UnexpandedInameSets + + import re + + def raise_loop_nest_input_error(msg): + valid_prio_rules = ( + 'Valid `must_nest` description formats: ' + '"iname, iname, ..." or (str, str, str, ...), ' + 'where str can be of form ' + '"iname" or "{iname, iname, ...}". No set complements allowed.\n' + 'Valid `must_not_nest` description tuples must have len <= 2: ' + '"iname, iname", "iname, ~iname", or ' + '(str, str), where str can be of form ' + '"iname", "~iname", "{iname, iname, ...}", or "~{iname, iname, ...}".' + ) + raise ValueError( + "Invalid loop nest prioritization: %s\n" + "Loop nest prioritization formatting rules:\n%s" + % (msg, valid_prio_rules)) + + def _error_on_regex_match(match_str, target_str): + if re.findall(match_str, target_str): + raise_loop_nest_input_error( + "Unrecognized character(s) %s in nest string %s" + % (re.findall(match_str, target_str), target_str)) + + def _process_iname_set_str(iname_set_str): + # convert something like ~{i,j} or ~i or "i,j" to an UnexpandedInameSet + + # remove leading/trailing whitespace + iname_set_str_stripped = iname_set_str.strip() + + if iname_set_str_stripped[0] == "~": + # Make sure compelement is allowed + if not complement_sets_allowed: + raise_loop_nest_input_error( + "Complement (~) not allowed in this loop nest string %s. " + "If you have a use-case where allowing a currently " + "disallowed set complement would be helpful, and the " + "desired nesting constraint cannot easily be expressed " + "another way, " + "please contact the Loo.py maintainers." + % (iname_set_str)) + + # Make sure that braces are included if multiple inames present + if "," in iname_set_str and not ( + iname_set_str.startswith("~{") and + iname_set_str.endswith("}")): + raise_loop_nest_input_error( + "Complements of sets containing multiple inames must " + "enclose inames in braces: %s is not valid." + % (iname_set_str)) + + complement = True + else: + complement = False + + # remove leading/trailing tilde, braces, and space + iname_set_str_stripped = iname_set_str_stripped.strip("~{} ") + + # should be no remaining special characters besides comma and space + _error_on_regex_match(r'([^,\w ])', iname_set_str_stripped) + + # split by commas or spaces to get inames + inames = re.findall(r'([\w]+)(?:[ |,]*|$)', iname_set_str_stripped) + + # make sure iname count matches what we expect from comma count + if len(inames) != iname_set_str_stripped.count(",") + 1: + raise_loop_nest_input_error( + "Found %d inames but expected %d in string %s." + % (len(inames), iname_set_str_stripped.count(",") + 1, + iname_set_str_stripped)) + + return UnexpandedInameSet( + set([s.strip() for s in iname_set_str_stripped.split(",")]), + complement=complement) + + if isinstance(nesting, str): + # Enforce that priorities involving iname sets be passed as tuple + # Iname sets defined negatively with a single iname are allowed here + + # Check for any special characters besides comma, space, and tilde. + # E.g., curly braces would indicate that an iname set was NOT + # passed as a tuple, which is not allowed. + _error_on_regex_match(r'([^,\w~ ])', nesting) + + # Split by comma and process each tier + nesting_as_tuple = tuple( + _process_iname_set_str(set_str) for set_str in nesting.split(",")) + else: + # nesting not passed as string; process each tier + nesting_as_tuple = tuple( + _process_iname_set_str(set_str) for set_str in nesting) + + # check max_inames_per_set + if max_tuple_size and len(nesting_as_tuple) > max_tuple_size: + raise_loop_nest_input_error( + "Loop nest prioritization tuple %s exceeds max tuple size %d." + % (nesting_as_tuple)) + + # make sure nesting has len > 1 + if len(nesting_as_tuple) <= 1: + raise_loop_nest_input_error( + "Loop nest prioritization tuple %s must have length > 1." + % (nesting_as_tuple)) + + # return tuple of UnexpandedInameSets + return nesting_as_tuple + +# }}} + + +# {{{ constrain_loop_nesting + +def constrain_loop_nesting( + kernel, must_nest=None, must_not_nest=None): + # TODO docstring + # TODO what if someone passes single-iname prio? + # TODO enforce that must_nest be a single tuple not list of tuples + # (or update implementation to allow list of tuples) + + # check for existing constraints + if kernel.loop_nest_constraints: + if kernel.loop_nest_constraints.must_nest: + must_nest_constraints_old = kernel.loop_nest_constraints.must_nest + else: + must_nest_constraints_old = set() + if kernel.loop_nest_constraints.must_not_nest: + must_not_nest_constraints_old = \ + kernel.loop_nest_constraints.must_not_nest + else: + must_not_nest_constraints_old = set() + if kernel.loop_nest_constraints.must_nest_graph: + must_nest_graph_old = kernel.loop_nest_constraints.must_nest_graph + else: + must_nest_graph_old = {} + else: + must_nest_constraints_old = set() + must_not_nest_constraints_old = set() + must_nest_graph_old = {} + + # {{{ process must_nest + + # TODO remove (TEMPORARY HACK TO KEEP LEGACY CODE RUNNING) + # expand_must_priorities = set() + + if must_nest: + # {{{ parse must_nest, check for conflicts, combine with old constraints + + # {{{ Parse must_nest; no complements allowed + must_nest_tuple = process_loop_nest_specification( + must_nest, complement_sets_allowed=False) + # }}} + + # {{{ Error if someone prioritizes concurrent iname + from loopy.kernel.data import ConcurrentTag + for iname_set in must_nest_tuple: + for iname in iname_set.inames: + if kernel.iname_tags_of_type(iname, ConcurrentTag): + raise ValueError( + "iname %s tagged with ConcurrentTag, " + "cannot use iname in must-nest constraint %s." + % (iname, must_nest_tuple)) + # }}} + + # {{{ must_nest_graph_new <- update_must_nest_graph(...) + + # (checks for cycles) + must_nest_graph_new = update_must_nest_graph( + must_nest_graph_old, must_nest_tuple, kernel.all_inames()) + + # }}} + + # {{{ make sure must_nest constraints don't violate must_not_nest + # this may not catch all problems (?) + check_must_not_nest_against_must_nest_graph( + must_not_nest_constraints_old, must_nest_graph_new) + # }}} + + # {{{ check for conflicts with inames tagged 'vec' (must be innermost) + from loopy.kernel.data import VectorizeTag + for iname in kernel.all_inames(): + if kernel.iname_tags_of_type(iname, VectorizeTag) and ( + must_nest_graph_new.get(iname, set())): + # Iname cannot be a leaf, error + raise ValueError( + "Iname %s tagged as 'vec', but loop priorities " + "%s require that iname %s nest outside of inames %s. " + "Vectorized inames must nest innermost; cannot " + "impose loop nest specification." + % (iname, must_nest, iname, + must_nest_graph_new.get(iname, set()))) + # }}} + + # TODO remove (TEMPORARY HACK TO KEEP LEGACY CODE RUNNING) + # expand_must_priorities = _expand_iname_sets_in_tuple( + # must_nest_tuple, kernel.all_inames()) + + # {{{ combine new must_nest constraints with old + must_nest_constraints_new = must_nest_constraints_old | set( + [must_nest_tuple, ]) + # }}} + + # }}} + else: + # {{{ no new must_nest constraints, keep the old ones + must_nest_constraints_new = must_nest_constraints_old + must_nest_graph_new = must_nest_graph_old + # }}} + + # }}} + + # {{{ process must_not_nest + + if must_not_nest: + # {{{ parse must_not_nest, check for conflicts, combine with old constraints + + # {{{ Parse must_not_nest; complements allowed; max_tuple_size=2 + must_not_nest_tuple = process_loop_nest_specification( + must_not_nest, max_tuple_size=2) + # }}} + + # {{{ make sure must_not_nest constraints don't violate must_nest + # (cycles are allowed in must_not_nest constraints) + import itertools + must_pairs = [] + for iname_before, inames_after in must_nest_graph_new.items(): + must_pairs.extend(list(itertools.product([iname_before], inames_after))) + + if not check_must_not_nest(must_pairs, must_not_nest_tuple): + raise ValueError( + "constrain_loop_nesting: nest constraint conflict detected. " + "must_not_nest constraints %s inconsistent with " + "must_nest constraints %s." + % (must_not_nest_tuple, must_nest_constraints_new)) + # }}} + + # {{{ combine new must_not_nest constraints with old + must_not_nest_constraints_new = must_not_nest_constraints_old | set([ + must_not_nest_tuple, ]) + # }}} + + # }}} + else: + # {{{ no new must_not_nest constraints, keep the old ones + must_not_nest_constraints_new = must_not_nest_constraints_old + # }}} + + # }}} + + nest_constraints = LoopNestConstraints( + must_nest=must_nest_constraints_new, + must_not_nest=must_not_nest_constraints_new, + must_nest_graph=must_nest_graph_new, + ) + + # TODO do something with old priorities??? + return kernel.copy( + # loop_priority=kernel.loop_priority.union(expand_must_priorities), + loop_nest_constraints=nest_constraints, + ) + +# }}} + + +# {{{ update_must_nest_graph + +def update_must_nest_graph(must_nest_graph, must_nest, all_inames): + # Note: there should not be any complements in the must_nest tuples + from copy import deepcopy + new_graph = deepcopy(must_nest_graph) + + # first, all inames must be a node in the graph: + for missing_iname in all_inames - new_graph.keys(): + new_graph[missing_iname] = set() + + # get (before, after) pairs: + must_nest_expanded = _expand_iname_sets_in_tuple(must_nest, all_inames) + + # update graph: + for before, after in must_nest_expanded: + new_graph[before].add(after) + + # compute transitive closure: + from pytools.graph import compute_transitive_closure + # Note: compute_transitive_closure now allows cycles, will not error + new_graph_closure = compute_transitive_closure(new_graph) + + # Check for inconsistent must_nest constraints by checking for cycle: + from pytools.graph import contains_cycle + if contains_cycle(new_graph_closure): + raise ValueError( + "update_must_nest_graph: Loop priority cycle detected. " + "must_nest constraints %s inconsistent with existing " + "must_nest constraints %s." + % (must_nest, must_nest_graph)) + return new_graph_closure + +# }}} + + +# {{{ _expand_iname_sets_in_tuple + +def _expand_iname_sets_in_tuple( + iname_sets_tuple, # (UnexpandedInameSet, Unex..., ...) + all_inames, + ): + + # First convert negatively defined iname sets to sets + positively_defined_iname_sets = [] + for iname_set in iname_sets_tuple: + positively_defined_iname_sets.append( + iname_set.get_inames_represented(all_inames)) + + # Now expand all priority tuples into (before, after) pairs using + # Cartesian product of all pairs of sets + # (Assumes prio_sets length > 1) + import itertools + loop_priority_pairs = set() + for i, before_set in enumerate(positively_defined_iname_sets[:-1]): + for after_set in positively_defined_iname_sets[i+1:]: + loop_priority_pairs.update( + list(itertools.product(before_set, after_set))) + + # Make sure no priority tuple contains an iname twice + for prio_tuple in loop_priority_pairs: + if len(set(prio_tuple)) != len(prio_tuple): + raise ValueError( + "Loop nesting %s contains cycle: %s. " + % (iname_sets_tuple, prio_tuple)) + return loop_priority_pairs + +# }}} + +# }}} + + +# {{{ checking constraints + +# {{{ check_must_nest (TODO copied in from old branch, not yet enabled) + +""" +def check_must_nest(all_loop_nests, must_nest, all_inames): + # in order to make sure must_nest is satisfied, we + # need to expand all must_nest tiers + + # TODO instead of expanding tiers into all pairs up front, + # create these pairs one at a time so that we can stop as soon as we fail + + must_nest_expanded = _expand_iname_sets_in_tuple(must_nest, all_inames) + # must_nest_expanded contains pairs + for before, after in must_nest_expanded: + found = False + for nesting in all_loop_nests: + if before in nesting and after in nesting and ( + nesting.index(before) < nesting.index(after)): + found = True + break + if not found: + return False + return True +""" + +# }}} + + +# {{{ check_must_not_nest + +def check_must_not_nest(all_loop_nests, must_not_nest): + # recall that must_not_nest may only contain two tiers + + for nesting in all_loop_nests: + # Go thru each pair in all_loop_nests + for i, iname_before in enumerate(nesting): + for iname_after in nesting[i+1:]: + # Check whether it violates must not nest + if (must_not_nest[0].contains(iname_before) + and must_not_nest[1].contains(iname_after)): + # Stop as soon as we fail + return False + return True + +# }}} + + +# {{{ check_all_must_not_nests (TODO copied in from old branch, not yet enabled) + +""" +def check_all_must_not_nests(all_loop_nests, must_not_nests): + # recall that must_not_nest may only contain two tiers + for must_not_nest in must_not_nests: + if not check_must_not_nest(all_loop_nests, must_not_nest): + return False + return True +""" + +# }}} + + +# {{{ is_loop_nesting_valid (TODO copied in from old branch, not yet enabled) + +""" +def is_loop_nesting_valid( + all_loop_nests, + must_nest_constraints, + must_not_nest_constraints, + all_inames): + + # check must-nest constraints + must_nest_valid = True + if must_nest_constraints: + for must_nest in must_nest_constraints: + if not check_must_nest( + all_loop_nests, must_nest, all_inames): + must_nest_valid = False + break + + # check must-not-nest constraints + must_not_nest_valid = True + if must_not_nest_constraints is not None: + for must_not_nest in must_not_nest_constraints: + if not check_must_not_nest( + all_loop_nests, must_not_nest): + must_not_nest_valid = False + break + + return must_nest_valid and must_not_nest_valid +""" + +# }}} + + +# {{{ check_must_not_nest_against_must_nest_graph + +def check_must_not_nest_against_must_nest_graph( + must_not_nest_constraints, must_nest_graph): + # make sure none of the must_nest constraints violate must_not_nest + # this may not catch all problems + + if must_not_nest_constraints and must_nest_graph: + import itertools + must_pairs = [] + for iname_before, inames_after in must_nest_graph.items(): + must_pairs.extend( + list(itertools.product([iname_before], inames_after))) + if any(not check_must_not_nest(must_pairs, must_not_nest_tuple) + for must_not_nest_tuple in must_not_nest_constraints): + raise ValueError( + "Nest constraint conflict detected. " + "must_not_nest constraints %s inconsistent with " + "must_nest relationships (must_nest graph: %s)." + % (must_not_nest_constraints, must_nest_graph)) + +# }}} + + +# {{{ get_iname_nestings (TODO copied in from old branch, not yet enabled) + +def get_iname_nestings(outline): + from loopy.schedule import EnterLoop, LeaveLoop + # return a list of tuples representing deepest nestings + nestings = [] + current_tiers = [] + already_exiting_loops = False + for outline_item in outline: + if isinstance(outline_item, EnterLoop): + already_exiting_loops = False + current_tiers.append(outline_item.iname) + elif isinstance(outline_item, LeaveLoop): + if not already_exiting_loops: + nestings.append(tuple(current_tiers)) + already_exiting_loops = True + del current_tiers[-1] + return nestings + +# }}} + +# }}} + +# }}} + + # {{{ split/chunk inames # {{{ backend From 0f27f079f6ebb76ffa6b022e69faa44410941d7a Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Sun, 25 Apr 2021 22:09:00 -0500 Subject: [PATCH 05/59] fix flake8 issues --- loopy/transform/iname.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index 673754a38..cdc4387f4 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -221,14 +221,15 @@ def process_loop_nest_specification( def raise_loop_nest_input_error(msg): valid_prio_rules = ( - 'Valid `must_nest` description formats: ' - '"iname, iname, ..." or (str, str, str, ...), ' - 'where str can be of form ' - '"iname" or "{iname, iname, ...}". No set complements allowed.\n' - 'Valid `must_not_nest` description tuples must have len <= 2: ' - '"iname, iname", "iname, ~iname", or ' - '(str, str), where str can be of form ' - '"iname", "~iname", "{iname, iname, ...}", or "~{iname, iname, ...}".' + "Valid `must_nest` description formats: " + "\"iname, iname, ...\" or (str, str, str, ...), " + "where str can be of form " + "\"iname\" or \"{iname, iname, ...}\". No set complements allowed.\n" + "Valid `must_not_nest` description tuples must have len <= 2: " + "\"iname, iname\", \"iname, ~iname\", or " + "(str, str), where str can be of form " + "\"iname\", \"~iname\", \"{iname, iname, ...}\", or " + "\"~{iname, iname, ...}\"." ) raise ValueError( "Invalid loop nest prioritization: %s\n" @@ -276,10 +277,10 @@ def _process_iname_set_str(iname_set_str): iname_set_str_stripped = iname_set_str_stripped.strip("~{} ") # should be no remaining special characters besides comma and space - _error_on_regex_match(r'([^,\w ])', iname_set_str_stripped) + _error_on_regex_match(r"([^,\w ])", iname_set_str_stripped) # split by commas or spaces to get inames - inames = re.findall(r'([\w]+)(?:[ |,]*|$)', iname_set_str_stripped) + inames = re.findall(r"([\w]+)(?:[ |,]*|$)", iname_set_str_stripped) # make sure iname count matches what we expect from comma count if len(inames) != iname_set_str_stripped.count(",") + 1: @@ -299,7 +300,7 @@ def _process_iname_set_str(iname_set_str): # Check for any special characters besides comma, space, and tilde. # E.g., curly braces would indicate that an iname set was NOT # passed as a tuple, which is not allowed. - _error_on_regex_match(r'([^,\w~ ])', nesting) + _error_on_regex_match(r"([^,\w~ ])", nesting) # Split by comma and process each tier nesting_as_tuple = tuple( From fcbe50062aad0eea2594401f964b870e5fc24e42 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 02:37:41 -0500 Subject: [PATCH 06/59] fix a few more invalid loop nest constraints cases --- loopy/transform/iname.py | 42 ++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index cdc4387f4..01ef4f9a9 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -248,6 +248,11 @@ def _process_iname_set_str(iname_set_str): # remove leading/trailing whitespace iname_set_str_stripped = iname_set_str.strip() + if not iname_set_str_stripped: + raise_loop_nest_input_error( + "Found 0 inames in string %s." + % (iname_set_str)) + if iname_set_str_stripped[0] == "~": # Make sure compelement is allowed if not complement_sets_allowed: @@ -260,10 +265,17 @@ def _process_iname_set_str(iname_set_str): "please contact the Loo.py maintainers." % (iname_set_str)) + # remove tilde + iname_set_str_stripped = iname_set_str_stripped[1:] + if "~" in iname_set_str_stripped: + raise_loop_nest_input_error( + "Multiple complement symbols found in iname set string %s" + % (iname_set_str)) + # Make sure that braces are included if multiple inames present - if "," in iname_set_str and not ( - iname_set_str.startswith("~{") and - iname_set_str.endswith("}")): + if "," in iname_set_str_stripped and not ( + iname_set_str_stripped.startswith("{") and + iname_set_str_stripped.endswith("}")): raise_loop_nest_input_error( "Complements of sets containing multiple inames must " "enclose inames in braces: %s is not valid." @@ -273,8 +285,21 @@ def _process_iname_set_str(iname_set_str): else: complement = False - # remove leading/trailing tilde, braces, and space - iname_set_str_stripped = iname_set_str_stripped.strip("~{} ") + # remove leading/trailing spaces + iname_set_str_stripped = iname_set_str_stripped.strip(" ") + + # make sure braces are valid and strip them + if iname_set_str_stripped[0] == "{": + if not iname_set_str_stripped[-1] == "}": + raise_loop_nest_input_error( + "Invalid braces: %s" % (iname_set_str)) + else: + # remove enclosing braces + iname_set_str_stripped = iname_set_str_stripped[1:-1] + # if there are dangling braces around, they will be caught next + + # remove any more spaces + iname_set_str_stripped = iname_set_str_stripped.strip() # should be no remaining special characters besides comma and space _error_on_regex_match(r"([^,\w ])", iname_set_str_stripped) @@ -287,7 +312,12 @@ def _process_iname_set_str(iname_set_str): raise_loop_nest_input_error( "Found %d inames but expected %d in string %s." % (len(inames), iname_set_str_stripped.count(",") + 1, - iname_set_str_stripped)) + iname_set_str)) + + if len(inames) == 0: + raise_loop_nest_input_error( + "Found empty set in string %s." + % (iname_set_str)) return UnexpandedInameSet( set([s.strip() for s in iname_set_str_stripped.split(",")]), From 75b3d804fb014f59a399bc48c8b03b3417f80f2c Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 02:38:21 -0500 Subject: [PATCH 07/59] create test for loop nest semantics parsing (copied in from old branch (https://gitlab.tiker.net/jdsteve2/loopy/iname-sets-in-loop-priorities)) --- test/test_loop_nest_semantics.py | 198 +++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 test/test_loop_nest_semantics.py diff --git a/test/test_loop_nest_semantics.py b/test/test_loop_nest_semantics.py new file mode 100644 index 000000000..3b6381b44 --- /dev/null +++ b/test/test_loop_nest_semantics.py @@ -0,0 +1,198 @@ +__copyright__ = "Copyright (C) 2021 James Stevens" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +import sys +import loopy as lp +import pyopencl as cl + +import logging +logger = logging.getLogger(__name__) + +try: + import faulthandler +except ImportError: + pass +else: + faulthandler.enable() + +from pyopencl.tools import pytest_generate_tests_for_pyopencl \ + as pytest_generate_tests + +__all__ = [ + "pytest_generate_tests", + "cl" # "cl.create_some_context" + ] + + +from loopy.version import LOOPY_USE_LANGUAGE_VERSION_2018_2 # noqa + + +def test_loop_constraint_strings_validity_check(): + ref_knl = lp.make_kernel( + "{ [g,h,i,j,k,xx]: 0<=g,h,i,j,k,xx 1: + exec(sys.argv[1]) + else: + from pytest import main + main([__file__]) + +# vim: foldmethod=marker From b2ec4bf0801abe2ce9f78721aa1b070ae2d70b22 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 02:58:21 -0500 Subject: [PATCH 08/59] un-comment-out (and slightly improve) more old functions copied in from previous branch (https://gitlab.tiker.net/jdsteve2/loopy/iname-sets-in-loop-priorities): check_must_nest(), is_loop_nesting_valid(); rename function is_loop_nesting_valid->loop_nest_constraints_satisfied --- loopy/transform/iname.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index 01ef4f9a9..3d7425b77 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -585,9 +585,8 @@ def _expand_iname_sets_in_tuple( # {{{ checking constraints -# {{{ check_must_nest (TODO copied in from old branch, not yet enabled) +# {{{ check_must_nest -""" def check_must_nest(all_loop_nests, must_nest, all_inames): # in order to make sure must_nest is satisfied, we # need to expand all must_nest tiers @@ -607,7 +606,6 @@ def check_must_nest(all_loop_nests, must_nest, all_inames): if not found: return False return True -""" # }}} @@ -645,35 +643,29 @@ def check_all_must_not_nests(all_loop_nests, must_not_nests): # }}} -# {{{ is_loop_nesting_valid (TODO copied in from old branch, not yet enabled) +# {{{ loop_nest_constraints_satisfied -""" -def is_loop_nesting_valid( +def loop_nest_constraints_satisfied( all_loop_nests, must_nest_constraints, must_not_nest_constraints, all_inames): # check must-nest constraints - must_nest_valid = True if must_nest_constraints: for must_nest in must_nest_constraints: if not check_must_nest( all_loop_nests, must_nest, all_inames): - must_nest_valid = False - break + return False # check must-not-nest constraints - must_not_nest_valid = True if must_not_nest_constraints is not None: for must_not_nest in must_not_nest_constraints: if not check_must_not_nest( all_loop_nests, must_not_nest): - must_not_nest_valid = False - break + return False - return must_nest_valid and must_not_nest_valid -""" + return True # }}} From 42578f6c5ef415da3885a99d4e424048e111d4f2 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 02:59:06 -0500 Subject: [PATCH 09/59] copy in test for loop nest constraint satisfaction from old branch (https://gitlab.tiker.net/jdsteve2/loopy/iname-sets-in-loop-priorities) --- test/test_loop_nest_semantics.py | 94 +++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/test/test_loop_nest_semantics.py b/test/test_loop_nest_semantics.py index 3b6381b44..866bce249 100644 --- a/test/test_loop_nest_semantics.py +++ b/test/test_loop_nest_semantics.py @@ -46,7 +46,9 @@ from loopy.version import LOOPY_USE_LANGUAGE_VERSION_2018_2 # noqa -def test_loop_constraint_strings_validity_check(): +# {{{ test_loop_constraint_string_parsing + +def test_loop_constraint_string_parsing(): ref_knl = lp.make_kernel( "{ [g,h,i,j,k,xx]: 0<=g,h,i,j,k,xx 1: From ddfc0e52cc3c05f4e60847c8816802d29036c2cc Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 03:04:10 -0500 Subject: [PATCH 10/59] test for adding (multiple) loop nest constraints to a kernel; copied in from old branch (https://gitlab.tiker.net/jdsteve2/loopy/iname-sets-in-loop-priorities) --- test/test_loop_nest_semantics.py | 55 ++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/test/test_loop_nest_semantics.py b/test/test_loop_nest_semantics.py index 866bce249..cc1b31a77 100644 --- a/test/test_loop_nest_semantics.py +++ b/test/test_loop_nest_semantics.py @@ -22,6 +22,7 @@ import sys import loopy as lp +import numpy as np import pyopencl as cl import logging @@ -280,6 +281,60 @@ def test_loop_nest_constraints_satisfied(): # }}} +# {{{ test_multiple_nest_constraints + +def test_multiple_nest_constraints(): + ref_knl = lp.make_kernel( + "{ [g,h,i,j,k,x,y,z]: 0<=g,h,i,j,k,x,y,z 1: exec(sys.argv[1]) From 8c51083e21063ef5c58095ccb0b7b1b56dc0ce82 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 03:06:51 -0500 Subject: [PATCH 11/59] use term 'nest constraints' instead of 'loop priority' in cycle error string --- loopy/transform/iname.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index 3d7425b77..e0e904c96 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -538,7 +538,7 @@ def update_must_nest_graph(must_nest_graph, must_nest, all_inames): from pytools.graph import contains_cycle if contains_cycle(new_graph_closure): raise ValueError( - "update_must_nest_graph: Loop priority cycle detected. " + "update_must_nest_graph: Nest constraint cycle detected. " "must_nest constraints %s inconsistent with existing " "must_nest constraints %s." % (must_nest, must_nest_graph)) From c3dec79a5a1260e86a39ab1149e07c2c9ab3c039 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 03:07:29 -0500 Subject: [PATCH 12/59] test for catching incompatible loop nest constraints; copied in from old branch (https://gitlab.tiker.net/jdsteve2/loopy/iname-sets-in-loop-priorities) --- test/test_loop_nest_semantics.py | 46 ++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/test/test_loop_nest_semantics.py b/test/test_loop_nest_semantics.py index cc1b31a77..3e5ac0df1 100644 --- a/test/test_loop_nest_semantics.py +++ b/test/test_loop_nest_semantics.py @@ -281,9 +281,9 @@ def test_loop_nest_constraints_satisfied(): # }}} -# {{{ test_multiple_nest_constraints +# {{{ test_adding_multiple_nest_constraints_to_knl -def test_multiple_nest_constraints(): +def test_adding_multiple_nest_constraints_to_knl(): ref_knl = lp.make_kernel( "{ [g,h,i,j,k,x,y,z]: 0<=g,h,i,j,k,x,y,z 1: exec(sys.argv[1]) From 308fba4293f5c313cfb92c021dca8cd50de71f45 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 03:24:33 -0500 Subject: [PATCH 13/59] comment out currently unused function --- loopy/transform/iname.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index e0e904c96..c634cee40 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -696,6 +696,7 @@ def check_must_not_nest_against_must_nest_graph( # {{{ get_iname_nestings (TODO copied in from old branch, not yet enabled) +""" def get_iname_nestings(outline): from loopy.schedule import EnterLoop, LeaveLoop # return a list of tuples representing deepest nestings @@ -712,6 +713,7 @@ def get_iname_nestings(outline): already_exiting_loops = True del current_tiers[-1] return nestings +""" # }}} From 7d5652aed4e392a8831e6183a885458a0bd84760 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 03:25:21 -0500 Subject: [PATCH 14/59] rename last_entered_loop->deepest_active_iname to be more accurate and precise --- loopy/schedule/__init__.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/loopy/schedule/__init__.py b/loopy/schedule/__init__.py index fc084fd68..962022e68 100644 --- a/loopy/schedule/__init__.py +++ b/loopy/schedule/__init__.py @@ -363,6 +363,7 @@ def find_loop_insn_dep_map( # If at least one of the three cases above succeeds for every # dep_insn_iname, we can add dep_insn to iname's set of insns # in result dict. + # (means dep_insn must be scheduled before entering iname loop) iname_dep.add(dep_insn_id) return result @@ -682,7 +683,7 @@ class SchedulerState(ImmutableRecord): # TODO document simplified_depends_on_graph @property - def last_entered_loop(self): + def deepest_active_iname(self): if self.active_inames: return self.active_inames[-1] else: @@ -1161,34 +1162,34 @@ def insn_sort_key(insn_id): # {{{ see if we're ready to leave the innermost loop - last_entered_loop = sched_state.last_entered_loop + deepest_active_iname = sched_state.deepest_active_iname - if last_entered_loop is not None: + if deepest_active_iname is not None: can_leave = True if ( - last_entered_loop in sched_state.prescheduled_inames + deepest_active_iname in sched_state.prescheduled_inames and not ( isinstance(next_preschedule_item, LeaveLoop) - and next_preschedule_item.iname == last_entered_loop)): + and next_preschedule_item.iname == deepest_active_iname)): # A prescheduled loop can only be left if the preschedule agrees. if debug_mode: print("cannot leave '%s' because of preschedule constraints" - % last_entered_loop) + % deepest_active_iname) can_leave = False - elif last_entered_loop not in sched_state.breakable_inames: + elif deepest_active_iname not in sched_state.breakable_inames: # If the iname is not breakable, then check that we've # scheduled all the instructions that require it. for insn_id in sched_state.unscheduled_insn_ids: insn = kernel.id_to_insn[insn_id] - if last_entered_loop in insn.within_inames: + if deepest_active_iname in insn.within_inames: if debug_mode: print("cannot leave '%s' because '%s' still depends on it" - % (last_entered_loop, format_insn(kernel, insn.id))) + % (deepest_active_iname, format_insn(kernel, insn.id))) # check if there's a dependency of insn that needs to be - # outside of last_entered_loop. + # outside of deepest_active_iname. for subdep_id in gen_dependencies_except( kernel, insn_id, sched_state.scheduled_insn_ids, @@ -1196,7 +1197,7 @@ def insn_sort_key(insn_id): want = (kernel.insn_inames(subdep_id) - sched_state.parallel_inames) if ( - last_entered_loop not in want): + deepest_active_iname not in want): print( "%(warn)swarning:%(reset_all)s '%(iname)s', " "which the schedule is " @@ -1210,7 +1211,7 @@ def insn_sort_key(insn_id): % { "warn": Fore.RED + Style.BRIGHT, "reset_all": Style.RESET_ALL, - "iname": last_entered_loop, + "iname": deepest_active_iname, "subdep": format_insn_id(kernel, subdep_id), "dep": format_insn_id(kernel, insn_id), "subdep_i": format_insn(kernel, subdep_id), @@ -1237,7 +1238,7 @@ def insn_sort_key(insn_id): if ignore_count: ignore_count -= 1 else: - assert sched_item.iname == last_entered_loop + assert sched_item.iname == deepest_active_iname if seen_an_insn: can_leave = True break @@ -1248,12 +1249,12 @@ def insn_sort_key(insn_id): sched_state.copy( schedule=( sched_state.schedule - + (LeaveLoop(iname=last_entered_loop),)), + + (LeaveLoop(iname=deepest_active_iname),)), active_inames=sched_state.active_inames[:-1], insn_ids_to_try=insn_ids_to_try, preschedule=( sched_state.preschedule - if last_entered_loop + if deepest_active_iname not in sched_state.prescheduled_inames else sched_state.preschedule[1:]), ), From 62ffb73a2bc3c790bee92f673dc7c9976b3db7a2 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 03:37:00 -0500 Subject: [PATCH 15/59] rename uninformative 'want' variable in various places, mostly want->nonconc_insn_inames_wanted --- loopy/schedule/__init__.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/loopy/schedule/__init__.py b/loopy/schedule/__init__.py index 962022e68..e0a12effc 100644 --- a/loopy/schedule/__init__.py +++ b/loopy/schedule/__init__.py @@ -1016,19 +1016,21 @@ def insn_sort_key(insn_id): if not is_ready: continue - want = insn.within_inames - sched_state.parallel_inames + nonconc_insn_inames_wanted = insn.within_inames - sched_state.parallel_inames have = active_inames_set - sched_state.parallel_inames - if want != have: + if nonconc_insn_inames_wanted != have: is_ready = False if debug_mode: - if want-have: + if nonconc_insn_inames_wanted-have: print("instruction '%s' is missing inames '%s'" - % (format_insn(kernel, insn.id), ",".join(want-have))) - if have-want: + % (format_insn(kernel, insn.id), ",".join( + nonconc_insn_inames_wanted-have))) + if have-nonconc_insn_inames_wanted: print("instruction '%s' won't work under inames '%s'" - % (format_insn(kernel, insn.id), ",".join(have-want))) + % (format_insn(kernel, insn.id), ",".join( + have-nonconc_insn_inames_wanted))) # {{{ check if scheduling this insn is compatible with preschedule @@ -1082,7 +1084,7 @@ def insn_sort_key(insn_id): # {{{ determine reachability - if (not is_ready and have <= want): + if (not is_ready and have <= nonconc_insn_inames_wanted): reachable_insn_ids.add(insn_id) # }}} @@ -1194,10 +1196,11 @@ def insn_sort_key(insn_id): kernel, insn_id, sched_state.scheduled_insn_ids, sched_state.simplified_depends_on_graph): - want = (kernel.insn_inames(subdep_id) + nonconc_subdep_insn_inames_wanted = ( + kernel.insn_inames(subdep_id) - sched_state.parallel_inames) - if ( - deepest_active_iname not in want): + if (deepest_active_iname + not in nonconc_subdep_insn_inames_wanted): print( "%(warn)swarning:%(reset_all)s '%(iname)s', " "which the schedule is " @@ -1373,9 +1376,9 @@ def insn_sort_key(insn_id): for insn_id in reachable_insn_ids: insn = kernel.id_to_insn[insn_id] - want = insn.within_inames + wanted_insn_inames = insn.within_inames - if hypothetically_active_loops <= want: + if hypothetically_active_loops <= wanted_insn_inames: if usefulness is None: usefulness = insn.priority else: From c7018002bced59674bbee77d3c4e79f6cd399026 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 03:39:06 -0500 Subject: [PATCH 16/59] rename have->nonconc_active_inames --- loopy/schedule/__init__.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/loopy/schedule/__init__.py b/loopy/schedule/__init__.py index e0a12effc..06de9e470 100644 --- a/loopy/schedule/__init__.py +++ b/loopy/schedule/__init__.py @@ -1017,20 +1017,20 @@ def insn_sort_key(insn_id): continue nonconc_insn_inames_wanted = insn.within_inames - sched_state.parallel_inames - have = active_inames_set - sched_state.parallel_inames + nonconc_active_inames = active_inames_set - sched_state.parallel_inames - if nonconc_insn_inames_wanted != have: + if nonconc_insn_inames_wanted != nonconc_active_inames: is_ready = False if debug_mode: - if nonconc_insn_inames_wanted-have: + if nonconc_insn_inames_wanted-nonconc_active_inames: print("instruction '%s' is missing inames '%s'" % (format_insn(kernel, insn.id), ",".join( - nonconc_insn_inames_wanted-have))) - if have-nonconc_insn_inames_wanted: + nonconc_insn_inames_wanted-nonconc_active_inames))) + if nonconc_active_inames-nonconc_insn_inames_wanted: print("instruction '%s' won't work under inames '%s'" % (format_insn(kernel, insn.id), ",".join( - have-nonconc_insn_inames_wanted))) + nonconc_active_inames-nonconc_insn_inames_wanted))) # {{{ check if scheduling this insn is compatible with preschedule @@ -1084,7 +1084,7 @@ def insn_sort_key(insn_id): # {{{ determine reachability - if (not is_ready and have <= nonconc_insn_inames_wanted): + if (not is_ready and nonconc_active_inames <= nonconc_insn_inames_wanted): reachable_insn_ids.add(insn_id) # }}} From 25bae77b27e54dffbce1ae5e85d1927950f54d00 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 04:09:34 -0500 Subject: [PATCH 17/59] remove use_loop_nest_constraints option (just use them if they exist) --- loopy/options.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/loopy/options.py b/loopy/options.py index 895e655f7..3742cb27b 100644 --- a/loopy/options.py +++ b/loopy/options.py @@ -172,8 +172,6 @@ class Options(ImmutableRecord): If equal to ``"no_check"``, then no check is performed. """ - # TODO document use_loop_nest_constraints - _legacy_options_map = { "cl_build_options": ("build_options", None), "write_cl": ("write_code", None), @@ -234,8 +232,6 @@ def __init__( False), check_dep_resolution=kwargs.get("check_dep_resolution", True), use_dependencies_v2=kwargs.get("use_dependencies_v2", False), - use_loop_nest_constraints=kwargs.get( - "use_loop_nest_constraints", False), enforce_variable_access_ordered=kwargs.get( "enforce_variable_access_ordered", True), From 7f9fcdc97ffa5e58736965874435a8d68c0207a0 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 04:10:57 -0500 Subject: [PATCH 18/59] add some informative comments --- loopy/schedule/__init__.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/loopy/schedule/__init__.py b/loopy/schedule/__init__.py index 06de9e470..33160b831 100644 --- a/loopy/schedule/__init__.py +++ b/loopy/schedule/__init__.py @@ -1020,6 +1020,7 @@ def insn_sort_key(insn_id): nonconc_active_inames = active_inames_set - sched_state.parallel_inames if nonconc_insn_inames_wanted != nonconc_active_inames: + # We don't have the inames we need, may need to open more loops is_ready = False if debug_mode: @@ -1082,7 +1083,8 @@ def insn_sort_key(insn_id): # }}} - # {{{ determine reachability + # {{{ determine reachability (no active inames conflict w/insn, but + # may need more inames) if (not is_ready and nonconc_active_inames <= nonconc_insn_inames_wanted): reachable_insn_ids.add(insn_id) @@ -1092,7 +1094,13 @@ def insn_sort_key(insn_id): if is_ready and debug_mode: print("ready to schedule '%s'" % format_insn(kernel, insn.id)) + # (if we wanted, we could check to see whether adding insn would + # violate dependencies_v2 here, as done in old in-progress branch: + # https://gitlab.tiker.net/jdsteve2/loopy/-/merge_requests/15/diffs) + if is_ready and not debug_mode: + # schedule this instruction and recurse + iid_set = frozenset([insn.id]) # {{{ update active group counts for added instruction @@ -1162,6 +1170,9 @@ def insn_sort_key(insn_id): # }}} + # No insns are ready to be scheduled now, but some may be reachable + # reachable_insn_ids = no active inames conflict w/insn, but may need more inames + # {{{ see if we're ready to leave the innermost loop deepest_active_iname = sched_state.deepest_active_iname @@ -1186,6 +1197,7 @@ def insn_sort_key(insn_id): for insn_id in sched_state.unscheduled_insn_ids: insn = kernel.id_to_insn[insn_id] if deepest_active_iname in insn.within_inames: + # cannot leave deepest_active_iname; insn still depends on it if debug_mode: print("cannot leave '%s' because '%s' still depends on it" % (deepest_active_iname, format_insn(kernel, insn.id))) From 86cdce775616ef8060a630298eb9ce39d277fd00 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 04:11:40 -0500 Subject: [PATCH 19/59] when linearizing, don't leave a loop if doing so would violate must_nest constraints --- loopy/schedule/__init__.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/loopy/schedule/__init__.py b/loopy/schedule/__init__.py index 33160b831..02407a8f2 100644 --- a/loopy/schedule/__init__.py +++ b/loopy/schedule/__init__.py @@ -1258,6 +1258,40 @@ def insn_sort_key(insn_id): can_leave = True break + # {{{ don't leave if doing so would violate must_nest constraints + + # don't leave if must_nest constraints require that + # additional inames be nested inside the current iname + if can_leave: + must_nest_graph = ( + sched_state.kernel.loop_nest_constraints.must_nest_graph + if sched_state.kernel.loop_nest_constraints else None) + + if must_nest_graph: + # get inames that must nest inside the current iname + must_nest_inside = must_nest_graph[deepest_active_iname] + + if must_nest_inside: + # get scheduled inames that are nested inside current iname + within_deepest_active_iname = False + actually_nested_inside = set() + for sched_item in sched_state.schedule: + if isinstance(sched_item, EnterLoop): + if within_deepest_active_iname: + actually_nested_inside.add(sched_item.iname) + elif sched_item.iname == deepest_active_iname: + within_deepest_active_iname = True + elif (isinstance(sched_item, LeaveLoop) and + sched_item.iname == deepest_active_iname): + break + + # don't leave if must_nest constraints require that + # additional inames be nested inside the current iname + if not must_nest_inside.issubset(actually_nested_inside): + can_leave = False + + # }}} + if can_leave and not debug_mode: for sub_sched in generate_loop_schedules_internal( From 6d8c8c7e062ed16b696bd511ad42a027d4d3b16a Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 04:15:51 -0500 Subject: [PATCH 20/59] rename needed_inames->unscheduled_nonconc_insn_inames_needed --- loopy/schedule/__init__.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/loopy/schedule/__init__.py b/loopy/schedule/__init__.py index 02407a8f2..759125084 100644 --- a/loopy/schedule/__init__.py +++ b/loopy/schedule/__init__.py @@ -1317,11 +1317,11 @@ def insn_sort_key(insn_id): # {{{ see if any loop can be entered now # Find inames that are being referenced by as yet unscheduled instructions. - needed_inames = set() + unscheduled_nonconc_insn_inames_needed = set() for insn_id in sched_state.unscheduled_insn_ids: - needed_inames.update(kernel.insn_inames(insn_id)) + unscheduled_nonconc_insn_inames_needed.update(kernel.insn_inames(insn_id)) - needed_inames = (needed_inames + unscheduled_nonconc_insn_inames_needed = (unscheduled_nonconc_insn_inames_needed # There's no notion of 'entering' a parallel loop - sched_state.parallel_inames @@ -1330,7 +1330,8 @@ def insn_sort_key(insn_id): if debug_mode: print(75*"-") - print("inames still needed :", ",".join(needed_inames)) + print("inames still needed :", ",".join( + unscheduled_nonconc_insn_inames_needed)) print("active inames :", ",".join(sched_state.active_inames)) print("inames entered so far :", ",".join(sched_state.entered_inames)) print("reachable insns:", ",".join(reachable_insn_ids)) @@ -1339,10 +1340,10 @@ def insn_sort_key(insn_id): for grp, c in sched_state.active_group_counts.items())) print(75*"-") - if needed_inames: + if unscheduled_nonconc_insn_inames_needed: iname_to_usefulness = {} - for iname in needed_inames: + for iname in unscheduled_nonconc_insn_inames_needed: # {{{ check if scheduling this iname now is allowed/plausible From c21efddfa04f3c53e1d3be5eff9dd6b4105ca933 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 04:32:23 -0500 Subject: [PATCH 21/59] more helpful comments --- loopy/schedule/__init__.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/loopy/schedule/__init__.py b/loopy/schedule/__init__.py index 759125084..e68572b7e 100644 --- a/loopy/schedule/__init__.py +++ b/loopy/schedule/__init__.py @@ -1346,6 +1346,9 @@ def insn_sort_key(insn_id): for iname in unscheduled_nonconc_insn_inames_needed: # {{{ check if scheduling this iname now is allowed/plausible + # based on preschedule constraints, loop_nest_around_map, + # loop_insn_dep_map, and data dependencies; + # if not, continue if ( iname in sched_state.prescheduled_inames @@ -1359,6 +1362,9 @@ def insn_sort_key(insn_id): currently_accessible_inames = ( active_inames_set | sched_state.parallel_inames) + + # check loop_nest_around_map to determine whether inames that must + # nest around iname are available if ( not sched_state.loop_nest_around_map[iname] <= currently_accessible_inames): @@ -1366,6 +1372,9 @@ def insn_sort_key(insn_id): print("scheduling %s prohibited by loop nest-around map" % iname) continue + # loop_insn_dep_map: dict mapping inames to other insn ids that need to + # be scheduled before the iname should be eligible for scheduling. + # If loop dependency map prohibits scheduling of iname, continue if ( not sched_state.loop_insn_dep_map.get(iname, set()) <= sched_state.scheduled_insn_ids): @@ -1415,11 +1424,18 @@ def insn_sort_key(insn_id): # }}} + # so far, scheduling of iname is allowed/plausible + # {{{ determine if that gets us closer to being able to schedule an insn usefulness = None # highest insn priority enabled by iname + # suppose we were to activate this iname... + # would that get us closer to scheduling an insn? + hypothetically_active_loops = active_inames_set | {iname} + # loop over reachable_insn_ids (reachable insn: no active inames + # conflict w/insn, but may need more inames) for insn_id in reachable_insn_ids: insn = kernel.id_to_insn[insn_id] @@ -1432,6 +1448,7 @@ def insn_sort_key(insn_id): usefulness = max(usefulness, insn.priority) if usefulness is None: + # iname won't get us closer to scheduling insn if debug_mode: print("iname '%s' deemed not useful" % iname) continue @@ -1440,6 +1457,9 @@ def insn_sort_key(insn_id): # }}} + # keys of iname_to_usefulness are now inames that get us closer to + # scheduling an insn + # {{{ tier building # Build priority tiers. If a schedule is found in the first tier, then From c9c79524f9a3c887864e6466cab1860c0f8bd99d Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 04:38:32 -0500 Subject: [PATCH 22/59] add some noqa --- loopy/transform/iname.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index c634cee40..3482305b1 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -221,15 +221,16 @@ def process_loop_nest_specification( def raise_loop_nest_input_error(msg): valid_prio_rules = ( - "Valid `must_nest` description formats: " - "\"iname, iname, ...\" or (str, str, str, ...), " - "where str can be of form " - "\"iname\" or \"{iname, iname, ...}\". No set complements allowed.\n" - "Valid `must_not_nest` description tuples must have len <= 2: " - "\"iname, iname\", \"iname, ~iname\", or " - "(str, str), where str can be of form " - "\"iname\", \"~iname\", \"{iname, iname, ...}\", or " - "\"~{iname, iname, ...}\"." + "Valid `must_nest` description formats: " # noqa + "\"iname, iname, ...\" or (str, str, str, ...), " # noqa + "where str can be of form " # noqa + "\"iname\" or \"{iname, iname, ...}\". " # noqa + "No set complements allowed.\n" # noqa + "Valid `must_not_nest` description tuples must have len <= 2: " # noqa + "\"iname, iname\", \"iname, ~iname\", or " # noqa + "(str, str), where str can be of form " # noqa + "\"iname\", \"~iname\", \"{iname, iname, ...}\", or " # noqa + "\"~{iname, iname, ...}\"." # noqa ) raise ValueError( "Invalid loop nest prioritization: %s\n" From 624f03c6d39277a748400181b22984b773315ecc Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 04:39:20 -0500 Subject: [PATCH 23/59] fix flake8 issues --- test/test_loop_nest_semantics.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_loop_nest_semantics.py b/test/test_loop_nest_semantics.py index 3e5ac0df1..50ea1e6d6 100644 --- a/test/test_loop_nest_semantics.py +++ b/test/test_loop_nest_semantics.py @@ -286,7 +286,7 @@ def test_loop_nest_constraints_satisfied(): def test_adding_multiple_nest_constraints_to_knl(): ref_knl = lp.make_kernel( "{ [g,h,i,j,k,x,y,z]: 0<=g,h,i,j,k,x,y,z Date: Mon, 26 Apr 2021 05:13:18 -0500 Subject: [PATCH 24/59] enable check_all_must_not_nests() --- loopy/transform/iname.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index 3482305b1..7686379bf 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -630,16 +630,14 @@ def check_must_not_nest(all_loop_nests, must_not_nest): # }}} -# {{{ check_all_must_not_nests (TODO copied in from old branch, not yet enabled) +# {{{ check_all_must_not_nests -""" def check_all_must_not_nests(all_loop_nests, must_not_nests): # recall that must_not_nest may only contain two tiers for must_not_nest in must_not_nests: if not check_must_not_nest(all_loop_nests, must_not_nest): return False return True -""" # }}} From d6cb58406368a732f6c241e3e20e40615ee116c9 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 05:14:15 -0500 Subject: [PATCH 25/59] during linearization, use must-nest and must-not-nest constraints to determine whether a loop can be entered (if kernel.loop_nest_constraints exists, otherwise fall back to old priorities) --- loopy/schedule/__init__.py | 212 ++++++++++++++++++++++++++++--------- 1 file changed, 160 insertions(+), 52 deletions(-) diff --git a/loopy/schedule/__init__.py b/loopy/schedule/__init__.py index e68572b7e..1fa67ded6 100644 --- a/loopy/schedule/__init__.py +++ b/loopy/schedule/__init__.py @@ -1460,67 +1460,127 @@ def insn_sort_key(insn_id): # keys of iname_to_usefulness are now inames that get us closer to # scheduling an insn - # {{{ tier building - - # Build priority tiers. If a schedule is found in the first tier, then - # loops in the second are not even tried (and so on). - loop_priority_set = set().union(*[set(prio) - for prio in - sched_state.kernel.loop_priority]) - useful_loops_set = set(iname_to_usefulness.keys()) - useful_and_desired = useful_loops_set & loop_priority_set - - if useful_and_desired: - wanted = ( - useful_and_desired - - sched_state.ilp_inames - - sched_state.vec_inames - ) - priority_tiers = [t for t in - get_priority_tiers(wanted, - sched_state.kernel.loop_priority - ) - ] - - # Update the loop priority set, because some constraints may have - # have been contradictary. - loop_priority_set = set().union(*[set(t) for t in priority_tiers]) - - priority_tiers.append( + if sched_state.kernel.loop_nest_constraints: + # {{{ use loop_nest_constraints in determining next_iname_candidates + + # inames not yet entered that would get us closer to scheduling an insn: + useful_loops_set = set(iname_to_usefulness.keys()) + + from loopy.transform.iname import ( + check_all_must_not_nests, + ) + from loopy.tools import ( + get_graph_sources, + ) + from pytools.graph import compute_induced_subgraph + + # since vec_inames must be innermost, + # they are not valid canidates unless only vec_inames remain + if useful_loops_set - sched_state.vec_inames: + useful_loops_set -= sched_state.vec_inames + + # to enter an iname without violating must_nest constraints, + # iname must be a source in the induced subgraph of must_nest_graph + # containing inames in useful_loops_set + must_nest_graph_full = ( + sched_state.kernel.loop_nest_constraints.must_nest_graph + if sched_state.kernel.loop_nest_constraints else None) + if must_nest_graph_full: + must_nest_graph_useful = compute_induced_subgraph( + must_nest_graph_full, useful_loops_set - - loop_priority_set - - sched_state.ilp_inames - - sched_state.vec_inames ) + source_inames = get_graph_sources(must_nest_graph_useful) + else: + source_inames = useful_loops_set + + # since graph has a key for every iname, + # sources should be the only valid iname candidates + + # check whether entering any source_inames violates + # must-not-nest constraints, given the currently active inames + must_not_nest_constraints = ( + sched_state.kernel.loop_nest_constraints.must_not_nest + if sched_state.kernel.loop_nest_constraints else None) + if must_not_nest_constraints: + next_iname_candidates = set() + for next_iname in source_inames: + iname_orders_to_check = [ + (active_iname, next_iname) + for active_iname in active_inames_set] + + if check_all_must_not_nests( + iname_orders_to_check, must_not_nest_constraints): + next_iname_candidates.add(next_iname) + else: + next_iname_candidates = source_inames + + # }}} else: - priority_tiers = [ - useful_loops_set + # {{{ old tier building + + # Build priority tiers. If a schedule is found in the first tier, then + # loops in the second are not even tried (and so on). + loop_priority_set = set().union(*[set(prio) + for prio in + sched_state.kernel.loop_priority]) + useful_loops_set = set(iname_to_usefulness.keys()) + useful_and_desired = useful_loops_set & loop_priority_set + + if useful_and_desired: + wanted = ( + useful_and_desired - sched_state.ilp_inames - sched_state.vec_inames - ] - - # vectorization must be the absolute innermost loop - priority_tiers.extend([ - [iname] - for iname in sched_state.ilp_inames - if iname in useful_loops_set - ]) + ) + priority_tiers = [t for t in + get_priority_tiers(wanted, + sched_state.kernel.loop_priority + ) + ] + + # Update the loop priority set, because some constraints may have + # have been contradictary. + loop_priority_set = set().union(*[set(t) for t in priority_tiers]) + + priority_tiers.append( + useful_loops_set + - loop_priority_set + - sched_state.ilp_inames + - sched_state.vec_inames + ) + else: + priority_tiers = [ + useful_loops_set + - sched_state.ilp_inames + - sched_state.vec_inames + ] + + # vectorization must be the absolute innermost loop + priority_tiers.extend([ + [iname] + for iname in sched_state.ilp_inames + if iname in useful_loops_set + ]) + + priority_tiers.extend([ + [iname] + for iname in sched_state.vec_inames + if iname in useful_loops_set + ]) - priority_tiers.extend([ - [iname] - for iname in sched_state.vec_inames - if iname in useful_loops_set - ]) + # }}} - # }}} + if sched_state.kernel.loop_nest_constraints: + # {{{ loop over next_iname_candidates generated w/ loop_nest_constraints - if debug_mode: - print("useful inames: %s" % ",".join(useful_loops_set)) - else: - for tier in priority_tiers: + if debug_mode: + print("useful inames: %s" % ",".join(useful_loops_set)) + else: found_viable_schedule = False - for iname in sorted(tier, + # loop over iname candidates; enter inames and recurse: + for iname in sorted(next_iname_candidates, key=lambda iname: ( iname_to_usefulness.get(iname, 0), # Sort by iname to achieve deterministic @@ -1528,6 +1588,7 @@ def insn_sort_key(insn_id): iname), reverse=True): + # enter the loop and recurse for sub_sched in generate_loop_schedules_internal( sched_state.copy( schedule=( @@ -1541,16 +1602,63 @@ def insn_sort_key(insn_id): insn_ids_to_try=insn_ids_to_try, preschedule=( sched_state.preschedule - if iname not in sched_state.prescheduled_inames + if iname not in + sched_state.prescheduled_inames else sched_state.preschedule[1:]), ), debug=debug): + found_viable_schedule = True yield sub_sched + # TODO what happened if found_viable_schedule is false? if found_viable_schedule: return + # }}} + else: + # {{{ old looping over tiers + + if debug_mode: + print("useful inames: %s" % ",".join(useful_loops_set)) + else: + for tier in priority_tiers: + found_viable_schedule = False + + for iname in sorted(tier, + key=lambda iname: ( + iname_to_usefulness.get(iname, 0), + # Sort by iname to achieve deterministic + # ordering of generated schedules. + iname), + reverse=True): + + for sub_sched in generate_loop_schedules_internal( + sched_state.copy( + schedule=( + sched_state.schedule + + (EnterLoop(iname=iname),)), + active_inames=( + sched_state.active_inames + (iname,)), + entered_inames=( + sched_state.entered_inames + | frozenset((iname,))), + insn_ids_to_try=insn_ids_to_try, + preschedule=( + sched_state.preschedule + if iname not in + sched_state.prescheduled_inames + else sched_state.preschedule[1:]), + ), + debug=debug): + found_viable_schedule = True + yield sub_sched + + if found_viable_schedule: + return + + # }}} + # }}} if debug_mode: From 94b6f27f8a970ed1fbdf3916eb8532259fc74e59 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 05:18:19 -0500 Subject: [PATCH 26/59] enable get_iname_nestings() --- loopy/transform/iname.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index 7686379bf..687a04965 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -693,9 +693,8 @@ def check_must_not_nest_against_must_nest_graph( # }}} -# {{{ get_iname_nestings (TODO copied in from old branch, not yet enabled) +# {{{ get_iname_nestings -""" def get_iname_nestings(outline): from loopy.schedule import EnterLoop, LeaveLoop # return a list of tuples representing deepest nestings @@ -712,7 +711,6 @@ def get_iname_nestings(outline): already_exiting_loops = True del current_tiers[-1] return nestings -""" # }}} From c246bc9729d6c8229a9d95795786a51b9ce28365 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 05:27:59 -0500 Subject: [PATCH 27/59] make sure ALL must_nest_constraints are satisfied before yielding a linearization --- loopy/schedule/__init__.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/loopy/schedule/__init__.py b/loopy/schedule/__init__.py index 1fa67ded6..0910f6aac 100644 --- a/loopy/schedule/__init__.py +++ b/loopy/schedule/__init__.py @@ -1669,10 +1669,32 @@ def insn_sort_key(insn_id): if inp: raise ScheduleDebugInput(inp) + # {{{ make sure ALL must_nest_constraints are satisfied + + # (the check above avoids contradicting some must_nest constraints, + # but we don't know if all required nestings are present) + # TODO is this the only place we need to check all must_nest constraints? + must_constraints_satisfied = True + if sched_state.kernel.loop_nest_constraints: + from loopy.transform.iname import ( + get_iname_nestings, + loop_nest_constraints_satisfied, + ) + must_nest_constraints = sched_state.kernel.loop_nest_constraints.must_nest + if must_nest_constraints: + sched_tiers = get_iname_nestings(sched_state.schedule) + must_constraints_satisfied = loop_nest_constraints_satisfied( + sched_tiers, must_nest_constraints, + must_not_nest_constraints=None, # (checked upon loop creation) + all_inames=kernel.all_inames()) + + # }}} + if ( not sched_state.active_inames and not sched_state.unscheduled_insn_ids - and not sched_state.preschedule): + and not sched_state.preschedule + and must_constraints_satisfied): # if done, yield result debug.log_success(sched_state.schedule) From 0bdc80e2a212ca7782e7ccff0e5e6a0f0c66e67c Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 05:35:36 -0500 Subject: [PATCH 28/59] add get_graph_sources() to iname.py for now --- loopy/schedule/__init__.py | 2 -- loopy/transform/iname.py | 11 +++++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/loopy/schedule/__init__.py b/loopy/schedule/__init__.py index 0910f6aac..d7068fae3 100644 --- a/loopy/schedule/__init__.py +++ b/loopy/schedule/__init__.py @@ -1468,8 +1468,6 @@ def insn_sort_key(insn_id): from loopy.transform.iname import ( check_all_must_not_nests, - ) - from loopy.tools import ( get_graph_sources, ) from pytools.graph import compute_induced_subgraph diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index 687a04965..ba0d9f7c2 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -714,6 +714,17 @@ def get_iname_nestings(outline): # }}} + +# {{{ get graph sources + +def get_graph_sources(graph): + sources = set(graph.keys()) + for non_sources in graph.values(): + sources -= non_sources + return sources + +# }}} + # }}} # }}} From ee59a67033cf5da16ac2aadd3e20d90b2dacd951 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 05:36:18 -0500 Subject: [PATCH 29/59] add test to ensure that vec inames are linearized innermost --- test/test_loop_nest_semantics.py | 96 +++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/test/test_loop_nest_semantics.py b/test/test_loop_nest_semantics.py index 50ea1e6d6..40749fe02 100644 --- a/test/test_loop_nest_semantics.py +++ b/test/test_loop_nest_semantics.py @@ -335,7 +335,7 @@ def test_adding_multiple_nest_constraints_to_knl(): # }}} -# {{{ +# {{{ test_incompatible_nest_constraints def test_incompatible_nest_constraints(): ref_knl = lp.make_kernel( @@ -377,6 +377,100 @@ def test_incompatible_nest_constraints(): # }}} +# {{{ test_vec_innermost: + +def test_vec_innermost(): + + def is_innermost(iname, linearization_items): + from loopy.schedule import (EnterLoop, LeaveLoop) + + # find EnterLoop(iname) in linearization + enter_iname_idx = None + for i, linearization_item in enumerate(linearization_items): + if isinstance(linearization_item, EnterLoop) and ( + linearization_item.iname == iname): + enter_iname_idx = i + break + else: + # iname not found + return False + + # now go through remaining linearization items after EnterLoop(iname) + for linearization_item in linearization_items[enter_iname_idx+1:]: + if isinstance(linearization_item, LeaveLoop): + # Break as soon as we find a LeaveLoop + # If this happens before we find an EnterLoop, iname is innermost + break + elif isinstance(linearization_item, EnterLoop): + # we found an EnterLoop inside iname + return False + + return True + + ref_knl = lp.make_kernel( + "{ [g,h,i,j,k]: 0<=g,h,i,j,k 1: exec(sys.argv[1]) From 83885f433767f1dc1954586860333cf8df538c20 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 05:46:58 -0500 Subject: [PATCH 30/59] allow debug_args to be passed through from get_one_linearized_kernel() --- loopy/schedule/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/loopy/schedule/__init__.py b/loopy/schedule/__init__.py index d7068fae3..95b11712c 100644 --- a/loopy/schedule/__init__.py +++ b/loopy/schedule/__init__.py @@ -2418,7 +2418,7 @@ def print_longest_dead_end(): key_builder=LoopyKeyBuilder()) -def _get_one_scheduled_kernel_inner(kernel): +def _get_one_scheduled_kernel_inner(kernel, debug_args={}): # This helper function exists to ensure that the generator chain is fully # out of scope after the function returns. This allows it to be # garbage-collected in the exit handler of the @@ -2428,7 +2428,7 @@ def _get_one_scheduled_kernel_inner(kernel): # # See https://gitlab.tiker.net/inducer/sumpy/issues/31 for context. - return next(iter(generate_loop_schedules(kernel))) + return next(iter(generate_loop_schedules(kernel, debug_args=debug_args))) def get_one_scheduled_kernel(kernel): @@ -2440,7 +2440,7 @@ def get_one_scheduled_kernel(kernel): return get_one_linearized_kernel(kernel) -def get_one_linearized_kernel(kernel): +def get_one_linearized_kernel(kernel, debug_args={}): from loopy import CACHING_ENABLED sched_cache_key = kernel @@ -2458,7 +2458,7 @@ def get_one_linearized_kernel(kernel): if not from_cache: with ProcessLogger(logger, "%s: schedule" % kernel.name): with MinRecursionLimitForScheduling(kernel): - result = _get_one_scheduled_kernel_inner(kernel) + result = _get_one_scheduled_kernel_inner(kernel, debug_args) if CACHING_ENABLED and not from_cache: schedule_cache.store_if_not_present(sched_cache_key, result) From 03cd9f1184d0c7b22647b5f5ec7202c18cedf922 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 05:49:08 -0500 Subject: [PATCH 31/59] add test for handling of loop nest constraints during linearization (copied in from old branch : https://gitlab.tiker.net/jdsteve2/loopy/iname-sets-in-loop-priorities) --- test/test_loop_nest_semantics.py | 162 +++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/test/test_loop_nest_semantics.py b/test/test_loop_nest_semantics.py index 40749fe02..b60cad569 100644 --- a/test/test_loop_nest_semantics.py +++ b/test/test_loop_nest_semantics.py @@ -471,6 +471,168 @@ def is_innermost(iname, linearization_items): # }}} +# {{{ test_linearization_with_nesting_constraints + +def test_linearization_with_nesting_constraints(): + + def loop_order(linearization_items): + from loopy.schedule import EnterLoop + order = [] + for linearization_item in linearization_items: + if isinstance(linearization_item, EnterLoop): + order.append(linearization_item.iname) + return order + + ref_knl = lp.make_kernel( + "{ [g,h,i,j,k]: 0<=g,h,i,j,k 1: exec(sys.argv[1]) From a75247aecaf673ac4c98add4425321d0005ab280 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Apr 2021 05:55:05 -0500 Subject: [PATCH 32/59] fix flake8 issues --- test/test_loop_nest_semantics.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/test_loop_nest_semantics.py b/test/test_loop_nest_semantics.py index b60cad569..bbfbfad93 100644 --- a/test/test_loop_nest_semantics.py +++ b/test/test_loop_nest_semantics.py @@ -340,7 +340,7 @@ def test_adding_multiple_nest_constraints_to_knl(): def test_incompatible_nest_constraints(): ref_knl = lp.make_kernel( "{ [g,h,i,j,k,x,y,z]: 0<=g,h,i,j,k,x,y,z Date: Tue, 27 Apr 2021 00:54:02 -0500 Subject: [PATCH 33/59] rename test file test_loop_nest_semantics.py->test_nest_constraints.py --- test/{test_loop_nest_semantics.py => test_nest_constraints.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{test_loop_nest_semantics.py => test_nest_constraints.py} (100%) diff --git a/test/test_loop_nest_semantics.py b/test/test_nest_constraints.py similarity index 100% rename from test/test_loop_nest_semantics.py rename to test/test_nest_constraints.py From 764d9032ea3150578d9760573a0abafed2977994 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Tue, 27 Apr 2021 01:50:25 -0500 Subject: [PATCH 34/59] copy in and update old functions for replacing inames in nest constraints (from old branch : https://gitlab.tiker.net/jdsteve2/loopy/iname-sets-in-loop-priorities) --- loopy/transform/iname.py | 303 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 301 insertions(+), 2 deletions(-) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index ba0d9f7c2..d2f41d5fc 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -113,7 +113,7 @@ def prioritize_loops(kernel, loop_priority): # }}} -# {{{ loop nest constraints +# {{{ handle loop nest constraints # {{{ classes to house loop nest constraints @@ -727,6 +727,287 @@ def get_graph_sources(graph): # }}} + +# {{{ updating constraints during transformation + +# {{{ replace_inames_in_nest_constraints + +def replace_inames_in_nest_constraints( + inames_to_replace, replacement_inames, old_constraints, + coalesce_duplicate_replacement_inames=False): + """ + :arg inames_to_replace: A set of inames that may exist in + `old_constraints`, each of which is to be replaced with all inames + in `replacement_inames`. + + :arg replacement_inames: A set of inames, all of which will repalce each + iname in `inames_to_replace` in `old_constraints`. + + :arg old_constraints: An iterable of tuples containing one or more + :class:`UnexpandedInameSet` objects. + """ + + # replace each iname in inames_to_replace + # with *all* inames in replacement_inames + + # loop through old_constraints and handle each nesting independently + new_constraints = set() + for old_nesting in old_constraints: + # loop through each iname_set in this nesting and perform replacement + new_nesting = [] + for iname_set in old_nesting: + + # find inames to be replaced + inames_found = inames_to_replace & iname_set.inames + + # create the new set of inames with the replacements + if inames_found: + new_inames = iname_set.inames - inames_found + new_inames.update(replacement_inames) + else: + new_inames = iname_set.inames.copy() + + new_nesting.append( + UnexpandedInameSet(new_inames, iname_set.complement)) + + # if we've removed things, new_nesting might only contain 1 item, + # in which case it's meaningless and we should just remove it + if len(new_nesting) > 1: + new_constraints.add(tuple(new_nesting)) + + # When joining inames, we may need to coalesce: + # e.g., if we join `i` and `j` into `ij`, and old_nesting was + # [{i, k}, {j, h}], at this point we have [{ij, k}, {ij, h}] + # which contains a cycle. If coalescing is enabled, change this + # to [{k}, ij, {h}] to remove the cycle. + if coalesce_duplicate_replacement_inames: + + def coalesce_duplicate_inames_in_nesting(nesting, iname_candidates): + # TODO would like this to be fully generic, but for now, assumes + # all UnexpandedInameSets have complement=False, which works if + # we're only using this for must_nest constraints since they cannot + # have complements + for iname_set in nesting: + assert not iname_set.complement + + import copy + # copy and convert nesting to list so we can modify + coalesced_nesting = list(copy.deepcopy(nesting)) + + # repeat coalescing step until we don't find any adjacent pairs + # containing duplicates (among iname_candidates) + found_duplicates = True + while found_duplicates: + found_duplicates = False + # loop through each iname_set in nesting and coalesce + # (assume new_nesting has at least 2 items) + i = 0 + while i < len(coalesced_nesting)-1: + iname_set_before = coalesced_nesting[i] + iname_set_after = coalesced_nesting[i+1] + # coalesce for each iname candidate + for iname in iname_candidates: + if (iname_set_before.inames == set([iname, ]) and + iname_set_after.inames == set([iname, ])): + # before/after contain single iname to be coalesced, + # -> remove iname_set_after + del coalesced_nesting[i+1] + found_duplicates = True + elif (iname_set_before.inames == set([iname, ]) and + iname in iname_set_after.inames): + # before contains single iname to be coalesced, + # after contains iname along with others, + # -> remove iname from iname_set_after.inames + coalesced_nesting[i+1] = UnexpandedInameSet( + inames=iname_set_after.inames - set([iname, ]), + complement=iname_set_after.complement, + ) + found_duplicates = True + elif (iname in iname_set_before.inames and + iname_set_after.inames == set([iname, ])): + # after contains single iname to be coalesced, + # before contains iname along with others, + # -> remove iname from iname_set_before.inames + coalesced_nesting[i] = UnexpandedInameSet( + inames=iname_set_before.inames - set([iname, ]), + complement=iname_set_before.complement, + ) + found_duplicates = True + elif (iname in iname_set_before.inames and + iname in iname_set_after.inames): + # before and after contain iname along with others, + # -> remove iname from iname_set_{before,after}.inames + # and insert it in between them + coalesced_nesting[i] = UnexpandedInameSet( + inames=iname_set_before.inames - set([iname, ]), + complement=iname_set_before.complement, + ) + coalesced_nesting[i+1] = UnexpandedInameSet( + inames=iname_set_after.inames - set([iname, ]), + complement=iname_set_after.complement, + ) + coalesced_nesting.insert(i+1, UnexpandedInameSet( + inames=set([iname, ]), + complement=False, + )) + found_duplicates = True + # else, iname was not found in both sets, so do nothing + i = i + 1 + + return tuple(coalesced_nesting) + + # loop through new_constraints; handle each nesting independently + coalesced_constraints = set() + for new_nesting in new_constraints: + coalesced_constraints.add( + coalesce_duplicate_inames_in_nesting( + new_nesting, replacement_inames)) + + return coalesced_constraints + else: + return new_constraints + +# }}} + + +# {{{ replace_inames_in_graph + +def replace_inames_in_graph( + inames_to_replace, replacement_inames, old_graph): + # replace each iname in inames_to_replace with all inames in replacement_inames + + new_graph = {} + iname_to_replace_found_as_key = False + union_of_inames_after_for_replaced_keys = set() + for iname, inames_after in old_graph.items(): + # create new inames_after + new_inames_after = inames_after.copy() + inames_found = inames_to_replace & new_inames_after + + if inames_found: + new_inames_after -= inames_found + new_inames_after.update(replacement_inames) + + # update dict + if iname in inames_to_replace: + iname_to_replace_found_as_key = True + union_of_inames_after_for_replaced_keys = \ + union_of_inames_after_for_replaced_keys | new_inames_after + # don't add this iname as a key in new graph, + # its replacements will be added below + else: + new_graph[iname] = new_inames_after + + # add replacement iname keys + if iname_to_replace_found_as_key: + for new_key in replacement_inames: + new_graph[new_key] = union_of_inames_after_for_replaced_keys.copy() + + # check for cycle + from pytools.graph import contains_cycle + if contains_cycle(new_graph): + raise ValueError( + "replace_inames_in_graph: Loop priority cycle detected. " + "Cannot replace inames %s with inames %s." + % (inames_to_replace, replacement_inames)) + + return new_graph + +# }}} + + +# {{{ replace_inames_in_all_nest_constraints + +def replace_inames_in_all_nest_constraints( + kernel, old_inames, new_inames, + coalesce_duplicate_replacement_inames=False, + pairs_that_must_not_voilate_constraints=set(), + ): + # replace each iname in old_inames with all inames in new_inames + # TODO What was pairs_that_must_not_voilate_constraints used for??? + # TODO handle case where we want to keep old inames around + + # get old must_nest and must_not_nest + # (must_nest_graph will be rebuilt) + if kernel.loop_nest_constraints: + old_must_nest = kernel.loop_nest_constraints.must_nest + old_must_not_nest = kernel.loop_nest_constraints.must_not_nest + # (these could still be None) + else: + old_must_nest = None + old_must_not_nest = None + + if old_must_nest: + # check to make sure special pairs don't conflict with constraints + for iname_before, iname_after in pairs_that_must_not_voilate_constraints: + if iname_before in kernel.loop_nest_constraints.must_nest_graph[ + iname_after]: + raise ValueError( + "Implied nestings violate existing must-nest constraints." + "\nimplied nestings: %s\nmust-nest constraints: %s" + % (pairs_that_must_not_voilate_constraints, old_must_nest)) + + new_must_nest = replace_inames_in_nest_constraints( + old_inames, new_inames, old_must_nest, + coalesce_duplicate_replacement_inames, + ) + else: + new_must_nest = None + + if old_must_not_nest: + # check to make sure special pairs don't conflict with constraints + if not check_all_must_not_nests( + pairs_that_must_not_voilate_constraints, old_must_not_nest): + raise ValueError( + "Implied nestings violate existing must-not-nest constraints." + "\nimplied nestings: %s\nmust-not-nest constraints: %s" + % (pairs_that_must_not_voilate_constraints, old_must_not_nest)) + + new_must_not_nest = replace_inames_in_nest_constraints( + old_inames, new_inames, old_must_not_nest, + coalesce_duplicate_replacement_inames=False, + # (for now, never coalesce must-not-nest constraints) + ) + # each must not nest constraint may only contain two tiers + # TODO coalesce_duplicate_replacement_inames? + else: + new_must_not_nest = None + + # Rebuild must_nest graph + if new_must_nest: + new_must_nest_graph = {} + new_all_inames = ( + kernel.all_inames() - set(old_inames)) | set(new_inames) + from pytools.graph import CycleError + for must_nest_tuple in new_must_nest: + try: + new_must_nest_graph = update_must_nest_graph( + new_must_nest_graph, must_nest_tuple, new_all_inames) + except CycleError: + raise ValueError( + "Loop priority cycle detected when replacing inames %s " + "with inames %s. Previous must_nest constraints: %s" + % (old_inames, new_inames, old_must_nest)) + + # make sure none of the must_nest constraints violate must_not_nest + # this may not catch all problems + check_must_not_nest_against_must_nest_graph( + new_must_not_nest, new_must_nest_graph) + else: + new_must_nest_graph = None + + return kernel.copy( + loop_nest_constraints=LoopNestConstraints( + must_nest=new_must_nest, + must_not_nest=new_must_not_nest, + must_nest_graph=new_must_nest_graph, + ) + ) + +# }}} + +# }}} + # }}} @@ -991,6 +1272,24 @@ def _split_iname_in_dependee(dep): new_prio = new_prio + (prio_iname,) new_priorities.append(new_prio) + # {{{ update nest constraints + + # TODO remove this + if within != parse_match(None): + raise NotImplementedError("within") + + # TODO due to 'within', should do this instead: + # Duplicate each constraint tuple containing split_iname, + # replace split_iname with (inner,outer) in the copy, while still keeping + # the original around. Then let remove_unused_inames handle removal of + # the old iname if necessary + + # update must_nest, must_not_nest, and must_nest_graph + kernel = replace_inames_in_all_nest_constraints( + kernel, set([iname_to_split, ]), [inner_iname, outer_iname]) + + # }}} + kernel = kernel.copy( domains=new_domains, iname_slab_increments=iname_slab_increments, @@ -1219,7 +1518,7 @@ def join_inames(kernel, inames, new_iname=None, tag=None, within=None): from loopy.match import parse_match within = parse_match(within) - # {{{ return the same kernel if no kernel matches + # {{{ return the same kernel if no insn matches if not any(within(kernel, insn) for insn in kernel.instructions): return kernel From c568e3b337b282d04962188ce0556e70a82f8e0f Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Tue, 27 Apr 2021 01:51:34 -0500 Subject: [PATCH 35/59] add test for nest constraint updating during split_iname --- test/test_nest_constraints.py | 83 +++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/test/test_nest_constraints.py b/test/test_nest_constraints.py index bbfbfad93..edfdc50c3 100644 --- a/test/test_nest_constraints.py +++ b/test/test_nest_constraints.py @@ -633,6 +633,89 @@ def loop_order(linearization_items): # }}} +# {{{ test constraint updating during transformation + +# {{{ test_constraint_updating_split_iname + +def test_constraint_updating_split_iname(): + + def loop_order(linearization_items): + from loopy.schedule import EnterLoop + order = [] + for linearization_item in linearization_items: + if isinstance(linearization_item, EnterLoop): + order.append(linearization_item.iname) + return order + + ref_knl = lp.make_kernel( + "{ [g,h,i,j,k]: 0<=g,h,i,j,k 1: exec(sys.argv[1]) From d78ac5b6346812872b8e55082e624b7502081f95 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Tue, 27 Apr 2021 02:43:08 -0500 Subject: [PATCH 36/59] allow replaced inames to be kept around when replacing inames in constraints; correctly handle 'within' in split_iname by keeping original iname around and having remove_unused_inames remove it if necessary --- loopy/transform/iname.py | 55 +++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index d2f41d5fc..4a38e73ac 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -734,7 +734,9 @@ def get_graph_sources(graph): def replace_inames_in_nest_constraints( inames_to_replace, replacement_inames, old_constraints, - coalesce_duplicate_replacement_inames=False): + coalesce_duplicate_replacement_inames=False, + keep_old_inames=False, + ): """ :arg inames_to_replace: A set of inames that may exist in `old_constraints`, each of which is to be replaced with all inames @@ -762,7 +764,11 @@ def replace_inames_in_nest_constraints( # create the new set of inames with the replacements if inames_found: - new_inames = iname_set.inames - inames_found + if keep_old_inames: + # TODO is copy necessary? + new_inames = iname_set.inames.copy() + else: + new_inames = iname_set.inames - inames_found new_inames.update(replacement_inames) else: new_inames = iname_set.inames.copy() @@ -782,7 +788,7 @@ def replace_inames_in_nest_constraints( # to [{k}, ij, {h}] to remove the cycle. if coalesce_duplicate_replacement_inames: - def coalesce_duplicate_inames_in_nesting(nesting, iname_candidates): + def coalesce_duplicate_inames_in_nesting(nesting, coalesce_candidates): # TODO would like this to be fully generic, but for now, assumes # all UnexpandedInameSets have complement=False, which works if # we're only using this for must_nest constraints since they cannot @@ -795,7 +801,7 @@ def coalesce_duplicate_inames_in_nesting(nesting, iname_candidates): coalesced_nesting = list(copy.deepcopy(nesting)) # repeat coalescing step until we don't find any adjacent pairs - # containing duplicates (among iname_candidates) + # containing duplicates (among coalesce_candidates) found_duplicates = True while found_duplicates: found_duplicates = False @@ -806,7 +812,7 @@ def coalesce_duplicate_inames_in_nesting(nesting, iname_candidates): iname_set_before = coalesced_nesting[i] iname_set_after = coalesced_nesting[i+1] # coalesce for each iname candidate - for iname in iname_candidates: + for iname in coalesce_candidates: if (iname_set_before.inames == set([iname, ]) and iname_set_after.inames == set([iname, ])): # before/after contain single iname to be coalesced, @@ -922,6 +928,7 @@ def replace_inames_in_all_nest_constraints( kernel, old_inames, new_inames, coalesce_duplicate_replacement_inames=False, pairs_that_must_not_voilate_constraints=set(), + keep_old_inames=False, ): # replace each iname in old_inames with all inames in new_inames # TODO What was pairs_that_must_not_voilate_constraints used for??? @@ -949,7 +956,8 @@ def replace_inames_in_all_nest_constraints( new_must_nest = replace_inames_in_nest_constraints( old_inames, new_inames, old_must_nest, - coalesce_duplicate_replacement_inames, + coalesce_duplicate_replacement_inames=coalesce_duplicate_replacement_inames, + keep_old_inames=keep_old_inames, ) else: new_must_nest = None @@ -967,6 +975,7 @@ def replace_inames_in_all_nest_constraints( old_inames, new_inames, old_must_not_nest, coalesce_duplicate_replacement_inames=False, # (for now, never coalesce must-not-nest constraints) + keep_old_inames=keep_old_inames, ) # each must not nest constraint may only contain two tiers # TODO coalesce_duplicate_replacement_inames? @@ -976,8 +985,11 @@ def replace_inames_in_all_nest_constraints( # Rebuild must_nest graph if new_must_nest: new_must_nest_graph = {} - new_all_inames = ( - kernel.all_inames() - set(old_inames)) | set(new_inames) + if keep_old_inames: + new_all_inames = kernel.all_inames() | set(new_inames) + else: + new_all_inames = ( + kernel.all_inames() - set(old_inames)) | set(new_inames) from pytools.graph import CycleError for must_nest_tuple in new_must_nest: try: @@ -1274,19 +1286,15 @@ def _split_iname_in_dependee(dep): # {{{ update nest constraints - # TODO remove this - if within != parse_match(None): - raise NotImplementedError("within") - - # TODO due to 'within', should do this instead: - # Duplicate each constraint tuple containing split_iname, - # replace split_iname with (inner,outer) in the copy, while still keeping - # the original around. Then let remove_unused_inames handle removal of - # the old iname if necessary + # Add {inner,outer} wherever iname_to_split is found in constraints, while + # still keeping the original around. Then let remove_unused_inames handle + # removal of the old iname if necessary # update must_nest, must_not_nest, and must_nest_graph kernel = replace_inames_in_all_nest_constraints( - kernel, set([iname_to_split, ]), [inner_iname, outer_iname]) + kernel, set([iname_to_split, ]), [inner_iname, outer_iname], + keep_old_inames=True, + ) # }}} @@ -2275,6 +2283,17 @@ def _remove_iname_from_dep(dep): # }}} + # # {{{ Remove inames from loop nest constraints + + kernel = replace_inames_in_all_nest_constraints( + kernel, old_inames=unused_inames, new_inames=[], + coalesce_duplicate_replacement_inames=False, + pairs_that_must_not_voilate_constraints=set(), + keep_old_inames=False, + ) + + # }}} + return kernel From 1218f18eb74d54dd777aa79d23cc83deb237dd9c Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Tue, 27 Apr 2021 02:44:26 -0500 Subject: [PATCH 37/59] test constraint handling with 'within' in splilt_iname --- test/test_nest_constraints.py | 39 +++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/test_nest_constraints.py b/test/test_nest_constraints.py index edfdc50c3..f2d6d56d0 100644 --- a/test/test_nest_constraints.py +++ b/test/test_nest_constraints.py @@ -639,6 +639,8 @@ def loop_order(linearization_items): def test_constraint_updating_split_iname(): + from loopy.transform.iname import get_iname_nestings + def loop_order(linearization_items): from loopy.schedule import EnterLoop order = [] @@ -711,8 +713,45 @@ def loop_order(linearization_items): "g_outer", "g_inner", "h"] assert set(loop_order(knl_linearized.linearization)[4:]) == set(["j", "k"]) + # Testing split_iname with 'within' + + ref_knl = lp.make_kernel( + "{ [g,h,i,j,k]: 0<=g,h,i,j,k Date: Tue, 27 Apr 2021 02:56:53 -0500 Subject: [PATCH 38/59] clean up test_constraint_updating_split_iname with better helper function --- test/test_nest_constraints.py | 63 ++++++++++++++++------------------- 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/test/test_nest_constraints.py b/test/test_nest_constraints.py index f2d6d56d0..ccedff52f 100644 --- a/test/test_nest_constraints.py +++ b/test/test_nest_constraints.py @@ -641,32 +641,30 @@ def test_constraint_updating_split_iname(): from loopy.transform.iname import get_iname_nestings - def loop_order(linearization_items): - from loopy.schedule import EnterLoop - order = [] - for linearization_item in linearization_items: - if isinstance(linearization_item, EnterLoop): - order.append(linearization_item.iname) - return order + def linearize_and_get_nestings(unlinearized_knl): + knl_linearized = lp.get_one_linearized_kernel( + lp.preprocess_kernel(unlinearized_knl)) + return get_iname_nestings(knl_linearized.linearization) ref_knl = lp.make_kernel( "{ [g,h,i,j,k]: 0<=g,h,i,j,k Date: Tue, 27 Apr 2021 03:03:11 -0500 Subject: [PATCH 39/59] shorten variable names to clean up test --- test/test_nest_constraints.py | 106 +++++++++++++++++----------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/test/test_nest_constraints.py b/test/test_nest_constraints.py index ccedff52f..1d7156843 100644 --- a/test/test_nest_constraints.py +++ b/test/test_nest_constraints.py @@ -381,14 +381,14 @@ def test_incompatible_nest_constraints(): def test_vec_innermost(): - def is_innermost(iname, linearization_items): + def is_innermost(iname, lin_items): from loopy.schedule import (EnterLoop, LeaveLoop) # find EnterLoop(iname) in linearization enter_iname_idx = None - for i, linearization_item in enumerate(linearization_items): - if isinstance(linearization_item, EnterLoop) and ( - linearization_item.iname == iname): + for i, lin_item in enumerate(lin_items): + if isinstance(lin_item, EnterLoop) and ( + lin_item.iname == iname): enter_iname_idx = i break else: @@ -396,12 +396,12 @@ def is_innermost(iname, linearization_items): return False # now go through remaining linearization items after EnterLoop(iname) - for linearization_item in linearization_items[enter_iname_idx+1:]: - if isinstance(linearization_item, LeaveLoop): + for lin_item in lin_items[enter_iname_idx+1:]: + if isinstance(lin_item, LeaveLoop): # Break as soon as we find a LeaveLoop # If this happens before we find an EnterLoop, iname is innermost break - elif isinstance(linearization_item, EnterLoop): + elif isinstance(lin_item, EnterLoop): # we found an EnterLoop inside iname return False @@ -418,25 +418,25 @@ def is_innermost(iname, linearization_items): knl = ref_knl knl = lp.tag_inames(knl, {"h": "vec"}) - knl_linearized = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) - assert is_innermost("h", knl_linearized.linearization) + lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + assert is_innermost("h", lin_knl.linearization) knl = ref_knl knl = lp.tag_inames(knl, {"h": "vec", "g": "l.1", "i": "l.0"}) - knl_linearized = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) - assert is_innermost("h", knl_linearized.linearization) + lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + assert is_innermost("h", lin_knl.linearization) knl = ref_knl knl = lp.tag_inames( knl, {"h": "vec", "g": "l.1", "i": "l.0", "k": "unr"}) - knl_linearized = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) - assert is_innermost("h", knl_linearized.linearization) + lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + assert is_innermost("h", lin_knl.linearization) knl = ref_knl knl = lp.tag_inames(knl, {"h": "vec"}) knl = lp.constrain_loop_nesting(knl, must_nest=("k", "i")) - knl_linearized = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) - assert is_innermost("h", knl_linearized.linearization) + lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + assert is_innermost("h", lin_knl.linearization) lp.set_caching_enabled(True) # try adding a must_nest constraint that conflicts with a vec tag @@ -475,12 +475,12 @@ def is_innermost(iname, linearization_items): def test_linearization_with_nesting_constraints(): - def loop_order(linearization_items): + def loop_order(lin_items): from loopy.schedule import EnterLoop order = [] - for linearization_item in linearization_items: - if isinstance(linearization_item, EnterLoop): - order.append(linearization_item.iname) + for lin_item in lin_items: + if isinstance(lin_item, EnterLoop): + order.append(lin_item.iname) return order ref_knl = lp.make_kernel( @@ -498,32 +498,32 @@ def loop_order(linearization_items): knl, must_nest=("i", "j", "h", "k", "g"), ) - knl_linearized = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) - assert loop_order(knl_linearized.linearization) == ["i", "j", "h", "k", "g"] + lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + assert loop_order(lin_knl.linearization) == ["i", "j", "h", "k", "g"] knl = ref_knl knl = lp.constrain_loop_nesting( knl, must_nest=("k", "{g, h, i, j}"), ) - knl_linearized = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) - assert loop_order(knl_linearized.linearization)[0] == "k" + lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + assert loop_order(lin_knl.linearization)[0] == "k" knl = ref_knl knl = lp.constrain_loop_nesting( knl, must_nest=("{g, h, i, j}", "k"), ) - knl_linearized = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) - assert loop_order(knl_linearized.linearization)[-1] == "k" + lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + assert loop_order(lin_knl.linearization)[-1] == "k" knl = ref_knl knl = lp.constrain_loop_nesting( knl, must_nest=("{g, h, i}", "{j, k}"), ) - knl_linearized = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) - assert set(loop_order(knl_linearized.linearization)[-2:]) == set(["j", "k"]) + lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + assert set(loop_order(lin_knl.linearization)[-2:]) == set(["j", "k"]) knl = ref_knl knl = lp.constrain_loop_nesting( @@ -534,20 +534,20 @@ def loop_order(linearization_items): knl, must_nest=("i", "{g, h}"), ) - knl_linearized = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) - assert set(loop_order(knl_linearized.linearization)[3:]) == set(["j", "k"]) - assert set(loop_order(knl_linearized.linearization)[1:3]) == set(["g", "h"]) - assert loop_order(knl_linearized.linearization)[0] == "i" + lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + assert set(loop_order(lin_knl.linearization)[3:]) == set(["j", "k"]) + assert set(loop_order(lin_knl.linearization)[1:3]) == set(["g", "h"]) + assert loop_order(lin_knl.linearization)[0] == "i" knl = ref_knl knl = lp.constrain_loop_nesting( knl, must_nest=("i", "{g, h}", "{j, k}"), ) - knl_linearized = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) - assert set(loop_order(knl_linearized.linearization)[3:]) == set(["j", "k"]) - assert set(loop_order(knl_linearized.linearization)[1:3]) == set(["g", "h"]) - assert loop_order(knl_linearized.linearization)[0] == "i" + lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + assert set(loop_order(lin_knl.linearization)[3:]) == set(["j", "k"]) + assert set(loop_order(lin_knl.linearization)[1:3]) == set(["g", "h"]) + assert loop_order(lin_knl.linearization)[0] == "i" # must_not_nest constraints @@ -556,24 +556,24 @@ def loop_order(linearization_items): knl, must_not_nest=("~k", "k"), ) - knl_linearized = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) - assert loop_order(knl_linearized.linearization)[0] == "k" + lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + assert loop_order(lin_knl.linearization)[0] == "k" knl = ref_knl knl = lp.constrain_loop_nesting( knl, must_not_nest=("k", "~k"), ) - knl_linearized = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) - assert loop_order(knl_linearized.linearization)[-1] == "k" + lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + assert loop_order(lin_knl.linearization)[-1] == "k" knl = ref_knl knl = lp.constrain_loop_nesting( knl, must_not_nest=("{j, k}", "~{j, k}"), ) - knl_linearized = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) - assert set(loop_order(knl_linearized.linearization)[-2:]) == set(["j", "k"]) + lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + assert set(loop_order(lin_knl.linearization)[-2:]) == set(["j", "k"]) knl = ref_knl knl = lp.constrain_loop_nesting( @@ -584,10 +584,10 @@ def loop_order(linearization_items): knl, must_nest=("i", "{g, h}"), ) - knl_linearized = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) - assert set(loop_order(knl_linearized.linearization)[3:]) == set(["j", "k"]) - assert set(loop_order(knl_linearized.linearization)[1:3]) == set(["g", "h"]) - assert loop_order(knl_linearized.linearization)[0] == "i" + lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + assert set(loop_order(lin_knl.linearization)[3:]) == set(["j", "k"]) + assert set(loop_order(lin_knl.linearization)[1:3]) == set(["g", "h"]) + assert loop_order(lin_knl.linearization)[0] == "i" # must_nest + must_not_nest knl = ref_knl @@ -596,18 +596,18 @@ def loop_order(linearization_items): must_nest=("{g, h, i}", "{j, k}"), must_not_nest=("i", "{g, h}"), ) - knl_linearized = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) - assert set(loop_order(knl_linearized.linearization)[3:]) == set(["j", "k"]) - assert set(loop_order(knl_linearized.linearization)[0:2]) == set(["g", "h"]) - assert loop_order(knl_linearized.linearization)[2] == "i" + lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + assert set(loop_order(lin_knl.linearization)[3:]) == set(["j", "k"]) + assert set(loop_order(lin_knl.linearization)[0:2]) == set(["g", "h"]) + assert loop_order(lin_knl.linearization)[2] == "i" knl = ref_knl knl = lp.constrain_loop_nesting( knl, must_not_nest=("i", "~i"), ) - knl_linearized = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) - assert loop_order(knl_linearized.linearization)[-1] == "i" + lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + assert loop_order(lin_knl.linearization)[-1] == "i" # contradictory must_not_nest @@ -642,9 +642,9 @@ def test_constraint_updating_split_iname(): from loopy.transform.iname import get_iname_nestings def linearize_and_get_nestings(unlinearized_knl): - knl_linearized = lp.get_one_linearized_kernel( + lin_knl = lp.get_one_linearized_kernel( lp.preprocess_kernel(unlinearized_knl)) - return get_iname_nestings(knl_linearized.linearization) + return get_iname_nestings(lin_knl.linearization) ref_knl = lp.make_kernel( "{ [g,h,i,j,k]: 0<=g,h,i,j,k Date: Tue, 27 Apr 2021 03:06:54 -0500 Subject: [PATCH 40/59] rename variable coalesce_duplicate_replacement_inames->coalesce_new_iname_duplicates --- loopy/transform/iname.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index 4a38e73ac..6767955c5 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -734,7 +734,7 @@ def get_graph_sources(graph): def replace_inames_in_nest_constraints( inames_to_replace, replacement_inames, old_constraints, - coalesce_duplicate_replacement_inames=False, + coalesce_new_iname_duplicates=False, keep_old_inames=False, ): """ @@ -786,7 +786,7 @@ def replace_inames_in_nest_constraints( # [{i, k}, {j, h}], at this point we have [{ij, k}, {ij, h}] # which contains a cycle. If coalescing is enabled, change this # to [{k}, ij, {h}] to remove the cycle. - if coalesce_duplicate_replacement_inames: + if coalesce_new_iname_duplicates: def coalesce_duplicate_inames_in_nesting(nesting, coalesce_candidates): # TODO would like this to be fully generic, but for now, assumes @@ -926,13 +926,12 @@ def replace_inames_in_graph( def replace_inames_in_all_nest_constraints( kernel, old_inames, new_inames, - coalesce_duplicate_replacement_inames=False, + coalesce_new_iname_duplicates=False, pairs_that_must_not_voilate_constraints=set(), keep_old_inames=False, ): # replace each iname in old_inames with all inames in new_inames # TODO What was pairs_that_must_not_voilate_constraints used for??? - # TODO handle case where we want to keep old inames around # get old must_nest and must_not_nest # (must_nest_graph will be rebuilt) @@ -956,7 +955,7 @@ def replace_inames_in_all_nest_constraints( new_must_nest = replace_inames_in_nest_constraints( old_inames, new_inames, old_must_nest, - coalesce_duplicate_replacement_inames=coalesce_duplicate_replacement_inames, + coalesce_new_iname_duplicates=coalesce_new_iname_duplicates, keep_old_inames=keep_old_inames, ) else: @@ -973,12 +972,12 @@ def replace_inames_in_all_nest_constraints( new_must_not_nest = replace_inames_in_nest_constraints( old_inames, new_inames, old_must_not_nest, - coalesce_duplicate_replacement_inames=False, + coalesce_new_iname_duplicates=False, # (for now, never coalesce must-not-nest constraints) keep_old_inames=keep_old_inames, ) # each must not nest constraint may only contain two tiers - # TODO coalesce_duplicate_replacement_inames? + # TODO coalesce_new_iname_duplicates? else: new_must_not_nest = None @@ -2287,7 +2286,7 @@ def _remove_iname_from_dep(dep): kernel = replace_inames_in_all_nest_constraints( kernel, old_inames=unused_inames, new_inames=[], - coalesce_duplicate_replacement_inames=False, + coalesce_new_iname_duplicates=False, pairs_that_must_not_voilate_constraints=set(), keep_old_inames=False, ) From f213da89b0fc0a27711f5fbfcdddb2172566b5ce Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Tue, 27 Apr 2021 03:28:03 -0500 Subject: [PATCH 41/59] fix flake8 issue --- test/test_nest_constraints.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_nest_constraints.py b/test/test_nest_constraints.py index 1d7156843..8fd84bdbe 100644 --- a/test/test_nest_constraints.py +++ b/test/test_nest_constraints.py @@ -712,10 +712,10 @@ def linearize_and_get_nestings(unlinearized_knl): ref_knl = lp.make_kernel( "{ [g,h,i,j,k]: 0<=g,h,i,j,k Date: Tue, 27 Apr 2021 05:56:11 -0500 Subject: [PATCH 42/59] eliminate unnecessary keep_old_inames option when replacing inames in nest constraints (can just include old inames in the replacement inames to accomplish same result) --- loopy/transform/iname.py | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index 6767955c5..9296e733e 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -735,7 +735,6 @@ def get_graph_sources(graph): def replace_inames_in_nest_constraints( inames_to_replace, replacement_inames, old_constraints, coalesce_new_iname_duplicates=False, - keep_old_inames=False, ): """ :arg inames_to_replace: A set of inames that may exist in @@ -764,11 +763,7 @@ def replace_inames_in_nest_constraints( # create the new set of inames with the replacements if inames_found: - if keep_old_inames: - # TODO is copy necessary? - new_inames = iname_set.inames.copy() - else: - new_inames = iname_set.inames - inames_found + new_inames = iname_set.inames - inames_found new_inames.update(replacement_inames) else: new_inames = iname_set.inames.copy() @@ -928,7 +923,6 @@ def replace_inames_in_all_nest_constraints( kernel, old_inames, new_inames, coalesce_new_iname_duplicates=False, pairs_that_must_not_voilate_constraints=set(), - keep_old_inames=False, ): # replace each iname in old_inames with all inames in new_inames # TODO What was pairs_that_must_not_voilate_constraints used for??? @@ -956,7 +950,6 @@ def replace_inames_in_all_nest_constraints( new_must_nest = replace_inames_in_nest_constraints( old_inames, new_inames, old_must_nest, coalesce_new_iname_duplicates=coalesce_new_iname_duplicates, - keep_old_inames=keep_old_inames, ) else: new_must_nest = None @@ -974,7 +967,6 @@ def replace_inames_in_all_nest_constraints( old_inames, new_inames, old_must_not_nest, coalesce_new_iname_duplicates=False, # (for now, never coalesce must-not-nest constraints) - keep_old_inames=keep_old_inames, ) # each must not nest constraint may only contain two tiers # TODO coalesce_new_iname_duplicates? @@ -984,11 +976,8 @@ def replace_inames_in_all_nest_constraints( # Rebuild must_nest graph if new_must_nest: new_must_nest_graph = {} - if keep_old_inames: - new_all_inames = kernel.all_inames() | set(new_inames) - else: - new_all_inames = ( - kernel.all_inames() - set(old_inames)) | set(new_inames) + new_all_inames = ( + kernel.all_inames() - set(old_inames)) | set(new_inames) from pytools.graph import CycleError for must_nest_tuple in new_must_nest: try: @@ -1291,8 +1280,8 @@ def _split_iname_in_dependee(dep): # update must_nest, must_not_nest, and must_nest_graph kernel = replace_inames_in_all_nest_constraints( - kernel, set([iname_to_split, ]), [inner_iname, outer_iname], - keep_old_inames=True, + kernel, + set([iname_to_split, ]), [iname_to_split, inner_iname, outer_iname], ) # }}} @@ -1891,6 +1880,11 @@ def duplicate_inames(kernel, inames, within, new_inames=None, suffix=None, from loopy.kernel.tools import DomainChanger domch = DomainChanger(kernel, frozenset([old_iname])) + # update must_nest, must_not_nest, and must_nest_graph + # (don't remove any unused inames yet, that happens later) + #knl = replace_inames_in_all_nest_constraints( + # knl, set([old_iname, ]), [old_iname, new_iname]) + from loopy.isl_helpers import duplicate_axes kernel = kernel.copy( domains=domch.get_domains_with( @@ -2288,7 +2282,6 @@ def _remove_iname_from_dep(dep): kernel, old_inames=unused_inames, new_inames=[], coalesce_new_iname_duplicates=False, pairs_that_must_not_voilate_constraints=set(), - keep_old_inames=False, ) # }}} From 4cc5175639e8d45e3459e2edb3c04f65b0e4fe44 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Tue, 27 Apr 2021 06:45:53 -0500 Subject: [PATCH 43/59] update loop nest constraints in duplicate_inames (and handle within cases) --- loopy/transform/iname.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index 9296e733e..da72f2277 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -1880,10 +1880,13 @@ def duplicate_inames(kernel, inames, within, new_inames=None, suffix=None, from loopy.kernel.tools import DomainChanger domch = DomainChanger(kernel, frozenset([old_iname])) - # update must_nest, must_not_nest, and must_nest_graph + # # {{{ update nest constraints + # (don't remove any unused inames yet, that happens later) - #knl = replace_inames_in_all_nest_constraints( - # knl, set([old_iname, ]), [old_iname, new_iname]) + kernel = replace_inames_in_all_nest_constraints( + kernel, set([old_iname, ]), [old_iname, new_iname]) + + # }}} from loopy.isl_helpers import duplicate_axes kernel = kernel.copy( @@ -1948,6 +1951,18 @@ def _rename_iname_in_dep_in(dep): # }}} + # TODO why isn't remove_unused_inames called on kernel here? + + # {{{ if there are any now unused inames, remove from nest constraints + + now_unused_inames = (set(inames) - get_used_inames(kernel)) & set(inames) + kernel = replace_inames_in_all_nest_constraints( + kernel, old_inames=now_unused_inames, new_inames=[], + coalesce_new_iname_duplicates=False, + ) + + # }}} + return kernel # }}} @@ -2276,12 +2291,11 @@ def _remove_iname_from_dep(dep): # }}} - # # {{{ Remove inames from loop nest constraints + # {{{ Remove inames from loop nest constraints kernel = replace_inames_in_all_nest_constraints( kernel, old_inames=unused_inames, new_inames=[], coalesce_new_iname_duplicates=False, - pairs_that_must_not_voilate_constraints=set(), ) # }}} From 725346304f2c16c08dcb59e587f6c0aeba8006d3 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Tue, 27 Apr 2021 06:46:09 -0500 Subject: [PATCH 44/59] add test for nest constraint updating in duplciate_inames --- test/test_nest_constraints.py | 124 ++++++++++++++++++++++++++++++---- 1 file changed, 112 insertions(+), 12 deletions(-) diff --git a/test/test_nest_constraints.py b/test/test_nest_constraints.py index 8fd84bdbe..00cff0034 100644 --- a/test/test_nest_constraints.py +++ b/test/test_nest_constraints.py @@ -635,16 +635,21 @@ def loop_order(lin_items): # {{{ test constraint updating during transformation -# {{{ test_constraint_updating_split_iname -def test_constraint_updating_split_iname(): +# {{{ helper functions +def _linearize_and_get_nestings(unlinearized_knl): from loopy.transform.iname import get_iname_nestings + lin_knl = lp.get_one_linearized_kernel( + lp.preprocess_kernel(unlinearized_knl)) + return get_iname_nestings(lin_knl.linearization) + +# }}} + + +# {{{ test_constraint_updating_split_iname - def linearize_and_get_nestings(unlinearized_knl): - lin_knl = lp.get_one_linearized_kernel( - lp.preprocess_kernel(unlinearized_knl)) - return get_iname_nestings(lin_knl.linearization) +def test_constraint_updating_split_iname(): ref_knl = lp.make_kernel( "{ [g,h,i,j,k]: 0<=g,h,i,j,k Date: Tue, 27 Apr 2021 07:01:55 -0500 Subject: [PATCH 45/59] raise NotImplementedError in rename_iname() when new_iname does exist --- loopy/transform/iname.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index da72f2277..60439428e 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -2144,6 +2144,16 @@ def rename_iname(kernel, old_iname, new_iname, existing_ok=False, within=None): "--cannot rename" % new_iname) if does_exist: + + # TODO implement this + if kernel.loop_nest_constraints and ( + kernel.loop_nest_constraints.must_nest or + kernel.loop_nest_constraints.must_not_nest or + kernel.loop_nest_constraints.must_nest_graph): + raise NotImplementedError( + "rename_iname() does not yet handle new loop nest " + "constraints when does_exist=True.") + # {{{ check that the domains match up dom = kernel.get_inames_domain(frozenset((old_iname, new_iname))) From 2f4297a026de95d09af3ac95354c7b5ffe83be8a Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Tue, 27 Apr 2021 07:02:11 -0500 Subject: [PATCH 46/59] add test for constraitn updating in rename_iname --- test/test_nest_constraints.py | 39 ++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/test/test_nest_constraints.py b/test/test_nest_constraints.py index 00cff0034..38ce3cd3b 100644 --- a/test/test_nest_constraints.py +++ b/test/test_nest_constraints.py @@ -748,7 +748,7 @@ def test_constraint_updating_split_iname(): # }}} -# {{{ +# {{{ test_constraint_updating_duplicate_inames def test_constraint_updating_duplicate_inames(): @@ -842,6 +842,43 @@ def test_constraint_updating_duplicate_inames(): # }}} + +# {{{ test_constraint_updating_rename_iname + +def test_constraint_updating_rename_iname(): + + ref_knl = lp.make_kernel( + "{ [g,h,i,j,k]: 0<=g,h,i,j,k Date: Tue, 27 Apr 2021 07:18:23 -0500 Subject: [PATCH 47/59] Don't allow tagging of iname found in must_nest constraint as concurrent --- loopy/transform/iname.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index 60439428e..d0beb85a8 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -1756,6 +1756,7 @@ def parse_tag(tag): # }}} + from loopy.kernel.data import ConcurrentTag knl_inames = kernel.inames.copy() for name, new_tag in iname_to_tag.items(): if not new_tag: @@ -1766,6 +1767,21 @@ def parse_tag(tag): knl_inames[name] = knl_inames[name].tagged(new_tag) + # {{{ Don't allow tagging of must_nest iname as concurrent + # TODO ...but what about 'vec'? + + if isinstance(new_tag, ConcurrentTag) and kernel.loop_nest_constraints: + must_nest = kernel.loop_nest_constraints.must_nest + if must_nest: + for nesting in must_nest: + for iname_set in nesting: + if iname in iname_set.inames: + raise ValueError("cannot tag '%s' as concurrent--" + "iname involved in must-nest constraint %s." + % (iname, nesting)) + + # }}} + return kernel.copy(inames=knl_inames) # }}} From 96d07d5a63a0d1a5e1c40b861e2cbcdc4726e2be Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Tue, 27 Apr 2021 07:18:46 -0500 Subject: [PATCH 48/59] test error when tagging iname found in must_nest constraint as concurrent --- test/test_nest_constraints.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/test_nest_constraints.py b/test/test_nest_constraints.py index 38ce3cd3b..929ddf331 100644 --- a/test/test_nest_constraints.py +++ b/test/test_nest_constraints.py @@ -879,6 +879,39 @@ def test_constraint_updating_rename_iname(): # }}} + +# {{{ test_constraint_handling_tag_inames + +def test_constraint_handling_tag_inames(): + + ref_knl = lp.make_kernel( + "{ [g,h,i,j,k]: 0<=g,h,i,j,k Date: Tue, 27 Apr 2021 07:45:18 -0500 Subject: [PATCH 49/59] handle constraint updating in join_inames (when within=None) --- loopy/transform/iname.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index d0beb85a8..0bb19532d 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -925,7 +925,6 @@ def replace_inames_in_all_nest_constraints( pairs_that_must_not_voilate_constraints=set(), ): # replace each iname in old_inames with all inames in new_inames - # TODO What was pairs_that_must_not_voilate_constraints used for??? # get old must_nest and must_not_nest # (must_nest_graph will be rebuilt) @@ -1609,6 +1608,37 @@ def subst_within_inames(fid): applied_iname_rewrites=kernel.applied_iname_rewrites + [subst_dict] )) + # {{{ update must_nest, must_not_nest, and must_nest_graph + + if kernel.loop_nest_constraints and ( + kernel.loop_nest_constraints.must_nest or + kernel.loop_nest_constraints.must_not_nest or + kernel.loop_nest_constraints.must_nest_graph): + + if within != parse_match(None): + raise NotImplementedError( + "join_inames() does not yet handle new loop nest " + "constraints when within is not None.") + + # When joining inames, we create several implied loop nestings. + # make sure that these implied nestings don't violate existing + # constraints. + + # (will fail if cycle is created in must-nest graph) + implied_nestings = set() + inames_orig_order = inames[::-1] # this was reversed above + for i, iname_before in enumerate(inames_orig_order[:-1]): + for iname_after in inames_orig_order[i+1:]: + implied_nestings.add((iname_before, iname_after)) + + kernel = replace_inames_in_all_nest_constraints( + kernel, set(inames), [new_iname], + coalesce_new_iname_duplicates=True, + pairs_that_must_not_voilate_constraints=implied_nestings, + ) + + # }}} + from loopy.match import parse_stack_match within = parse_stack_match(within) From 7ec404f243322d3386822ac97c1911c3fafbdd2f Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Tue, 27 Apr 2021 07:45:37 -0500 Subject: [PATCH 50/59] test constraint handling in join_inames() --- test/test_nest_constraints.py | 223 ++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) diff --git a/test/test_nest_constraints.py b/test/test_nest_constraints.py index 929ddf331..40e52420a 100644 --- a/test/test_nest_constraints.py +++ b/test/test_nest_constraints.py @@ -912,6 +912,229 @@ def test_constraint_handling_tag_inames(): # }}} + +# {{{ test_constraint_updating_join_inames + +def test_constraint_updating_join_inames(): + + ref_knl = lp.make_kernel( + "{ [g,h,i,j,k]: 0<=g,h,i,j,k<1024 }", + """ + out[g,h,i,j,k] = 2*a[g,h,i,j,k] {id=insn} + """, + ) + ref_knl = lp.add_and_infer_dtypes(ref_knl, {"a": np.dtype(np.float32)}) + + knl = ref_knl + knl = lp.constrain_loop_nesting( + knl, + must_nest=("i", "{g, h, j, k}"), + must_not_nest=("h", "g"), + ) + knl = lp.constrain_loop_nesting( + knl, + must_nest=("{g, h, i}", "{j, k}"), + ) + knl = lp.join_inames(knl, inames=["g", "h"], new_iname="gh") + loop_nesting = _linearize_and_get_nestings(knl)[0] # only one nesting + assert loop_nesting[0] == "i" + assert loop_nesting[1] == "gh" + assert set(loop_nesting[2:]) == set(["j", "k"]) + + knl = ref_knl + knl = lp.constrain_loop_nesting( + knl, + must_nest=("i", "{g, h, j, k}"), + must_not_nest=("h", "g"), + ) + knl = lp.constrain_loop_nesting( + knl, + must_nest=("{g, h, i}", "{j, k}"), + ) + knl = lp.join_inames(knl, inames=["j", "k"], new_iname="jk") + loop_nesting = _linearize_and_get_nestings(knl)[0] # only one nesting + assert loop_nesting[0] == "i" + assert loop_nesting[1:3] == ("g", "h") + assert loop_nesting[3] == "jk" + + knl = ref_knl + knl = lp.constrain_loop_nesting( + knl, + must_nest=("h", "i", "g", "{j, k}"), + ) + knl = lp.join_inames(knl, inames=["i", "g"], new_iname="ig") + loop_nesting = _linearize_and_get_nestings(knl)[0] # only one nesting + assert loop_nesting[0] == "h" + assert loop_nesting[1] == "ig" + assert set(loop_nesting[2:4]) == set(["j", "k"]) + + # test cycle detection + knl = ref_knl + knl = lp.constrain_loop_nesting( + knl, + must_nest=("i", "{g, h}", "{j, k}"), + ) + try: + lp.join_inames(knl, inames=["i", "k"], new_iname="ik") + assert False + except ValueError as e: + assert "cycle" in str(e) + + # test implied nesting that creates constraint violation + knl = ref_knl + knl = lp.constrain_loop_nesting( + knl, + must_not_nest=("i", "k"), + ) + try: + lp.join_inames(knl, inames=["i", "k"], new_iname="ik") + assert False + except ValueError as e: + assert "Implied nestings violate existing must-not-nest" in str(e) + +# }}} + + +# {{{ test_iname_coalescing_in_loop_nest_constraints + +def test_iname_coalescing_in_loop_nest_constraints(): + + def get_sets_of_inames(iname_sets_tuple, iname_universe): + # convert UnexpandedInameSets to sets + sets_of_inames = [] + for iname_set in iname_sets_tuple: + sets_of_inames.append( + iname_set.get_inames_represented(iname_universe)) + return sets_of_inames + + ref_knl = lp.make_kernel( + "{ [g,h,i,j,k]: 0<=g,h,i,j,k<1024 }", + """ + out[g,h,i,j,k] = 2*a[g,h,i,j,k] {id=insn} + """, + ) + # (join_inames errors if domain bound is variable) + + ref_knl = lp.add_and_infer_dtypes(ref_knl, {"a": np.dtype(np.float32)}) + + knl = ref_knl + knl = lp.constrain_loop_nesting( + knl, + must_nest=("i", "g", "h", "j", "k"), + ) + knl = lp.join_inames(knl, inames=["g", "h"], new_iname="gh") + new_must_nest = get_sets_of_inames( + list(knl.loop_nest_constraints.must_nest)[0], knl.all_inames()) + expected_must_nest = [ + set(["i", ]), set(["gh", ]), set(["j", ]), set(["k", ])] + assert new_must_nest == expected_must_nest + + knl = ref_knl + knl = lp.constrain_loop_nesting( + knl, + must_nest=("{i, g}", "h", "j", "k"), + ) + knl = lp.join_inames(knl, inames=["g", "h"], new_iname="gh") + new_must_nest = get_sets_of_inames( + list(knl.loop_nest_constraints.must_nest)[0], knl.all_inames()) + expected_must_nest = [ + set(["i", ]), set(["gh", ]), set(["j", ]), set(["k", ])] + assert new_must_nest == expected_must_nest + + knl = ref_knl + knl = lp.constrain_loop_nesting( + knl, + must_nest=("i", "g", "{h, j}", "k"), + ) + knl = lp.join_inames(knl, inames=["g", "h"], new_iname="gh") + new_must_nest = get_sets_of_inames( + list(knl.loop_nest_constraints.must_nest)[0], knl.all_inames()) + expected_must_nest = [ + set(["i", ]), set(["gh", ]), set(["j", ]), set(["k", ])] + assert new_must_nest == expected_must_nest + + knl = ref_knl + knl = lp.constrain_loop_nesting( + knl, + must_nest=("i", "g", "{h, j, k}"), + ) + knl = lp.join_inames(knl, inames=["g", "h"], new_iname="gh") + new_must_nest = get_sets_of_inames( + list(knl.loop_nest_constraints.must_nest)[0], knl.all_inames()) + expected_must_nest = [ + set(["i", ]), set(["gh", ]), set(["j", "k"])] + assert new_must_nest == expected_must_nest + + knl = ref_knl + knl = lp.constrain_loop_nesting( + knl, + must_nest=("i", "{g, h}", "j", "k"), + ) + knl = lp.join_inames(knl, inames=["g", "h"], new_iname="gh") + new_must_nest = get_sets_of_inames( + list(knl.loop_nest_constraints.must_nest)[0], knl.all_inames()) + expected_must_nest = [ + set(["i", ]), set(["gh", ]), set(["j", ]), set(["k", ])] + assert new_must_nest == expected_must_nest + + knl = ref_knl + knl = lp.constrain_loop_nesting( + knl, + must_nest=("{i, g}", "{h, j, k}"), + ) + knl = lp.join_inames(knl, inames=["g", "h"], new_iname="gh") + new_must_nest = get_sets_of_inames( + list(knl.loop_nest_constraints.must_nest)[0], knl.all_inames()) + expected_must_nest = [ + set(["i", ]), set(["gh", ]), set(["j", "k"])] + assert new_must_nest == expected_must_nest + + knl = ref_knl + knl = lp.constrain_loop_nesting( + knl, + must_nest=("i", "g", "j", "h", "k"), + ) + try: + knl = lp.join_inames(knl, inames=["g", "h"], new_iname="gh") + assert False + except ValueError as e: + assert "contains cycle" in str(e) + + knl = ref_knl + knl = lp.constrain_loop_nesting( + knl, + must_nest=("{i, g}", "j", "{h, k}"), + ) + try: + knl = lp.join_inames(knl, inames=["g", "h"], new_iname="gh") + assert False + except ValueError as e: + assert "contains cycle" in str(e) + + knl = ref_knl + knl = lp.constrain_loop_nesting( + knl, + must_nest=("{i, h}", "j", "{g, k}"), + ) + try: + knl = lp.join_inames(knl, inames=["g", "h"], new_iname="gh") + assert False + except ValueError as e: + assert "nestings violate existing must-nest" in str(e) + + knl = ref_knl + knl = lp.constrain_loop_nesting( + knl, + must_not_nest=("g", "h"), + ) + try: + knl = lp.join_inames(knl, inames=["g", "h"], new_iname="gh") + assert False + except ValueError as e: + assert "nestings violate existing must-not-nest" in str(e) + +# }}} + # TODO make standalone test for constraint updating functions that # doesn't bother with transforms/linearization From 65cdfb3c055a5ca6413bc7102e317e654bdb50e6 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Tue, 27 Apr 2021 07:51:58 -0500 Subject: [PATCH 51/59] when inames are tagged as vec, see if there is a must-nest constraint that conflicts with them being nested innermost --- loopy/transform/iname.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index 0bb19532d..78abb0702 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -1786,7 +1786,7 @@ def parse_tag(tag): # }}} - from loopy.kernel.data import ConcurrentTag + from loopy.kernel.data import ConcurrentTag, VectorizeTag knl_inames = kernel.inames.copy() for name, new_tag in iname_to_tag.items(): if not new_tag: @@ -1797,10 +1797,24 @@ def parse_tag(tag): knl_inames[name] = knl_inames[name].tagged(new_tag) - # {{{ Don't allow tagging of must_nest iname as concurrent - # TODO ...but what about 'vec'? + # {{{ loop nest constraint handling - if isinstance(new_tag, ConcurrentTag) and kernel.loop_nest_constraints: + if isinstance(new_tag, VectorizeTag): + # {{{ vec_inames will be nested innermost, check whether this + # conflicts with must-nest constraints + must_nest_graph = (kernel.loop_nest_constraints.must_nest_graph + if kernel.loop_nest_constraints else None) + if must_nest_graph and must_nest_graph.get(iname, set()): + # iname is not a leaf + raise ValueError( + "Loop priorities provided specify that iname %s nest " + "outside of inames %s, but vectorized inames " + "must nest innermost. Cannot tag %s with 'vec' tag." + % (iname, must_nest_graph.get(iname, set()), iname)) + # }}} + + elif isinstance(new_tag, ConcurrentTag) and kernel.loop_nest_constraints: + # {{{ Don't allow tagging of must_nest iname as concurrent must_nest = kernel.loop_nest_constraints.must_nest if must_nest: for nesting in must_nest: @@ -1809,6 +1823,7 @@ def parse_tag(tag): raise ValueError("cannot tag '%s' as concurrent--" "iname involved in must-nest constraint %s." % (iname, nesting)) + # }}} # }}} From 0cf937d242fd200b66f8d668d69876e6c4fcd1c8 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Sat, 8 May 2021 22:42:04 -0500 Subject: [PATCH 52/59] clean up nest constraint code and add more comments/documentation --- loopy/transform/iname.py | 315 ++++++++++++++++++++++------------ test/test_nest_constraints.py | 17 +- 2 files changed, 207 insertions(+), 125 deletions(-) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index 6504c2ff0..a5f6176f3 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -113,9 +113,9 @@ def prioritize_loops(kernel, loop_priority): # }}} -# {{{ handle loop nest constraints +# {{{ Handle loop nest constraints -# {{{ classes to house loop nest constraints +# {{{ Classes to house loop nest constraints # {{{ UnexpandedInameSet @@ -127,13 +127,13 @@ def __init__(self, inames, complement=False): complement=complement, ) - def contains(self, iname): - return (iname not in self.inames if self.complement - else iname in self.inames) - - def contains_all(self, iname_set): - return (not (iname_set & self.inames) if self.complement - else iname_set.issubset(self.inames)) + def contains(self, inames): + if isinstance(inames, set): + return (not (iname_set & self.inames) if self.complement + else iname_set.issubset(self.inames)) + else: + return (inames not in self.inames if self.complement + else inames in self.inames) def get_inames_represented(self, iname_universe=None): """Return the set of inames represented by the UnexpandedInameSet @@ -148,7 +148,7 @@ def get_inames_represented(self, iname_universe=None): return self.inames.copy() def __lt__(self, other): - # TODO is this function really necessary? If so, what should it return? + # FIXME is this function really necessary? If so, what should it return? return self.__hash__() < other.__hash__() def __hash__(self): @@ -205,7 +205,7 @@ def __str__(self): # }}} -# {{{ initial loop nest constraint creation +# {{{ Initial loop nest constraint creation # {{{ process_loop_nest_specification @@ -214,19 +214,20 @@ def process_loop_nest_specification( max_tuple_size=None, complement_sets_allowed=True, ): - # make sure user-supplied nesting conforms to rules + + # Ensure that user-supplied nesting conforms to syntax rules, and # convert string representations of nestings to tuple of UnexpandedInameSets import re - def raise_loop_nest_input_error(msg): + def _raise_loop_nest_input_error(msg): valid_prio_rules = ( "Valid `must_nest` description formats: " # noqa "\"iname, iname, ...\" or (str, str, str, ...), " # noqa "where str can be of form " # noqa "\"iname\" or \"{iname, iname, ...}\". " # noqa "No set complements allowed.\n" # noqa - "Valid `must_not_nest` description tuples must have len <= 2: " # noqa + "Valid `must_not_nest` description tuples must have length 2: " # noqa "\"iname, iname\", \"iname, ~iname\", or " # noqa "(str, str), where str can be of form " # noqa "\"iname\", \"~iname\", \"{iname, iname, ...}\", or " # noqa @@ -239,25 +240,26 @@ def raise_loop_nest_input_error(msg): def _error_on_regex_match(match_str, target_str): if re.findall(match_str, target_str): - raise_loop_nest_input_error( + _raise_loop_nest_input_error( "Unrecognized character(s) %s in nest string %s" % (re.findall(match_str, target_str), target_str)) def _process_iname_set_str(iname_set_str): - # convert something like ~{i,j} or ~i or "i,j" to an UnexpandedInameSet + # Convert something like ~{i,j} or ~i or "i,j" to an UnexpandedInameSet - # remove leading/trailing whitespace + # Remove leading/trailing whitespace iname_set_str_stripped = iname_set_str.strip() if not iname_set_str_stripped: - raise_loop_nest_input_error( + _raise_loop_nest_input_error( "Found 0 inames in string %s." % (iname_set_str)) + # Process complement sets if iname_set_str_stripped[0] == "~": # Make sure compelement is allowed if not complement_sets_allowed: - raise_loop_nest_input_error( + _raise_loop_nest_input_error( "Complement (~) not allowed in this loop nest string %s. " "If you have a use-case where allowing a currently " "disallowed set complement would be helpful, and the " @@ -266,10 +268,10 @@ def _process_iname_set_str(iname_set_str): "please contact the Loo.py maintainers." % (iname_set_str)) - # remove tilde + # Remove tilde iname_set_str_stripped = iname_set_str_stripped[1:] if "~" in iname_set_str_stripped: - raise_loop_nest_input_error( + _raise_loop_nest_input_error( "Multiple complement symbols found in iname set string %s" % (iname_set_str)) @@ -277,7 +279,7 @@ def _process_iname_set_str(iname_set_str): if "," in iname_set_str_stripped and not ( iname_set_str_stripped.startswith("{") and iname_set_str_stripped.endswith("}")): - raise_loop_nest_input_error( + _raise_loop_nest_input_error( "Complements of sets containing multiple inames must " "enclose inames in braces: %s is not valid." % (iname_set_str)) @@ -286,47 +288,49 @@ def _process_iname_set_str(iname_set_str): else: complement = False - # remove leading/trailing spaces + # Remove leading/trailing spaces iname_set_str_stripped = iname_set_str_stripped.strip(" ") - # make sure braces are valid and strip them + # Make sure braces are valid and strip them if iname_set_str_stripped[0] == "{": if not iname_set_str_stripped[-1] == "}": - raise_loop_nest_input_error( + _raise_loop_nest_input_error( "Invalid braces: %s" % (iname_set_str)) else: - # remove enclosing braces + # Remove enclosing braces iname_set_str_stripped = iname_set_str_stripped[1:-1] - # if there are dangling braces around, they will be caught next + # (If there are dangling braces around, they will be caught next) - # remove any more spaces + # Remove any more spaces iname_set_str_stripped = iname_set_str_stripped.strip() - # should be no remaining special characters besides comma and space + # Should be no remaining special characters besides comma and space _error_on_regex_match(r"([^,\w ])", iname_set_str_stripped) - # split by commas or spaces to get inames + # Split by commas or spaces to get inames inames = re.findall(r"([\w]+)(?:[ |,]*|$)", iname_set_str_stripped) - # make sure iname count matches what we expect from comma count + # Make sure iname count matches what we expect from comma count if len(inames) != iname_set_str_stripped.count(",") + 1: - raise_loop_nest_input_error( + _raise_loop_nest_input_error( "Found %d inames but expected %d in string %s." % (len(inames), iname_set_str_stripped.count(",") + 1, iname_set_str)) if len(inames) == 0: - raise_loop_nest_input_error( + _raise_loop_nest_input_error( "Found empty set in string %s." % (iname_set_str)) + # NOTE this won't catch certain cases of bad syntax, e.g., ("{h i j,,}", "k") + return UnexpandedInameSet( set([s.strip() for s in iname_set_str_stripped.split(",")]), complement=complement) if isinstance(nesting, str): - # Enforce that priorities involving iname sets be passed as tuple - # Iname sets defined negatively with a single iname are allowed here + # Enforce that constraints involving iname sets be passed as tuple. + # Iname sets defined negatively with a *single* iname are allowed here. # Check for any special characters besides comma, space, and tilde. # E.g., curly braces would indicate that an iname set was NOT @@ -337,23 +341,24 @@ def _process_iname_set_str(iname_set_str): nesting_as_tuple = tuple( _process_iname_set_str(set_str) for set_str in nesting.split(",")) else: - # nesting not passed as string; process each tier + assert isinstance(nesting, (tuple, list)) + # Process each tier nesting_as_tuple = tuple( _process_iname_set_str(set_str) for set_str in nesting) - # check max_inames_per_set + # Check max_inames_per_set if max_tuple_size and len(nesting_as_tuple) > max_tuple_size: - raise_loop_nest_input_error( + _raise_loop_nest_input_error( "Loop nest prioritization tuple %s exceeds max tuple size %d." % (nesting_as_tuple)) - # make sure nesting has len > 1 + # Make sure nesting has len > 1 if len(nesting_as_tuple) <= 1: - raise_loop_nest_input_error( + _raise_loop_nest_input_error( "Loop nest prioritization tuple %s must have length > 1." % (nesting_as_tuple)) - # return tuple of UnexpandedInameSets + # Return tuple of UnexpandedInameSets return nesting_as_tuple # }}} @@ -363,22 +368,33 @@ def _process_iname_set_str(iname_set_str): def constrain_loop_nesting( kernel, must_nest=None, must_not_nest=None): - # TODO docstring - # TODO what if someone passes single-iname prio? - # TODO enforce that must_nest be a single tuple not list of tuples - # (or update implementation to allow list of tuples) + """Add the provided constraints to the kernel. + + :arg must_nest: A tuple or comma-separated string representing + an ordering of loop nesting tiers that must appear in the + linearized kernel. Each item in the tuple represents a + :class:`UnexpandedInameSet`\ s. - # check for existing constraints + :arg must_not_nest: A two-tuple or comma-separated string representing + an ordering of loop nesting tiers that must not appear in the + linearized kernel. Each item in the tuple represents a + :class:`UnexpandedInameSet`\ s. + + """ + + # {{{ Get any current constraints, if they exist if kernel.loop_nest_constraints: if kernel.loop_nest_constraints.must_nest: must_nest_constraints_old = kernel.loop_nest_constraints.must_nest else: must_nest_constraints_old = set() + if kernel.loop_nest_constraints.must_not_nest: must_not_nest_constraints_old = \ kernel.loop_nest_constraints.must_not_nest else: must_not_nest_constraints_old = set() + if kernel.loop_nest_constraints.must_nest_graph: must_nest_graph_old = kernel.loop_nest_constraints.must_nest_graph else: @@ -388,20 +404,20 @@ def constrain_loop_nesting( must_not_nest_constraints_old = set() must_nest_graph_old = {} - # {{{ process must_nest + # }}} - # TODO remove (TEMPORARY HACK TO KEEP LEGACY CODE RUNNING) - # expand_must_priorities = set() + # {{{ Process must_nest if must_nest: - # {{{ parse must_nest, check for conflicts, combine with old constraints + # {{{ Parse must_nest, check for conflicts, combine with old constraints - # {{{ Parse must_nest; no complements allowed + # {{{ Parse must_nest (no complements allowed) must_nest_tuple = process_loop_nest_specification( must_nest, complement_sets_allowed=False) # }}} # {{{ Error if someone prioritizes concurrent iname + from loopy.kernel.data import ConcurrentTag for iname_set in must_nest_tuple: for iname in iname_set.inames: @@ -410,66 +426,69 @@ def constrain_loop_nesting( "iname %s tagged with ConcurrentTag, " "cannot use iname in must-nest constraint %s." % (iname, must_nest_tuple)) + # }}} - # {{{ must_nest_graph_new <- update_must_nest_graph(...) + # {{{ Update must_nest graph (and check for cycles) - # (checks for cycles) must_nest_graph_new = update_must_nest_graph( must_nest_graph_old, must_nest_tuple, kernel.all_inames()) # }}} - # {{{ make sure must_nest constraints don't violate must_not_nest - # this may not catch all problems (?) + # {{{ Make sure must_nest constraints don't violate must_not_nest + # (this may not catch all problems) check_must_not_nest_against_must_nest_graph( must_not_nest_constraints_old, must_nest_graph_new) # }}} - # {{{ check for conflicts with inames tagged 'vec' (must be innermost) + # {{{ Check for conflicts with inames tagged 'vec' (must be innermost) + from loopy.kernel.data import VectorizeTag for iname in kernel.all_inames(): if kernel.iname_tags_of_type(iname, VectorizeTag) and ( must_nest_graph_new.get(iname, set())): - # Iname cannot be a leaf, error + # Must-nest graph doesn't allow iname to be a leaf, error raise ValueError( - "Iname %s tagged as 'vec', but loop priorities " + "Iname %s tagged as 'vec', but loop nest constraints " "%s require that iname %s nest outside of inames %s. " "Vectorized inames must nest innermost; cannot " "impose loop nest specification." % (iname, must_nest, iname, must_nest_graph_new.get(iname, set()))) - # }}} - # TODO remove (TEMPORARY HACK TO KEEP LEGACY CODE RUNNING) - # expand_must_priorities = _expand_iname_sets_in_tuple( - # must_nest_tuple, kernel.all_inames()) + # }}} - # {{{ combine new must_nest constraints with old + # {{{ Add new must_nest constraints to existing must_nest constraints must_nest_constraints_new = must_nest_constraints_old | set( [must_nest_tuple, ]) # }}} # }}} else: - # {{{ no new must_nest constraints, keep the old ones + # {{{ No new must_nest constraints, just keep the old ones + must_nest_constraints_new = must_nest_constraints_old must_nest_graph_new = must_nest_graph_old + # }}} # }}} - # {{{ process must_not_nest + # {{{ Process must_not_nest if must_not_nest: - # {{{ parse must_not_nest, check for conflicts, combine with old constraints + # {{{ Parse must_not_nest, check for conflicts, combine with old constraints # {{{ Parse must_not_nest; complements allowed; max_tuple_size=2 + must_not_nest_tuple = process_loop_nest_specification( must_not_nest, max_tuple_size=2) + # }}} - # {{{ make sure must_not_nest constraints don't violate must_nest + # {{{ Make sure must_not_nest constraints don't violate must_nest + # (cycles are allowed in must_not_nest constraints) import itertools must_pairs = [] @@ -482,17 +501,20 @@ def constrain_loop_nesting( "must_not_nest constraints %s inconsistent with " "must_nest constraints %s." % (must_not_nest_tuple, must_nest_constraints_new)) + # }}} - # {{{ combine new must_not_nest constraints with old + # {{{ Add new must_not_nest constraints to exisitng must_not_nest constraints must_not_nest_constraints_new = must_not_nest_constraints_old | set([ must_not_nest_tuple, ]) # }}} # }}} else: - # {{{ no new must_not_nest constraints, keep the old ones + # {{{ No new must_not_nest constraints, just keep the old ones + must_not_nest_constraints_new = must_not_nest_constraints_old + # }}} # }}} @@ -503,11 +525,7 @@ def constrain_loop_nesting( must_nest_graph=must_nest_graph_new, ) - # TODO do something with old priorities??? - return kernel.copy( - # loop_priority=kernel.loop_priority.union(expand_must_priorities), - loop_nest_constraints=nest_constraints, - ) + return kernel.copy(loop_nest_constraints=nest_constraints) # }}} @@ -515,34 +533,35 @@ def constrain_loop_nesting( # {{{ update_must_nest_graph def update_must_nest_graph(must_nest_graph, must_nest, all_inames): - # Note: there should not be any complements in the must_nest tuples + # Note: there should *not* be any complements in the must_nest tuples + from copy import deepcopy new_graph = deepcopy(must_nest_graph) - # first, all inames must be a node in the graph: + # First, each iname must be a node in the graph for missing_iname in all_inames - new_graph.keys(): new_graph[missing_iname] = set() - # get (before, after) pairs: + # Expand must_nest into (before, after) pairs must_nest_expanded = _expand_iname_sets_in_tuple(must_nest, all_inames) - # update graph: + # Update must_nest_graph with new pairs for before, after in must_nest_expanded: new_graph[before].add(after) - # compute transitive closure: - from pytools.graph import compute_transitive_closure - # Note: compute_transitive_closure now allows cycles, will not error + # Compute transitive closure + from pytools.graph import compute_transitive_closure, contains_cycle new_graph_closure = compute_transitive_closure(new_graph) + # Note: compute_transitive_closure now allows cycles, will not error # Check for inconsistent must_nest constraints by checking for cycle: - from pytools.graph import contains_cycle if contains_cycle(new_graph_closure): raise ValueError( "update_must_nest_graph: Nest constraint cycle detected. " "must_nest constraints %s inconsistent with existing " "must_nest constraints %s." % (must_nest, must_nest_graph)) + return new_graph_closure # }}} @@ -551,15 +570,15 @@ def update_must_nest_graph(must_nest_graph, must_nest, all_inames): # {{{ _expand_iname_sets_in_tuple def _expand_iname_sets_in_tuple( - iname_sets_tuple, # (UnexpandedInameSet, Unex..., ...) - all_inames, + iname_sets_tuple, + iname_universe=None, ): - # First convert negatively defined iname sets to sets - positively_defined_iname_sets = [] - for iname_set in iname_sets_tuple: - positively_defined_iname_sets.append( - iname_set.get_inames_represented(all_inames)) + # First convert UnexpandedInameSets to sets. + # Note that must_nest constraints cannot be negatively defined. + positively_defined_iname_sets = [ + iname_set.get_inames_represented(iname_universe) + for iname_set in iname_sets_tuple] # Now expand all priority tuples into (before, after) pairs using # Cartesian product of all pairs of sets @@ -577,6 +596,7 @@ def _expand_iname_sets_in_tuple( raise ValueError( "Loop nesting %s contains cycle: %s. " % (iname_sets_tuple, prio_tuple)) + return loop_priority_pairs # }}} @@ -584,18 +604,33 @@ def _expand_iname_sets_in_tuple( # }}} -# {{{ checking constraints +# {{{ Checking constraints # {{{ check_must_nest def check_must_nest(all_loop_nests, must_nest, all_inames): - # in order to make sure must_nest is satisfied, we + """Determine whether must_nest constraint is satisfied by + all_loop_nests + + :arg all_loop_nests: A list of lists of inames, each representing + the nesting order of nested loops. + + :arg must_nest: A tuple of :class:`UnexpandedInameSet`\ s describing + nestings that must appear in all_loop_nests. + + :returns: A :class:`bool` indicating whether the must nest constraints + are satisfied by the provided loop nesting. + + """ + + # In order to make sure must_nest is satisfied, we # need to expand all must_nest tiers - # TODO instead of expanding tiers into all pairs up front, + # FIXME instead of expanding tiers into all pairs up front, # create these pairs one at a time so that we can stop as soon as we fail - must_nest_expanded = _expand_iname_sets_in_tuple(must_nest, all_inames) + must_nest_expanded = _expand_iname_sets_in_tuple(must_nest) + # must_nest_expanded contains pairs for before, after in must_nest_expanded: found = False @@ -614,12 +649,28 @@ def check_must_nest(all_loop_nests, must_nest, all_inames): # {{{ check_must_not_nest def check_must_not_nest(all_loop_nests, must_not_nest): - # recall that must_not_nest may only contain two tiers + """Determine whether must_not_nest constraint is satisfied by + all_loop_nests + + :arg all_loop_nests: A list of lists of inames, each representing + the nesting order of nested loops. + + :arg must_not_nest: A two-tuple of :class:`UnexpandedInameSet`\ s + describing nestings that must not appear in all_loop_nests. + + :returns: A :class:`bool` indicating whether the must_not_nest constraints + are satisfied by the provided loop nesting. + + """ + + # Note that must_not_nest may only contain two tiers for nesting in all_loop_nests: - # Go thru each pair in all_loop_nests + + # Go through each pair in all_loop_nests for i, iname_before in enumerate(nesting): for iname_after in nesting[i+1:]: + # Check whether it violates must not nest if (must_not_nest[0].contains(iname_before) and must_not_nest[1].contains(iname_after)): @@ -633,7 +684,20 @@ def check_must_not_nest(all_loop_nests, must_not_nest): # {{{ check_all_must_not_nests def check_all_must_not_nests(all_loop_nests, must_not_nests): - # recall that must_not_nest may only contain two tiers + """Determine whether all must_not_nest constraints are satisfied by + all_loop_nests + + :arg all_loop_nests: A list of lists of inames, each representing + the nesting order of nested loops. + + :arg must_not_nests: A set of two-tuples of :class:`UnexpandedInameSet`\ s + describing nestings that must not appear in all_loop_nests. + + :returns: A :class:`bool` indicating whether the must_not_nest constraints + are satisfied by the provided loop nesting. + + """ + for must_not_nest in must_not_nests: if not check_must_not_nest(all_loop_nests, must_not_nest): return False @@ -646,19 +710,37 @@ def check_all_must_not_nests(all_loop_nests, must_not_nests): def loop_nest_constraints_satisfied( all_loop_nests, - must_nest_constraints, - must_not_nest_constraints, - all_inames): + must_nest_constraints=None, + must_not_nest_constraints=None, + all_inames=None): + """Determine whether must_not_nest constraint is satisfied by + all_loop_nests + + :arg all_loop_nests: A set of lists of inames, each representing + the nesting order of loops. + + :arg must_nest_constraints: An iterable of tuples of + :class:`UnexpandedInameSet`\ s, each describing nestings that must + appear in all_loop_nests. + + :arg must_not_nest_constraints: An iterable of two-tuples of + :class:`UnexpandedInameSet`\ s, each describing nestings that must not + appear in all_loop_nests. - # check must-nest constraints + :returns: A :class:`bool` indicating whether the constraints + are satisfied by the provided loop nesting. + + """ + + # Check must-nest constraints if must_nest_constraints: for must_nest in must_nest_constraints: if not check_must_nest( all_loop_nests, must_nest, all_inames): return False - # check must-not-nest constraints - if must_not_nest_constraints is not None: + # Check must-not-nest constraints + if must_not_nest_constraints: for must_not_nest in must_not_nest_constraints: if not check_must_not_nest( all_loop_nests, must_not_nest): @@ -673,8 +755,17 @@ def loop_nest_constraints_satisfied( def check_must_not_nest_against_must_nest_graph( must_not_nest_constraints, must_nest_graph): - # make sure none of the must_nest constraints violate must_not_nest - # this may not catch all problems + """Ensure none of the must_not_nest constraints are violated by + nestings represented in the must_nest_graph + + :arg must_not_nest_constraints: A set of two-tuples of + :class:`UnexpandedInameSet`\ s describing nestings that must not appear + in loop nestings. + + :arg must_nest_graph: A :class:`dict` mapping each iname to other inames + that must be nested inside it. + + """ if must_not_nest_constraints and must_nest_graph: import itertools @@ -695,17 +786,19 @@ def check_must_not_nest_against_must_nest_graph( # {{{ get_iname_nestings -def get_iname_nestings(outline): +def get_iname_nestings(linearization): + """Return a list of iname tuples representing the deepest loop nestings + in a kernel linearization. + """ from loopy.schedule import EnterLoop, LeaveLoop - # return a list of tuples representing deepest nestings nestings = [] current_tiers = [] already_exiting_loops = False - for outline_item in outline: - if isinstance(outline_item, EnterLoop): + for lin_item in linearization: + if isinstance(lin_item, EnterLoop): already_exiting_loops = False - current_tiers.append(outline_item.iname) - elif isinstance(outline_item, LeaveLoop): + current_tiers.append(lin_item.iname) + elif isinstance(lin_item, LeaveLoop): if not already_exiting_loops: nestings.append(tuple(current_tiers)) already_exiting_loops = True @@ -715,7 +808,7 @@ def get_iname_nestings(outline): # }}} -# {{{ get graph sources +# {{{ get_graph_sources def get_graph_sources(graph): sources = set(graph.keys()) diff --git a/test/test_nest_constraints.py b/test/test_nest_constraints.py index 40e52420a..4f00dbac8 100644 --- a/test/test_nest_constraints.py +++ b/test/test_nest_constraints.py @@ -153,17 +153,7 @@ def test_loop_constraint_string_parsing(): "Unrecognized character(s) [\'}\'] in nest string h}" ) in str(e) - # TODO these should pass - """ - try: - lp.constrain_loop_nesting(ref_knl, must_nest=("{h i j,,}", "k")) - assert False - except ValueError as e: - assert("Unrecognized character(s) [\'{\', \'}\'] in nest string {h i j,,}" - ) in str(e) - """ - - # valid syntax + # Valid syntax lp.constrain_loop_nesting(ref_knl, must_not_nest=("~{j,i}", "{j,i}")) lp.constrain_loop_nesting(ref_knl, must_not_nest=("{h}", "{j,i}")) lp.constrain_loop_nesting(ref_knl, must_not_nest=("h", "{j,i}")) @@ -172,7 +162,7 @@ def test_loop_constraint_string_parsing(): lp.constrain_loop_nesting(ref_knl, must_not_nest="~j,j") lp.constrain_loop_nesting(ref_knl, must_nest="k,h,j") - # handling spaces + # Handling spaces knl = lp.constrain_loop_nesting(ref_knl, must_nest=("k", "{h }", " { j , i } ")) assert list(knl.loop_nest_constraints.must_nest)[0][0].inames == set("k") assert list(knl.loop_nest_constraints.must_nest)[0][1].inames == set("h") @@ -205,7 +195,7 @@ def test_loop_nest_constraints_satisfied(): must_nest_constraints = [ process_loop_nest_specification( - nesting=("{g,h}", "~{g,h}"), + nesting=("{g,h}", "{i,j,k}"), complement_sets_allowed=True), ] must_not_nest_constraints = [ @@ -351,7 +341,6 @@ def test_incompatible_nest_constraints(): """, assumptions="n >= 1", ) - ref_knl = lp.add_and_infer_dtypes(ref_knl, {"a,a2,a3": np.dtype(np.float32)}) knl = ref_knl knl = lp.constrain_loop_nesting( knl, must_not_nest=("{k,i}", "~{k,i}")) From c7432a3d579783c105fecd876181b57350a4fa70 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Sat, 8 May 2021 23:22:52 -0500 Subject: [PATCH 53/59] fix some typos --- loopy/transform/iname.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index a5f6176f3..036fdd2ef 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -129,8 +129,8 @@ def __init__(self, inames, complement=False): def contains(self, inames): if isinstance(inames, set): - return (not (iname_set & self.inames) if self.complement - else iname_set.issubset(self.inames)) + return (not (inames & self.inames) if self.complement + else inames.issubset(self.inames)) else: return (inames not in self.inames if self.complement else inames in self.inames) @@ -368,7 +368,7 @@ def _process_iname_set_str(iname_set_str): def constrain_loop_nesting( kernel, must_nest=None, must_not_nest=None): - """Add the provided constraints to the kernel. + r"""Add the provided constraints to the kernel. :arg must_nest: A tuple or comma-separated string representing an ordering of loop nesting tiers that must appear in the @@ -609,7 +609,7 @@ def _expand_iname_sets_in_tuple( # {{{ check_must_nest def check_must_nest(all_loop_nests, must_nest, all_inames): - """Determine whether must_nest constraint is satisfied by + r"""Determine whether must_nest constraint is satisfied by all_loop_nests :arg all_loop_nests: A list of lists of inames, each representing @@ -649,7 +649,7 @@ def check_must_nest(all_loop_nests, must_nest, all_inames): # {{{ check_must_not_nest def check_must_not_nest(all_loop_nests, must_not_nest): - """Determine whether must_not_nest constraint is satisfied by + r"""Determine whether must_not_nest constraint is satisfied by all_loop_nests :arg all_loop_nests: A list of lists of inames, each representing @@ -684,7 +684,7 @@ def check_must_not_nest(all_loop_nests, must_not_nest): # {{{ check_all_must_not_nests def check_all_must_not_nests(all_loop_nests, must_not_nests): - """Determine whether all must_not_nest constraints are satisfied by + r"""Determine whether all must_not_nest constraints are satisfied by all_loop_nests :arg all_loop_nests: A list of lists of inames, each representing @@ -713,7 +713,7 @@ def loop_nest_constraints_satisfied( must_nest_constraints=None, must_not_nest_constraints=None, all_inames=None): - """Determine whether must_not_nest constraint is satisfied by + r"""Determine whether must_not_nest constraint is satisfied by all_loop_nests :arg all_loop_nests: A set of lists of inames, each representing @@ -755,7 +755,7 @@ def loop_nest_constraints_satisfied( def check_must_not_nest_against_must_nest_graph( must_not_nest_constraints, must_nest_graph): - """Ensure none of the must_not_nest constraints are violated by + r"""Ensure none of the must_not_nest constraints are violated by nestings represented in the must_nest_graph :arg must_not_nest_constraints: A set of two-tuples of From e07d0ecf9e324e604c26adb6c277602fddb006a0 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Sat, 8 May 2021 23:38:11 -0500 Subject: [PATCH 54/59] fix typo in comment --- loopy/transform/iname.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index 036fdd2ef..a62ba5e1c 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -346,7 +346,7 @@ def _process_iname_set_str(iname_set_str): nesting_as_tuple = tuple( _process_iname_set_str(set_str) for set_str in nesting) - # Check max_inames_per_set + # Check max_tuple_size if max_tuple_size and len(nesting_as_tuple) > max_tuple_size: _raise_loop_nest_input_error( "Loop nest prioritization tuple %s exceeds max tuple size %d." From c421d3813f5b82e14fa0b0ae64a50334cbbb1ecd Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Thu, 3 Jun 2021 15:39:35 -0500 Subject: [PATCH 55/59] add within_inames arg to add_barrier --- loopy/transform/add_barrier.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/loopy/transform/add_barrier.py b/loopy/transform/add_barrier.py index bc324d7fa..922616dd2 100644 --- a/loopy/transform/add_barrier.py +++ b/loopy/transform/add_barrier.py @@ -35,7 +35,8 @@ # {{{ add_barrier def add_barrier(kernel, insn_before="", insn_after="", id_based_on=None, - tags=None, synchronization_kind="global", mem_kind=None): + tags=None, synchronization_kind="global", mem_kind=None, + within_inames=None): """Takes in a kernel that needs to be added a barrier and returns a kernel which has a barrier inserted into it. It takes input of 2 instructions and then adds a barrier in between those 2 instructions. The expressions can @@ -49,8 +50,11 @@ def add_barrier(kernel, insn_before="", insn_after="", id_based_on=None, :arg tags: The tag of the group to which the barrier must be added :arg synchronization_kind: Kind of barrier to be added. May be "global" or "local" - :arg kind: Type of memory to be synchronied. May be "global" or "local". Ignored - for "global" bariers. If not supplied, defaults to *synchronization_kind* + :arg kind: Type of memory to be synchronized. May be "global" or "local". Ignored + for "global" barriers. If not supplied, defaults to *synchronization_kind* + :arg within_inames: A :class:`frozenset` of inames identifying the loops + within which the barrier will be executed. + """ if mem_kind is None: @@ -69,6 +73,7 @@ def add_barrier(kernel, insn_before="", insn_after="", id_based_on=None, barrier_to_add = BarrierInstruction(depends_on=frozenset(insn_before_list), depends_on_is_final=True, id=id, + within_inames=within_inames, tags=tags, synchronization_kind=synchronization_kind, mem_kind=mem_kind) From 6377454e08e6c5bc03b237616f646bfbb731e89c Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Wed, 30 Jun 2021 17:27:54 -0500 Subject: [PATCH 56/59] count globals pretending to be temporary variables --- loopy/statistics.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/loopy/statistics.py b/loopy/statistics.py index ef335abce..fbd44bdb3 100755 --- a/loopy/statistics.py +++ b/loopy/statistics.py @@ -1069,13 +1069,23 @@ def map_subscript(self, expr): except AttributeError: var_tags = frozenset() + is_temp = False if name in self.knl.arg_dict: array = self.knl.arg_dict[name] + elif name in self.knl.temporary_variables: + # this a temporary variable, but might have global address space + from loopy.kernel.data import AddressSpace + array = self.knl.temporary_variables[name] + if array.address_space != AddressSpace.GLOBAL: + # this is a temporary variable + return self.rec(expr.index) + # this is a temporary variable with global address space + is_temp = True else: # this is a temporary variable return self.rec(expr.index) - if not isinstance(array, lp.ArrayArg): + if (not is_temp) and not isinstance(array, lp.ArrayArg): # this array is not in global memory return self.rec(expr.index) From 2f7c583ca17dcd4107475e2dec6730a5cf7a239f Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Jul 2021 19:00:26 -0500 Subject: [PATCH 57/59] add @for_each_kernel to constrain_loop_nesting --- loopy/transform/iname.py | 1 + 1 file changed, 1 insertion(+) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index 7a56e9679..16847a3b6 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -377,6 +377,7 @@ def _process_iname_set_str(iname_set_str): # {{{ constrain_loop_nesting +@for_each_kernel def constrain_loop_nesting( kernel, must_nest=None, must_not_nest=None): r"""Add the provided constraints to the kernel. From 5fe7e52225e5c48a8225e147051775bffd56145a Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Jul 2021 19:01:00 -0500 Subject: [PATCH 58/59] fixes after kernel callables update --- test/test_nest_constraints.py | 145 +++++++++++++++++++--------------- 1 file changed, 82 insertions(+), 63 deletions(-) diff --git a/test/test_nest_constraints.py b/test/test_nest_constraints.py index 4f00dbac8..63913b1b4 100644 --- a/test/test_nest_constraints.py +++ b/test/test_nest_constraints.py @@ -24,6 +24,7 @@ import loopy as lp import numpy as np import pyopencl as cl +from loopy import preprocess_kernel, get_one_linearized_kernel import logging logger = logging.getLogger(__name__) @@ -47,6 +48,24 @@ from loopy.version import LOOPY_USE_LANGUAGE_VERSION_2018_2 # noqa +# {{{ Helper functions + +def _process_and_linearize(prog, knl_name="loopy_kernel"): + # Return linearized kernel + proc_prog = preprocess_kernel(prog) + lin_prog = get_one_linearized_kernel( + proc_prog[knl_name], proc_prog.callables_table) + return lin_prog + + +def _linearize_and_get_nestings(prog, knl_name="loopy_kernel"): + from loopy.transform.iname import get_iname_nestings + lin_knl = _process_and_linearize(prog, knl_name) + return get_iname_nestings(lin_knl.linearization) + +# }}} + + # {{{ test_loop_constraint_string_parsing def test_loop_constraint_string_parsing(): @@ -58,80 +77,80 @@ def test_loop_constraint_string_parsing(): try: lp.constrain_loop_nesting(ref_knl, "{g,h,k},{j,i}") - assert False + raise AssertionError() except ValueError as e: assert "Unrecognized character(s)" in str(e) try: lp.constrain_loop_nesting(ref_knl, "{g,h,i,k},{j}") - assert False + raise AssertionError() except ValueError as e: assert "Unrecognized character(s)" in str(e) try: lp.constrain_loop_nesting(ref_knl, "{g,{h,i,k}") - assert False + raise AssertionError() except ValueError as e: assert "Unrecognized character(s)" in str(e) try: lp.constrain_loop_nesting(ref_knl, "{g,~h,i,k}") - assert False + raise AssertionError() except ValueError as e: assert "Unrecognized character(s)" in str(e) try: lp.constrain_loop_nesting(ref_knl, "{g,#h,i,k}") - assert False + raise AssertionError() except ValueError as e: assert "Unrecognized character(s)" in str(e) try: lp.constrain_loop_nesting(ref_knl, ("{g,{h}", "i,k")) - assert False + raise AssertionError() except ValueError as e: assert "Unrecognized character(s)" in str(e) try: lp.constrain_loop_nesting(ref_knl, ("{g,~h}", "i,k")) - assert False + raise AssertionError() except ValueError as e: assert "Unrecognized character(s)" in str(e) try: lp.constrain_loop_nesting(ref_knl, ("k", "~{g,h}", "{g,h}")) - assert False + raise AssertionError() except ValueError as e: assert "Complement (~) not allowed" in str(e) try: lp.constrain_loop_nesting(ref_knl, ("k", "{i,j,k}", "{g,h}")) - assert False + raise AssertionError() except ValueError as e: assert "contains cycle" in str(e) try: lp.constrain_loop_nesting(ref_knl, must_not_nest=("~j,i", "{j,i}")) - assert False + raise AssertionError() except ValueError as e: assert ("Complements of sets containing multiple inames " "must enclose inames in braces") in str(e) try: lp.constrain_loop_nesting(ref_knl, must_nest=("k", "{h}", "{j,i,}")) - assert False + raise AssertionError() except ValueError as e: assert ("Found 2 inames but expected 3") in str(e) try: lp.constrain_loop_nesting(ref_knl, must_nest=("k", "{h}", "{j, x x, i}")) - assert False + raise AssertionError() except ValueError as e: assert ("Found 4 inames but expected 3") in str(e) try: lp.constrain_loop_nesting(ref_knl, must_nest="{h}}") - assert False + raise AssertionError() except ValueError as e: assert ( "Unrecognized character(s) ['{', '}', '}'] in nest string {h}}" @@ -139,7 +158,7 @@ def test_loop_constraint_string_parsing(): try: lp.constrain_loop_nesting(ref_knl, must_nest="{h i j,,}") - assert False + raise AssertionError() except ValueError as e: assert( "Unrecognized character(s) [\'{\', \'}\'] in nest string {h i j,,}" @@ -147,7 +166,7 @@ def test_loop_constraint_string_parsing(): try: lp.constrain_loop_nesting(ref_knl, must_nest=("{h}}", "i")) - assert False + raise AssertionError() except ValueError as e: assert ( "Unrecognized character(s) [\'}\'] in nest string h}" @@ -163,20 +182,21 @@ def test_loop_constraint_string_parsing(): lp.constrain_loop_nesting(ref_knl, must_nest="k,h,j") # Handling spaces - knl = lp.constrain_loop_nesting(ref_knl, must_nest=("k", "{h }", " { j , i } ")) + knl = lp.constrain_loop_nesting( + ref_knl, must_nest=("k", "{h }", " { j , i } "))["loopy_kernel"] assert list(knl.loop_nest_constraints.must_nest)[0][0].inames == set("k") assert list(knl.loop_nest_constraints.must_nest)[0][1].inames == set("h") assert list(knl.loop_nest_constraints.must_nest)[0][2].inames == set(["j", "i"]) try: knl = lp.constrain_loop_nesting(ref_knl, ("j", "{}")) - assert False + raise AssertionError() except ValueError as e: assert "Found 0 inames" in str(e) try: knl = lp.constrain_loop_nesting(ref_knl, ("j", "")) - assert False + raise AssertionError() except ValueError as e: assert "Found 0 inames" in str(e) @@ -302,7 +322,7 @@ def test_adding_multiple_nest_constraints_to_knl(): knl = lp.constrain_loop_nesting( knl, must_nest=("x", "y")) - must_nest_knl = knl.loop_nest_constraints.must_nest + must_nest_knl = knl["loopy_kernel"].loop_nest_constraints.must_nest from loopy.transform.iname import UnexpandedInameSet must_nest_expected = set([ (UnexpandedInameSet(set(["g"], )), UnexpandedInameSet(set(["h", "i"], ))), @@ -315,7 +335,7 @@ def test_adding_multiple_nest_constraints_to_knl(): ]) assert must_nest_knl == must_nest_expected - must_not_nest_knl = knl.loop_nest_constraints.must_not_nest + must_not_nest_knl = knl["loopy_kernel"].loop_nest_constraints.must_not_nest must_not_nest_expected = set([ (UnexpandedInameSet(set(["k", "i"], )), UnexpandedInameSet(set(["k", "i"], ), complement=True)), @@ -348,7 +368,7 @@ def test_incompatible_nest_constraints(): try: knl = lp.constrain_loop_nesting( knl, must_nest=("k", "h")) # (should fail) - assert False + raise AssertionError() except ValueError as e: assert "Nest constraint conflict detected" in str(e) @@ -359,7 +379,7 @@ def test_incompatible_nest_constraints(): try: knl = lp.constrain_loop_nesting( knl, must_nest=("j", "g")) # (should fail) - assert False + raise AssertionError() except ValueError as e: assert "Nest constraint cycle detected" in str(e) @@ -407,24 +427,24 @@ def is_innermost(iname, lin_items): knl = ref_knl knl = lp.tag_inames(knl, {"h": "vec"}) - lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + lin_knl = _process_and_linearize(knl) assert is_innermost("h", lin_knl.linearization) knl = ref_knl knl = lp.tag_inames(knl, {"h": "vec", "g": "l.1", "i": "l.0"}) - lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + lin_knl = _process_and_linearize(knl) assert is_innermost("h", lin_knl.linearization) knl = ref_knl knl = lp.tag_inames( knl, {"h": "vec", "g": "l.1", "i": "l.0", "k": "unr"}) - lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + lin_knl = _process_and_linearize(knl) assert is_innermost("h", lin_knl.linearization) knl = ref_knl knl = lp.tag_inames(knl, {"h": "vec"}) knl = lp.constrain_loop_nesting(knl, must_nest=("k", "i")) - lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + lin_knl = _process_and_linearize(knl) assert is_innermost("h", lin_knl.linearization) lp.set_caching_enabled(True) @@ -433,7 +453,7 @@ def is_innermost(iname, lin_items): knl = lp.tag_inames(knl, {"h": "vec"}) try: lp.constrain_loop_nesting(knl, must_nest=("{g,h,i,j}", "{k}")) - assert False + raise AssertionError() except ValueError as e: assert ( "iname h tagged with ConcurrentTag, " @@ -446,7 +466,7 @@ def is_innermost(iname, lin_items): knl = lp.constrain_loop_nesting(knl, must_nest=("{g,h,i,j}", "{k}")) try: lp.tag_inames(knl, {"h": "vec"}) - assert False + raise AssertionError() except ValueError as e: assert ( "cannot tag 'h' as concurrent--iname involved " @@ -487,7 +507,7 @@ def loop_order(lin_items): knl, must_nest=("i", "j", "h", "k", "g"), ) - lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + lin_knl = _process_and_linearize(knl) assert loop_order(lin_knl.linearization) == ["i", "j", "h", "k", "g"] knl = ref_knl @@ -495,7 +515,7 @@ def loop_order(lin_items): knl, must_nest=("k", "{g, h, i, j}"), ) - lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + lin_knl = _process_and_linearize(knl) assert loop_order(lin_knl.linearization)[0] == "k" knl = ref_knl @@ -503,7 +523,7 @@ def loop_order(lin_items): knl, must_nest=("{g, h, i, j}", "k"), ) - lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + lin_knl = _process_and_linearize(knl) assert loop_order(lin_knl.linearization)[-1] == "k" knl = ref_knl @@ -511,7 +531,7 @@ def loop_order(lin_items): knl, must_nest=("{g, h, i}", "{j, k}"), ) - lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + lin_knl = _process_and_linearize(knl) assert set(loop_order(lin_knl.linearization)[-2:]) == set(["j", "k"]) knl = ref_knl @@ -523,7 +543,7 @@ def loop_order(lin_items): knl, must_nest=("i", "{g, h}"), ) - lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + lin_knl = _process_and_linearize(knl) assert set(loop_order(lin_knl.linearization)[3:]) == set(["j", "k"]) assert set(loop_order(lin_knl.linearization)[1:3]) == set(["g", "h"]) assert loop_order(lin_knl.linearization)[0] == "i" @@ -533,7 +553,7 @@ def loop_order(lin_items): knl, must_nest=("i", "{g, h}", "{j, k}"), ) - lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + lin_knl = _process_and_linearize(knl) assert set(loop_order(lin_knl.linearization)[3:]) == set(["j", "k"]) assert set(loop_order(lin_knl.linearization)[1:3]) == set(["g", "h"]) assert loop_order(lin_knl.linearization)[0] == "i" @@ -545,7 +565,7 @@ def loop_order(lin_items): knl, must_not_nest=("~k", "k"), ) - lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + lin_knl = _process_and_linearize(knl) assert loop_order(lin_knl.linearization)[0] == "k" knl = ref_knl @@ -553,7 +573,7 @@ def loop_order(lin_items): knl, must_not_nest=("k", "~k"), ) - lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + lin_knl = _process_and_linearize(knl) assert loop_order(lin_knl.linearization)[-1] == "k" knl = ref_knl @@ -561,7 +581,7 @@ def loop_order(lin_items): knl, must_not_nest=("{j, k}", "~{j, k}"), ) - lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + lin_knl = _process_and_linearize(knl) assert set(loop_order(lin_knl.linearization)[-2:]) == set(["j", "k"]) knl = ref_knl @@ -573,7 +593,7 @@ def loop_order(lin_items): knl, must_nest=("i", "{g, h}"), ) - lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + lin_knl = _process_and_linearize(knl) assert set(loop_order(lin_knl.linearization)[3:]) == set(["j", "k"]) assert set(loop_order(lin_knl.linearization)[1:3]) == set(["g", "h"]) assert loop_order(lin_knl.linearization)[0] == "i" @@ -585,7 +605,7 @@ def loop_order(lin_items): must_nest=("{g, h, i}", "{j, k}"), must_not_nest=("i", "{g, h}"), ) - lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + lin_knl = _process_and_linearize(knl) assert set(loop_order(lin_knl.linearization)[3:]) == set(["j", "k"]) assert set(loop_order(lin_knl.linearization)[0:2]) == set(["g", "h"]) assert loop_order(lin_knl.linearization)[2] == "i" @@ -595,7 +615,7 @@ def loop_order(lin_items): knl, must_not_nest=("i", "~i"), ) - lin_knl = lp.get_one_linearized_kernel(lp.preprocess_kernel(knl)) + lin_knl = _process_and_linearize(knl) assert loop_order(lin_knl.linearization)[-1] == "i" # contradictory must_not_nest @@ -611,11 +631,13 @@ def loop_order(lin_items): ) try: - lp.get_one_linearized_kernel( - lp.preprocess_kernel(knl), + proc_prog = preprocess_kernel(knl) + get_one_linearized_kernel( + proc_prog["loopy_kernel"], + proc_prog.callables_table, debug_args={"interactive": False}, ) - assert False + raise AssertionError() except RuntimeError as e: assert "no valid schedules found" in str(e) @@ -625,17 +647,6 @@ def loop_order(lin_items): # {{{ test constraint updating during transformation -# {{{ helper functions - -def _linearize_and_get_nestings(unlinearized_knl): - from loopy.transform.iname import get_iname_nestings - lin_knl = lp.get_one_linearized_kernel( - lp.preprocess_kernel(unlinearized_knl)) - return get_iname_nestings(lin_knl.linearization) - -# }}} - - # {{{ test_constraint_updating_split_iname def test_constraint_updating_split_iname(): @@ -770,7 +781,8 @@ def test_constraint_updating_duplicate_inames(): (iname, set()) for iname in ["g", "h", "j", "k", "gg", "hh"]]) must_nest_graph_exp["i"] = set(["g", "h", "j", "k", "gg", "hh"]) - assert knl.loop_nest_constraints.must_nest_graph == must_nest_graph_exp + assert knl[ + "loopy_kernel"].loop_nest_constraints.must_nest_graph == must_nest_graph_exp nesting_for_insn, nesting_for_insn0 = _linearize_and_get_nestings(knl) @@ -802,7 +814,8 @@ def test_constraint_updating_duplicate_inames(): (iname, set()) for iname in ["j", "k", "gg", "hh"]]) must_nest_graph_exp["i"] = set(["j", "k", "gg", "hh"]) - assert knl.loop_nest_constraints.must_nest_graph == must_nest_graph_exp + assert knl[ + "loopy_kernel"].loop_nest_constraints.must_nest_graph == must_nest_graph_exp loop_nestings = _linearize_and_get_nestings(knl) assert len(loop_nestings) == 1 @@ -891,7 +904,7 @@ def test_constraint_handling_tag_inames(): ) try: lp.tag_inames(knl, {"i": "l.0"}) - assert False + raise AssertionError() except ValueError as e: assert ( "cannot tag 'i' as concurrent--iname involved in must-nest constraint" @@ -965,7 +978,7 @@ def test_constraint_updating_join_inames(): ) try: lp.join_inames(knl, inames=["i", "k"], new_iname="ik") - assert False + raise AssertionError() except ValueError as e: assert "cycle" in str(e) @@ -977,7 +990,7 @@ def test_constraint_updating_join_inames(): ) try: lp.join_inames(knl, inames=["i", "k"], new_iname="ik") - assert False + raise AssertionError() except ValueError as e: assert "Implied nestings violate existing must-not-nest" in str(e) @@ -1012,6 +1025,7 @@ def get_sets_of_inames(iname_sets_tuple, iname_universe): must_nest=("i", "g", "h", "j", "k"), ) knl = lp.join_inames(knl, inames=["g", "h"], new_iname="gh") + knl = knl["loopy_kernel"] new_must_nest = get_sets_of_inames( list(knl.loop_nest_constraints.must_nest)[0], knl.all_inames()) expected_must_nest = [ @@ -1024,6 +1038,7 @@ def get_sets_of_inames(iname_sets_tuple, iname_universe): must_nest=("{i, g}", "h", "j", "k"), ) knl = lp.join_inames(knl, inames=["g", "h"], new_iname="gh") + knl = knl["loopy_kernel"] new_must_nest = get_sets_of_inames( list(knl.loop_nest_constraints.must_nest)[0], knl.all_inames()) expected_must_nest = [ @@ -1036,6 +1051,7 @@ def get_sets_of_inames(iname_sets_tuple, iname_universe): must_nest=("i", "g", "{h, j}", "k"), ) knl = lp.join_inames(knl, inames=["g", "h"], new_iname="gh") + knl = knl["loopy_kernel"] new_must_nest = get_sets_of_inames( list(knl.loop_nest_constraints.must_nest)[0], knl.all_inames()) expected_must_nest = [ @@ -1048,6 +1064,7 @@ def get_sets_of_inames(iname_sets_tuple, iname_universe): must_nest=("i", "g", "{h, j, k}"), ) knl = lp.join_inames(knl, inames=["g", "h"], new_iname="gh") + knl = knl["loopy_kernel"] new_must_nest = get_sets_of_inames( list(knl.loop_nest_constraints.must_nest)[0], knl.all_inames()) expected_must_nest = [ @@ -1060,6 +1077,7 @@ def get_sets_of_inames(iname_sets_tuple, iname_universe): must_nest=("i", "{g, h}", "j", "k"), ) knl = lp.join_inames(knl, inames=["g", "h"], new_iname="gh") + knl = knl["loopy_kernel"] new_must_nest = get_sets_of_inames( list(knl.loop_nest_constraints.must_nest)[0], knl.all_inames()) expected_must_nest = [ @@ -1072,6 +1090,7 @@ def get_sets_of_inames(iname_sets_tuple, iname_universe): must_nest=("{i, g}", "{h, j, k}"), ) knl = lp.join_inames(knl, inames=["g", "h"], new_iname="gh") + knl = knl["loopy_kernel"] new_must_nest = get_sets_of_inames( list(knl.loop_nest_constraints.must_nest)[0], knl.all_inames()) expected_must_nest = [ @@ -1085,7 +1104,7 @@ def get_sets_of_inames(iname_sets_tuple, iname_universe): ) try: knl = lp.join_inames(knl, inames=["g", "h"], new_iname="gh") - assert False + raise AssertionError() except ValueError as e: assert "contains cycle" in str(e) @@ -1096,7 +1115,7 @@ def get_sets_of_inames(iname_sets_tuple, iname_universe): ) try: knl = lp.join_inames(knl, inames=["g", "h"], new_iname="gh") - assert False + raise AssertionError() except ValueError as e: assert "contains cycle" in str(e) @@ -1107,7 +1126,7 @@ def get_sets_of_inames(iname_sets_tuple, iname_universe): ) try: knl = lp.join_inames(knl, inames=["g", "h"], new_iname="gh") - assert False + raise AssertionError() except ValueError as e: assert "nestings violate existing must-nest" in str(e) @@ -1118,7 +1137,7 @@ def get_sets_of_inames(iname_sets_tuple, iname_universe): ) try: knl = lp.join_inames(knl, inames=["g", "h"], new_iname="gh") - assert False + raise AssertionError() except ValueError as e: assert "nestings violate existing must-not-nest" in str(e) From 2f5e5c0a874766968869efb3c3c7691d97f437ff Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Mon, 26 Jul 2021 19:02:31 -0500 Subject: [PATCH 59/59] add TODO --- test/test_nest_constraints.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_nest_constraints.py b/test/test_nest_constraints.py index 63913b1b4..a931e9e72 100644 --- a/test/test_nest_constraints.py +++ b/test/test_nest_constraints.py @@ -954,6 +954,7 @@ def test_constraint_updating_join_inames(): must_nest=("{g, h, i}", "{j, k}"), ) knl = lp.join_inames(knl, inames=["j", "k"], new_iname="jk") + # TODO figure out reason for jk key error loop_nesting = _linearize_and_get_nestings(knl)[0] # only one nesting assert loop_nesting[0] == "i" assert loop_nesting[1:3] == ("g", "h")