@@ -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 )
@@ -1180,6 +1197,11 @@ def inherit_add(attr: str, default_value: Any = fills) -> Any:
11801197 inherit_add ("total_skipped_first_reads_to_parent" )
11811198 inherit_add ("min_per_parent_skipped_first_reads_to_parent" )
11821199
1200+ # Reset temporal reuse tracking: this Storage node just set fresh
1201+ # parent-facing stats; irrelevant temporals above should not
1202+ # multiply them until a relevant temporal intervenes.
1203+ stats ._has_relevant_temporal_above = False
1204+
11831205 # ==============================================================================
11841206 # Convert to actions. These are not used used upward; they are used to get
11851207 # energy and latency.
@@ -1358,6 +1380,10 @@ def analyze_compute(
13581380 stats .total_skipped_first_reads_to_parent = 1
13591381 stats .min_per_parent_skipped_first_reads_to_parent = 1
13601382 stats .max_occupancy = 1
1383+ # Compute-level accesses have no buffering: every iteration reads from
1384+ # parent regardless of relevancy. Mark as having a "relevant temporal
1385+ # above" so that irrelevant temporal loops still multiply parent attrs.
1386+ stats ._has_relevant_temporal_above = True
13611387 result_accumulator .buffet_stats [buffet ] = stats
13621388
13631389 network_node = info .job .spec_one_einsum .arch .find_first_of_type_above (
0 commit comments