44from accelforge .frontend .spec import Spec
55from accelforge .frontend .workload import EinsumName
66from accelforge ._accelerated_imports import pd
7- from accelforge .frontend .workload import TensorName
87from 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