2929 iterations2col ,
3030)
3131from 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
3333from 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]:
542542def 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