@@ -146,6 +146,12 @@ class BuffetStats:
146146
147147 persistent : bool = field (default = False )
148148
149+ # Temporal reuse tracking: True if a relevant temporal loop has processed
150+ # this buffet since the last Storage node set total_reads_to_parent.
151+ # When False and an irrelevant temporal is encountered, parent-facing attrs
152+ # are not multiplied (the buffer persists across irrelevant iterations).
153+ _has_relevant_temporal_above : bool = field (default = False )
154+
149155 @property
150156 def n_loops_above (self ) -> int :
151157 if self .persistent :
@@ -158,14 +164,22 @@ def n_loops_above(self, value: int):
158164
159165 def repeat_temporal (self , factor : int , is_fully_relevant : bool ) -> "BuffetStats" :
160166 new = copy .copy (self )
167+ # Temporal reuse: if the loop is irrelevant and no relevant temporal
168+ # has intervened since the Storage node set parent-facing stats, the
169+ # buffer persists across iterations — skip parent-facing attrs.
170+ skip_parent = not is_fully_relevant and not self ._has_relevant_temporal_above
161171 for attr in self .__dict__ :
162172 if not attr .startswith (("total_" , "max_" , "min_" )):
163173 continue
164174 if "skipped_first" in attr and not is_fully_relevant :
165175 continue # First actions occur once per relevant iteration.
166176 if attr == "max_occupancy" :
167177 continue # Max occupancy is not affected by temporal loops above
178+ if "parent" in attr and skip_parent :
179+ continue # Temporal reuse: buffer persists across irrelevant iters.
168180 setattr (new , attr , getattr (new , attr ) * factor )
181+ if is_fully_relevant :
182+ new ._has_relevant_temporal_above = True
169183 return new
170184
171185 def repeat_spatial (self , factor : int , reuse_parent_accesses : bool ) -> "BuffetStats" :
@@ -204,7 +218,10 @@ def min(self, **kwargs: Any):
204218 def __add__ (self , other : "BuffetStats" ) -> "BuffetStats" :
205219 new = copy .copy (self )
206220 for attr in self .__dict__ :
207- if attr .startswith ("min_" ):
221+ if attr == "_has_relevant_temporal_above" :
222+ # Combine conservatively: if either has relevant above, so does result
223+ setattr (new , attr , getattr (self , attr ) or getattr (other , attr ))
224+ elif attr .startswith ("min_" ):
208225 setattr (
209226 new , attr , min_nonzero (getattr (self , attr ), getattr (other , attr ))
210227 )
@@ -1178,6 +1195,11 @@ def inherit_add(attr: str, default_value: Any = fills) -> Any:
11781195 inherit_add ("total_skipped_first_reads_to_parent" )
11791196 inherit_add ("min_per_parent_skipped_first_reads_to_parent" )
11801197
1198+ # Reset temporal reuse tracking: this Storage node just set fresh
1199+ # parent-facing stats; irrelevant temporals above should not
1200+ # multiply them until a relevant temporal intervenes.
1201+ stats ._has_relevant_temporal_above = False
1202+
11811203 # ==============================================================================
11821204 # Convert to actions. These are not used used upward; they are used to get
11831205 # energy and latency.
@@ -1354,6 +1376,10 @@ def analyze_compute(
13541376 stats .total_skipped_first_reads_to_parent = 1
13551377 stats .min_per_parent_skipped_first_reads_to_parent = 1
13561378 stats .max_occupancy = 1
1379+ # Compute-level accesses have no buffering: every iteration reads from
1380+ # parent regardless of relevancy. Mark as having a "relevant temporal
1381+ # above" so that irrelevant temporal loops still multiply parent attrs.
1382+ stats ._has_relevant_temporal_above = True
13571383 result_accumulator .buffet_stats [buffet ] = stats
13581384
13591385 network_node = info .job .spec .arch .find_first_of_type_above (
0 commit comments