accelforge 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. accelforge/__init__.py +21 -0
  2. accelforge/_accelerated_imports.py +16 -0
  3. accelforge/_deprecate/_simanneal/evalmapping.py +271 -0
  4. accelforge/_deprecate/_simanneal/mapspaceglobals.py +298 -0
  5. accelforge/_deprecate/_simanneal/simanneal.py +666 -0
  6. accelforge/_deprecate/_simanneal/tracking.py +105 -0
  7. accelforge/_deprecate/_simanneal/wrappers.py +218 -0
  8. accelforge/_deprecate/_simanneal2/__init__.py +7 -0
  9. accelforge/_deprecate/_simanneal2/simanneal.py +493 -0
  10. accelforge/_deprecate/_simanneal2/tracking.py +116 -0
  11. accelforge/_deprecate/compatibility_util.py +181 -0
  12. accelforge/_deprecate/layerdeduplication/__init__.py +2 -0
  13. accelforge/_deprecate/layerdeduplication/group_similar_einsums.py +160 -0
  14. accelforge/_deprecate/layerdeduplication/grouped_einsums.py +84 -0
  15. accelforge/_deprecate/mapping_filter_tags/__init__.py +2 -0
  16. accelforge/_deprecate/mapping_filter_tags/ffmt.py +212 -0
  17. accelforge/_deprecate/mapping_filter_tags/onesplit.py +24 -0
  18. accelforge/_deprecate/mapping_filter_tags/util.py +24 -0
  19. accelforge/_deprecate/tags.py +69 -0
  20. accelforge/_deprecate/viz/__init__.py +0 -0
  21. accelforge/_deprecate/viz/interactive.py +159 -0
  22. accelforge/_deprecate/viz/reservationtree.py +307 -0
  23. accelforge/_deprecate/viz/ski_slope.py +88 -0
  24. accelforge/_version.py +15 -0
  25. accelforge/examples.py +39 -0
  26. accelforge/frontend/__init__.py +10 -0
  27. accelforge/frontend/_binding.py +129 -0
  28. accelforge/frontend/_workload_isl/__init__.py +2 -0
  29. accelforge/frontend/_workload_isl/_isl.py +149 -0
  30. accelforge/frontend/_workload_isl/_symbolic.py +141 -0
  31. accelforge/frontend/arch copy.py +1544 -0
  32. accelforge/frontend/arch.py +1642 -0
  33. accelforge/frontend/config.py +63 -0
  34. accelforge/frontend/mapper/__init__.py +5 -0
  35. accelforge/frontend/mapper/ffm.py +126 -0
  36. accelforge/frontend/mapper/mapper.py +7 -0
  37. accelforge/frontend/mapper/metrics.py +30 -0
  38. accelforge/frontend/mapping/__init__.py +1 -0
  39. accelforge/frontend/mapping/mapping.py +1736 -0
  40. accelforge/frontend/model.py +14 -0
  41. accelforge/frontend/renames.py +150 -0
  42. accelforge/frontend/spec copy.py +230 -0
  43. accelforge/frontend/spec.py +301 -0
  44. accelforge/frontend/variables.py +12 -0
  45. accelforge/frontend/workload.py +952 -0
  46. accelforge/mapper/FFM/__init__.py +9 -0
  47. accelforge/mapper/FFM/_join_pmappings/__init__.py +0 -0
  48. accelforge/mapper/FFM/_join_pmappings/compatibility.py +653 -0
  49. accelforge/mapper/FFM/_join_pmappings/compress_pmappings.py +140 -0
  50. accelforge/mapper/FFM/_join_pmappings/join_pmappings.py +703 -0
  51. accelforge/mapper/FFM/_join_pmappings/pmapping_dataframe.py +901 -0
  52. accelforge/mapper/FFM/_join_pmappings/pmapping_group.py +337 -0
  53. accelforge/mapper/FFM/_make_pmappings/contraints/__init__.py +0 -0
  54. accelforge/mapper/FFM/_make_pmappings/contraints/constraints.py +360 -0
  55. accelforge/mapper/FFM/_make_pmappings/make_pmapping_templates/__init__.py +1 -0
  56. accelforge/mapper/FFM/_make_pmappings/make_pmapping_templates/make_loops.py +373 -0
  57. accelforge/mapper/FFM/_make_pmappings/make_pmapping_templates/make_pmapping_templates.py +463 -0
  58. accelforge/mapper/FFM/_make_pmappings/make_pmapping_templates/make_reservations.py +95 -0
  59. accelforge/mapper/FFM/_make_pmappings/make_pmapping_templates/make_storage_order.py +382 -0
  60. accelforge/mapper/FFM/_make_pmappings/make_pmapping_templates/make_storages.py +155 -0
  61. accelforge/mapper/FFM/_make_pmappings/make_pmappings.py +411 -0
  62. accelforge/mapper/FFM/_make_pmappings/make_pmappings_from_templates/__init__.py +1 -0
  63. accelforge/mapper/FFM/_make_pmappings/make_pmappings_from_templates/make_pmappings_from_templates.py +407 -0
  64. accelforge/mapper/FFM/_make_pmappings/make_pmappings_from_templates/make_tile_shapes.py +1681 -0
  65. accelforge/mapper/FFM/_make_pmappings/make_pmappings_from_templates/run_model.py +170 -0
  66. accelforge/mapper/FFM/_make_pmappings/make_pmappings_from_templates/symbol_relations.py +174 -0
  67. accelforge/mapper/FFM/_make_pmappings/pmapper_job.py +282 -0
  68. accelforge/mapper/FFM/_pareto_df/df_convention.py +273 -0
  69. accelforge/mapper/FFM/_pareto_df/pareto copy.py +836 -0
  70. accelforge/mapper/FFM/_pareto_df/pareto.py +508 -0
  71. accelforge/mapper/FFM/data.py +61 -0
  72. accelforge/mapper/FFM/main copy.py +236 -0
  73. accelforge/mapper/FFM/main.py +208 -0
  74. accelforge/mapper/FFM/mappings.py +510 -0
  75. accelforge/mapper/FFM/pmappings.py +310 -0
  76. accelforge/mapper/__init__.py +4 -0
  77. accelforge/mapper.py +0 -0
  78. accelforge/model/__init__.py +1 -0
  79. accelforge/model/_looptree/__init__.py +0 -0
  80. accelforge/model/_looptree/accesses.py +335 -0
  81. accelforge/model/_looptree/capacity/__init__.py +1 -0
  82. accelforge/model/_looptree/capacity/aggregators.py +36 -0
  83. accelforge/model/_looptree/capacity/capacity.py +47 -0
  84. accelforge/model/_looptree/energy.py +150 -0
  85. accelforge/model/_looptree/equivalent_ranks.py +29 -0
  86. accelforge/model/_looptree/latency/__init__.py +1 -0
  87. accelforge/model/_looptree/latency/latency.py +98 -0
  88. accelforge/model/_looptree/latency/memory.py +120 -0
  89. accelforge/model/_looptree/latency/processors.py +92 -0
  90. accelforge/model/_looptree/mapping_utilities.py +71 -0
  91. accelforge/model/_looptree/reuse/__init__.py +4 -0
  92. accelforge/model/_looptree/reuse/isl/__init__.py +1 -0
  93. accelforge/model/_looptree/reuse/isl/des.py +59 -0
  94. accelforge/model/_looptree/reuse/isl/isl_functions.py +374 -0
  95. accelforge/model/_looptree/reuse/isl/mapping_to_isl/__init__.py +4 -0
  96. accelforge/model/_looptree/reuse/isl/mapping_to_isl/analyze_mapping.py +297 -0
  97. accelforge/model/_looptree/reuse/isl/mapping_to_isl/skews_from_mapping.py +236 -0
  98. accelforge/model/_looptree/reuse/isl/mapping_to_isl/tiling.py +685 -0
  99. accelforge/model/_looptree/reuse/isl/mapping_to_isl/types.py +188 -0
  100. accelforge/model/_looptree/reuse/isl/spatial.py +260 -0
  101. accelforge/model/_looptree/reuse/isl/temporal.py +182 -0
  102. accelforge/model/_looptree/reuse/symbolic/__init__.py +1 -0
  103. accelforge/model/_looptree/reuse/symbolic/symbolic copy 2.py +1346 -0
  104. accelforge/model/_looptree/reuse/symbolic/symbolic copy.py +1408 -0
  105. accelforge/model/_looptree/reuse/symbolic/symbolic.py +1396 -0
  106. accelforge/model/_looptree/run.py +122 -0
  107. accelforge/model/_looptree/types.py +26 -0
  108. accelforge/model/_looptree/visualization/__init__.py +0 -0
  109. accelforge/model/_looptree/visualization/occupancy.py +11 -0
  110. accelforge/model/main.py +222 -0
  111. accelforge/plotting/__init__.py +2 -0
  112. accelforge/plotting/mappings.py +219 -0
  113. accelforge/plotting/specs.py +57 -0
  114. accelforge/util/__init__.py +4 -0
  115. accelforge/util/_base_analysis_types.py +24 -0
  116. accelforge/util/_basetypes.py +1089 -0
  117. accelforge/util/_frozenset.py +36 -0
  118. accelforge/util/_isl.py +29 -0
  119. accelforge/util/_itertools.py +14 -0
  120. accelforge/util/_mathfuncs.py +57 -0
  121. accelforge/util/_parse_expressions.py +339 -0
  122. accelforge/util/_picklecache.py +32 -0
  123. accelforge/util/_setexpressions.py +268 -0
  124. accelforge/util/_sympy/__init__.py +0 -0
  125. accelforge/util/_sympy/broadcast_max.py +18 -0
  126. accelforge/util/_visualization.py +112 -0
  127. accelforge/util/_yaml.py +579 -0
  128. accelforge/util/parallel.py +193 -0
  129. accelforge-0.0.1.dist-info/METADATA +64 -0
  130. accelforge-0.0.1.dist-info/RECORD +258 -0
  131. accelforge-0.0.1.dist-info/WHEEL +5 -0
  132. accelforge-0.0.1.dist-info/licenses/LICENSE +19 -0
  133. accelforge-0.0.1.dist-info/top_level.txt +5 -0
  134. docs/_build/html/_sources/fastfusion.frontend.mapper.rst.txt +37 -0
  135. docs/_build/html/_sources/fastfusion.frontend.rst.txt +70 -0
  136. docs/_build/html/_sources/fastfusion.frontend.workload.rst.txt +21 -0
  137. docs/_build/html/_sources/fastfusion.mapper.FFM.rst.txt +37 -0
  138. docs/_build/html/_sources/fastfusion.mapper.rst.txt +18 -0
  139. docs/_build/html/_sources/fastfusion.rst.txt +20 -0
  140. docs/_build/html/_sources/fastfusion.util.rst.txt +21 -0
  141. docs/_build/html/_sources/index.rst.txt +87 -0
  142. docs/_build/html/_sources/modules.rst.txt +7 -0
  143. docs/_build/html/_sources/notes/citation.rst.txt +45 -0
  144. docs/_build/html/_sources/notes/definitions.rst.txt +43 -0
  145. docs/_build/html/_sources/notes/faqs.rst.txt +39 -0
  146. docs/_build/html/_sources/notes/modeling/accelerator_energy_latency.rst.txt +72 -0
  147. docs/_build/html/_sources/notes/modeling/component_energy_area.rst.txt +96 -0
  148. docs/_build/html/_sources/notes/modeling/mapping.rst.txt +100 -0
  149. docs/_build/html/_sources/notes/modeling.rst.txt +33 -0
  150. docs/_build/html/_sources/notes/parsing/arithmetic_parsing.rst.txt +136 -0
  151. docs/_build/html/_sources/notes/parsing/setexpressions.rst.txt +63 -0
  152. docs/_build/html/_sources/notes/parsing/yaml_parsing.rst.txt +176 -0
  153. docs/_build/html/_sources/notes/quickstart_and_installation.rst.txt +9 -0
  154. docs/_build/html/_sources/notes/spec/architecture.rst.txt +133 -0
  155. docs/_build/html/_sources/notes/spec/mapping.rst.txt +12 -0
  156. docs/_build/html/_sources/notes/spec/workload.rst.txt +83 -0
  157. docs/_build/html/_sources/notes/spec.rst.txt +36 -0
  158. docs/source/_ext/include_attrs.py +213 -0
  159. docs/source/_ext/include_docstring.py +364 -0
  160. docs/source/_ext/include_functions.py +154 -0
  161. docs/source/_ext/include_notebook.py +131 -0
  162. docs/source/_ext/include_yaml.py +119 -0
  163. docs/source/_ext/inherited_attributes.py +222 -0
  164. docs/source/_ext/paths.py +4 -0
  165. docs/source/conf.py +79 -0
  166. examples/arches/compute_in_memory/_include.yaml +74 -0
  167. examples/arches/compute_in_memory/_include_functions.py +229 -0
  168. examples/arches/compute_in_memory/_load_spec.py +57 -0
  169. examples/arches/compute_in_memory/components/c2c_multiplier.py +181 -0
  170. examples/arches/compute_in_memory/components/dac_c2c_r2r.py +605 -0
  171. examples/arches/compute_in_memory/components/misc.py +195 -0
  172. examples/arches/compute_in_memory/components/util/bit_functions.py +51 -0
  173. examples/arches/compute_in_memory/components/zero_comparator.py +92 -0
  174. examples/arches/compute_in_memory/isaac.yaml +233 -0
  175. examples/arches/compute_in_memory/memory_cells/ecram_demo.yaml +63 -0
  176. examples/arches/compute_in_memory/memory_cells/rram_example.yaml +63 -0
  177. examples/arches/compute_in_memory/memory_cells/rram_isaac_isca_2016.yaml +64 -0
  178. examples/arches/compute_in_memory/memory_cells/rram_neurosim_default.yaml +63 -0
  179. examples/arches/compute_in_memory/memory_cells/rram_raella_isca_2023.yaml +70 -0
  180. examples/arches/compute_in_memory/memory_cells/rram_wan_nature_2022.yaml +63 -0
  181. examples/arches/compute_in_memory/memory_cells/sram_colonnade_jssc_2021.yaml +63 -0
  182. examples/arches/compute_in_memory/memory_cells/sram_example.yaml +63 -0
  183. examples/arches/compute_in_memory/memory_cells/sram_jia_jssc_2020.yaml +63 -0
  184. examples/arches/compute_in_memory/memory_cells/sram_sinangil_jssc_2021.yaml +63 -0
  185. examples/arches/compute_in_memory/memory_cells/sram_wang_vlsi_2022.yaml +63 -0
  186. examples/arches/compute_in_memory/wang_vlsi_2022.yaml +289 -0
  187. examples/arches/eyeriss.yaml +68 -0
  188. examples/arches/fanout_variations/at_glb.yaml +31 -0
  189. examples/arches/fanout_variations/at_glb_with_fanout_node.yaml +34 -0
  190. examples/arches/fanout_variations/at_mac.yaml +31 -0
  191. examples/arches/fanout_variations/at_mac_with_constraints.yaml +38 -0
  192. examples/arches/fanout_variations/at_mac_with_fanout_node.yaml +34 -0
  193. examples/arches/nvdla.yaml +47 -0
  194. examples/arches/simple.yaml +28 -0
  195. examples/arches/tpu_v4i.yaml +67 -0
  196. examples/mappings/unfused_matmuls_to_simple.yaml +33 -0
  197. examples/misc/component_annotated.yaml +33 -0
  198. examples/workloads/gpt3_6.7B.yaml +124 -0
  199. examples/workloads/matmuls.yaml +20 -0
  200. examples/workloads/mobilenet_28.yaml +81 -0
  201. examples/workloads/mobilenet_various_separate.yaml +106 -0
  202. examples/workloads/three_matmuls_annotated.yaml +59 -0
  203. notebooks/.ipynb_checkpoints/fastfusion_arch_study_michael-checkpoint.ipynb +359 -0
  204. notebooks/compute_in_memory/_scripts.py +339 -0
  205. notebooks/compute_in_memory/isaac.guide.ipynb +270 -0
  206. notebooks/compute_in_memory/wang_vlsi_2022.ipynb +602 -0
  207. notebooks/paths.py +4 -0
  208. notebooks/tutorials/.ipynb_checkpoints/1_FFM-checkpoint.ipynb +3110 -0
  209. notebooks/tutorials/FFM.ipynb +3498 -0
  210. notebooks/tutorials/_include.py +48 -0
  211. notebooks/tutorials/component_energy_area.ipynb +363 -0
  212. tests/Q_mapping.yaml +38 -0
  213. tests/__init__.py +0 -0
  214. tests/conv.mapping.yaml +27 -0
  215. tests/conv.workload.yaml +13 -0
  216. tests/conv_sym.mapping.yaml +43 -0
  217. tests/copy.mapping.yaml +35 -0
  218. tests/copy.workload.yaml +15 -0
  219. tests/distribuffers/__init__.py +0 -0
  220. tests/distribuffers/multicast/test_cases.yaml +482 -0
  221. tests/distribuffers/spec/binding/valid_bindings.yaml +97 -0
  222. tests/distribuffers/spec/distributed.yaml +100 -0
  223. tests/distribuffers/spec/logical_arch.yaml +32 -0
  224. tests/distribuffers/spec/physical_arch.yaml +69 -0
  225. tests/distribuffers/test_binding.py +48 -0
  226. tests/frontend/__init__.py +0 -0
  227. tests/frontend/test_mapping_viz.py +52 -0
  228. tests/mapper/__init__.py +0 -0
  229. tests/mapper/configs/conv1d/conv1d.mapping.yaml +31 -0
  230. tests/mapper/configs/conv1d/conv1d.workload.yaml +11 -0
  231. tests/mapper/configs/two_conv1d/two_conv1d.expected.yaml +38 -0
  232. tests/mapper/configs/two_conv1d/two_conv1d.mapping.yaml +54 -0
  233. tests/mapper/configs/two_conv1d/two_conv1d.workload.yaml +19 -0
  234. tests/mapper/test_mapping_to_isl.py +90 -0
  235. tests/mapper/test_spatial_reuse_analysis.py +67 -0
  236. tests/mapper/test_temporal_reuse_analysis.py +56 -0
  237. tests/mapper/util.py +58 -0
  238. tests/matmul.mapping.yaml +29 -0
  239. tests/matmul.workload.yaml +12 -0
  240. tests/matmul_spatial.mapping.yaml +44 -0
  241. tests/mha.renames.yaml +65 -0
  242. tests/mha.workload.yaml +67 -0
  243. tests/mha.yaml +59 -0
  244. tests/mha_full.workload.yaml +67 -0
  245. tests/mobilenet.workload.yaml +35 -0
  246. tests/mobilenet_long.workload.yaml +64 -0
  247. tests/pmappingcache.py +24 -0
  248. tests/processing_stage.arch.yaml +40 -0
  249. tests/snowcat.arch.yaml +36 -0
  250. tests/test_ffm_join_pmappings.py +106 -0
  251. tests/test_ffm_make_pmappings.py +82 -0
  252. tests/test_ffm_make_tile_shapes.py +49 -0
  253. tests/test_mapper.py +100 -0
  254. tests/test_model.py +37 -0
  255. tests/test_plotting.py +72 -0
  256. tests/test_processing_stage.py +46 -0
  257. tests/test_symbolic_model.py +248 -0
  258. tests/test_workload.py +141 -0
