Skip to content

Commit f5f1401

Browse files
committed
[model,frontend,tests,examples] Model hops
1 parent 1660529 commit f5f1401

8 files changed

Lines changed: 315 additions & 25 deletions

File tree

accelforge/frontend/arch/components.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,8 +1001,46 @@ def _render_node_color(self) -> str:
10011001

10021002

10031003
class Network(Component, Leaf):
1004+
"""
1005+
Defines a network component.
1006+
1007+
The routing is currently defined using the mapping, the routing follows the order
1008+
of the spatial nodes from top to bottom.
1009+
"""
1010+
bits_per_value_scale: EvalsTo[dict] = {"All": 1}
1011+
"""
1012+
A scaling factor for the bits per value of the tensors in this `TensorHolder`. If
1013+
this is a dictionary, keys in the dictionary are evaluated as expressions and may
1014+
reference one or more tensors.
1015+
"""
1016+
1017+
bits_per_action: EvalsTo[int | float | None] = None
1018+
"""
1019+
The number of bits accessed in each of this component's actions. Overridden by
1020+
bits_per_action in any action of this component. If set here, acts as a default
1021+
value for the bits_per_action of all actions of this component.
1022+
"""
1023+
10041024
def _render_node_shape(self) -> str:
10051025
return "Msquare"
10061026

10071027
def _render_node_color(self) -> str:
10081028
return "#FAF8C8"
1029+
1030+
def _eval_expressions(self, *args, **kwargs):
1031+
# Sometimes the same component object may appear in the mapping and the
1032+
# architecture, in which case we don't want parsing to happen twice.
1033+
if getattr(self, "_evaluated", False):
1034+
return super()._eval_expressions(*args, **kwargs)
1035+
1036+
class MyPostCall(_PostCall):
1037+
def __call__(self, field, value, evaluated, symbol_table):
1038+
if field == "bits_per_value_scale":
1039+
evaluated = _eval_tensor2bits(
1040+
evaluated,
1041+
location="bits_per_value_scale",
1042+
symbol_table=symbol_table,
1043+
)
1044+
return evaluated
1045+
1046+
return super()._eval_expressions(*args, **kwargs, post_calls=(MyPostCall(),))

accelforge/frontend/arch/structure.py

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929

3030
_FIND_SENTINEL = object()
3131

32+
D = TypeVar("D")
33+
T = TypeVar("T")
34+
3235

3336
class ArchNode(EvalableModel):
3437
"""A node in the architecture."""
@@ -71,6 +74,46 @@ def find(self, name: str, default: Any = _FIND_SENTINEL) -> Union["Leaf", Any]:
7174
return default
7275
raise ValueError(f"Leaf {name} not found in {self}")
7376

