2525import joblib
2626from accelforge .mapper .FFM ._join_pmappings .compatibility import Compatibility
2727from accelforge .mapper ._simanneal2 .tracking import EvaluationsScoreTracker
28+ from accelforge .util .parallel import get_n_parallel_jobs
2829
2930# Simulated annealing algorithm
3031# -----------------------------
@@ -50,16 +51,16 @@ class MapspaceGlobals:
5051 def __init__ (
5152 self ,
5253 einsum2sims : dict [EinsumName , list [PmappingGroup ]],
53- resource2capacity : dict [str , int ],
54+ mixable_ranks : dict [str , set [ str ] ],
5455 aliased_tensors : dict [str , set [str ]],
5556 objective_function : Callable [[pd .Series ], float ],
5657 tracker : EvaluationsScoreTracker ,
5758 ) -> None :
5859 self .einsum2sims : dict [EinsumName , list [PmappingGroup ]] = einsum2sims
59- self .resource2capacity : dict [str , int ] = resource2capacity
6060 self .aliased_tensors : dict [str , set [str ]] = aliased_tensors
6161 self .objective_function : Callable [[pd .Series ], float ] = objective_function
6262 self .tracker : EvaluationsScoreTracker = tracker
63+ self .mixable_ranks : dict [str , set [str ]] = mixable_ranks
6364
6465
6566class SimAnnealMapping :
@@ -200,7 +201,11 @@ def _access_index(self, e: EinsumName, index_override: int | None = None):
200201 i %= len (data )
201202 return PmappingGroup (
202203 compatibility = s .compatibility ,
203- mappings = PmappingDataframe (data .iloc [i : i + 1 ]),
204+ mappings = PmappingDataframe (
205+ data .iloc [i : i + 1 ],
206+ n_total_pmappings = s .mappings .n_total_pmappings ,
207+ n_valid_pmappings = s .mappings .n_valid_pmappings ,
208+ ),
204209 )
205210
206211 def get_score (self ) -> float :
@@ -220,7 +225,6 @@ def get_score(self) -> float:
220225 def _merge_next (
221226 left : PmappingGroup ,
222227 right : PmappingGroup ,
223- apply_resource_limit : bool = True ,
224228 ) -> PmappingGroup :
225229 try :
226230 return left .merge_next (
@@ -229,15 +233,12 @@ def _merge_next(
229233 live_tensors_with_right = live_tensors | right_tensors ,
230234 aliased_tensors = self .mapspace_globals .aliased_tensors ,
231235 compatibility_joined = joined .compatibility .merge_next (
232- s .compatibility , live_tensors
233- ),
234- resource2capacity = (
235- self .mapspace_globals .resource2capacity
236- if apply_resource_limit
237- else None
236+ s .compatibility , live_tensors ,
237+ mixable_ranks = self .mapspace_globals .mixable_ranks
238238 ),
239239 drop_valid_reservations = True ,
240240 delay = False ,
241+ ignored_resources = set (),
241242 )
242243 except ValueError as err :
243244 # print(err)
@@ -261,7 +262,6 @@ def _merge_next(
261262 joined_new = _merge_next (
262263 joined ,
263264 s ,
264- apply_resource_limit = False ,
265265 )
266266 s .mappings ._data = s .mappings .data .drop (columns = ["_INDEX" ])
267267 try :
@@ -314,14 +314,13 @@ def get_random_mapping(mapspace_globals: MapspaceGlobals) -> SimAnnealMapping:
314314 continue
315315
316316
317- def join_pmappings (
317+ def _join_pmappings (
318318 pmapping_groups : dict [EinsumName , list [PmappingGroup ]],
319319 spec : Spec ,
320- resource2capacity : dict [str , int ],
321320 tracker : EvaluationsScoreTracker ,
322321 pop_size_per_thread : int ,
323322) -> PmappingGroup :
324- objective = spec .mapper .ffm . metrics
323+ objective = spec .mapper .metrics
325324 if objective == Metrics .ENERGY :
326325 objective_function = lambda x : x ["Total<SEP>energy" ]
327326 elif objective == Metrics .LATENCY :
@@ -330,13 +329,12 @@ def join_pmappings(
330329 objective_function = lambda x : x ["Total<SEP>energy" ] * x ["Total<SEP>latency" ]
331330 else :
332331 raise ValueError (f"Unknown objective { objective } " )
333- # print(f'Resource2capacity: {resource2capacity}')
334332 mapspace_globals = MapspaceGlobals (
335333 einsum2sims = pmapping_groups ,
336- resource2capacity = resource2capacity ,
337334 aliased_tensors = spec .workload .get_tensor_copies (),
338335 objective_function = objective_function ,
339336 tracker = tracker ,
337+ mixable_ranks = spec .workload ._get_ranks_that_share_indexing_rank_variables ()
340338 )
341339
342340 mappings = []
@@ -347,9 +345,7 @@ def join_pmappings(
347345 print (f"Completed making initial population of { len (mappings )} mappings" )
348346
349347 i = 0
350- while True :
351- if i > 1e6 :
352- break
348+ while not tracker .finished ():
353349 i += 1
354350 for i , m in enumerate (list (mappings )):
355351 try :
@@ -388,34 +384,34 @@ def get_n_tile_shapes(sim: PmappingGroup) -> int:
388384
389385
390386def join_pmappings (
391- spec : Spec ,
392387 pmappings : MultiEinsumPmappings ,
393388 max_evaluations : int = 1 ,
394389 population_size = 100 ,
395390 score_target : float | None = None ,
396391) -> EvaluationsScoreTracker :
392+ spec = pmappings .spec
397393 tracker = EvaluationsScoreTracker (
398- max_evaluations = max_evaluations / util . N_PARALLEL_PROCESSES ,
394+ max_evaluations = max_evaluations / get_n_parallel_jobs () ,
399395 stop_at_score = None ,
400396 print_period = 1 ,
401397 )
402398
399+ compressed , decompress_data = compress_einsum2pmappings (pmappings .einsum2pmappings )
400+
403401 if score_target is not None :
404- tracker .multiply_score_by ( 1 / score_target )
402+ tracker ._scale_score_by *= 1 / score_target
405403
406- pop_size_per_thread = population_size // util . N_PARALLEL_PROCESSES
404+ pop_size_per_thread = population_size // get_n_parallel_jobs ()
407405
408406 # Multiply by the number of einsums
409- tracker .multiply_scale_by (len (pmappings .einsum2pmappings ))
407+ print (f'Multiplying scale by { len (pmappings .einsum2pmappings )} for number of einsums' )
408+ tracker ._scale_by *= len (pmappings .einsum2pmappings )
410409
411- # Expected #pmappings before a Pareto-optimal one is found
412- # tracker.multiply_scale_by(pmappings._evaluated_pmappings_for_simanneal_baseline_compare() / pmappings.n_pareto_optimal_pmappings())
413- tracker .multiply_scale_by (
414- pmappings .n_evaluated_pmappings () / pmappings .n_pareto_optimal_pmappings ()
415- )
416-
417- # Normalize to the speed of the intra-Einsum pmapper
418- tracker .multiply_scale_by (1 / pmappings .n_evaluated_pmappings ())
410+ # We allow FFM to query n_pareto_optimal_pmappings mappings from the mapspace, so we
411+ # scale by 1 / n_pareto_optimal_pmappings to get simanneal 1 evaluation = same
412+ # number of mappings as FFM
413+ print (f'Multiplying scale by { 1 / pmappings .n_pareto_optimal_pmappings ()} for Pareto-optimal mapspace size' )
414+ tracker ._scale_by *= 1 / pmappings .n_pareto_optimal_pmappings ()
419415
420416 for einsum_name , einsum_pmappings in pmappings .einsum2pmappings .items ():
421417 total = sum (len (p .mappings .data ) for p in einsum_pmappings )
@@ -426,50 +422,56 @@ def join_pmappings(
426422 if total == 0 :
427423 raise ValueError (f"Einsum { einsum_name } has no pmappings" )
428424
429- print (f"TODO: Populate PmappingGroups with all permutations" )
430-
431- compressed , decompress_data = compress_einsum2pmappings (pmappings .einsum2pmappings )
432-
433425 permuted = {}
426+ n_total = 1
427+ n_evaluated = 1
434428 for einsum_name , einsum_sims in compressed .items ():
435429 for s in einsum_sims :
430+ n_pmappings = s .mappings .n_total_pmappings
431+ n_evaluated += n_pmappings
432+ subsets = set ()
433+
434+ # Count all permutations as separate choices
436435 for c_perm , _ in s .compatibility .make_equivalent_permutations ():
437436 permuted .setdefault (einsum_name , []).append (
438437 PmappingGroup (
439438 compatibility = c_perm ,
440439 mappings = s .mappings ,
441440 )
442441 )
442+ subsets .add (tuple (c_perm .loops ))
443+
444+ # Count mappings with fewer loops as separate choices
445+ for subset in list (subsets ):
446+ for i in range (2 ** len (subset )):
447+ subsets .add (tuple (subset [j ] for j in range (len (subset )) if i & (1 << j )))
443448
444- tile_shapes = [
445- get_n_tile_shapes (s )
446- for pmapping_groups in compressed .values ()
447- for s in pmapping_groups
448- ]
449+ n_total += len (subsets ) * n_pmappings * get_n_tile_shapes (s )
449450
450- # average_tile_shapes = sum(tile_shapes) / len(tile_shapes)
451- # print(f"Average tile shapes: {average_tile_shapes}")
452- # tracker.multiply_scale_by(average_tile_shapes)
451+ # The pmapper does mappings with varied permutations, subsets of loops, and tile
452+ # shapes in one shot, which won't be the case if a simulated annealing mapper is
453+ # used to select the top part of the mapping. So scale to account for the number
454+ # that we get for free.
455+ print (f'Multiplying scale by { n_total / n_evaluated } for number of total pmappings' )
456+ tracker ._scale_by *= n_total / n_evaluated
453457
454458 def parallel_join (
455459 permuted : dict [EinsumName , list [PmappingGroup ]],
456460 spec : Spec ,
457- resource2capacity : dict [str , int ],
458461 tracker : EvaluationsScoreTracker ,
459462 pop_size_per_thread : int ,
460463 ) -> EvaluationsScoreTracker :
461- join_pmappings (permuted , spec , resource2capacity , tracker , pop_size_per_thread )
464+ _join_pmappings (permuted , spec , tracker , pop_size_per_thread )
462465 return tracker
463466
464467 trackers = util .parallel (
465468 joblib .delayed (parallel_join )(
466469 permuted ,
467470 spec ,
468- pmappings .resource2capacity ,
469471 tracker ,
470472 pop_size_per_thread ,
471473 )
472- for _ in range (util . N_PARALLEL_PROCESSES )
474+ for _ in range (get_n_parallel_jobs () )
473475 )
474476
475477 t0 = trackers [0 ]
0 commit comments