Skip to content

Commit cbd3e92

Browse files
Speedup for few procs to join, EDP early prune improvements
1 parent 771016d commit cbd3e92

9 files changed

Lines changed: 108 additions & 65 deletions

File tree

accelforge/frontend/arch/_flattened_arch.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class FlattenedArch:
2020
`frontend`, this one is intentionally *not* an
2121
`EvalableModel`.
2222
"""
23+
2324
def __init__(self, nodes: list["Leaf"]):
2425
self.nodes = nodes
2526

accelforge/frontend/mapper/ffm.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,3 @@ class FFM(EvalableModel):
232232
If set, append per-step runtime as JSON lines to this file. Used for ablation study
233233
measurements.
234234
"""
235-
236-
_metric_aggregator: Literal["sum", "prod", "any"] = "any"
237-
"""
238-
How to aggregate metrics together to determine whether one pmapping is better than
239-
another. "sum" means that the metrics are added together, "prod" means that the
240-
metrics are multiplied together, and "any" means that any metric being better than
241-
the other is enough to consider it non-dominated.
242-
"""

accelforge/mapper/FFM/_join_pmappings/join_pmappings.py

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,13 @@
3434
Compatibility,
3535
)
3636
from accelforge.mapper.FFM._pareto_df.df_convention import col2reservation
37-
from accelforge.util import _fillna_and__numeric_cast, parallel, delayed, oset
37+
from accelforge.util import (
38+
_fillna_and__numeric_cast,
39+
delayed,
40+
get_n_parallel_jobs,
41+
oset,
42+
parallel,
43+
)
3844

3945
logger = logging.getLogger(__name__)
4046

@@ -62,13 +68,16 @@ def log_total_time(self):
6268
logger.info(f"============================\n")
6369

6470

65-
def _apply_edp_columns(df: pd.DataFrame, metrics: Metrics) -> pd.DataFrame:
66-
if not (metrics & Metrics.ENERGY_DELAY_PRODUCT):
67-
return df
68-
if not (metrics & Metrics.ENERGY):
69-
del df["Total<SEP>energy"]
70-
if not (metrics & Metrics.LATENCY):
71-
del df["Total<SEP>latency"]
71+
def _apply_edp_columns(
72+
df: pd.DataFrame, metrics: Metrics, return_only_objectives: bool = False
73+
) -> pd.DataFrame:
74+
if metrics & Metrics.ENERGY_DELAY_PRODUCT:
75+
if not (metrics & Metrics.ENERGY):
76+
del df["Total<SEP>energy"]
77+
if not (metrics & Metrics.LATENCY):
78+
del df["Total<SEP>latency"]
79+
if return_only_objectives:
80+
df = df[[c for c in df.columns if col_used_in_pareto(c)]]
7281
return df
7382

7483