77+
def is_above(self, node_a: str, node_b: str) -> bool:
78+
"""Returns whether node_a is above node_b in a hierarchy."""
79+
self.find(node_a)
80+
self.find(node_b)
81+
for node, parents in self.iterate_hierarchically():
82+
if node.name != node_b:
83+
continue
84+
return any(p.name == node_a for p in parents)
85+
86+
def find_first_of_type_above(
87+
self,
88+
node_type: T,
89+
name: str,
90+
default: D = _FIND_SENTINEL
91+
) -> T | D:
92+
"""
93+
Returns the first node with type `node_type` above `name`.
94+
95+
If `name` does not exist, raises an error.
96+
97+
If no node of `node_type` is found, either `default` is
98+
returned (if provided) or raises an error.
99+
"""
100+
# Check if name exists
101+
# This *should* raise even if default != _FIND_SENTINEL
102+
self.find(name)
103+
104+
for node, parents in self.iterate_hierarchically():
105+
if node.name != name:
106+
continue
107+
for p in reversed(parents):
108+
if isinstance(p, node_type):
109+
return p
110+
if default is not _FIND_SENTINEL:
111+
return default
112+
raise ValueError(f"Parent of {name} with type {node_type} not found")
113+
raise RuntimeError(
114+
"BUG: find() finds node but iterate_hierarchically() does not"
115+
)
116+
74117
def iterate_hierarchically(self, _parents=None):
75118
"""
76119
Iterates over all Leaf nodes while also yielding the list of all Leaf
@@ -170,9 +213,6 @@ def _render_make_children(self) -> list[pydot.Node]:
170213
return [self._render_node()]
171214

172215

173-
T = TypeVar("T")
174-
175-
176216
@_uninstantiable
177217
class Branch(ArchNode):
178218
nodes: ArchNodes[

accelforge/model/_looptree/energy.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
1-
from collections import defaultdict
21
from collections.abc import Mapping as MappingABC
3-
from dataclasses import dataclass
42
import logging
53
from numbers import Number
64
from numbers import Real
75

86
from accelforge.frontend import arch
9-
from accelforge.frontend.mapping.mapping import MappingNode
107
from accelforge.frontend.spec import Spec
118
from accelforge.model._looptree.reuse.symbolic import SymbolicAnalysisOutput
129
from accelforge.util._base_analysis_types import (
1310
ActionCount,
1411
ActionKey,
1512
VerboseActionKey,
1613
)
17-
from accelforge.frontend.workload import Workload
18-
from accelforge.frontend.mapping import Mapping
14+
from accelforge.model._looptree.types import Network
1915

2016

2117
def gather_actions(
@@ -29,6 +25,7 @@ def gather_actions(
2925

3026
buffet_keyer = _get_buffet_keyer(verbose, use_name, bindings)
3127
compute_keyer = _get_compute_keyer(verbose, use_name, bindings)
28+
network_keyer = _get_network_keyer(verbose, use_name, bindings)
3229

3330
for buffet, accesses in looptree_results.buffet_stats.items():
3431
if buffet.level in compute_levels:
@@ -60,6 +57,13 @@ def gather_actions(
6057
actions[key].total += ops.total_ops
6158
actions[key].max_per_unit += ops.max_per_unit_ops
6259

60+
for network, stats in looptree_results.network_stats.items():
61+
key = network_keyer(network, "hops")
62+
if key not in actions:
63+
actions[key] = ActionCount.default()
64+
actions[key].total += stats.total_hops
65+
actions[key].max_per_unit += stats.max_hops
66+
6367
return actions
6468

6569

@@ -111,6 +115,22 @@ def compute_keyer(compute, action_name):
111115
return compute_keyer
112116

113117

118+
def _get_network_keyer(verbose, use_name, bindings):
119+
if not verbose:
120+
def network_keyer(network: Network, action_name: str):
121+
component = network.component
122+
if not use_name:
123+
component = bindings[component]
124+
return ActionKey(component, action_name)
125+
else:
126+
def network_keyer(network: Network, action_name: str):
127+
component = network.component
128+
if not use_name:
129+
component = bindings[component]
130+
return VerboseActionKey(component, action_name, network.tensor, network.einsum)
131+
return network_keyer
132+
133+
114134
def compute_energy_from_actions(
115135
spec: Spec,
116136
action_counts: MappingABC[ActionKey, Real],
@@ -135,7 +155,7 @@ def compute_energy_from_actions(
135155
energy_per_ac = component_obj.actions[key.action].energy
136156
except KeyError as e:
137157
raise KeyError(
138-
f"Action {key.action} not found in component {key.component}. Action occurred "
158+
f"Action {key.action} not found in component {key.level}. Action occurred "
139159
f"{counts.total} times."
140160
) from None
141161
energy_result[key] = counts.total * energy_per_ac

accelforge/model/_looptree/reuse/symbolic/mapping_utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from accelforge.frontend.mapping import (
66
Mapping,
77
TensorHolder,
8+
Compute as ComputeSpec
89
)
910
from accelforge.frontend.workload import (
1011
TensorName,
@@ -54,6 +55,13 @@ def from_pmapping(cls, pmapping):
5455
if src is not None:
5556
src_to_dst[src] = buffet
5657
src_to_dst[buffet] = None
58+
elif isinstance(node, ComputeSpec):
59+
for tensor in tensor_to_last_src:
60+
dst_buffet = Buffet(tensor, einsum_name, node.component)
61+
src = tensor_to_last_src[tensor]
62+
src_to_dst[src] = dst_buffet
63+
dst_to_src[dst_buffet] = src
64+
5765
result = DataMovementConnections()
5866
result.src_to_dst = src_to_dst
5967
result.dst_to_src = dst_to_src

0 commit comments

Comments
 (0)