Skip to content

Commit 7b017a1

Browse files
More stat accessors
1 parent a01dee9 commit 7b017a1

1 file changed

Lines changed: 150 additions & 19 deletions

File tree

accelforge/mapper/FFM/mappings.py

Lines changed: 150 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from accelforge.frontend.spec import Spec
55
from accelforge.frontend.workload import EinsumName
66
from accelforge._accelerated_imports import pd
7-
from accelforge.frontend.workload import TensorName
87
from accelforge.mapper.FFM._make_pmappings.make_pmappings import (
98
get_num_computes,
109
get_per_tensor_size,
@@ -40,8 +39,8 @@ class Mappings:
4039
contain the evaluated and flattened architecture node for that particular Einsum
4140
and compute combination.
4241
evaluated_specs:
43-
A dictionary of Einsum names to evaluated specifications. These contain the evaluated
44-
specification for that particular Einsum.
42+
A dictionary of Einsum names to evaluated specifications. These contain the
43+
evaluated specification for that particular Einsum.
4544
"""
4645

4746
def __init__(
@@ -249,15 +248,14 @@ def sum(self, keep_key_index: list[int] | int | None = None) -> "Mappings":
249248
for col in self.data.columns:
250249
if len(col.split("<SEP>")) != len(columns[0].split("<SEP>")):
251250
raise ValueError(
252-
f"Can only sum columns with same-length keys. Try first calling "
253-
f'access("key") or drop("key") to make all columns '
254-
f"have the same number of keys."
251+
"Can only sum columns with same-length keys. Try first calling "
252+
'access("key") or drop("key") to make all columns '
253+
"have the same number of keys."
255254
)
256255

257-
if any(k < 0 or k >= len(columns[0].split("<SEP>")) for k in keep_key_index):
258-
raise ValueError(
259-
f"Keep indices must be in the range [0, {len(columns[0].split('<SEP>'))})"
260-
)
256+
max_n = len(columns[0].split("<SEP>"))
257+
if any(k < 0 or k >= max_n for k in keep_key_index):
258+
raise ValueError(f"Keep indices must be in the range [0, {max_n})")
261259

262260
target2sources = {}
263261
for col in columns:
@@ -293,14 +291,35 @@ def to_dict(self, value_if_one_mapping: bool = True) -> dict[str, list[float]]:
293291
Returns
294292
-------
295293
dict[str, list[float]]
296-
A dictionary with the same keys as the columns of the dataframe, and values that
297-
are either a single value or a list of values.
294+
A dictionary with the same keys as the columns of the dataframe, and values
295+
that are either a single value or a list of values.
298296
"""
299297
new = self.data.to_dict(orient="list")
300298
if value_if_one_mapping and len(self) == 1:
301299
new = {k: v[0] for k, v in new.items()}
302300
return new
303301

302+
def items(
303+
self, value_if_one_mapping: bool = True
304+
) -> list[tuple[str, float | list[float]]]:
305+
"""
306+
Shorthand for ``to_dict().items()``. Casts this Mappings object to a dictionary,
307+
then returns the items of the dictionary.
308+
309+
Parameters
310+
----------
311+
value_if_one_mapping:
312+
If True and there is only one mapping, then values in the returned list will
313+
be a single value, rather than a list of values. Otherwise, they will always
314+
be a list of values.
315+
316+
Returns
317+
-------
318+
list[tuple[str, float | list[float]]]
319+
A list of tuples of (key, value).
320+
"""
321+
return self.to_dict(value_if_one_mapping=value_if_one_mapping).items()
322+
304323
def per_compute(self, per_einsum: bool = False) -> "Mappings":
305324
"""
306325
Returns the per-compute evaluation results by dividing all statistics by the
@@ -382,7 +401,7 @@ def render(self, index: int | None = None) -> str:
382401
f"Can only render a single mapping, but got {len(self)}. Try calling "
383402
f"mappings[i].render() instead, for some integer 0 <= i < {len(self)}."
384403
)
385-
return self.data.iloc[0][f"Total<SEP>mapping"].render()
404+
return self.data.iloc[0]["Total<SEP>mapping"].render()
386405

387406
def energy(
388407
self: "Mappings",
@@ -396,10 +415,11 @@ def energy(
396415
Returns the energy consumed. A dictionary is returned with keys that are tuples
397416
of (Einsum name, Component name, Tensor name, Action name), with any of these
398417
being omitted if the corresponding parameter is not set to True. If neither of
399-
the ``per_`` parameters are set to True, a float or a list of floats is returned.
418+
the ``per_`` parameters are set to True, a float or a list of floats is
419+
returned.
400420
401-
NOTE: Leak power is not per-tensor. If ``per_tensor`` is True, then the tensor name
402-
for leak will be None.
421+
NOTE: Leak power is not per-tensor. If ``per_tensor`` is True, then the tensor
422+
name for leak will be None.
403423
404424
Parameters
405425
----------
@@ -432,7 +452,7 @@ def energy(
432452
for einsum in self.einsum_names:
433453
einsum_accessed = energy.access(einsum, col_idx=0)
434454
# None for computes
435-
for tensor in list[TensorName](
455+
for tensor in list(
436456
self.spec.workload.einsums[einsum].tensor_names
437457
) + ["None"]:
438458
tensor_accessed = einsum_accessed.access(tensor, col_idx=1)
@@ -467,7 +487,7 @@ def energy(
467487
if value_if_one_mapping and len(self.data) == 1:
468488
return {k: v.iloc[0] for k, v in result.items()}
469489

470-
return result
490+
return {k: list(v) for k, v in result.items()}
471491

472492
def latency(
473493
self: "Mappings",
@@ -544,4 +564,115 @@ def latency(
544564
return {k: v.iloc[0] for k, v in result.items()}
545565
return result # Numpy sum already pulls out the number
546566

547-
return result
567+
return {k: list(v) for k, v in result.items()}
568+
569+
def resource_usage(
570+
self,
571+
value_if_one_mapping: bool = True,
572+
) -> dict[str, float | list[float]]:
573+
"""
574+
Returns the usage of each resource in the mapping.
575+
576+
Parameters
577+
----------
578+
value_if_one_mapping:
579+
If True and there is only one mapping, then values in the returned
580+
dictionary will be a single value, rather than a list of values. Otherwise,
581+
they will always be a list of values.
582+
583+
Returns
584+
-------
585+
dict[str, float | list[float]]
586+
A dictionary with the usage of each resource.
587+
"""
588+
try:
589+
reservations = self.access("reservation")
590+
except KeyError:
591+
raise ValueError(f"No reservations found in columns {sorted(self.columns)}")
592+
593+
usage = {}
594+
for col in reservations._get_keys_of_length(3):
595+
resource, _, _ = col.split("<SEP>")
596+
if resource not in usage:
597+
usage[resource] = 0
598+
usage[resource] = np.maximum(usage[resource], reservations[col])
599+
600+
if value_if_one_mapping and len(self.data) == 1:
601+
return {k: v.iloc[0] for k, v in usage.items()}
602+
return {k: list(v) for k, v in usage.items()}
603+
604+
def actions(
605+
self: "Mappings",
606+
per_einsum: bool = False,
607+
per_component: bool = True,
608+
per_tensor: bool = False,
609+
value_if_one_mapping: bool = True,
610+
) -> dict[tuple[str, ...] | str, float | list[float]] | float | list[float]:
611+
"""
612+
Returns the number of actions performed in the mapping. A dictionary is returned
613+
with keys that are tuples of (Einsum name, Component name, Tensor name, Action
614+
name), with any of these being omitted if the corresponding parameter is not set
615+
to True. If neither of the ``per_`` parameters are set to True, a float or a
616+
list of floats is returned.
617+
618+
Parameters
619+
----------
620+
per_einsum:
621+
If True, then the number of actions will reported per-Einsum.
622+
per_component:
623+
If True, then the number of actions will reported per-component.
624+
per_tensor:
625+
If True, then the number of actions will reported per-tensor.
626+
value_if_one_mapping:
627+
If True and there is only one mapping, then values in the returned
628+
dictionary will be a single value, rather than a list of values. Otherwise,
629+
they will always be a list of values.
630+
631+
Returns
632+
-------
633+
dict[tuple[str, ...], float | list[float]] | float | list[float]
634+
A dictionary with the number of actions performed for each Einsum,
635+
Component, Tensor, and Action. Keys are tuples of (Einsum name, Component
636+
name, Tensor name, Action name), with any of these being omitted if the
637+
corresponding parameter is not set to True. If none of the ``per_``
638+
parameters are set to True, a float or a list of floats is returned.
639+
"""
640+
641+
actions = self.access("action")
642+
643+
result = {}
644+
for einsum in self.einsum_names:
645+
einsum_accessed = actions.access(einsum, col_idx=0)
646+
# None for computes
647+
for tensor in list(
648+
self.spec.workload.einsums[einsum].tensor_names
649+
) + ["None"]:
650+
tensor_accessed = einsum_accessed.access(tensor, col_idx=1)
651+
for col in tensor_accessed._get_keys_of_length(2):
652+
component, action = col.split("<SEP>")
653+
result[(einsum, component, tensor, action)] = tensor_accessed[col]
654+
655+
keep_indices = []
656+
for i, idx in enumerate([per_einsum, per_component, per_tensor, True]):
657+
if idx:
658+
keep_indices.append(i)
659+
660+
if not keep_indices:
661+
v = sum(result.values())
662+
if value_if_one_mapping and len(self.data) == 1:
663+
return v.iloc[0] if isinstance(v, pd.Series) else v
664+
return v
665+
666+
new_result = defaultdict(float)
667+
for key, value in result.items():
668+
newkey = tuple(key[i] for i in keep_indices)
669+
new_result[newkey] += value
670+
result = new_result
671+
672+
if len(keep_indices) == 1:
673+
result = {k[0]: v for k, v in result.items()}
674+
675+
if value_if_one_mapping and len(self.data) == 1:
676+
return {k: v.iloc[0] for k, v in result.items()}
677+
678+
return {k: list(v) for k, v in result.items()}

0 commit comments

Comments
 (0)