@@ -0,0 +1,510 @@
1
+ from collections import defaultdict
2
+ from accelforge.frontend import arch
3
+ from accelforge.frontend.spec import Spec
4
+ from accelforge.frontend.workload import EinsumName
5
+ from accelforge._accelerated_imports import pd
6
+ from accelforge.frontend.workload import TensorName
7
+ from accelforge.mapper.FFM._make_pmappings.make_pmappings import (
8
+ get_num_computes,
9
+ get_per_tensor_size,
10
+ )
11
+ from typing import Union
12
+ import numpy as np
13
+
14
+
15
+ class Mappings:
16
+ """
17
+ A collection of mappings and their evaluation results, generated by
18
+ :func:`~accelforge.mapper.FFM.join_pmappings`.
19
+
20
+ Attributes
21
+ ----------
22
+ spec:
23
+ The specification used to generate the mappings.
24
+ einsum_names:
25
+ The names of the Einsums in these mappings.
26
+ data:
27
+ A DataFrame containing the mappings and their evaluation results. Column names
28
+ in the dataframe are are string separated by "<SEP>", such as
29
+ "Total<SEP>energy".
30
+ total_mappings:
31
+ The total number of mappings that have been explored in order to get the
32
+ mappings in the dataframe, equal to the full mapspace size.
33
+ valid_mappings:
34
+ The number of valid mappings that have been explored in order to get the
35
+ mappings in the dataframe, equal to the valid mapspace size.
36
+ flattened_arches:
37
+ A dictionary of (EinsumName, Compute Name) to lists of architecture nodes. These
38
+ contain the parsed and flattened architecture node for that particular Einsum
39
+ and compute combination.
40
+ parsed_specs:
41
+ A dictionary of Einsum names to parsed specifications. These contain the parsed
42
+ specification for that particular Einsum.
43
+ """
44
+
45
+ def __init__(
46
+ self,
47
+ spec: Spec,
48
+ einsum_names: list[EinsumName],
49
+ data: pd.DataFrame,
50
+ total_mappings: int,
51
+ valid_mappings: int,
52
+ flattened_arches: dict[(EinsumName, str), list[arch.Leaf]],
53
+ parsed_specs: dict[EinsumName, Spec],
54
+ ):
55
+ self.spec: Spec = spec
56
+ self.einsum_names: list[EinsumName] = einsum_names
57
+ self.data: pd.DataFrame = data
58
+ self.total_mappings: int = total_mappings
59
+ self.valid_mappings: int = valid_mappings
60
+ self.flattened_arches: dict[(EinsumName, str), list[arch.Leaf]] = (
61
+ flattened_arches
62
+ )
63
+ self.parsed_specs: dict[EinsumName, Spec] = parsed_specs
64
+
65
+ def num_computes(self, einsum_name: EinsumName | None = None) -> int:
66
+ """
67
+ Returns the number of computes for the given Einsum name, or total computes if
68
+ ``einsum_name`` is ``None``.
69
+ """
70
+ # TODO: this is not correct if there are recomputations.
71
+ if einsum_name is None:
72
+ return sum(get_num_computes(self.spec, e) for e in self.einsum_names)
73
+ return get_num_computes(self.spec, einsum_name)
74
+
75
+ def per_tensor_size(self) -> dict[TensorName, int]:
76
+ """
77
+ Returns a dictionary of: {Tensor name: Number of elements} for each tensor in
78
+ the spec.
79
+ """
80
+ return get_per_tensor_size(self.spec)
81
+
82
+ def _update(self, **kwargs):
83
+ data = dict(
84
+ spec=self.spec,
85
+ einsum_names=self.einsum_names,
86
+ data=self.data,
87
+ total_mappings=self.total_mappings,
88
+ valid_mappings=self.valid_mappings,
89
+ flattened_arches=self.flattened_arches,
90
+ parsed_specs=self.parsed_specs,
91
+ )
92
+ data.update(kwargs)
93
+ return Mappings(**data)
94
+
95
+ def __getitem__(self, key: str | int) -> Union[pd.Series, "Mappings"]:
96
+ if isinstance(key, int):
97
+ return self._update(data=pd.DataFrame(self.data.iloc[key]).T)
98
+ return self.data[key]
99
+
100
+ def __len__(self):
101
+ return len(self.data)
102
+
103
+ def __iter__(self):
104
+ return iter(self.data)
105
+
106
+ def _get_cols(self, key: str) -> list[str]:
107
+ found_index = None
108
+ found = []
109
+ for col in self.data.columns:
110
+ col = col.split("<SEP>")
111
+ if key not in col:
112
+ continue
113
+
114
+ if sum(c == key for c in col) > 1:
115
+ raise ValueError(
116
+ f"Key {key} found multiple times in the column names. "
117
+ f'Columns: "{col}"'
118
+ )
119
+
120
+ if found_index is not None and col.index(key) != found_index:
121
+ raise ValueError(
122
+ f"Key {key} found at varying indexes in the column names. "
123
+ f'Columns: "{col}" and "{found}"'
124
+ )
125
+ found_index = col.index(key)
126
+ found.append("<SEP>".join(col))
127
+ return found
128
+
129
+ def access(self, *keys: str) -> "Mappings":
130
+ """
131
+ Returns a new Mappings object with only the columns that contain the given keys.
132
+ Column names are strings separated by "<SEP>", and this method will return
133
+ columns with one <SEP>-separated string matching the given key. Then, for all
134
+ remaining columns, the key will be removed.
135
+
136
+ For example, if the columns are "Compute<SEP>Energy", and "DRAM<SEP>Energy", and
137
+ "DRAM<SEP>Latency", then access("Energy") will return a Mappings object with
138
+ columns "Compute" and "DRAM", and access("DRAM") will return a Mappings object
139
+ with columns "Compute" and "Latency".
140
+
141
+ If multiple keys are given, then the procedure is repeated for each key.
142
+
143
+ Parameters
144
+ ----------
145
+ keys:
146
+ The keys to access from the columns.
147
+
148
+ Returns
149
+ -------
150
+ A new Mappings object with only the given keys.
151
+ """
152
+ assert len(set(self.data.columns)) == len(
153
+ self.data.columns
154
+ ), "Columns must be unique"
155
+
156
+ if len(keys) != 1:
157
+ for k in keys:
158
+ self = self.access(k)
159
+ return self
160
+
161
+ key = keys[0]
162
+ col_renames = {}
163
+ for col in self._get_cols(key):
164
+ col_renames[col] = "<SEP>".join(c for c in col.split("<SEP>") if c != key)
165
+
166
+ return self._update(
167
+ data=self.data[list(col_renames.keys())].rename(columns=col_renames)
168
+ )
169
+
170
+ def _get_keys_of_length(self, length: int) -> list[str]:
171
+ cols = [c for c in self.columns if len(c.split("<SEP>")) == length]
172
+ return cols
173
+
174
+ def drop(self, *keys: str) -> "Mappings":
175
+ """
176
+ Returns a new Mappings object with the given keys dropped from all columns.
177
+ Column names are strings separated by "<SEP>", and this method will will drop
178
+ columns with one <SEP>-separated string matching the given key. Then, for all
179
+ remaining columns, the key will be removed.
180
+
181
+ For example, if the columns are "Compute<SEP>Energy", and "DRAM<SEP>Energy", and
182
+ "DRAM<SEP>Latency", then drop("Energy") will drop "Compute<SEP>Energy" and
183
+ "DRAM<SEP>Energy", and drop("DRAM") will drop "DRAM<SEP>Energy" and
184
+ "DRAM<SEP>Latency".
185
+
186
+ If multiple keys are given, then the procedure is repeated for each key.
187
+
188
+ Parameters
189
+ ----------
190
+ keys:
191
+ The keys to drop from the columns.
192
+
193
+ Returns
194
+ -------
195
+ A new Mappings object with the given keys dropped from all columns.
196
+ """
197
+ assert len(set(self.data.columns)) == len(
198
+ self.data.columns
199
+ ), "Columns must be unique"
200
+
201
+ if len(keys) != 1:
202
+ for k in keys:
203
+ self = self.drop(k)
204
+ return self
205
+
206
+ return self._update(data=self.data.drop(columns=self._get_cols(keys[0])))
207
+
208
+ def sum(self, keep_key_index: list[int] | int | None = None) -> "Mappings":
209
+ if len(self.data.columns) == 1:
210
+ return self
211
+
212
+ if isinstance(keep_key_index, int):
213
+ keep_key_index = [keep_key_index]
214
+ elif keep_key_index is None:
215
+ keep_key_index = []
216
+
217
+ columns = list(self.data.columns)
218
+ for col in self.data.columns:
219
+ if len(col.split("<SEP>")) != len(columns[0].split("<SEP>")):
220
+ raise ValueError(
221
+ f"Can only sum columns with same-length keys. Try first calling "
222
+ f'access("key") or drop("key") to make all columns '
223
+ f"have the same number of keys."
224
+ )
225
+
226
+ if any(k < 0 or k >= len(columns[0].split("<SEP>")) for k in keep_key_index):
227
+ raise ValueError(
228
+ f"Keep indices must be in the range [0, {len(columns[0].split('<SEP>'))})"
229
+ )
230
+
231
+ target2sources = {}
232
+ for col in columns:
233
+ target = col.split("<SEP>")
234
+ target = "<SEP>".join(target[i] for i in keep_key_index)
235
+ target2sources.setdefault(target, []).append(col)
236
+
237
+ new_data = pd.DataFrame(index=self.data.index)
238
+ for target, sources in target2sources.items():
239
+ new_data[target] = self.data[sources].sum(axis=1)
240
+
241
+ return self._update(data=new_data)
242
+
243
+ @property
244
+ def columns(self) -> list[str]:
245
+ """The columns of the dataframe."""
246
+ return list(self.data.columns)
247
+
248
+ def to_dict(self, value_if_one_mapping: bool = True) -> dict[str, list[float]]:
249
+ """
250
+ Returns the data in this Mappings object as a dictionary. Each column in the
251
+ this Mappings' data becomes a key in the dictionary. Values in the dictionary
252
+ may be a single value if there is only one mapping, or a list of values if there
253
+ are multiple mappings.
254
+
255
+ Parameters
256
+ ----------
257
+ value_if_one_mapping:
258
+ If True and there is only one mapping, then values in the returned
259
+ dictionary will be a single value, rather than a list of values. Otherwise,
260
+ they will always be a list of values.
261
+
262
+ Returns
263
+ -------
264
+ A dictionary with the same keys as the columns of the dataframe, and values that
265
+ are either a single value or a list of values.
266
+ """
267
+ new = self.data.to_dict(orient="list")
268
+ if value_if_one_mapping and len(self) == 1:
269
+ new = {k: v[0] for k, v in new.items()}
270
+ return new
271
+
272
+ def per_compute(self, per_einsum: bool = False) -> "Mappings":
273
+ """
274
+ Returns the per-compute evaluation results by dividing all statistics by the
275
+ number of computes.
276
+
277
+ Parameters
278
+ ----------
279
+ per_einsum:
280
+ If True, then for each Einsum, statistics will be divided only by the number
281
+ of computes for that Einsum. This makes it clear to see per-compute stats
282
+ for each Einsum, but note that total energy/compute will not be a direct sum
283
+ of the per-compute stats then (and the same for latency and other
284
+ statistics).
285
+
286
+ Returns
287
+ -------
288
+ A new Mappings object with the per-compute evaluation results.
289
+ """
290
+ new_df = self.data.copy()
291
+ total_computes = self.num_computes()
292
+ for col in new_df.columns:
293
+ n_computes = total_computes
294
+ if per_einsum:
295
+ einsum_name = col.split("<SEP>")[0]
296
+ if einsum_name not in self.einsum_names and einsum_name != "Total":
297
+ raise ValueError(
298
+ f"Einsum name {einsum_name} not found. Ensure that all "
299
+ f"columns are prefixed with the Einsum name if per_einsum "
300
+ f"is True."
301
+ )
302
+ if einsum_name != "Total":
303
+ n_computes = self.num_computes(einsum_name)
304
+ # Check if the column can be converted to numeric
305
+ try:
306
+ new_df[col] /= n_computes
307
+ except (ValueError, TypeError):
308
+ # Skip columns that can't be converted to numeric
309
+ continue
310
+ return self._update(data=new_df)
311
+
312
+ def drop_zeros(self) -> "Mappings":
313
+ """
314
+ Returns a new Mappings object with all columns that have only zeros dropped.
315
+ """
316
+ new_df = self.data.copy()
317
+ new_df = new_df[(c for c in new_df.columns if (new_df[c] != 0).any())]
318
+ return self._update(data=new_df)
319
+
320
+ def _repr_svg_(self) -> str:
321
+ return self.render()
322
+
323
+ def render(self, index: int | None = None) -> str:
324
+ """
325
+ Renders the mapping as a Pydot graph. Returns an SVG string. This is only
326
+ supported if there is a single mapping; if there are multiple mappings, then
327
+ either index into this Mappings object first, or pass in an index.
328
+
329
+ Parameters
330
+ ----------
331
+ index:
332
+ The index of the mapping to render. If None and there are multiple mappings,
333
+ then an error is raised.
334
+
335
+ Returns
336
+ -------
337
+ An SVG string of the mapping.
338
+
339
+ Raises
340
+ ------
341
+ ValueError:
342
+ If there are multiple mappings and no index is provided.
343
+ """
344
+ if index is not None:
345
+ self = self[index]
346
+ if len(self) != 1:
347
+ raise ValueError(
348
+ f"Can only render a single mapping, but got {len(self)}. Try calling "
349
+ f"mappings[i].render() instead, for some integer 0 <= i < {len(self)}."
350
+ )
351
+ return self.data.iloc[0][f"Total<SEP>mapping"].render()
352
+
353
+ def energy(
354
+ self: "Mappings",
355
+ per_einsum: bool = False,
356
+ per_component: bool = False,
357
+ per_tensor: bool = False,
358
+ per_action: bool = False,
359
+ value_if_one_mapping: bool = True,
360
+ ) -> dict[tuple[str, ...] | str, float | list[float]] | float | list[float]:
361
+ """
362
+ Returns the energy consumed. A dictionary is returned with keys that are tuples
363
+ of (Einsum name, Component name, Tensor name, Action name), with any of these
364
+ being omitted if the corresponding parameter is not set to True. If neither of
365
+ the per_... parameters are set to True, a float or a list of floats is returned.
366
+
367
+ NOTE: Leak power is not per-tensor. If per_tensor is True, then the tensor name
368
+ for leak will be None.
369
+
370
+ Parameters
371
+ ----------
372
+ per_einsum:
373
+ If True, then the energy will reported per-Einsum.
374
+ per_component:
375
+ If True, then the energy will reported per-component.
376
+ per_tensor:
377
+ If True, then the energy will reported per-tensor.
378
+ per_action:
379
+ If True, then the energy will reported per-action.
380
+ value_if_one_mapping:
381
+ If True and there is only one mapping, then values in the returned
382
+ dictionary will be a single value, rather than a list of values. Otherwise,
383
+ they will always be a list of values.
384
+
385
+ Returns
386
+ -------
387
+ dict[tuple[str, ...], float | list[float]] | float | list[float]:
388
+ A dictionary with the energy consumed for each Einsum, Component, Tensor,
389
+ and Action. Keys are tuples of (Einsum name, Component name, Tensor name,
390
+ Action name), with any of these being omitted if the corresponding parameter
391
+ is not set to True. If none of the per_... parameters are set to True, a
392
+ float or a list of floats is returned.
393
+ """
394
+
395
+ energy = self.access("energy")
396
+
397
+ result = {}
398
+ for einsum in self.einsum_names:
399
+ einsum_accessed = energy.access(einsum)
400
+ for tensor in self.spec.workload.einsums[einsum].tensor_names:
401
+ tensor_accessed = einsum_accessed.access(tensor)
402
+ for col in tensor_accessed._get_keys_of_length(2):
403
+ component, action = col.split("<SEP>")
404
+ result[(einsum, component, tensor, action)] = tensor_accessed[col]
405
+ for col in einsum_accessed._get_keys_of_length(2):
406
+ component, action = col.split("<SEP>")
407
+ if action == "leak":
408
+ result[(einsum, component, None, action)] = einsum_accessed[col]
409
+
410
+ keep_indices = []
411
+ for i, idx in enumerate([per_einsum, per_component, per_tensor, per_action]):
412
+ if idx:
413
+ keep_indices.append(i)
414
+
415
+ if not keep_indices:
416
+ v = sum(result.values())
417
+ if value_if_one_mapping and len(self.data) == 1:
418
+ return v.iloc[0]
419
+ return v
420
+
421
+ new_result = defaultdict(float)
422
+ for key, value in result.items():
423
+ newkey = tuple(key[i] for i in keep_indices)
424
+ new_result[newkey] += value
425
+ result = new_result
426
+
427
+ if len(keep_indices) == 1:
428
+ result = {k[0]: v for k, v in result.items()}
429
+
430
+ if value_if_one_mapping and len(self.data) == 1:
431
+ return {k: v.iloc[0] for k, v in result.items()}
432
+
433
+ return result
434
+
435
+ def latency(
436
+ self: "Mappings",
437
+ per_einsum: bool = False,
438
+ per_component: bool = False,
439
+ value_if_one_mapping: bool = True,
440
+ ) -> dict[tuple[str, ...] | str, float | list[float]] | float | list[float]:
441
+ """
442
+ Returns the latency consumed. A dictionary is returned with keys that are tuples
443
+ of (Einsum name, Component name), with either being omitted if the corresponding
444
+ parameter is not set to True. If neither of the per_... parameters are set to
445
+ True, a float or a list of floats is returned.
446
+
447
+ NOTE: If per-Einsum is False and per-component is True, then the latency of each
448
+ component will be summed across all Einsums. THE TOTAL LATENCY MAY BE GREATER
449
+ THAN THE MAX OF THE PER-COMPONENT LATENCIES. This is because different
450
+ components can be the bottleneck for different Einsums.
451
+
452
+ Parameters
453
+ ----------
454
+ per_einsum:
455
+ If True, then the latency will reported per-Einsum.
456
+ per_component:
457
+ If True, then the latency will reported per-component.
458
+ value_if_one_mapping:
459
+ If True and there is only one mapping, then values in the returned
460
+ dictionary will be a single value, rather than a list of values. Otherwise,
461
+ they will always be a list of values.
462
+
463
+ Returns
464
+ -------
465
+ dict[tuple[str, ...], float | list[float]] | float | list[float]:
466
+ A dictionary with the latency for each Einsum+Component pair. Keys are
467
+ tuples of (Einsum name, Component name), with either being omitted if the
468
+ corresponding parameter is not set to True. If neither of the per_...
469
+ parameters are set to True, a float or a list of floats is returned.
470
+ """
471
+
472
+ energy = self.access("latency")
473
+
474
+ result = {}
475
+ for einsum in self.einsum_names:
476
+ einsum_accessed = energy.access(einsum)
477
+ for component in einsum_accessed._get_keys_of_length(1):
478
+ result[(einsum, component)] = einsum_accessed[component]
479
+
480
+ # If not per-component, aggregate into the per-Einsum latency
481
+ if not per_component:
482
+ new_result = {}
483
+ for (einsum, component), value in result.items():
484
+ if einsum not in new_result:
485
+ new_result[einsum] = value
486
+ else:
487
+ new_result[einsum] = np.maximum(new_result[einsum], value)
488
+ result = new_result
489
+
490
+ if not per_einsum:
491
+ # Not per-Einsum and per-component: for each component, sum across Einsums
492
+ if per_component:
493
+ new_result = {}
494
+ for (einsum, component), value in result.items():
495
+ if component not in new_result:
496
+ new_result[component] = value
497
+ else:
498
+ new_result[component] += value
499
+ result = new_result
500
+
501
+ # Not per-Einsum and not per-component: sum into a single value
502
+ else:
503
+ result = np.sum(list(result.values()))
504
+
505
+ if value_if_one_mapping and len(self.data) == 1:
506
+ if isinstance(result, dict):
507
+ return {k: v.iloc[0] for k, v in result.items()}
508
+ return result # Numpy sum already pulls out the number
509
+
510
+ return result