@@ -176,13 +176,39 @@ def blank(cls):
176176 stats .n_loops_above = None # Inherit from whoever is added to this
177177 return stats
178178
179+ def _scale_op_dict (d : dict [str , Any ], factor : Any ) -> dict [str , Any ]:
180+ """Multiply every per-op-kind value by `factor`. Identity at factor==1."""
181+ if factor == 1 :
182+ return dict (d )
183+ if isinstance (factor , float ) and factor == int (factor ):
184+ factor = int (factor )
185+ return {k : v * factor for k , v in d .items ()}
186+
187+
188+ def _sum_op_dicts (a : dict [str , Any ], b : dict [str , Any ]) -> dict [str , Any ]:
189+ """Per-op-kind sum. Keys present in only one operand are kept as-is."""
190+ out = dict (a )
191+ for k , v in b .items ():
192+ out [k ] = out [k ] + v if k in out else v
193+ return out
194+
195+
196+ def _max_op_dicts (a : dict [str , Any ], b : dict [str , Any ]) -> dict [str , Any ]:
197+ """Per-op-kind MaxGeqZero. Keys present in only one operand are kept as-is."""
198+ out = dict (a )
199+ for k , v in b .items ():
200+ out [k ] = MaxGeqZero (out [k ], v ) if k in out else v
201+ return out
179202
180203@dataclass
181204class ComputeStats :
182- total_ops : Any = field (default = 0 )
183- max_per_unit_ops : Any = field (default = 0 )
205+ # Per-op-kind counts. Keys are op_kind strings (e.g. "mul", "add", "mac")
206+ # matching the ComputeAction.op_kind values declared on the arch's Compute
207+ # component. An empty dict means no contribution.
208+ total_ops : dict [str , Any ] = field (default_factory = dict )
209+ max_per_unit_ops : dict [str , Any ] = field (default_factory = dict )
184210 # "max" below refers to the longest latency of any iteration
185- max_latency : Any = field (default = 0 )
211+ max_latency : dict [ str , Any ] = field (default_factory = dict )
186212 # Mapping from the loop-index (0 at top) to the latency of the first
187213 # iteration of that loop. "Max" because we may have loops above that and we
188214 # will take the maximum of the firsts.
@@ -194,9 +220,9 @@ def repeat_temporal(self, factor: int) -> "ComputeStats":
194220 return new
195221 if type (factor ) is float and factor == int (factor ):
196222 factor = int (factor )
197- new .total_ops = new .total_ops * factor
198- new .max_per_unit_ops = new .max_per_unit_ops * factor
199- new .max_latency = new .max_latency * factor
223+ new .total_ops = _scale_op_dict ( new .total_ops , factor )
224+ new .max_per_unit_ops = _scale_op_dict ( new .max_per_unit_ops , factor )
225+ new .max_latency = _scale_op_dict ( new .max_latency , factor )
200226 # NOTE: max_first_latency does not change
201227 return new
202228
@@ -206,14 +232,14 @@ def repeat_spatial(self, factor: int) -> "ComputeStats":
206232 return new
207233 if type (factor ) is float and factor == int (factor ):
208234 factor = int (factor )
209- new .total_ops = new .total_ops * factor
235+ new .total_ops = _scale_op_dict ( new .total_ops , factor )
210236 return new
211237
212238 def __add__ (self , other : "ComputeStats" ) -> "ComputeStats" :
213239 new = copy .copy (self )
214- new .total_ops += other .total_ops
215- new .max_per_unit_ops += other .max_per_unit_ops
216- new .max_latency += other .max_latency
240+ new .total_ops = _sum_op_dicts ( new . total_ops , other .total_ops )
241+ new .max_per_unit_ops = _sum_op_dicts ( new . max_per_unit_ops , other .max_per_unit_ops )
242+ new .max_latency = _sum_op_dicts ( new . max_latency , other .max_latency )
217243 # max_first_latency is only ever updated across loops ABOVE the loop
218244 # for which we calculated that first latency, so we should MAX
219245 new .max_first_latency = max_dict (
@@ -222,21 +248,19 @@ def __add__(self, other: "ComputeStats") -> "ComputeStats":
222248 return new
223249
224250 def combine_temporal (self , other : "ComputeStats" ):
225- self .total_ops += other .total_ops
226- self .max_per_unit_ops += other .max_per_unit_ops
227- self .max_latency += other .max_latency
251+ self .total_ops = _sum_op_dicts ( self . total_ops , other .total_ops )
252+ self .max_per_unit_ops = _sum_op_dicts ( self . max_per_unit_ops , other .max_per_unit_ops )
253+ self .max_latency = _sum_op_dicts ( self . max_latency , other .max_latency )
228254 # max_first_latency is only ever updated across loops ABOVE the loop
229255 # for which we calculated that first latency, so we should MAX
230256 self .max_first_latency = max_dict (
231257 self .max_first_latency , other .max_first_latency
232258 ) # FIRST LATENCY
233259
234260 def combine_spatial (self , other : "ComputeStats" ):
235- self .total_ops += other .total_ops
236- self .max_per_unit_ops = MaxGeqZero (
237- self .max_per_unit_ops , other .max_per_unit_ops
238- )
239- self .max_latency = MaxGeqZero (self .max_latency , other .max_latency )
261+ self .total_ops = _sum_op_dicts (self .total_ops , other .total_ops )
262+ self .max_per_unit_ops = _max_op_dicts (self .max_per_unit_ops , other .max_per_unit_ops )
263+ self .max_latency = _max_op_dicts (self .max_latency , other .max_latency )
240264 # max_first_latency is only ever updated across loops ABOVE the loop
241265 # for which we calculated that first latency, so we should MAX
242266 self .max_first_latency = max_dict (
0 commit comments