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.
- accelforge/__init__.py +21 -0
- accelforge/_accelerated_imports.py +16 -0
- accelforge/_deprecate/_simanneal/evalmapping.py +271 -0
- accelforge/_deprecate/_simanneal/mapspaceglobals.py +298 -0
- accelforge/_deprecate/_simanneal/simanneal.py +666 -0
- accelforge/_deprecate/_simanneal/tracking.py +105 -0
- accelforge/_deprecate/_simanneal/wrappers.py +218 -0
- accelforge/_deprecate/_simanneal2/__init__.py +7 -0
- accelforge/_deprecate/_simanneal2/simanneal.py +493 -0
- accelforge/_deprecate/_simanneal2/tracking.py +116 -0
- accelforge/_deprecate/compatibility_util.py +181 -0
- accelforge/_deprecate/layerdeduplication/__init__.py +2 -0
- accelforge/_deprecate/layerdeduplication/group_similar_einsums.py +160 -0
- accelforge/_deprecate/layerdeduplication/grouped_einsums.py +84 -0
- accelforge/_deprecate/mapping_filter_tags/__init__.py +2 -0
- accelforge/_deprecate/mapping_filter_tags/ffmt.py +212 -0
- accelforge/_deprecate/mapping_filter_tags/onesplit.py +24 -0
- accelforge/_deprecate/mapping_filter_tags/util.py +24 -0
- accelforge/_deprecate/tags.py +69 -0
- accelforge/_deprecate/viz/__init__.py +0 -0
- accelforge/_deprecate/viz/interactive.py +159 -0
- accelforge/_deprecate/viz/reservationtree.py +307 -0
- accelforge/_deprecate/viz/ski_slope.py +88 -0
- accelforge/_version.py +15 -0
- accelforge/examples.py +39 -0
- accelforge/frontend/__init__.py +10 -0
- accelforge/frontend/_binding.py +129 -0
- accelforge/frontend/_workload_isl/__init__.py +2 -0
- accelforge/frontend/_workload_isl/_isl.py +149 -0
- accelforge/frontend/_workload_isl/_symbolic.py +141 -0
- accelforge/frontend/arch copy.py +1544 -0
- accelforge/frontend/arch.py +1642 -0
- accelforge/frontend/config.py +63 -0
- accelforge/frontend/mapper/__init__.py +5 -0
- accelforge/frontend/mapper/ffm.py +126 -0
- accelforge/frontend/mapper/mapper.py +7 -0
- accelforge/frontend/mapper/metrics.py +30 -0
- accelforge/frontend/mapping/__init__.py +1 -0
- accelforge/frontend/mapping/mapping.py +1736 -0
- accelforge/frontend/model.py +14 -0
- accelforge/frontend/renames.py +150 -0
- accelforge/frontend/spec copy.py +230 -0
- accelforge/frontend/spec.py +301 -0
- accelforge/frontend/variables.py +12 -0
- accelforge/frontend/workload.py +952 -0
- accelforge/mapper/FFM/__init__.py +9 -0
- accelforge/mapper/FFM/_join_pmappings/__init__.py +0 -0
- accelforge/mapper/FFM/_join_pmappings/compatibility.py +653 -0
- accelforge/mapper/FFM/_join_pmappings/compress_pmappings.py +140 -0
- accelforge/mapper/FFM/_join_pmappings/join_pmappings.py +703 -0
- accelforge/mapper/FFM/_join_pmappings/pmapping_dataframe.py +901 -0
- accelforge/mapper/FFM/_join_pmappings/pmapping_group.py +337 -0
- accelforge/mapper/FFM/_make_pmappings/contraints/__init__.py +0 -0
- accelforge/mapper/FFM/_make_pmappings/contraints/constraints.py +360 -0
- accelforge/mapper/FFM/_make_pmappings/make_pmapping_templates/__init__.py +1 -0
- accelforge/mapper/FFM/_make_pmappings/make_pmapping_templates/make_loops.py +373 -0
- accelforge/mapper/FFM/_make_pmappings/make_pmapping_templates/make_pmapping_templates.py +463 -0
- accelforge/mapper/FFM/_make_pmappings/make_pmapping_templates/make_reservations.py +95 -0
- accelforge/mapper/FFM/_make_pmappings/make_pmapping_templates/make_storage_order.py +382 -0
- accelforge/mapper/FFM/_make_pmappings/make_pmapping_templates/make_storages.py +155 -0
- accelforge/mapper/FFM/_make_pmappings/make_pmappings.py +411 -0
- accelforge/mapper/FFM/_make_pmappings/make_pmappings_from_templates/__init__.py +1 -0
- accelforge/mapper/FFM/_make_pmappings/make_pmappings_from_templates/make_pmappings_from_templates.py +407 -0
- accelforge/mapper/FFM/_make_pmappings/make_pmappings_from_templates/make_tile_shapes.py +1681 -0
- accelforge/mapper/FFM/_make_pmappings/make_pmappings_from_templates/run_model.py +170 -0
- accelforge/mapper/FFM/_make_pmappings/make_pmappings_from_templates/symbol_relations.py +174 -0
- accelforge/mapper/FFM/_make_pmappings/pmapper_job.py +282 -0
- accelforge/mapper/FFM/_pareto_df/df_convention.py +273 -0
- accelforge/mapper/FFM/_pareto_df/pareto copy.py +836 -0
- accelforge/mapper/FFM/_pareto_df/pareto.py +508 -0
- accelforge/mapper/FFM/data.py +61 -0
- accelforge/mapper/FFM/main copy.py +236 -0
- accelforge/mapper/FFM/main.py +208 -0
- accelforge/mapper/FFM/mappings.py +510 -0
- accelforge/mapper/FFM/pmappings.py +310 -0
- accelforge/mapper/__init__.py +4 -0
- accelforge/mapper.py +0 -0
- accelforge/model/__init__.py +1 -0
- accelforge/model/_looptree/__init__.py +0 -0
- accelforge/model/_looptree/accesses.py +335 -0
- accelforge/model/_looptree/capacity/__init__.py +1 -0
- accelforge/model/_looptree/capacity/aggregators.py +36 -0
- accelforge/model/_looptree/capacity/capacity.py +47 -0
- accelforge/model/_looptree/energy.py +150 -0
- accelforge/model/_looptree/equivalent_ranks.py +29 -0
- accelforge/model/_looptree/latency/__init__.py +1 -0
- accelforge/model/_looptree/latency/latency.py +98 -0
- accelforge/model/_looptree/latency/memory.py +120 -0
- accelforge/model/_looptree/latency/processors.py +92 -0
- accelforge/model/_looptree/mapping_utilities.py +71 -0
- accelforge/model/_looptree/reuse/__init__.py +4 -0
- accelforge/model/_looptree/reuse/isl/__init__.py +1 -0
- accelforge/model/_looptree/reuse/isl/des.py +59 -0
- accelforge/model/_looptree/reuse/isl/isl_functions.py +374 -0
- accelforge/model/_looptree/reuse/isl/mapping_to_isl/__init__.py +4 -0
- accelforge/model/_looptree/reuse/isl/mapping_to_isl/analyze_mapping.py +297 -0
- accelforge/model/_looptree/reuse/isl/mapping_to_isl/skews_from_mapping.py +236 -0
- accelforge/model/_looptree/reuse/isl/mapping_to_isl/tiling.py +685 -0
- accelforge/model/_looptree/reuse/isl/mapping_to_isl/types.py +188 -0
- accelforge/model/_looptree/reuse/isl/spatial.py +260 -0
- accelforge/model/_looptree/reuse/isl/temporal.py +182 -0
- accelforge/model/_looptree/reuse/symbolic/__init__.py +1 -0
- accelforge/model/_looptree/reuse/symbolic/symbolic copy 2.py +1346 -0
- accelforge/model/_looptree/reuse/symbolic/symbolic copy.py +1408 -0
- accelforge/model/_looptree/reuse/symbolic/symbolic.py +1396 -0
- accelforge/model/_looptree/run.py +122 -0
- accelforge/model/_looptree/types.py +26 -0
- accelforge/model/_looptree/visualization/__init__.py +0 -0
- accelforge/model/_looptree/visualization/occupancy.py +11 -0
- accelforge/model/main.py +222 -0
- accelforge/plotting/__init__.py +2 -0
- accelforge/plotting/mappings.py +219 -0
- accelforge/plotting/specs.py +57 -0
- accelforge/util/__init__.py +4 -0
- accelforge/util/_base_analysis_types.py +24 -0
- accelforge/util/_basetypes.py +1089 -0
- accelforge/util/_frozenset.py +36 -0
- accelforge/util/_isl.py +29 -0
- accelforge/util/_itertools.py +14 -0
- accelforge/util/_mathfuncs.py +57 -0
- accelforge/util/_parse_expressions.py +339 -0
- accelforge/util/_picklecache.py +32 -0
- accelforge/util/_setexpressions.py +268 -0
- accelforge/util/_sympy/__init__.py +0 -0
- accelforge/util/_sympy/broadcast_max.py +18 -0
- accelforge/util/_visualization.py +112 -0
- accelforge/util/_yaml.py +579 -0
- accelforge/util/parallel.py +193 -0
- accelforge-0.0.1.dist-info/METADATA +64 -0
- accelforge-0.0.1.dist-info/RECORD +258 -0
- accelforge-0.0.1.dist-info/WHEEL +5 -0
- accelforge-0.0.1.dist-info/licenses/LICENSE +19 -0
- accelforge-0.0.1.dist-info/top_level.txt +5 -0
- docs/_build/html/_sources/fastfusion.frontend.mapper.rst.txt +37 -0
- docs/_build/html/_sources/fastfusion.frontend.rst.txt +70 -0
- docs/_build/html/_sources/fastfusion.frontend.workload.rst.txt +21 -0
- docs/_build/html/_sources/fastfusion.mapper.FFM.rst.txt +37 -0
- docs/_build/html/_sources/fastfusion.mapper.rst.txt +18 -0
- docs/_build/html/_sources/fastfusion.rst.txt +20 -0
- docs/_build/html/_sources/fastfusion.util.rst.txt +21 -0
- docs/_build/html/_sources/index.rst.txt +87 -0
- docs/_build/html/_sources/modules.rst.txt +7 -0
- docs/_build/html/_sources/notes/citation.rst.txt +45 -0
- docs/_build/html/_sources/notes/definitions.rst.txt +43 -0
- docs/_build/html/_sources/notes/faqs.rst.txt +39 -0
- docs/_build/html/_sources/notes/modeling/accelerator_energy_latency.rst.txt +72 -0
- docs/_build/html/_sources/notes/modeling/component_energy_area.rst.txt +96 -0
- docs/_build/html/_sources/notes/modeling/mapping.rst.txt +100 -0
- docs/_build/html/_sources/notes/modeling.rst.txt +33 -0
- docs/_build/html/_sources/notes/parsing/arithmetic_parsing.rst.txt +136 -0
- docs/_build/html/_sources/notes/parsing/setexpressions.rst.txt +63 -0
- docs/_build/html/_sources/notes/parsing/yaml_parsing.rst.txt +176 -0
- docs/_build/html/_sources/notes/quickstart_and_installation.rst.txt +9 -0
- docs/_build/html/_sources/notes/spec/architecture.rst.txt +133 -0
- docs/_build/html/_sources/notes/spec/mapping.rst.txt +12 -0
- docs/_build/html/_sources/notes/spec/workload.rst.txt +83 -0
- docs/_build/html/_sources/notes/spec.rst.txt +36 -0
- docs/source/_ext/include_attrs.py +213 -0
- docs/source/_ext/include_docstring.py +364 -0
- docs/source/_ext/include_functions.py +154 -0
- docs/source/_ext/include_notebook.py +131 -0
- docs/source/_ext/include_yaml.py +119 -0
- docs/source/_ext/inherited_attributes.py +222 -0
- docs/source/_ext/paths.py +4 -0
- docs/source/conf.py +79 -0
- examples/arches/compute_in_memory/_include.yaml +74 -0
- examples/arches/compute_in_memory/_include_functions.py +229 -0
- examples/arches/compute_in_memory/_load_spec.py +57 -0
- examples/arches/compute_in_memory/components/c2c_multiplier.py +181 -0
- examples/arches/compute_in_memory/components/dac_c2c_r2r.py +605 -0
- examples/arches/compute_in_memory/components/misc.py +195 -0
- examples/arches/compute_in_memory/components/util/bit_functions.py +51 -0
- examples/arches/compute_in_memory/components/zero_comparator.py +92 -0
- examples/arches/compute_in_memory/isaac.yaml +233 -0
- examples/arches/compute_in_memory/memory_cells/ecram_demo.yaml +63 -0
- examples/arches/compute_in_memory/memory_cells/rram_example.yaml +63 -0
- examples/arches/compute_in_memory/memory_cells/rram_isaac_isca_2016.yaml +64 -0
- examples/arches/compute_in_memory/memory_cells/rram_neurosim_default.yaml +63 -0
- examples/arches/compute_in_memory/memory_cells/rram_raella_isca_2023.yaml +70 -0
- examples/arches/compute_in_memory/memory_cells/rram_wan_nature_2022.yaml +63 -0
- examples/arches/compute_in_memory/memory_cells/sram_colonnade_jssc_2021.yaml +63 -0
- examples/arches/compute_in_memory/memory_cells/sram_example.yaml +63 -0
- examples/arches/compute_in_memory/memory_cells/sram_jia_jssc_2020.yaml +63 -0
- examples/arches/compute_in_memory/memory_cells/sram_sinangil_jssc_2021.yaml +63 -0
- examples/arches/compute_in_memory/memory_cells/sram_wang_vlsi_2022.yaml +63 -0
- examples/arches/compute_in_memory/wang_vlsi_2022.yaml +289 -0
- examples/arches/eyeriss.yaml +68 -0
- examples/arches/fanout_variations/at_glb.yaml +31 -0
- examples/arches/fanout_variations/at_glb_with_fanout_node.yaml +34 -0
- examples/arches/fanout_variations/at_mac.yaml +31 -0
- examples/arches/fanout_variations/at_mac_with_constraints.yaml +38 -0
- examples/arches/fanout_variations/at_mac_with_fanout_node.yaml +34 -0
- examples/arches/nvdla.yaml +47 -0
- examples/arches/simple.yaml +28 -0
- examples/arches/tpu_v4i.yaml +67 -0
- examples/mappings/unfused_matmuls_to_simple.yaml +33 -0
- examples/misc/component_annotated.yaml +33 -0
- examples/workloads/gpt3_6.7B.yaml +124 -0
- examples/workloads/matmuls.yaml +20 -0
- examples/workloads/mobilenet_28.yaml +81 -0
- examples/workloads/mobilenet_various_separate.yaml +106 -0
- examples/workloads/three_matmuls_annotated.yaml +59 -0
- notebooks/.ipynb_checkpoints/fastfusion_arch_study_michael-checkpoint.ipynb +359 -0
- notebooks/compute_in_memory/_scripts.py +339 -0
- notebooks/compute_in_memory/isaac.guide.ipynb +270 -0
- notebooks/compute_in_memory/wang_vlsi_2022.ipynb +602 -0
- notebooks/paths.py +4 -0
- notebooks/tutorials/.ipynb_checkpoints/1_FFM-checkpoint.ipynb +3110 -0
- notebooks/tutorials/FFM.ipynb +3498 -0
- notebooks/tutorials/_include.py +48 -0
- notebooks/tutorials/component_energy_area.ipynb +363 -0
- tests/Q_mapping.yaml +38 -0
- tests/__init__.py +0 -0
- tests/conv.mapping.yaml +27 -0
- tests/conv.workload.yaml +13 -0
- tests/conv_sym.mapping.yaml +43 -0
- tests/copy.mapping.yaml +35 -0
- tests/copy.workload.yaml +15 -0
- tests/distribuffers/__init__.py +0 -0
- tests/distribuffers/multicast/test_cases.yaml +482 -0
- tests/distribuffers/spec/binding/valid_bindings.yaml +97 -0
- tests/distribuffers/spec/distributed.yaml +100 -0
- tests/distribuffers/spec/logical_arch.yaml +32 -0
- tests/distribuffers/spec/physical_arch.yaml +69 -0
- tests/distribuffers/test_binding.py +48 -0
- tests/frontend/__init__.py +0 -0
- tests/frontend/test_mapping_viz.py +52 -0
- tests/mapper/__init__.py +0 -0
- tests/mapper/configs/conv1d/conv1d.mapping.yaml +31 -0
- tests/mapper/configs/conv1d/conv1d.workload.yaml +11 -0
- tests/mapper/configs/two_conv1d/two_conv1d.expected.yaml +38 -0
- tests/mapper/configs/two_conv1d/two_conv1d.mapping.yaml +54 -0
- tests/mapper/configs/two_conv1d/two_conv1d.workload.yaml +19 -0
- tests/mapper/test_mapping_to_isl.py +90 -0
- tests/mapper/test_spatial_reuse_analysis.py +67 -0
- tests/mapper/test_temporal_reuse_analysis.py +56 -0
- tests/mapper/util.py +58 -0
- tests/matmul.mapping.yaml +29 -0
- tests/matmul.workload.yaml +12 -0
- tests/matmul_spatial.mapping.yaml +44 -0
- tests/mha.renames.yaml +65 -0
- tests/mha.workload.yaml +67 -0
- tests/mha.yaml +59 -0
- tests/mha_full.workload.yaml +67 -0
- tests/mobilenet.workload.yaml +35 -0
- tests/mobilenet_long.workload.yaml +64 -0
- tests/pmappingcache.py +24 -0
- tests/processing_stage.arch.yaml +40 -0
- tests/snowcat.arch.yaml +36 -0
- tests/test_ffm_join_pmappings.py +106 -0
- tests/test_ffm_make_pmappings.py +82 -0
- tests/test_ffm_make_tile_shapes.py +49 -0
- tests/test_mapper.py +100 -0
- tests/test_model.py +37 -0
- tests/test_plotting.py +72 -0
- tests/test_processing_stage.py +46 -0
- tests/test_symbolic_model.py +248 -0
- 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
|