예제 #1
0
파일: subsets.py 프로젝트: gronerl/dace
 def __init__(self, indices):
     if indices is None or len(indices) == 0:
         raise TypeError('Expected an array of index expressions: got empty'
                         ' array or None')
     if isinstance(indices, str):
         raise TypeError("Expected collection of index expression: got str")
     if isinstance(indices, tuple):
         self.indices = symbolic.SymExpr(indices[0], indices[1])
     else:
         self.indices = symbolic.pystr_to_symbolic(indices)
     self.tile_sizes = [1]
예제 #2
0
    def from_string(string):

        # The following code uses regular expressions in order to support the
        # use of comma not only for separating range dimensions, but also
        # inside function calls.

        # Example (with 2 dimensions):
        # tile_i * ts_i : min(int_ceil(M, rs_i), tile_i * ts_i + ts_i),
        # regtile_j * rs_j : min(K, regtile_j * rs_j + rs_j)

        ranges = []

        # Split string to tokens separated by colons.
        # tokens = [
        #   'tile_i * ts_i ',
        #   'min(int_ceil(M, rs_i), tile_i * ts_i + ts_i), regtile_j * rs_j ',
        #   'min(K, regtile_j * rs_j + rs_j)'
        # ]
        tokens = string.split(':')

        # In the example, the second token must be split to 2 separate tokens.

        # List of list of tokens (one list per range dimension)
        multi_dim_tokens = []
        # List of tokens (single dimension)
        uni_dim_tokens = []

        for token in tokens:

            i = 0  # Character index in the token
            count = 0  # Number of open parenthesis

            while i < len(token):
                # Comma found while not in a function or any other expression
                # with parenthesis. This is a comma separating range dimensions.
                if token[i] == ',' and count == 0:
                    # Split the token to token[:i] and token[i+1:]
                    # Append token[:i] to the current range dimension
                    uni_dim_tokens.append(token[0:i])
                    # Append current range dimension to the list of lists
                    multi_dim_tokens.append(uni_dim_tokens)
                    # Start a new range dimension
                    uni_dim_tokens = []
                    # Adjust the token
                    token = token[i + 1:]
                    i = 0
                    continue
                # Open parenthesis found, increase count by 1
                if token[i] == '(':
                    count += 1
                # Closing parenthesis found, decrease cound by 1
                elif token[i] == ')':
                    count -= 1
                # Move to the next character
                i += 1

            # Append token to the current range dimension
            uni_dim_tokens.append(token)

        # Append current range dimension to the list of lists
        multi_dim_tokens.append(uni_dim_tokens)

        # Generate ranges
        for uni_dim_tokens in multi_dim_tokens:
            # If dimension has only 1 token, then it is an index (not a range),
            # treat as range of size 1
            if len(uni_dim_tokens) < 2:
                ranges.append(
                    (symbolic.pystr_to_symbolic(uni_dim_tokens[0]),
                     symbolic.pystr_to_symbolic(uni_dim_tokens[0]), 1))
                continue
                #return Range(ranges)
            # If dimension has more than 4 tokens, the range is invalid
            if len(uni_dim_tokens) > 4:
                raise SyntaxError("Invalid range: {}".format(multi_dim_tokens))
            # Support for SymExpr
            tokens = []
            for token in uni_dim_tokens:
                expr = token.split('|')
                if len(expr) == 1:
                    tokens.append(expr[0])
                elif len(expr) == 2:
                    tokens.append((expr[0], expr[1]))
                else:
                    raise SyntaxError(
                        "Invalid range: {}".format(multi_dim_tokens))
            # Parse tokens
            try:
                if isinstance(tokens[0], tuple):
                    begin = symbolic.SymExpr(tokens[0][0], tokens[0][1])
                else:
                    begin = symbolic.pystr_to_symbolic(tokens[0])
                if len(tokens) >= 3:
                    if isinstance(tokens[2], tuple):
                        step = symbolic.SymExpr(tokens[2][0], tokens[2][1])
                    else:
                        step = symbolic.SymExpr(tokens[2])
                else:
                    step = 1
                eoff = -1
                if (step < 0) == True:
                    eoff = 1
                if isinstance(tokens[1], tuple):
                    end = symbolic.SymExpr(tokens[1][0], tokens[1][1]) + eoff
                else:
                    end = symbolic.pystr_to_symbolic(tokens[1]) + eoff
                if len(tokens) >= 4:
                    if isinstance(tokens[3], tuple):
                        tsize = tokens[3][0]
                    else:
                        tsize = tokens[3]
                else:
                    tsize = 1
            except sympy.SympifyError:
                raise SyntaxError("Invalid range: {}".format(string))
            # Append range
            ranges.append((begin, end, step, tsize))

        return Range(ranges)