@@ -87,8 +96,8 @@ def __init__(
8796

8897
compare_to = compare_to.sort_values(by=compare_cols, ascending=False)
8998

90-
if len(compare_to) > 10:
91-
chosen_indices = np.round(np.linspace(0, len(compare_to) - 1, 10))
99+
if len(compare_to) > 100:
100+
chosen_indices = np.round(np.linspace(0, len(compare_to) - 1, 100))
92101
else:
93102
chosen_indices = np.round(np.arange(len(compare_to)))
94103

@@ -713,6 +722,28 @@ def grab_einsum_pmappings() -> (
713722
left = PmappingGroup.group(left, right_tensors)
714723
# print_time("Grouping")
715724

725+
# =============================================================================
726+
# If we're multiprocessing and the left side has fewer groups than the number of
727+
# processes, repeatedly split the largest group in half so that the merge work
728+
# below can fan out across all workers.
729+
# =============================================================================
730+
n_procs = get_n_parallel_jobs()
731+
n_groups = sum(len(v) for v in left.values())
732+
for _ in range(n_procs - n_groups):
733+
best_k, best_i, best_len = None, None, -1
734+
for k, vs in left.items():
735+
for i, (pg, _perm) in enumerate(vs):
736+
length = len(pg.mappings.data) if pg.mappings is not None else 0
737+
if length > best_len:
738+
best_len = length
739+
best_k, best_i = k, i
740+
if best_k is None or best_len < 2:
741+
break # nothing left worth splitting
742+
pg, perm = left[best_k][best_i]
743+
first, second = pg.split_in_half()
744+
left[best_k][best_i] = (first, perm)
745+
left[best_k].append((second, perm))
746+
716747
# ======================================================================
717748
# Remove dead tensors from left and right. This happens after grouping because
718749
# we only reserve space for shared tensors after they're dead (alive is handled

accelforge/mapper/FFM/_join_pmappings/pmapping_dataframe.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,24 @@ def copy(self, copy_df: bool = True) -> "PmappingDataframe":
772772
check_above_subset_below=False,
773773
)
774774

775+
def split_in_half(self) -> tuple["PmappingDataframe", "PmappingDataframe"]:
776+
mid = len(self.data) // 2
777+
half_total = self.n_total_pmappings / 2
778+
half_valid = self.n_valid_pmappings / 2
779+
first = self.update(
780+
data=self.data.iloc[:mid].copy(),
781+
skip_pareto=True,
782+
n_total_pmappings=half_total,
783+
n_valid_pmappings=half_valid,
784+
)
785+
second = self.update(
786+
data=self.data.iloc[mid:].copy(),
787+
skip_pareto=True,
788+
n_total_pmappings=half_total,
789+
n_valid_pmappings=half_valid,
790+
)
791+
return first, second
792+
775793
def limit_capacity(
776794
self,
777795
next_shared_loop_index: int = None,

accelforge/mapper/FFM/_join_pmappings/pmapping_group.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ def tensor_names(self) -> set[str]:
4747
def copy(self) -> "PmappingGroup":
4848
return PmappingGroup(self.compatibility, self.mappings.copy())
4949

50+
def split_in_half(self) -> tuple["PmappingGroup", "PmappingGroup"]:
51+
first_mappings, second_mappings = self.mappings.split_in_half()
52+
return (
53+
PmappingGroup(self.compatibility, first_mappings),
54+
PmappingGroup(self.compatibility, second_mappings),
55+
)
56+
5057
def __len__(self) -> int:
5158
return len(self.mappings)
5259

accelforge/mapper/FFM/_make_pmappings/make_pmappings.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,11 @@ def get_n_computes(spec: Spec, einsum_name: EinsumName | None = None) -> int:
7070
return sum(get_operation_space_size(spec.workload, e) for e in einsums)
7171

7272

73-
def get_per_tensor_size(
74-
workload: Workload, return_n_elements: bool = False
75-
) -> dict[TensorName, int]:
73+
def get_per_tensor_n_elements(workload: Workload) -> dict[TensorName, int]:
7674
sizes = {}
7775
for einsum in workload.einsums:
7876
for tensor in einsum.tensor_names:
7977
sizes[tensor] = get_tensor_size(workload, tensor)
80-
if not return_n_elements:
81-
sizes[tensor] *= einsum.tensor_accesses[tensor].bits_per_value
8278
return sizes
8379

8480

@@ -265,7 +261,7 @@ def get_memories_to_track(
265261
)
266262

267263
tensor_sizes = {}
268-
for tensor, size in get_per_tensor_size(spec.workload).items():
264+
for tensor, size in get_per_tensor_n_elements(spec.workload).items():
269265
scale = 1
270266
for einsum in spec.workload.einsums_with_tensor(tensor):
271267
if einsum.tensor_accesses[tensor].persistent:
@@ -292,9 +288,7 @@ def get_memories_to_track(
292288
.bits_per_value
293289
)
294290
effective_bpv = mem.bits_per_value.get(tensor, workload_bpv)
295-
usage += (
296-
tensor_sizes[tensor] * effective_bpv / (workload_bpv * mem.size)
297-
)
291+
usage += tensor_sizes[tensor] * effective_bpv / mem.size
298292

299293
if usage <= 1:
300294
ignored_resources.add(memory)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,7 @@ def _factorize_imperfect(n: int) -> np.ndarray:
723723
factors.append(math.ceil(n / i))
724724
return np.array(sorted(oset(factors)))
725725

726+
726727
@lru_cache(maxsize=10000)
727728
def get_possible_factor_sizes(
728729
outer_size: int, imperfect: bool, inner_size: int, coarseness: float = 1

accelforge/mapper/FFM/mappings.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from accelforge.util._frozenset import oset
88
from accelforge.mapper.FFM._make_pmappings.make_pmappings import (
99
get_n_computes,
10-
get_per_tensor_size,
10+
get_per_tensor_n_elements,
1111
)
1212
from typing import Union
1313
from accelforge._accelerated_imports import numpy as np
@@ -123,10 +123,15 @@ def per_tensor_size(self, return_n_elements: bool = False) -> dict[TensorName, i
123123
assert (
124124
self.evaluated_specs is not None
125125
), "Can't get per-tensor size if no Einsums have been mapped."
126-
return get_per_tensor_size(
127-
next(iter(self.evaluated_specs.values())).workload,
128-
return_n_elements=return_n_elements,
129-
)
126+
workload = next(iter(self.evaluated_specs.values())).workload
127+
sizes = get_per_tensor_n_elements(workload)
128+
if not return_n_elements:
129+
for einsum in workload.einsums:
130+
for tensor in einsum.tensor_names:
131+
sizes[tensor] = (
132+
sizes[tensor] * einsum.tensor_accesses[tensor].bits_per_value
133+
)
134+
return sizes
130135

131136
def _update(self, **kwargs):
132137
data = dict(

tests/test_network.py

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def test_hierarchical_1d(self):
8282
* (KN / MAC_TILE) # number of used Scratchpad
8383
* M_TILE
8484
* KN # temporal for n1 in mapping
85-
* sum(i+1 for i in range(MAC_TILE)) # unicast along X-axis of MacArray
85+
* sum(i + 1 for i in range(MAC_TILE)) # unicast along X-axis of MacArray
8686
* BITS_PER_VALUE,
8787
)
8888
# NOTE: assuming XY routing (as defined in mapping)
@@ -92,7 +92,7 @@ def test_hierarchical_1d(self):
9292
* (KN / MAC_TILE)
9393
* M_TILE
9494
* KN # temporal for n1 in mapping
95-
* MAC_TILE # multicast along X-axis of MacArray
95+
* MAC_TILE # multicast along X-axis of MacArray
9696
* BITS_PER_VALUE,
9797
)
9898
self.assertEqual(
@@ -101,14 +101,16 @@ def test_hierarchical_1d(self):
101101
* (KN / MAC_TILE)
102102
* M_TILE
103103
* KN
104-
* sum(i+1 for i in range(MAC_TILE))
104+
* sum(i + 1 for i in range(MAC_TILE))
105105
* BITS_PER_VALUE,
106106
)
107107

108108
self.assertEqual(
109109
result.data["Matmul0<SEP>action<SEP>PeArray<SEP>T0<SEP>hops"].iloc[0],
110110
(M / M_TILE)
111-
* sum(i+1 for i in range(KN // MAC_TILE)) # unicast along X-axis of PeArray
111+
* sum(
112+
i + 1 for i in range(KN // MAC_TILE)
113+
) # unicast along X-axis of PeArray
112114
* M_TILE
113115
* MAC_TILE
114116
* BITS_PER_VALUE,
@@ -117,15 +119,16 @@ def test_hierarchical_1d(self):
117119
self.assertEqual(
118120
result.data["Matmul0<SEP>action<SEP>PeArray<SEP>T1<SEP>hops"].iloc[0],
119121
(M / M_TILE)
120-
* KN // MAC_TILE # multicast along X-axis of PeArray
122+
* KN
123+
// MAC_TILE # multicast along X-axis of PeArray
121124
* M_TILE
122125
* KN
123126
* BITS_PER_VALUE,
124127
)
125128
self.assertEqual(
126129
result.data["Matmul0<SEP>action<SEP>PeArray<SEP>W0<SEP>hops"].iloc[0],
127130
(M / M_TILE)
128-
* sum(i+1 for i in range(KN // MAC_TILE)) # unicast along PeArray
131+
* sum(i + 1 for i in range(KN // MAC_TILE)) # unicast along PeArray
129132
* MAC_TILE
130133
* KN
131134
* BITS_PER_VALUE,
@@ -160,9 +163,8 @@ def test_hierarchical(self):
160163
* (KN / MAC_TILE) ** 2
161164
* M_TILE
162165
* (
163-
sum(i+1 for i in range(MAC_TILE)) # unicasting along X
164-
+
165-
MAC_TILE * MAC_TILE # multicast along Y for each column
166+
sum(i + 1 for i in range(MAC_TILE)) # unicasting along X
167+
+ MAC_TILE * MAC_TILE # multicast along Y for each column
166168
)
167169
* BITS_PER_VALUE,
168170
)
@@ -173,9 +175,10 @@ def test_hierarchical(self):
173175
* (KN / MAC_TILE) ** 2
174176
* M_TILE
175177
* (
176-
MAC_TILE * MAC_TILE # multicast along X (the tile is shape N1, which is MAC_TILE here)
177-
+
178-
MAC_TILE * sum(i+1 for i in range(MAC_TILE)) # unicasting along Y for each row
178+
MAC_TILE
179+
* MAC_TILE # multicast along X (the tile is shape N1, which is MAC_TILE here)
180+
+ MAC_TILE
181+
* sum(i + 1 for i in range(MAC_TILE)) # unicasting along Y for each row
179182
)
180183
* BITS_PER_VALUE,
181184
)
@@ -185,35 +188,27 @@ def test_hierarchical(self):
185188
* (KN / MAC_TILE) ** 2
186189
* M_TILE
187190
* (
188-
MAC_TILE * sum(i+1 for i in range(MAC_TILE)) # unicast along X (the tile is shape N1, which is MAC_TILE here)
189-
+
190-
MAC_TILE * sum(i+1 for i in range(MAC_TILE)) # unicasting along Y for each row
191+
MAC_TILE
192+
* sum(
193+
i + 1 for i in range(MAC_TILE)
194+
) # unicast along X (the tile is shape N1, which is MAC_TILE here)
195+
+ MAC_TILE
196+
* sum(i + 1 for i in range(MAC_TILE)) # unicasting along Y for each row
191197
)
192198
* BITS_PER_VALUE,
193199
)
194200

195201
self.assertEqual(
196202
result.data["Matmul0<SEP>action<SEP>PeArray<SEP>T0<SEP>hops"].iloc[0],
197-
(M / M_TILE)
198-
* (
199-
sum(i+1 for i in range(PE_TILE))
200-
+
201-
PE_TILE * PE_TILE
202-
)
203+
(M / M_TILE) * (sum(i + 1 for i in range(PE_TILE)) + PE_TILE * PE_TILE)
203204
# tile shape
204-
* M_TILE
205-
* MAC_TILE
206-
* BITS_PER_VALUE,
205+
* M_TILE * MAC_TILE * BITS_PER_VALUE,
207206
)
208207
# NOTE: assuming XY routing (as defined in mapping)
209208
self.assertEqual(
210209
result.data["Matmul0<SEP>action<SEP>PeArray<SEP>T1<SEP>hops"].iloc[0],
211210
(M / M_TILE)
212-
* (
213-
PE_TILE * PE_TILE
214-
+
215-
PE_TILE * sum(i+1 for i in range(PE_TILE))
216-
)
211+
* (PE_TILE * PE_TILE + PE_TILE * sum(i + 1 for i in range(PE_TILE)))
217212
* M_TILE
218213
* MAC_TILE
219214
* BITS_PER_VALUE,
@@ -222,9 +217,8 @@ def test_hierarchical(self):
222217
result.data["Matmul0<SEP>action<SEP>PeArray<SEP>W0<SEP>hops"].iloc[0],
223218
(M / M_TILE)
224219
* (
225-
PE_TILE * sum(i+1 for i in range(PE_TILE))
226-
+
227-
PE_TILE * sum(i+1 for i in range(PE_TILE))
220+
PE_TILE * sum(i + 1 for i in range(PE_TILE))
221+
+ PE_TILE * sum(i + 1 for i in range(PE_TILE))
228222
)
229223
* MAC_TILE**2
230224
* BITS_PER_VALUE,
@@ -252,4 +246,4 @@ def test_hierarchical(self):
252246
"M_TILE": M_TILE,
253247
},
254248
)
255-
result = spec.map_workload_to_arch()
249+
result = spec.map_workload_to_arch()

0 commit comments

Comments
 (0)