@@ -724,21 +724,47 @@ def _factorize_imperfect(n: int) -> np.ndarray:
724724 return np .array (sorted (oset (factors )))
725725
726726
727- def get_possible_factor_sizes (n : int , imperfect : bool , inner_size : int ) -> list [int ]:
727+ @lru_cache (maxsize = 10000 )
728+ def get_possible_factor_sizes (
729+ outer_size : int , imperfect : bool , inner_size : int , coarseness : float = 1
730+ ) -> list [int ]:
728731 # Int to protect from numpy overflows
729- n = int (n )
730- inner_size = int (inner_size )
731- if not imperfect :
732- factors = _factorize (math .ceil (n / inner_size ))
733- return factors * inner_size
732+ outer_size , inner_size = int (outer_size ), int (inner_size )
733+
734+ factors = set ()
735+ n_tiles = set ()
736+
737+ def _try_admit (n : int ) -> bool :
738+ if not imperfect and outer_size % n != 0 :
739+ return False
740+ if n > outer_size :
741+ return False
742+
743+ # This check: Avoid redundant tile shapes that lead to the same number of tiles,
744+ # which causes the same accesses (from same average shape) but worse latency &
745+ # memory usage (from worse long pole shape).
746+ cur_n_tiles = math .ceil (outer_size / n )
747+ if cur_n_tiles not in n_tiles :
748+ n_tiles .add (cur_n_tiles )
749+ # new_n calculation: Grab the smallest-possible tile shape that would get us
750+ # this number of tiles, which is the best (see above comment).
751+ new_n = math .ceil (outer_size / cur_n_tiles )
752+ factors .add (new_n )
753+ return True
754+
755+ n = inner_size
756+ while n <= outer_size :
757+ # If we successfully admit an n, don't try another n until we hit our coarseness
758+ # target.
759+ if _try_admit (n ):
760+ n = max (n + 1 , math .ceil (n * coarseness ))
761+ else :
762+ n += 1
763+
764+ # One more in case coarseness jumped the max value
765+ _try_admit (outer_size )
734766
735- factors = _factorize_imperfect (n )
736- if n % inner_size == 0 :
737- perfects = _factorize (math .ceil (n / inner_size )) * inner_size
738- assert all (
739- f in factors for f in perfects
740- ), f"perfects: { perfects } not in factors: { factors } "
741- return factors [factors >= inner_size ]
767+ return np .array (sorted (factors ))
742768
743769
744770def append_vector (matrix : np .ndarray , vector : np .ndarray ):
@@ -1470,6 +1496,7 @@ def eval_objective(
14701496 raise RuntimeError ("BUG: both inner and outer tiles are unknown" )
14711497
14721498 symbol_imperfect = _symbol_is_imperfect (symbol )
1499+ coarseness = job .spec_one_einsum .mapper .tiling_coarseness
14731500 # Use inner size and outer size to generate choices
14741501 if inner_tiles_type in oset (
14751502 ["set" , "unknown" ]
@@ -1480,7 +1507,7 @@ def eval_objective(
14801507 ]
14811508 ):
14821509 factors = get_possible_factor_sizes (
1483- outer_size , symbol_imperfect , inner_size
1510+ outer_size , symbol_imperfect , inner_size , coarseness
14841511 )
14851512 choices .append (append_vector (choices_enumerated , factors ))
14861513 elif inner_tiles_type == "enumerated" :
@@ -1491,7 +1518,7 @@ def eval_objective(
14911518 np .where (choices_enumerated [:, i ] == inner_choice )
14921519 ]
14931520 factors = get_possible_factor_sizes (
1494- outer_size , symbol_imperfect , inner_choice
1521+ outer_size , symbol_imperfect , inner_choice , coarseness
14951522 )
14961523 choices .append (append_vector (partition , factors ))
14971524 else :
@@ -1503,7 +1530,7 @@ def eval_objective(
15031530 np .where (choices_enumerated [:, i ] == outer_choice )
15041531 ]
15051532 factors = get_possible_factor_sizes (
1506- outer_choice , symbol_imperfect , inner_size
1533+ outer_choice , symbol_imperfect , inner_size , coarseness
15071534 )
15081535 choices .append (append_vector (partition , factors ))
15091536 elif what_tiles_symbol .is_initial_tile_shape (symbol ):
0 commit comments