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,32 @@
1
+ arch:
2
+ nodes:
3
+ - !Component
4
+ name: DRAM # offchip DRAM is the source of all datatypes
5
+ class: DRAM # assume DRAM is large enough to store all the data, so no depth specification needed
6
+ attributes:
7
+ width: 64 # width in bits
8
+
9
+ - !Container
10
+ name: PE
11
+ spatial: {meshX: 1, meshY: 2}
12
+
13
+ # registers for the mac unit
14
+ - !Component
15
+ name: weight_reg
16
+ class: reg_storage
17
+ attributes: {depth: 1, width: 8}
18
+
19
+ - !Component
20
+ name: input_activation_reg
21
+ class: reg_storage
22
+ attributes: {depth: 1, width: 8}
23
+
24
+ - !Component
25
+ name: output_activation_reg
26
+ class: reg_storage
27
+ attributes: {depth: 1, width: 8}
28
+
29
+ - !Component
30
+ name: mac
31
+ class: mac_compute
32
+ attributes: {num_pipline_stages: 2}
@@ -0,0 +1,69 @@
1
+ arch:
2
+ name: "test_distributed_spec"
3
+ nodes:
4
+ - !Container
5
+ name: system_arch
6
+ attributes:
7
+ # Top-level attributes inherited by all components unless overridden
8
+ technology: "45nm"
9
+ cycle_period: 1e-9
10
+
11
+ - !Network
12
+ name: L2
13
+ topology: Mesh
14
+ dims:
15
+ - x
16
+ constraints:
17
+ - 0 <= x < 2
18
+ - !Network
19
+ name: L1
20
+ topology: Mesh
21
+ dims:
22
+ - x
23
+ - y
24
+ constraints:
25
+ - 0 <= x < 1
26
+ - -1 <= y <= 1
27
+ port: |
28
+ {L2[x] -> L1[x', y'] : x = 1 and x' = 0 and y' = 0}
29
+
30
+ - !Component
31
+ name: DRAM # offchip DRAM is the source of all datatypes
32
+ class: DRAM # assume DRAM is large enough to store all the data, so no depth specification needed
33
+ attributes:
34
+ width: 64 # width in bits
35
+ network:
36
+ level: L2
37
+ placement: "{ DRAM[i] -> L2[i] | i = 1 }"
38
+
39
+ - !Container
40
+ name: PE
41
+ network:
42
+ level: L1
43
+ placement: |
44
+ {
45
+ PE[i, j] -> L1[x, y] :
46
+ j = y and x < 0 and i = -x - 1;
47
+ j = y and x > 0 and i = x;
48
+ }
49
+
50
+ # registers for the mac unit
51
+ - !Component
52
+ name: weight_reg
53
+ class: reg_storage
54
+ attributes: {depth: 1, width: 8}
55
+
56
+ - !Component
57
+ name: input_activation_reg
58
+ class: reg_storage
59
+ attributes: {depth: 1, width: 8}
60
+
61
+ - !Component
62
+ name: output_activation_reg
63
+ class: reg_storage
64
+ attributes: {depth: 1, width: 8}
65
+
66
+ - !Component
67
+ name: mac
68
+ class: mac_compute
69
+ attributes: {num_pipline_stages: 2}
@@ -0,0 +1,48 @@
1
+ from pathlib import Path
2
+ import unittest
3
+
4
+ from typing import Dict, List
5
+ from islpy import DEFAULT_CONTEXT, Map
6
+
7
+ import yaml
8
+ from accelforge.frontend._binding import Binding, BindingNode
9
+
10
+ TESTS_DIR = Path(__file__).parent / "spec" / "binding"
11
+
12
+
13
+ class TestBindingMapper(unittest.TestCase):
14
+ def test_valid_bindings(self):
15
+ """
16
+ Tests that the valid bindings translate into the appropriate
17
+ ISL strings.
18
+ """
19
+ specs_file: str = TESTS_DIR / "valid_bindings.yaml"
20
+ with open(specs_file, mode="r", encoding="utf-8") as f:
21
+ specs: List = yaml.safe_load(f)
22
+
23
+ spec: Dict
24
+ for spec in specs:
25
+ binding: Binding = Binding.model_validate(spec["binding"])
26
+
27
+ soln: Dict = spec["solution"]
28
+
29
+ soln_node: Dict[str, str]
30
+ binding_node: BindingNode
31
+ for soln_node, binding_node in zip(soln["nodes"], binding.nodes):
32
+ isl_relations: Dict[str, Map] = binding_node.isl_relations
33
+ assert soln_node.keys() == isl_relations.keys(), (
34
+ "Not all isl_relations read in properly. Missing \n"
35
+ f"{set(soln_node.keys()).difference(isl_relations.keys())} "
36
+ "\nfrom isl_relations and \n"
37
+ f"{set(isl_relations.keys()).difference(soln_node.keys())} "
38
+ "\nfrom solutions.\n"
39
+ )
40
+
41
+ tensor: str
42
+ for tensor in soln_node:
43
+ soln_relation: Map = Map.read_from_str(
44
+ DEFAULT_CONTEXT, soln_node[tensor]
45
+ )
46
+ assert soln_relation.is_equal(
47
+ isl_relations[tensor]
48
+ ), f"\n{soln_relation} != \n{isl_relations[tensor]}"
File without changes
@@ -0,0 +1,52 @@
1
+ """
2
+ Uncommenting the lines with `graph.write_png` will generate PNG pictures of
3
+ LoopTrees so they can be inspected.
4
+
5
+ TODO: an automated way to check for correctness.
6
+ """
7
+ import unittest
8
+ from pathlib import Path
9
+
10
+ from accelforge.frontend.spec import Spec
11
+ from accelforge.model.main import evaluate_mapping
12
+ from accelforge.util.parallel import set_n_parallel_jobs
13
+
14
+ set_n_parallel_jobs(1)
15
+
16
+
17
+ EXAMPLES_DIR = Path(__file__).parent.parent.parent / "examples"
18
+
19
+
20
+ class TestModel(unittest.TestCase):
21
+ def test_default(self):
22
+ spec = Spec.from_yaml(
23
+ EXAMPLES_DIR / "arches" / "simple.yaml",
24
+ EXAMPLES_DIR / "workloads" / "matmuls.yaml",
25
+ EXAMPLES_DIR / "mappings" / "unfused_matmuls_to_simple.yaml",
26
+ jinja_parse_data={"N_EINSUMS": 2, "M": 64, "KN": 64},
27
+ )
28
+ result = evaluate_mapping(spec)
29
+ graph = result.data["Total<SEP>mapping"].iloc[0]().render_pydot()
30
+ # graph.write_png("default.png")
31
+
32
+ def test_without_reservations(self):
33
+ spec = Spec.from_yaml(
34
+ EXAMPLES_DIR / "arches" / "simple.yaml",
35
+ EXAMPLES_DIR / "workloads" / "matmuls.yaml",
36
+ EXAMPLES_DIR / "mappings" / "unfused_matmuls_to_simple.yaml",
37
+ jinja_parse_data={"N_EINSUMS": 2, "M": 64, "KN": 64},
38
+ )
39
+ result = evaluate_mapping(spec)
40
+ graph = result.data["Total<SEP>mapping"].iloc[0]().render_pydot(with_reservations=False)
41
+ # graph.write_png("without_reservations.png")
42
+
43
+ def test_without_stride(self):
44
+ spec = Spec.from_yaml(
45
+ EXAMPLES_DIR / "arches" / "simple.yaml",
46
+ EXAMPLES_DIR / "workloads" / "matmuls.yaml",
47
+ EXAMPLES_DIR / "mappings" / "unfused_matmuls_to_simple.yaml",
48
+ jinja_parse_data={"N_EINSUMS": 2, "M": 64, "KN": 64},
49
+ )
50
+ result = evaluate_mapping(spec)
51
+ graph = result.data["Total<SEP>mapping"].iloc[0]().render_pydot(with_tile_shape=False)
52
+ # graph.write_png("without_stride.png")
File without changes
@@ -0,0 +1,31 @@
1
+ mapping:
2
+ nodes:
3
+ - !Storage
4
+ tensors: [I, W, O]
5
+ component: MainMemory
6
+ - !Temporal
7
+ rank_variable: p
8
+ # bound is 2
9
+ tile_pattern:
10
+ initial_tile_shape: 8
11
+ stride: 8
12
+ - !Storage
13
+ tensors: [I, W, O]
14
+ component: LocalBuffer
15
+ - !Temporal
16
+ rank_variable: p
17
+ # bound is 8
18
+ tile_pattern:
19
+ initial_tile_shape: 1
20
+ stride: 1
21
+ - !Spatial
22
+ rank_variable: r
23
+ # bound is 3
24
+ tile_pattern:
25
+ initial_tile_shape: 1
26
+ stride: 1
27
+ component: MAC
28
+ name: X
29
+ - !Compute
30
+ einsum: conv
31
+ compute: MAC
@@ -0,0 +1,11 @@
1
+ workload:
2
+ shape:
3
+ r: 0 <= r < 3
4
+ p: 0 <= p < 16
5
+
6
+ einsums:
7
+ - name: conv
8
+ tensor_accesses:
9
+ - {name: I, projection: [ r, p ]}
10
+ - {name: W, projection: [ r ]}
11
+ - {name: O, projection: [ p ], output: True}
@@ -0,0 +1,38 @@
1
+ mapping_to_isl:
2
+ BufferTensorEinsum(buffer='MainMemory', tensor='I', einsum=Compute(type=None, einsum='convA', compute='MAC', component_object=None)): |
3
+ { MainMemory_spacetime[] -> I[PI] : 0 <= PI < 18 }
4
+ BufferTensorEinsum(buffer='MainMemory', tensor='WA', einsum=Compute(type=None, einsum='convA', compute='MAC', component_object=None)): |
5
+ { MainMemory_spacetime[] -> WA[RA] : 0 <= RA < 3 }
6
+ BufferTensorEinsum(buffer='LocalBuffer', tensor='I', einsum=Compute(type=None, einsum='convA', compute='MAC', component_object=None)): |
7
+ { LocalBuffer_spacetime[pb0=0] -> I[PI] : 0 <= PI < 12;
8
+ LocalBuffer_spacetime[pb0=1] -> I[PI] : 10 <= PI < 18 }
9
+ BufferTensorEinsum(buffer='LocalBuffer', tensor='WA', einsum=Compute(type=None, einsum='convA', compute='MAC', component_object=None)): |
10
+ { LocalBuffer_spacetime[pb0] -> WA[RA] : 0 <= RA <= 2 and 0 <= pb0 < 2 }
11
+ BufferTensorEinsum(buffer='LocalBuffer', tensor='A', einsum=Compute(type=None, einsum='convA', compute='MAC', component_object=None)): |
12
+ { LocalBuffer_spacetime[pb0=0] -> A[PA] : 0 <= PA < 10;
13
+ LocalBuffer_spacetime[pb0=1] -> A[PA] : 10 <= PA < 16 }
14
+ BufferTensorEinsum(buffer='MAC', tensor='I', einsum=Compute(type=None, einsum='convA', compute='MAC', component_object=None)): |
15
+ { MAC_spacetime[pb0=0, 0, pa1, ra0] -> I[PI = pa1 + ra0] : 0 <= pa1 < 10 and 0 <= ra0 < 3;
16
+ MAC_spacetime[pb0=1, 0, pa1, ra0] -> I[PA = 10 + pa1 + ra0] : 0 <= pa1 < 6 and 0 <= ra0 < 3 }
17
+ BufferTensorEinsum(buffer='MAC', tensor='WA', einsum=Compute(type=None, einsum='convA', compute='MAC', component_object=None)): |
18
+ { MAC_spacetime[pb0=0, 0, pa1, ra0] -> WA[RA=ra0] : 0 <= pa1 < 10 and 0 <= ra0 < 3;
19
+ MAC_spacetime[pb0=1, 0, pa1, ra0] -> WA[RA=ra0] : 0 <= pa1 < 6 and 0 <= ra0 < 3 }
20
+ BufferTensorEinsum(buffer='MAC', tensor='A', einsum=Compute(type=None, einsum='convA', compute='MAC', component_object=None)): |
21
+ { MAC_spacetime[pb0=0, 0, pa1, ra0] -> A[PA=pa1] : 0 <= pa1 < 10 and 0 <= ra0 < 3;
22
+ MAC_spacetime[pb0=1, 0, pa1, ra0] -> A[PA=10 + pa1] : 0 <= pa1 < 6 and 0 <= ra0 < 3 }
23
+ BufferTensorEinsum(buffer='MainMemory', tensor='WB', einsum=Compute(type=None, einsum='convB', compute='MAC', component_object=None)): |
24
+ { MainMemory_spacetime[] -> WB[RB] : 0 <= RB < 3 }
25
+ BufferTensorEinsum(buffer='MainMemory', tensor='B', einsum=Compute(type=None, einsum='convB', compute='MAC', component_object=None)): |
26
+ { MainMemory_spacetime[] -> B[PB] : 0 <= PB < 16 }
27
+ BufferTensorEinsum(buffer='LocalBuffer', tensor='A', einsum=Compute(type=None, einsum='convB', compute='MAC', component_object=None)): |
28
+ { LocalBuffer_spacetime[pb0] -> A[PA] : 0 <= pb0 < 2 and 8pb0 <= PA <= 9 + 8pb0 }
29
+ BufferTensorEinsum(buffer='LocalBuffer', tensor='B', einsum=Compute(type=None, einsum='convB', compute='MAC', component_object=None)): |
30
+ { LocalBuffer_spacetime[pb0] -> B[PB] : 0 <= pb0 < 2 and 8pb0 <= PB < 8 + 8pb0 }
31
+ BufferTensorEinsum(buffer='LocalBuffer', tensor='WB', einsum=Compute(type=None, einsum='convB', compute='MAC', component_object=None)): |
32
+ { LocalBuffer_spacetime[pb0] -> WB[RB] : 0 <= pb0 < 2 and 0 <= RB <= 2 }
33
+ BufferTensorEinsum(buffer='MAC', tensor='A', einsum=Compute(type=None, einsum='convB', compute='MAC', component_object=None)): |
34
+ { MAC_spacetime[pb0, 1, pb2, rb0] -> A[PA = 8pb0 + pb2 + rb0] : 0 <= pb0 <= 1 and 0 <= pb2 <= 7 and 0 <= rb0 <= 2 }
35
+ BufferTensorEinsum(buffer='MAC', tensor='WB', einsum=Compute(type=None, einsum='convB', compute='MAC', component_object=None)): |
36
+ { MAC_spacetime[pb0, 1, pb2, rb0] -> WB[RB = rb0] : 0 <= pb0 <= 1 and 0 <= pb2 <= 7 and 0 <= rb0 <= 2 }
37
+ BufferTensorEinsum(buffer='MAC', tensor='B', einsum=Compute(type=None, einsum='convB', compute='MAC', component_object=None)): |
38
+ { MAC_spacetime[pb0, 1, pb2, rb0] -> B[PB = 8pb0 + pb2] : 0 <= pb0 <= 1 and 0 <= pb2 <= 7 and 0 <= rb0 <= 2 }
@@ -0,0 +1,54 @@
1
+ mapping:
2
+ nodes:
3
+ - !Storage
4
+ tensors: [I, WA, WB, B]
5
+ component: MainMemory
6
+ - !Temporal
7
+ rank_variable: pb
8
+ # bound is 2
9
+ tile_pattern:
10
+ initial_tile_shape: 8
11
+ stride: 8
12
+ - !Storage
13
+ tensors: [I, WA, A, WB, B]
14
+ component: LocalBuffer
15
+ - !Sequential
16
+ nodes:
17
+ - !Nested
18
+ nodes:
19
+ - !Temporal
20
+ rank_variable: pa
21
+ # bound is 8
22
+ tile_pattern:
23
+ initial_tile_shape: 1
24
+ stride: 1
25
+ - !Spatial
26
+ rank_variable: ra
27
+ # bound is 3
28
+ tile_pattern:
29
+ initial_tile_shape: 1
30
+ stride: 1
31
+ component: MAC
32
+ name: X
33
+ - !Compute
34
+ einsum: convA
35
+ compute: MAC
36
+ - !Nested
37
+ nodes:
38
+ - !Temporal
39
+ rank_variable: pb
40
+ # bound is 8
41
+ tile_pattern:
42
+ initial_tile_shape: 1
43
+ stride: 1
44
+ - !Spatial
45
+ rank_variable: rb
46
+ # bound is 3
47
+ tile_pattern:
48
+ initial_tile_shape: 1
49
+ stride: 1
50
+ component: MAC
51
+ name: X
52
+ - !Compute
53
+ einsum: convB
54
+ compute: MAC
@@ -0,0 +1,19 @@
1
+ workload:
2
+ shape:
3
+ ra: 0 <= ra < 3
4
+ pa: 0 <= pa < 16
5
+ rb: 0 <= rb < 3
6
+ pb: 0 <= pb < 16
7
+
8
+ einsums:
9
+ - name: convA
10
+ tensor_accesses:
11
+ - {name: I, projection: { PI: pa+ra }}
12
+ - {name: WA, projection: [ ra ]}
13
+ - {name: A, projection: [ pa ], output: True}
14
+
15
+ - name: convB
16
+ tensor_accesses:
17
+ - {name: A, projection: { PA: pb+rb }}
18
+ - {name: WB, projection: [ rb ]}
19
+ - {name: B, projection: [ pb ], output: True}
@@ -0,0 +1,90 @@
1
+ """
2
+ Some non-exhaustive tests to make sure core functionality of the non-fusing and
3
+ fusing portions of ISL tiling is not violated under changes.
4
+ """
5
+
6
+ from pathlib import Path
7
+ from pprint import pformat
8
+ import unittest
9
+
10
+ import islpy as isl
11
+
12
+ from accelforge.frontend.workload import Workload
13
+ from accelforge.frontend.mapping import Mapping
14
+
15
+ from accelforge.model._looptree.reuse.isl.mapping_to_isl import analyze_mapping
16
+ from accelforge.model._looptree.reuse.isl.mapping_to_isl.types import (
17
+ MappingAnalysisResult,
18
+ )
19
+
20
+ from .util import TEST_CONFIG_PATH, load_solutions
21
+
22
+
23
+ class TestMappingToIsl(unittest.TestCase):
24
+ """
25
+ Tests taking a `Mapping` and `Workload` and converting it into relevant isl
26
+ objects.
27
+ """
28
+
29
+ def test_conv1d(self):
30
+ """
31
+ Non-fusing one 1-dimensional convolution test.
32
+ """
33
+ # Loads in the CONV1D Config
34
+ config_path: Path = TEST_CONFIG_PATH / "conv1d"
35
+ workload: Workload = Workload.from_yaml(config_path / "conv1d.workload.yaml")
36
+
37
+ mapping: Mapping = Mapping.from_yaml(config_path / "conv1d.mapping.yaml")
38
+ occupancies: MappingAnalysisResult = analyze_mapping.occupancies_from_mapping(
39
+ mapping, workload
40
+ )
41
+
42
+ for buffer, occupancy in occupancies.buffet_to_occupancy.items():
43
+ if buffer == list(occupancies.buffet_to_occupancy.keys())[-1]:
44
+ soln: isl.Map = isl.Map.read_from_str(
45
+ isl.DEFAULT_CONTEXT,
46
+ "{ MAC_spacetime[P1, P0, R] -> O[P=8*P1 + P0] : "
47
+ "0 <= R < 3 and 0 <= P1 < 2 and 0 <= P0 < 8}",
48
+ )
49
+ assert occupancy.map_ == soln
50
+
51
+ def test_two_conv1d(self):
52
+ """
53
+ Fusing two 1-dimensional convolutions test.
54
+ """
55
+ # Loads in the CONV1D Config
56
+ config_path: Path = TEST_CONFIG_PATH / "two_conv1d"
57
+ workload: Workload = Workload.from_yaml(
58
+ config_path / "two_conv1d.workload.yaml"
59
+ )
60
+
61
+ mapping: Mapping = Mapping.from_yaml(config_path / "two_conv1d.mapping.yaml")
62
+ occupancies: MappingAnalysisResult = analyze_mapping.occupancies_from_mapping(
63
+ mapping, workload
64
+ )
65
+ solns: dict = load_solutions(config_path / "two_conv1d.expected.yaml")[
66
+ "mapping_to_isl"
67
+ ]
68
+
69
+ errors: list = []
70
+ buffers_seen: set = set()
71
+ for buffer, occupancy in occupancies.buffet_to_occupancy.items():
72
+ try:
73
+ soln = solns[repr(buffer)]
74
+ assert (
75
+ occupancy.map_ == soln
76
+ ), f"{buffer} should hold:\n{soln}\ninstead holds:\n{occupancy.map_}"
77
+ except (AssertionError, KeyError) as e:
78
+ errors.append(e)
79
+ buffers_seen.add(repr(buffer))
80
+
81
+ buffers_known: set = set(solns.keys())
82
+ try:
83
+ assert buffers_seen == buffers_known, (
84
+ f"Buffers Missing: {pformat(buffers_seen - buffers_known)}\n"
85
+ f"Buffers Extra: {pformat(buffers_known - buffers_seen)}"
86
+ )
87
+ except AssertionError as e:
88
+ errors.append(e)
89
+
90
+ assert len(errors) == 0, pformat(errors)
@@ -0,0 +1,67 @@
1
+ """
2
+ Tests the spatial ISL models.
3
+ """
4
+
5
+ import unittest
6
+
7
+ import islpy as isl
8
+
9
+ from accelforge.frontend.mapping import MappingNode
10
+ from accelforge.model._looptree.reuse.isl.mapping_to_isl.types import (
11
+ Fill,
12
+ Occupancy,
13
+ SpatialTag,
14
+ TemporalTag,
15
+ )
16
+ from accelforge.model._looptree.reuse.isl.spatial import (
17
+ SimpleLinkTransferModel,
18
+ TransferInfo,
19
+ )
20
+
21
+
22
+ class TestSimpleLinkTransferModel(unittest.TestCase):
23
+ """
24
+ Test the simple link transfer model. Adapted from the original test at the
25
+ following link:
26
+ https://github.com/NVlabs/timeloop/blob/master/src/unit-test/test-simple-link-transfer.cpp
27
+ """
28
+
29
+ def test_simple_link_transfer_model_sandbox(self):
30
+ """Independent sanity check of `SimpleLinkTransferModel`."""
31
+ buffer: MappingNode = MappingNode()
32
+ fill: Fill = Fill(
33
+ [TemporalTag(), SpatialTag(0, buffer), SpatialTag(1, buffer)], # type: ignore
34
+ isl.Map.read_from_str(
35
+ isl.DEFAULT_CONTEXT,
36
+ "{ spacetime[t, x, y] -> data[t+x+y] : 0 <= x < 2 and 0 <= y < 2 and 0 <= t < 2}",
37
+ ),
38
+ )
39
+ occ: Occupancy = Occupancy(fill.tags, fill.map_)
40
+ link_transfer_model: SimpleLinkTransferModel = SimpleLinkTransferModel()
41
+ info: TransferInfo = link_transfer_model.apply(buffer, fill, occ)
42
+
43
+ assert info.fulfilled_fill.map_ == (
44
+ fulfilled_soln := isl.Map.read_from_str(
45
+ isl.DEFAULT_CONTEXT,
46
+ "{ spacetime[t = 1, x, y = 0] -> data[1 + x] : 0 <= x <= 1; "
47
+ " spacetime[t = 1, x = 0, y] -> data[1 + y] : 0 <= y <= 1 }",
48
+ )
49
+ ), (
50
+ "`fulfilled_fill` and solution mismatch\n"
51
+ "--------------------------------------\n"
52
+ f"solution:\n{fulfilled_soln}\n"
53
+ f"received:\n{info.fulfilled_fill.map_}\n"
54
+ )
55
+
56
+ assert info.unfulfilled_fill.map_ == (
57
+ unfulfilled_soln := isl.Map.read_from_str(
58
+ isl.DEFAULT_CONTEXT,
59
+ "{ spacetime[t = 0, x, y] -> data[x + y] : 0 <= x <= 1 and 0 <= y <= 1; "
60
+ " spacetime[t = 1, x = 1, y = 1] -> data[3] }",
61
+ )
62
+ ), (
63
+ "`unfulfilled_fill` and solution mismatch\n"
64
+ "----------------------------------------\n"
65
+ f"solution:\n{unfulfilled_soln}\n"
66
+ f"received:\n{info.unfulfilled_fill.map_}\n"
67
+ )
@@ -0,0 +1,56 @@
1
+ """
2
+ Tests the temporal reuse analysis in ISL.
3
+ """
4
+
5
+ import unittest
6
+
7
+ import islpy as isl
8
+
9
+ from accelforge.model._looptree.reuse.isl.mapping_to_isl.types import (
10
+ Occupancy,
11
+ SpatialTag,
12
+ TemporalTag,
13
+ )
14
+ from accelforge.model._looptree.reuse.isl.temporal import (
15
+ TemporalReuse,
16
+ analyze_temporal_reuse,
17
+ )
18
+
19
+
20
+ class TestTemporalReuseAnalysis(unittest.TestCase):
21
+ """
22
+ Tests the temporal reuse analysis in ISL.
23
+ """
24
+
25
+ def test_multiple_loop_reuse_sandbox(self):
26
+ """
27
+ Tests a very basic temporal reuse analysis case without needing the
28
+ `occupancies_from_mapping` function.
29
+ """
30
+ # Loads in the CONV1D Config
31
+ occ: Occupancy = Occupancy(
32
+ [TemporalTag(), SpatialTag(0, None), TemporalTag()],
33
+ isl.Map.read_from_str(
34
+ isl.DEFAULT_CONTEXT,
35
+ (
36
+ "{ generic_iteration[t1, x, t0] -> tensor[d] : "
37
+ "t0 <= d < t0+3 and 0 <= t1 < 2 and 0 <= x < 2 and 0 <= t0 < 2 }"
38
+ ),
39
+ ).coalesce(),
40
+ )
41
+
42
+ result: TemporalReuse = analyze_temporal_reuse(occ, True, True)
43
+ soln: isl.Map = isl.Map.read_from_str(
44
+ isl.DEFAULT_CONTEXT,
45
+ "{ generic_iteration[t1, x, t0] -> tensor[d] : "
46
+ "0 <= x < 2 and "
47
+ "(((t1 = 0) and (t0 = 0) and (0 <= d < 3)) or "
48
+ " ((t1 = 0) and (t0 = 1) and (d = 3)) or "
49
+ " ((t1 = 1) and (t0 = 0) and (d = 0)) or "
50
+ " ((t1 = 1) and (t0 = 1) and (d = 3))"
51
+ ")}",
52
+ ).coalesce()
53
+ fill: isl.Map = result.fill.map_
54
+ eff_occ: isl.Map = result.effective_occupancy.map_
55
+ print(eff_occ)
56
+ assert soln.is_equal(fill), f"Expected:\n{soln}\nReceived:\n{fill}"
tests/mapper/util.py ADDED
@@ -0,0 +1,58 @@
1
+ """
2
+ Utility functions common to testing the isl mapper functions.
3
+ """
4
+
5
+ from pathlib import Path
6
+ import islpy as isl
7
+
8
+ from ruamel.yaml import YAML
9
+
10
+ TEST_CONFIG_PATH: Path = Path(__file__).parent / "configs"
11
+
12
+
13
+ def to_isl_maps(obj: str | list | dict) -> dict:
14
+ """
15
+ Given an object, attempt to reduce all strings in tree with isl.Map
16
+
17
+ Parameters
18
+ ----------
19
+ obj:
20
+ A DAG which can be explored and contains isl.Map strings within it.
21
+
22
+ Returns
23
+ -------
24
+ `obj` but all strings are converted to isl.Map.
25
+ """
26
+
27
+ def _to_isl_maps(obj: str | dict | list) -> isl.Map | dict | list:
28
+ """Recursively convert string ISL maps to isl.Map; leave others alone."""
29
+ if isinstance(obj, str):
30
+ return isl.Map.read_from_str(isl.DEFAULT_CONTEXT, obj)
31
+ if isinstance(obj, dict):
32
+ return {k: _to_isl_maps(v) for k, v in obj.items()}
33
+ if isinstance(obj, list):
34
+ return [_to_isl_maps(v) for v in obj]
35
+ return obj
36
+
37
+ return _to_isl_maps(obj) # type: ignore
38
+
39
+
40
+ def load_solutions(path: Path) -> dict:
41
+ """
42
+ Loads in a dictionary with the isl solutions to a workload problem.
43
+
44
+ Parameters
45
+ ----------
46
+ path:
47
+ The path to the solutions.
48
+
49
+ Returns
50
+ -------
51
+ A dictionary relating Python-based keys generated by the mapper (e.g.,
52
+ `BufferTensorEinsum` to their corresponding isl.Map.)
53
+ """
54
+ # Load expected solutions (YAML file with string ISL maps)
55
+ yaml: YAML = YAML(typ="safe")
56
+
57
+ with open(path, "r", encoding="utf-8") as f:
58
+ return to_isl_maps(yaml.load(f))
@@ -0,0 +1,29 @@
1
+ mapping:
2
+ nodes:
3
+ - !Storage
4
+ tensors: [T1, T0, W0]
5
+ component: MainMemory
6
+ - !Temporal
7
+ rank_variable: m
8
+ tile_shape: 128
9
+ - !Storage
10
+ tensors: [T1]
11
+ component: LocalBuffer
12
+ - !Temporal
13
+ rank_variable: n0
14
+ tile_shape: 1
15
+ - !Storage
16
+ tensors: [T0]
17
+ component: LocalBuffer
18
+ - !Temporal
19
+ rank_variable: n1
20
+ tile_shape: 1
21
+ - !Storage
22
+ tensors: [W0]
23
+ component: LocalBuffer
24
+ - !Temporal
25
+ rank_variable: m
26
+ tile_shape: 1
27
+ - !Compute
28
+ einsum: Matmul1
29
+ compute: MAC
@@ -0,0 +1,12 @@
1
+ workload:
2
+ shape:
3
+ m: 0 <= m < 128
4
+ n0: 0 <= n0 < 64
5
+ n1: 0 <= n1 < 128
6
+
7
+ einsums:
8
+ - name: Matmul1
9
+ tensor_accesses:
10
+ - {name: T0, projection: [m, n0]}
11
+ - {name: W0, projection: [n0, n1]}
12
+ - {name: T1, projection: [m, n1], output: True}