Skip to content

Commit e4675c5

Browse files
Mapper speed ++, mapper memory usage --
1 parent 9097c03 commit e4675c5

2 files changed

Lines changed: 134 additions & 79 deletions

File tree

accelforge/mapper/FFM/_make_pmappings/make_pmappings.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,12 @@ def make_jobs_for_einsum(einsum_name: EinsumName, spec: Spec):
159159
total_jobs = sum(len(j) for j in compatibility_jobs.values())
160160
if print_progress:
161161
print(f"Einsum {einsum_name} has {total_jobs} pmapping templates:")
162+
i = 0
162163
for job_list in compatibility_jobs.values():
163164
for job in job_list:
164165
if print_progress:
165-
print(f"\t{job.mapping.compact_str()}")
166+
print(f"\t{i}\t{job.mapping.compact_str()}")
167+
i += 1
166168
job.memory_limit = memory_limit
167169
job.time_limit = time_limit
168170

accelforge/mapper/FFM/_make_pmappings/make_pmappings_from_templates/make_tile_shapes.py

Lines changed: 131 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
iterations2col,
3030
)
3131
from accelforge.mapper.FFM._pareto_df.pareto import makepareto_numpy
32-
from accelforge.model._looptree.reuse.symbolic import IMPERFECT
32+
from accelforge.model._looptree.reuse.symbolic import IMPERFECT, PRINT_FORMULAS
3333
from accelforge.mapper.FFM._join_pmappings.pmapping_dataframe import (
3434
nameloop2col,
3535
tensor2col,
@@ -542,13 +542,22 @@ def get_possible_factor_sizes(n: int, imperfect: bool = False) -> list[int]:
542542
def append_vector(matrix: np.ndarray, vector: np.ndarray):
543543
if matrix is None:
544544
return vector.reshape(-1, 1)
545-
return np.concatenate(
546-
(
547-
np.repeat(matrix, vector.shape[0], axis=0),
548-
np.tile(vector.reshape(-1, 1), (matrix.shape[0], 1)),
549-
),
550-
axis=1,
551-
)
545+
a = np.repeat(matrix, vector.shape[0], axis=0)
546+
b = np.tile(vector.reshape(-1, 1), (matrix.shape[0], 1))
547+
max_val = max(np.max(a), np.max(b))
548+
min_val = min(np.min(a), np.min(b))
549+
assert min_val >= 0, f"min_val is {min_val}"
550+
551+
if max_val >= 2**32:
552+
dtype = np.uint64
553+
elif max_val >= 2**16:
554+
dtype = np.uint32
555+
elif max_val >= 2**8:
556+
dtype = np.uint16
557+
else:
558+
dtype = np.uint8
559+
560+
return np.concatenate((a.astype(dtype), b.astype(dtype)), axis=1)
552561

553562

554563
@lru_cache(maxsize=10000)
@@ -642,27 +651,20 @@ def get_size(x: Symbol | int):
642651
return x
643652

644653
def has_fanout(x: Symbol | int):
645-
outer = get_size(what_tiles_symbol.get_inner_tiles(x))
654+
try:
655+
outer = get_size(what_tiles_symbol.get_outer_tiles(x))
656+
except ValueError:
657+
return False
646658
inner = get_size(x)
647659
return outer != inner
648660

649-
def can_check(x: Symbol | int):
650-
if isinstance(x, Symbol) and x not in symbols_enumerated:
651-
return False
652-
# tiles = what_tiles_symbol.get_outer_tiles(x, none_if_fail=True)
653-
# if tiles is not None and isinstance(tiles, Symbol) and tiles not in symbols_enumerated:
654-
# return False
655-
return True
656-
657661
for limit, group in max_loop_check_groups:
658-
prev_len = choices_enumerated.shape[0]
659662
if len(group) <= limit:
660663
continue
661664

662665
n = 0
663666
for g in group:
664-
if can_check(g):
665-
n += has_fanout(g)
667+
n += has_fanout(g)
666668

667669
if isinstance(n, np.ndarray):
668670
choices_enumerated = choices_enumerated[n <= limit]
@@ -862,17 +864,15 @@ def get_tile_shape_choices(
862864
# the outer loops, so it does those symbols (which end up multiplying our choices)
863865
# last. Outer to inner is faster if there's no symbols to keep because that's what
864866
# happened on exactly one workload that Tanner tested.
865-
# TILE_SHAPE_ORDER = "inner_to_outer_one_rv_at_a_time" if keep_symbols else "outer_to_inner_one_rv_at_a_time"
866-
TILE_SHAPE_ORDER = "inner_to_outer_one_rv_at_a_time"
867-
# TILE_SHAPE_ORDER = "inner_to_outer"
867+
TILE_SHAPE_ORDER = "inner_to_outer_prod_others"
868868

869869
# For imperfect, we make inner tile shapes, then create outer tile shapes that are
870-
# multiples of the non-residual part of the inner tile shape. This way, the very last
871-
# iteration of the outer tile shape fully contains the reisudal part of the inner tile
872-
# shape, and we don't have any cases where there are residuals stacking across multiple
873-
# loop levels.
870+
# multiples of the non-residual part of the inner tile shape. This way, the very
871+
# last iteration of the outer tile shape fully contains the reisudal part of the
872+
# inner tile shape, and we don't have any cases where there are residuals stacking
873+
# across multiple loop levels.
874874
if IMPERFECT:
875-
assert TILE_SHAPE_ORDER == "inner_to_outer_one_rv_at_a_time"
875+
assert "inner_to_outer" in TILE_SHAPE_ORDER
876876

877877
paretoed_by = []
878878

@@ -890,7 +890,8 @@ def log_message(message: str, *args: str):
890890
t = time.time() - prev_time
891891
s = "**" if t > 1 else ""
892892
job.log_message(f"{s}{t:.2f}s: {message} {' '.join(args)}")
893-
# print(f"{time.time() - prev_time:.2f}s: {message} {' '.join(args)}")
893+
if PRINT_FORMULAS:
894+
print(f"{time.time() - prev_time:.2f}s: {message} {' '.join(args)}")
894895
time_end(message)
895896

896897
log_message("init")
@@ -919,58 +920,107 @@ def eval_objective(
919920
)
920921

921922
def grab_symbol(prev_symbol: Symbol = None):
922-
# TODO: Maybe start with a symbol that would result in more pruning up front?
923-
# Maximize the # of choices that can be resolved easily
924-
if TILE_SHAPE_ORDER == "inner_to_outer":
925-
return symbols_remaining.pop(-1)
926-
if TILE_SHAPE_ORDER == "outer_to_inner":
923+
strides = [s for s in symbols_remaining if what_tiles_symbol.is_stride(s)]
924+
if not strides:
927925
return symbols_remaining.pop(0)
928926

929-
if TILE_SHAPE_ORDER == "inner_to_outer_one_rv_at_a_time":
930-
# Continue with a symbol representing the parent tile of the last symbol
931-
# if possible. Otherwise (see return), just grab any symbol.
932-
choice = what_tiles_symbol.get_outer_tiles(prev_symbol, none_if_fail=True)
933-
if choice is not None and choice in symbols_remaining:
934-
symbols_remaining.remove(choice)
935-
return choice
936-
# Pick a symbol that has:
937-
# - Nobody tiling it
938-
# - The smallest maximum size
939-
strides = [s for s in symbols_remaining if what_tiles_symbol.is_stride(s)]
940-
choice = -1
941-
if strides:
942-
max_size = what_tiles_symbol.get_max_size(strides[choice])
943-
for i, s in enumerate(strides):
944-
if what_tiles_symbol.get_inner_tiles(s, none_if_fail=True) is None:
945-
if what_tiles_symbol.get_max_size(s) < max_size:
946-
choice = i
947-
max_size = what_tiles_symbol.get_max_size(s)
948-
choice = symbols_remaining.index(strides[choice])
949-
return symbols_remaining.pop(choice)
950-
elif TILE_SHAPE_ORDER == "outer_to_inner_one_rv_at_a_time":
951-
# Continue with a symbol representing the child tile of the last symbol
952-
# if possible. Otherwise (see return), just grab any symbol.
953-
choice = what_tiles_symbol.get_inner_tiles(prev_symbol, none_if_fail=True)
954-
if choice is not None and choice in symbols_remaining:
955-
symbols_remaining.remove(choice)
956-
return choice
957-
# Pick a symbol that has:
958-
# - Tiles nobody
959-
# - The smallest maximum size
960-
strides = [s for s in symbols_remaining if what_tiles_symbol.is_stride(s)]
961-
choice = 0
962-
if strides:
963-
max_size = what_tiles_symbol.get_max_size(strides[choice])
964-
for i, s in enumerate(strides):
965-
if what_tiles_symbol.get_outer_tiles(s, none_if_fail=True) is None:
966-
if what_tiles_symbol.get_max_size(s) < max_size:
967-
choice = i
968-
max_size = what_tiles_symbol.get_max_size(s)
969-
choice = symbols_remaining.index(strides[choice])
970-
return symbols_remaining.pop(choice)
927+
tile_shape_order = TILE_SHAPE_ORDER
928+
929+
if "inner_to_outer" in tile_shape_order:
930+
strides = strides[::-1]
931+
prev_tile = what_tiles_symbol.get_inner_tiles
932+
next_tile = what_tiles_symbol.get_outer_tiles
933+
elif "outer_to_inner" in tile_shape_order:
934+
prev_tile = what_tiles_symbol.get_outer_tiles
935+
next_tile = what_tiles_symbol.get_inner_tiles
971936
else:
972937
raise RuntimeError(f"BUG: invalid TILE_SHAPE_ORDER: {TILE_SHAPE_ORDER}")
973938

939+
if "one_rv_at_a_time" in tile_shape_order:
940+
n = next_tile(prev_symbol, none_if_fail=True)
941+
if n is not None and n in symbols_remaining:
942+
symbols_remaining.remove(n)
943+
return n
944+
tile_shape_order = tile_shape_order.replace(
945+
"one_rv_at_a_time", "smallest_first"
946+
)
947+
948+
def max_formulas(s):
949+
prev = prev_tile(s, none_if_fail=True)
950+
constrains_prev = isinstance(prev, Symbol) and prev not in keep_symbols
951+
return (
952+
# Sum number of times it appears in all objectives
953+
sum(o.formula.count(s) for o in objectives) + 4**constrains_prev,
954+
# Number of objectives that have this symbol
955+
len([o for o in objectives if s in o.formula.free_symbols])
956+
+ 4**constrains_prev,
957+
# Minimize size
958+
min_size(s),
959+
)
960+
961+
def _porp_of_formula(s):
962+
prev = prev_tile(s, none_if_fail=True)
963+
n = 0
964+
for o in objectives:
965+
f = o.formula
966+
if s in f.free_symbols:
967+
# n += f.count(s) / sum(1 for _ in f.atoms(Symbol))
968+
n += 1 / len(f.free_symbols)
969+
constrains_prev = isinstance(prev, Symbol) and prev not in keep_symbols
970+
if constrains_prev:
971+
n += 1
972+
return n
973+
974+
def porp_of_formulas(s):
975+
n = 0
976+
scale = 1
977+
cur = s
978+
while cur is not None and isinstance(cur, Symbol):
979+
n += _porp_of_formula(cur)
980+
break
981+
cur = next_tile(cur, none_if_fail=True)
982+
scale /= 2
983+
984+
return n / what_tiles_symbol.get_max_size(s) # (
985+
# n,
986+
# *max_formulas(s)
987+
# )
988+
989+
def prod_others(s):
990+
return prod(max_formulas(s))
991+
992+
def min_size(s):
993+
return 1 / what_tiles_symbol.get_max_size(s)
994+
995+
if "smallest_first" in tile_shape_order:
996+
objective = min_size
997+
elif "most_formulas_first" in tile_shape_order:
998+
objective = max_formulas
999+
elif "prod_others" in tile_shape_order:
1000+
objective = prod_others
1001+
elif tile_shape_order in ["inner_to_outer", "outer_to_inner"]:
1002+
objective = lambda s: 1
1003+
elif "porp_of_formulas" in tile_shape_order:
1004+
objective = porp_of_formulas
1005+
else:
1006+
raise RuntimeError(f"BUG: invalid tile_shape_order: {tile_shape_order}")
1007+
1008+
choice, best = 0, objective(strides[0])
1009+
for i, s in enumerate(strides):
1010+
prev = prev_tile(s, none_if_fail=True)
1011+
if prev is not None and prev in symbols_remaining:
1012+
# Haven't made the prerequisite tile shape yet, so skip this symbol
1013+
continue
1014+
value = objective(s)
1015+
if value > best:
1016+
best = value
1017+
choice = i
1018+
choice = symbols_remaining.index(strides[choice])
1019+
log_message("Selected symbol", f"{symbols_remaining[choice]} with value {best}")
1020+
return symbols_remaining.pop(choice)
1021+
1022+
raise RuntimeError(f"BUG: invalid TILE_SHAPE_ORDER: {TILE_SHAPE_ORDER}")
1023+
9741024
last_stride_symbol = None # track the last stride symbol to select next symbol
9751025
symbol = None
9761026
while symbols_remaining:
@@ -1085,6 +1135,7 @@ def grab_symbol(prev_symbol: Symbol = None):
10851135

10861136
prev_size = choices_enumerated.shape[0] if choices_enumerated is not None else 1
10871137
choices_enumerated = np.concatenate(choices, axis=0)
1138+
choices = None # Let it be freed by the garbage collector
10881139
job.n_total_pmappings *= choices_enumerated.shape[0] / max(1, prev_size)
10891140
symbols_enumerated.append(symbol)
10901141
log_message("enumerate", f"{symbol}", f"size={choices_enumerated.shape[0]}")
@@ -1101,11 +1152,12 @@ def grab_symbol(prev_symbol: Symbol = None):
11011152
what_tiles_symbol,
11021153
)
11031154
job.log_porp_pmappings_kept(
1104-
f"max_fused_loops_per_rank_variable",
1155+
f"max_fused_loops_or_max_per_rank_variable",
11051156
choices_enumerated.shape[0] / max(1, prev_size),
11061157
)
11071158
log_message(
1108-
"max_fused_loops_per_rank_variable", f"size={choices_enumerated.shape[0]}"
1159+
"max_fused_loops_or_max_per_rank_variable",
1160+
f"size {prev_size} -> {choices_enumerated.shape[0]}",
11091161
)
11101162

11111163
# ==============================================================================
@@ -1367,6 +1419,7 @@ def update_symbol2goal(
13671419
# Rearrange in tile shape order
13681420
if choices_enumerated is None:
13691421
return np.array([])
1422+
choices_enumerated = choices_enumerated.astype(np.int64)
13701423
return choices_enumerated[:, [symbols_enumerated.index(s) for s in symbols]]
13711424

13721425

0 commit comments

Comments
 (0)