Skip to content

Commit 8db8810

Browse files
may_reuse defaults to same as reuse
1 parent 6c3e22a commit 8db8810

10 files changed

Lines changed: 175 additions & 23 deletions

File tree

accelforge/frontend/arch/spatialable.py

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import copy
12
import math
23

34
from accelforge.util._basetypes import (
@@ -24,9 +25,21 @@ class Spatial(EvalableModel):
2425
fanout: EvalsTo[int]
2526
""" The size of this fanout. """
2627

27-
may_reuse: TryEvalTo[InvertibleSet[TensorName]] = "All"
28-
""" The tensors that can be reused spatially across instances of this fanout. This
29-
expression will be evaluated for each mapping template. """
28+
may_reuse: TryEvalTo[InvertibleSet[TensorName]] = (
29+
"<Same as reuse if reuse is defined, else All>"
30+
)
31+
"""
32+
A set of a tensors or set expression representing tensors that the hardware may
33+
reuse across spatial iterations. If a tensor is not in this set, then hardware must
34+
unicast its values to all spatial instances that use it. If this is not defined and
35+
``reuse`` is also not defined, all tensors may be reused. If ``reuse`` is defined,
36+
then ``may_reuse`` defaults to the same value as ``reuse``.
37+
38+
Note that this behaves differently from ``reuse`` in that ``may_reuse`` has no
39+
effect on whether a particular spatial loop is valid, only on how data is moved
40+
to/from spatial instances. On the other hand, ``reuse`` constrains which loops are
41+
valid.
42+
"""
3043

3144
loop_bounds: EvalableList[Comparison] = EvalableList()
3245
""" Bounds for loops over this dimension. This is a list of :class:`~.Comparison`
@@ -43,13 +56,19 @@ class Spatial(EvalableModel):
4356
return the highest-usage mappings. These constraints are disabled for copy Einsums.
4457
"""
4558

46-
reuse: TryEvalTo[InvertibleSet[TensorName]] = "Nothing"
47-
""" A set of tensors or a set expression representing tensors that must be reused
48-
across spatial iterations. Spatial loops may only be placed that reuse ALL tensors
49-
given here.
59+
reuse: TryEvalTo[InvertibleSet[TensorName]] = "<Defaults to Nothing>"
60+
"""
61+
A set of tensors or a set expression representing tensors that must be reused across
62+
spatial iterations. Spatial loops may only be placed that reuse ALL tensors given
63+
here.
64+
65+
Note that this behaves differently from ``may_reuse`` in that ``may_reuse`` has no
66+
effect on whether a particular spatial loop is valid, only on how data is moved
67+
to/from spatial instances. On the other hand, ``reuse`` constrains which loops are
68+
valid.
5069
51-
Note: Loops may be removed if they do not reuse a tensor given here and they do not
52-
appear in another loop bound constraint.
70+
Note that loops may be removed from the mapping if they do not reuse a tensor given
71+
here and they do not appear in another loop bound constraint.
5372
"""
5473

5574
usage_scale: EvalsTo[int | float | str] = 1
@@ -64,6 +83,15 @@ class Spatial(EvalableModel):
6483
will be power gated if not used by a particular Einsum.
6584
"""
6685

86+
def _eval_expressions(self, *args, **kwargs):
87+
self = copy.copy(self)
88+
reuse, may_reuse = self.reuse, self.may_reuse
89+
if may_reuse == "<Same as reuse if reuse is defined, else All>":
90+
self.may_reuse = "All" if reuse == "<Defaults to Nothing>" else reuse
91+
if reuse == "<Defaults to Nothing>":
92+
self.reuse = "Nothing"
93+
return super(self.__class__, self)._eval_expressions(*args, **kwargs)
94+
6795

6896
class Spatialable(EvalableModel):
6997
"""Something that can be duplicated to create an array of."""

examples/arches/compute_in_memory/basic_analog.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ arch:
107107
spatial:
108108
- name: row_ARRAY_ROWS
109109
fanout: 32
110-
may_reuse: output
111110
reuse: output
112111
min_usage: 1
113112

examples/arches/compute_in_memory/isaac_isca_2016.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,6 @@ arch:
212212
usage_scale: n_weight_slices
213213
- name: rows_ARRAY_ROWS # Special name that determines array size
214214
fanout: 128
215-
may_reuse: output
216215
reuse: output
217216
min_usage: 1
218217

examples/arches/eyeriss.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ arch:
2828
- !Container
2929
name: ProcessingElement
3030
spatial:
31-
- {name: reuse_weight, fanout: 14, may_reuse: weight, reuse: weight, min_usage: 1}
32-
- {name: reuse_output, fanout: 12, may_reuse: output, reuse: output, min_usage: 1}
31+
- {name: reuse_weight, fanout: 14, reuse: weight, min_usage: 1}
32+
- {name: reuse_output, fanout: 12, reuse: output, min_usage: 1}
3333

3434
- !Memory # Input scratchpad
3535
name: InputScratchpad

examples/arches/nvdla.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ arch:
2727
- !Container
2828
name: ProcessingElement
2929
spatial:
30-
- {name: reuse_input, fanout: 32, may_reuse: input, reuse: input, min_usage: 1}
31-
- {name: reuse_output, fanout: 192, may_reuse: output, reuse: output, min_usage: 1}
30+
- {name: reuse_input, fanout: 32, reuse: input, min_usage: 1}
31+
- {name: reuse_output, fanout: 192, reuse: output, min_usage: 1}
3232

3333
- !Memory
3434
name: Register

examples/arches/simba.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ arch:
4343
- !Container
4444
name: VectorMAC
4545
spatial:
46-
- {name: reuse_output, fanout: 8, min_usage: 1, may_reuse: output, reuse: output}
47-
- {name: reuse_input, fanout: 8, min_usage: 1, may_reuse: input, reuse: input}
46+
- {name: reuse_output, fanout: 8, min_usage: 1, reuse: output}
47+
- {name: reuse_input, fanout: 8, min_usage: 1, reuse: input}
4848

4949
- !Memory
5050
name: Register

examples/arches/tpu_v4i.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ arch:
5353
- !Container
5454
name: ProcessingElement
5555
spatial:
56-
- {name: reuse_input, fanout: 128, may_reuse: input, reuse: input, min_usage: 1}
57-
- {name: reuse_output, fanout: 128, may_reuse: output, reuse: output, min_usage: 1}
56+
- {name: reuse_input, fanout: 128, reuse: input, min_usage: 1}
57+
- {name: reuse_output, fanout: 128, reuse: output, min_usage: 1}
5858

5959
- !Memory
6060
name: Register

tests/vibe_see_readme_in_this_dir/test_parsed_values.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -691,14 +691,19 @@ def test_array_fanout_reuse_input(self):
691691
ri = af.spatial[0]
692692
self.assertEqual(ri.name, "reuse_input")
693693
self.assertEqual(ri.fanout, 128)
694-
self.assertEqual(ri.may_reuse, "input")
694+
self.assertEqual(ri.reuse, "input")
695+
# may_reuse defaults to reuse when reuse is defined
696+
evaluated, _ = ri._eval_expressions()
697+
self.assertEqual(evaluated.may_reuse, "input")
695698

696699
def test_array_fanout_reuse_output(self):
697700
af = self.arch.find("ProcessingElement")
698701
ro = af.spatial[1]
699702
self.assertEqual(ro.name, "reuse_output")
700703
self.assertEqual(ro.fanout, 128)
701-
self.assertEqual(ro.may_reuse, "output")
704+
self.assertEqual(ro.reuse, "output")
705+
evaluated, _ = ro._eval_expressions()
706+
self.assertEqual(evaluated.may_reuse, "output")
702707

703708
def test_register_size_expression(self):
704709
reg = self.arch.find("Register")
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"""
2+
Tests for the Spatial.reuse / Spatial.may_reuse default behavior.
3+
4+
The defaults mirror Tensors.keep / Tensors.may_keep:
5+
- If neither is set, reuse defaults to "Nothing" and may_reuse defaults to "All".
6+
- If reuse is set, may_reuse defaults to the same value as reuse (so the spatial
7+
loop's eligible-for-reuse set equals its required-reuse set).
8+
- If may_reuse is set explicitly, the user value is preserved.
9+
"""
10+
11+
import unittest
12+
13+
from accelforge.frontend.arch import Spatial as ArchSpatial
14+
15+
16+
class TestSpatialReuseDefaults(unittest.TestCase):
17+
def _eval(self, **kwargs):
18+
s = ArchSpatial(name="X", fanout=4, **kwargs)
19+
evaluated, _ = s._eval_expressions()
20+
return evaluated
21+
22+
def test_neither_defined_may_reuse_is_all(self):
23+
e = self._eval()
24+
self.assertEqual(e.may_reuse, "All")
25+
26+
def test_neither_defined_reuse_is_nothing(self):
27+
e = self._eval()
28+
self.assertEqual(e.reuse, "Nothing")
29+
30+
def test_reuse_defined_may_reuse_defaults_to_reuse(self):
31+
e = self._eval(reuse="input")
32+
self.assertEqual(e.reuse, "input")
33+
self.assertEqual(e.may_reuse, "input")
34+
35+
def test_reuse_defined_with_complex_set(self):
36+
e = self._eval(reuse="input | weight")
37+
self.assertEqual(e.reuse, "input | weight")
38+
self.assertEqual(e.may_reuse, "input | weight")
39+
40+
def test_may_reuse_explicit_when_reuse_undefined(self):
41+
e = self._eval(may_reuse="output")
42+
self.assertEqual(e.reuse, "Nothing")
43+
self.assertEqual(e.may_reuse, "output")
44+
45+
def test_may_reuse_explicit_nothing_when_reuse_undefined(self):
46+
e = self._eval(may_reuse="Nothing")
47+
self.assertEqual(e.reuse, "Nothing")
48+
self.assertEqual(e.may_reuse, "Nothing")
49+
50+
def test_both_explicit_preserves_both(self):
51+
e = self._eval(reuse="input", may_reuse="All")
52+
self.assertEqual(e.reuse, "input")
53+
self.assertEqual(e.may_reuse, "All")
54+
55+
def test_may_reuse_disjoint_from_reuse_when_both_explicit(self):
56+
e = self._eval(reuse="input", may_reuse="output")
57+
self.assertEqual(e.reuse, "input")
58+
self.assertEqual(e.may_reuse, "output")
59+
60+
61+
class TestSpatialReuseDefaultsFromYaml(unittest.TestCase):
62+
"""Verify YAML parsing applies the same defaults via Spec.from_yaml.
63+
64+
These check the post-Spatial-eval values (sentinels resolved) before the
65+
full spec evaluation, so values are still the raw parsed strings."""
66+
67+
def _spatial_from_yaml(self, spatial_yaml: str):
68+
import os
69+
import tempfile
70+
from pathlib import Path
71+
from accelforge.frontend.spec import Spec
72+
73+
repo_root = Path(__file__).parent.parent.parent
74+
yaml_text = f"""
75+
arch:
76+
nodes:
77+
- !Container
78+
name: PE
79+
spatial:
80+
- {spatial_yaml}
81+
workload:
82+
rank_sizes: {{M: 8}}
83+
einsums:
84+
- name: E
85+
tensor_accesses:
86+
- {{name: a, projection: [m], bits_per_value: 8}}
87+
- {{name: b, projection: [m], output: True, bits_per_value: 8}}
88+
"""
89+
with tempfile.NamedTemporaryFile(
90+
mode="w", suffix=".yaml", delete=False, dir=str(repo_root)
91+
) as f:
92+
f.write(yaml_text)
93+
f.flush()
94+
try:
95+
spec = Spec.from_yaml(f.name)
96+
spatial = spec.arch.find("PE").spatial[0]
97+
evaluated, _ = spatial._eval_expressions()
98+
return evaluated
99+
finally:
100+
os.unlink(f.name)
101+
102+
def test_yaml_no_reuse_no_may_reuse(self):
103+
s = self._spatial_from_yaml("{name: dim, fanout: 4}")
104+
self.assertEqual(s.reuse, "Nothing")
105+
self.assertEqual(s.may_reuse, "All")
106+
107+
def test_yaml_reuse_only(self):
108+
s = self._spatial_from_yaml("{name: dim, fanout: 4, reuse: a}")
109+
self.assertEqual(s.reuse, "a")
110+
self.assertEqual(s.may_reuse, "a")
111+
112+
def test_yaml_may_reuse_only(self):
113+
s = self._spatial_from_yaml("{name: dim, fanout: 4, may_reuse: b}")
114+
self.assertEqual(s.reuse, "Nothing")
115+
self.assertEqual(s.may_reuse, "b")
116+
117+
118+
if __name__ == "__main__":
119+
unittest.main()

tests/vibe_see_readme_in_this_dir/test_yaml_and_expressions.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -541,11 +541,13 @@ class TestSpatialReuse(unittest.TestCase):
541541

542542
def test_reuse_default_nothing(self):
543543
s = ArchSpatial(name="X", fanout=4)
544-
self.assertEqual(s.reuse, "Nothing")
544+
evaluated, _ = s._eval_expressions()
545+
self.assertEqual(evaluated.reuse, "Nothing")
545546

546547
def test_may_reuse_default_all(self):
547548
s = ArchSpatial(name="X", fanout=4)
548-
self.assertEqual(s.may_reuse, "All")
549+
evaluated, _ = s._eval_expressions()
550+
self.assertEqual(evaluated.may_reuse, "All")
549551

550552
def test_reuse_custom(self):
551553
s = ArchSpatial(name="X", fanout=4, reuse="input")

0 commit comments

Comments
 (0)