Skip to content

Commit 9097c03

Browse files
Simanneal baseline updates
1 parent 3a0e864 commit 9097c03

5 files changed

Lines changed: 57 additions & 56 deletions

File tree

accelforge/_deprecate/_simanneal/wrappers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from accelforge.mapper.simanneal.simanneal import MapspaceGlobals, _fuse_sims
1515
from accelforge.mapper.simanneal.tracking import EvaluationsScoreTracker
1616
from accelforge.util._frozenset import fzs
17-
from accelforge.util.parallel import parallel, util
17+
from accelforge.util.parallel import parallel, get_n_parallel_jobs
1818

1919

2020
def mapping2sims(einsum_to_result: Compatibility):
@@ -188,7 +188,7 @@ def join_pmappings(
188188
flattened_architecture,
189189
)
190190

191-
n_threads = util.N_PARALLEL_PROCESSES
191+
n_threads = get_n_parallel_jobs()
192192
while n_threads >= 1:
193193
try:
194194
results_and_trackers = parallel(
@@ -201,7 +201,7 @@ def join_pmappings(
201201
)
202202
for _ in range(n_threads)
203203
],
204-
n_jobs=n_threads if util.PARALLELIZE else 1,
204+
n_jobs=get_n_parallel_jobs()
205205
)
206206
results = pd.concat([r[0] for r in results_and_trackers])
207207
break

accelforge/frontend/workload.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -785,7 +785,7 @@ class Workload(EvalableModel):
785785
bits_per_value: EvalableDict[str, int | str] = EvalableDict()
786786
"""
787787
Bits per value for each tensor. The workload-level bits_per_value is overridden if
788-
bits_per_action is specified for any given tensor access. This is a dictionary of
788+
bits_per_value is specified for any given tensor access. This is a dictionary of
789789
set expressions to bits per value for the tensors given by those expressions. For
790790
example, we may write "Inputs: 8" to set the bits per value to 8 for all input
791791
tensors, unless overridden.
File renamed without changes.

accelforge/_deprecate/_simanneal2/simanneal.py renamed to accelforge/mapper/_simanneal2/simanneal.py

Lines changed: 50 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import joblib
2626
from accelforge.mapper.FFM._join_pmappings.compatibility import Compatibility
2727
from 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

6566
class 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

390386
def 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]

accelforge/_deprecate/_simanneal2/tracking.py renamed to accelforge/mapper/_simanneal2/tracking.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ def __init__(
2020

2121
def add_evaluation(self, n_evaluations: int, best_score: float):
2222
self.evaluations += n_evaluations * self._scale_by
23+
new_score = best_score * self._scale_score_by
24+
if new_score < self.score:
25+
print(f'New score {new_score} after evaluation {self.evaluations}')
2326
self.score = min(self.score, best_score * self._scale_score_by)
2427
# Same score as before, remove the last entry
2528
if len(self.history) > 2 and self.history[-2][1] == self.score:
@@ -32,7 +35,6 @@ def add_evaluation(self, n_evaluations: int, best_score: float):
3235
or cur_time - self.prev_print_time > self.print_period
3336
):
3437
self.prev_print_time = cur_time
35-
print(f"Evaluations: {self.evaluations}, Score: {self.score}")
3638

3739
if self.max_evaluations is not None and self.evaluations > self.max_evaluations:
3840
self.clean_history()
@@ -59,9 +61,6 @@ def finished(self):
5961
)
6062
return enough_evaluations or enough_score
6163

62-
def multiply_scale_by(self, scale_by: float):
63-
self._scale_by *= scale_by
64-
6564
def multiply_score_by(self, scale_by: float):
6665
self._scale_score_by *= scale_by
6766

0 commit comments

Comments
 (0)