예제 #3
0
 def p2s(x):
     pts = symbolic.pystr_to_symbolic
     if isinstance(x, str):
         return pts(x)
     else:
         return symbolic.SymExpr(pts(x['main']), pts(x['approx']))
예제 #4
0
def _tuple_to_symexpr(val):
    return (symbolic.SymExpr(val[0], val[1])
            if isinstance(val, tuple) else symbolic.pystr_to_symbolic(val))
예제 #5
0
    def apply(self, sdfg):
        graph = sdfg.nodes()[self.state_id]
        subgraph = self.subgraph_view(sdfg)
        map_entries = helpers.get_outermost_scope_maps(sdfg, graph, subgraph)

        result = StencilTiling.topology(sdfg, graph, map_entries)
        (children_dict, parent_dict, sink_maps) = result

        # next up, calculate inferred ranges for each map
        # for each map entry, this contains a tuple of dicts:
        # each of those maps from data_name of the array to
        # inferred outer ranges. An inferred outer range is created
        # by taking the union of ranges of inner subsets corresponding
        # to that data and substituting this subset by the min / max of the
        # parametrized map boundaries
        # finally, from these outer ranges we can easily calculate
        # strides and tile sizes required for every map
        inferred_ranges = defaultdict(dict)

        # create array of reverse topologically sorted map entries
        # to iterate over
        topo_reversed = []
        queue = set(sink_maps.copy())
        while len(queue) > 0:
            element = next(e for e in queue
                           if not children_dict[e] - set(topo_reversed))
            topo_reversed.append(element)
            queue.remove(element)
            for parent in parent_dict[element]:
                queue.add(parent)

        # main loop
        # first get coverage dicts for each map entry
        # for each map, contains a tuple of two dicts
        # each of those two maps from data name to outer range
        coverage = {}
        for map_entry in map_entries:
            coverage[map_entry] = StencilTiling.coverage_dicts(
                sdfg, graph, map_entry, outer_range=True)

        # we have a mapping from data name to outer range
        # however we want a mapping from map parameters to outer ranges
        # for this we need to find out how all array dimensions map to
        # outer ranges

        variable_mapping = defaultdict(list)
        for map_entry in topo_reversed:
            map = map_entry.map

            # first find out variable mapping
            for e in itertools.chain(
                    graph.out_edges(map_entry),
                    graph.in_edges(graph.exit_node(map_entry))):
                mapping = []
                for dim in e.data.subset:
                    syms = set()
                    for d in dim:
                        syms |= symbolic.symlist(d).keys()
                    if len(syms) > 1:
                        raise NotImplementedError(
                            "One incoming or outgoing stencil subset is indexed "
                            "by multiple map parameters. "
                            "This is not supported yet.")
                    try:
                        mapping.append(syms.pop())
                    except KeyError:
                        # just append None if there is no map symbol in it.
                        # we don't care for now.
                        mapping.append(None)

                if e.data in variable_mapping:
                    # assert that this is the same everywhere.
                    # else we might run into problems
                    assert variable_mapping[e.data.data] == mapping
                else:
                    variable_mapping[e.data.data] = mapping

            # now do mapping data name -> outer range
            # and from that infer mapping variable -> outer range
            local_ranges = {dn: None for dn in coverage[map_entry][1].keys()}
            for data_name, cov in coverage[map_entry][1].items():
                local_ranges[data_name] = subsets.union(
                    local_ranges[data_name], cov)
                # now look at proceeding maps
                # and union those subsets -> could be larger with stencil indent
                for child_map in children_dict[map_entry]:
                    if data_name in coverage[child_map][0]:
                        local_ranges[data_name] = subsets.union(
                            local_ranges[data_name],
                            coverage[child_map][0][data_name])

            # final assignent: combine local_ranges and variable_mapping
            # together into inferred_ranges
            inferred_ranges[map_entry] = {p: None for p in map.params}
            for data_name, ranges in local_ranges.items():
                for param, r in zip(variable_mapping[data_name], ranges):
                    # create new range from this subset and assign
                    rng = subsets.Range((r, ))
                    if param:
                        inferred_ranges[map_entry][param] = subsets.union(
                            inferred_ranges[map_entry][param], rng)

        # get parameters -- should all be the same
        params = next(iter(map_entries)).map.params.copy()
        # define reference range as inferred range of one of the sink maps
        self.reference_range = inferred_ranges[next(iter(sink_maps))]
        if self.debug:
            print("StencilTiling::Reference Range", self.reference_range)
        # next up, search for the ranges that don't change
        invariant_dims = []
        for idx, p in enumerate(params):
            different = False
            if self.reference_range[p] is None:
                invariant_dims.append(idx)
                warnings.warn(
                    f"StencilTiling::No Stencil pattern detected for parameter {p}"
                )
                continue
            for m in map_entries:
                if inferred_ranges[m][p] != self.reference_range[p]:
                    different = True
                    break
            if not different:
                invariant_dims.append(idx)
                warnings.warn(
                    f"StencilTiling::No Stencil pattern detected for parameter {p}"
                )

        # during stripmining, we will create new outer map entries
        # for easy access
        self._outer_entries = set()
        # with inferred_ranges constructed, we can begin to strip mine
        for map_entry in map_entries:
            # Retrieve map entry and exit nodes.
            map = map_entry.map

            stripmine_subgraph = {
                StripMining._map_entry: graph.nodes().index(map_entry)
            }

            sdfg_id = sdfg.sdfg_id
            last_map_entry = None
            original_schedule = map_entry.schedule
            self.tile_sizes = []
            self.tile_offset_lower = []
            self.tile_offset_upper = []

            # strip mining each dimension where necessary
            removed_maps = 0
            for dim_idx, param in enumerate(map_entry.map.params):
                # get current_node tile size
                if dim_idx >= len(self.strides):
                    tile_stride = symbolic.pystr_to_symbolic(self.strides[-1])
                else:
                    tile_stride = symbolic.pystr_to_symbolic(
                        self.strides[dim_idx])

                trivial = False

                if dim_idx in invariant_dims:
                    self.tile_sizes.append(tile_stride)
                    self.tile_offset_lower.append(0)
                    self.tile_offset_upper.append(0)
                else:
                    target_range_current = inferred_ranges[map_entry][param]
                    reference_range_current = self.reference_range[param]

                    min_diff = symbolic.SymExpr(reference_range_current.min_element()[0] \
                                    - target_range_current.min_element()[0])
                    max_diff = symbolic.SymExpr(target_range_current.max_element()[0] \
                                    - reference_range_current.max_element()[0])

                    try:
                        min_diff = symbolic.evaluate(min_diff, {})
                        max_diff = symbolic.evaluate(max_diff, {})
                    except TypeError:
                        raise RuntimeError("Symbolic evaluation of map "
                                           "ranges failed. Please check "
                                           "your parameters and match.")

                    self.tile_sizes.append(tile_stride + max_diff + min_diff)
                    self.tile_offset_lower.append(
                        symbolic.pystr_to_symbolic(str(min_diff)))
                    self.tile_offset_upper.append(
                        symbolic.pystr_to_symbolic(str(max_diff)))

                # get calculated parameters
                tile_size = self.tile_sizes[-1]

                dim_idx -= removed_maps
                # If map or tile sizes are trivial, skip strip-mining map dimension
                # special cases:
                # if tile size is trivial AND we have an invariant dimension, skip
                if tile_size == map.range.size()[dim_idx] and (
                        dim_idx + removed_maps) in invariant_dims:
                    continue

                # trivial map: we just continue
                if map.range.size()[dim_idx] in [0, 1]:
                    continue

                if tile_size == 1 and tile_stride == 1 and (
                        dim_idx + removed_maps) in invariant_dims:
                    trivial = True
                    removed_maps += 1

                # indent all map ranges accordingly and then perform
                # strip mining on these. Offset inner maps accordingly afterwards

                range_tuple = (map.range[dim_idx][0] +
                               self.tile_offset_lower[-1],
                               map.range[dim_idx][1] -
                               self.tile_offset_upper[-1],
                               map.range[dim_idx][2])
                map.range[dim_idx] = range_tuple
                stripmine = StripMining(sdfg_id, self.state_id,
                                        stripmine_subgraph, 0)

                stripmine.tiling_type = 'ceilrange'
                stripmine.dim_idx = dim_idx
                stripmine.new_dim_prefix = self.prefix if not trivial else ''
                # use tile_stride for both -- we will extend
                # the inner tiles later
                stripmine.tile_size = str(tile_stride)
                stripmine.tile_stride = str(tile_stride)
                outer_map = stripmine.apply(sdfg)
                outer_map.schedule = original_schedule

                # apply to the new map the schedule of the original one
                map_entry.schedule = self.schedule

                # if tile stride is 1, we can make a nice simplification by just
                # taking the overapproximated inner range as inner range
                # this eliminates the min/max in the range which
                # enables loop unrolling
                if tile_stride == 1:
                    map_entry.range[dim_idx] = tuple(
                        symbolic.SymExpr(el._approx_expr) if isinstance(
                            el, symbolic.SymExpr) else el
                        for el in map_entry.range[dim_idx])

                # in map_entry: enlarge tiles by upper and lower offset
                # doing it this way and not via stripmine strides ensures
                # that the max gets changed as well
                old_range = map_entry.range[dim_idx]
                map_entry.range[dim_idx] = ((old_range[0] -
                                             self.tile_offset_lower[-1]),
                                            (old_range[1] +
                                             self.tile_offset_upper[-1]),
                                            old_range[2])

                # We have to propagate here for correct outer volume and subset sizes
                _propagate_node(graph, map_entry)
                _propagate_node(graph, graph.exit_node(map_entry))

                # usual tiling pipeline
                if last_map_entry:
                    new_map_entry = graph.in_edges(map_entry)[0].src
                    mapcollapse_subgraph = {
                        MapCollapse._outer_map_entry:
                        graph.node_id(last_map_entry),
                        MapCollapse._inner_map_entry:
                        graph.node_id(new_map_entry)
                    }
                    mapcollapse = MapCollapse(sdfg_id, self.state_id,
                                              mapcollapse_subgraph, 0)
                    mapcollapse.apply(sdfg)
                last_map_entry = graph.in_edges(map_entry)[0].src
            # add last instance of map entries to _outer_entries
            if last_map_entry:
                self._outer_entries.add(last_map_entry)

            # Map Unroll Feature: only unroll if conditions are met:
            # Only unroll if at least one of the inner map ranges is strictly larger than 1
            # Only unroll if strides all are one
            if self.unroll_loops and all(s == 1 for s in self.strides) and any(
                    s not in [0, 1] for s in map_entry.range.size()):
                l = len(map_entry.params)
                if l > 1:
                    subgraph = {
                        MapExpansion.map_entry: graph.nodes().index(map_entry)
                    }
                    trafo_expansion = MapExpansion(sdfg.sdfg_id,
                                                   sdfg.nodes().index(graph),
                                                   subgraph, 0)
                    trafo_expansion.apply(sdfg)
                maps = [map_entry]
                for _ in range(l - 1):
                    map_entry = graph.out_edges(map_entry)[0].dst
                    maps.append(map_entry)

                for map in reversed(maps):
                    # MapToForLoop
                    subgraph = {
                        MapToForLoop._map_entry: graph.nodes().index(map)
                    }
                    trafo_for_loop = MapToForLoop(sdfg.sdfg_id,
                                                  sdfg.nodes().index(graph),
                                                  subgraph, 0)
                    trafo_for_loop.apply(sdfg)
                    nsdfg = trafo_for_loop.nsdfg

                    # LoopUnroll

                    guard = trafo_for_loop.guard
                    end = trafo_for_loop.after_state
                    begin = next(e.dst for e in nsdfg.out_edges(guard)
                                 if e.dst != end)

                    subgraph = {
                        DetectLoop._loop_guard: nsdfg.nodes().index(guard),
                        DetectLoop._loop_begin: nsdfg.nodes().index(begin),
                        DetectLoop._exit_state: nsdfg.nodes().index(end)
                    }
                    transformation = LoopUnroll(0, 0, subgraph, 0)
                    transformation.apply(nsdfg)
            elif self.unroll_loops:
                warnings.warn(
                    "Did not unroll loops. Either all ranges are equal to "
                    "one or range difference is symbolic.")

        self._outer_entries = list(self._outer_entries)