From a83f2192539581d957bc6f555a87b63990601339 Mon Sep 17 00:00:00 2001 From: George Hotz <72895+geohot@users.noreply.github.com> Date: Tue, 30 Sep 2025 19:30:21 +0800 Subject: [PATCH] fix bad range merges (#12368) * fix bad range merges * fix rng * fix uop gc --- test/external/external_uop_gc.py | 1 + test/test_rangeify.py | 5 +++++ tinygrad/codegen/opt/postrange.py | 7 +++++++ tinygrad/codegen/simplify.py | 22 +++++++++++++--------- tinygrad/uop/ops.py | 8 ++++++-- 5 files changed, 32 insertions(+), 11 deletions(-) diff --git a/test/external/external_uop_gc.py b/test/external/external_uop_gc.py index c72bacd9aa..a773ac5053 100644 --- a/test/external/external_uop_gc.py +++ b/test/external/external_uop_gc.py @@ -63,6 +63,7 @@ if __name__ == "__main__": views_to_valid_uop.cache_clear() new_uops = uops_allocated() + print_uops() gc.collect() new_uops_gc = uops_allocated() print(f"{t.__name__:30s}: {new_uops:3d} -> {new_uops_gc:3d}") diff --git a/test/test_rangeify.py b/test/test_rangeify.py index e27bacdc4e..a7cb8a4a54 100644 --- a/test/test_rangeify.py +++ b/test/test_rangeify.py @@ -40,6 +40,11 @@ class TestRangeifyOpt(unittest.TestCase): @unittest.skipIf(RANGEIFY<1, "tests only for RANGEIFY") class TestRangeify(unittest.TestCase): + def test_groupnorm(self): + # ranges 1 and 3 are merging + x = nn.GroupNorm(32, 128) + x(Tensor.empty(1, 128, 64, 64)).realize() + def test_expand_children(self): A = Tensor.empty(N, N).sum(axis=1) ba = A.expand(N, N) diff --git a/tinygrad/codegen/opt/postrange.py b/tinygrad/codegen/opt/postrange.py index 4f234e047c..1fe4897242 100644 --- a/tinygrad/codegen/opt/postrange.py +++ b/tinygrad/codegen/opt/postrange.py @@ -71,6 +71,13 @@ class Scheduler: or (x.op is Ops.BUFFERIZE and x.arg == AddrSpace.LOCAL)] for ls in local_store_rngs: store_rngs = tuple([x for x in store_rngs if x in ls]) + # filter any not in reduces + # TODO: enable this + """ + reduce_rngs = [x.ranges for x in self.ast.toposort() if x.op is Ops.REDUCE] + for ls in reduce_rngs: store_rngs = tuple([x for x in store_rngs if x in ls]) + """ + return [x for x in UOp.sink(*store_rngs).toposort() if x.op is Ops.RANGE and x.arg[1] == AxisType.LOOP] if store_rngs else [] def convert_loop_to_global(self): diff --git a/tinygrad/codegen/simplify.py b/tinygrad/codegen/simplify.py index c1bdeff186..b3d295287f 100644 --- a/tinygrad/codegen/simplify.py +++ b/tinygrad/codegen/simplify.py @@ -17,20 +17,24 @@ pm_flatten_range = PatternMatcher([ def count_divmod(x:UOp): return len([u for u in x.toposort() if u.op in {Ops.IDIV, Ops.MOD}]) def simplify_merge_adjacent(u:UOp) -> UOp|None: + reduce_ranges = [x.ranges for x in u.sparents if x.op is Ops.REDUCE] i = range_start[u.op] while i < len(u.src)-1: r0, r1 = u.src[i], u.src[i+1] # check same type if r0.arg[-1] == r1.arg[-1]: - s0, s1 = r0.src[0], r1.src[0] - # do the merge - new_range = r0.replace(src=(s0*s1,)) - nidx = graph_rewrite(u, _substitute+symbolic_flat+pm_flatten_range, ctx={r0:new_range//s1, r1:new_range%s1}, - name=f"check_merge_{r0.arg[0]}_{r1.arg[0]}") - # check if it simplifies - if count_divmod(nidx) <= count_divmod(u): - u = nidx - continue + # check if the ranges to merge are in the same reduces + if all((r0 in rngs) == (r1 in rngs) for rngs in reduce_ranges): + s0, s1 = r0.src[0], r1.src[0] + # do the merge + new_range = r0.replace(src=(s0*s1,)) + nidx = graph_rewrite(u, _substitute+symbolic_flat+pm_flatten_range, ctx={r0:new_range//s1, r1:new_range%s1}, + name=f"check_merge_{r0.arg[0]}_{r1.arg[0]}") + + # check if it simplifies + if count_divmod(nidx) <= count_divmod(u): + u = nidx + continue i += 1 return u diff --git a/tinygrad/uop/ops.py b/tinygrad/uop/ops.py index c3516aaa3c..cd611ffc28 100644 --- a/tinygrad/uop/ops.py +++ b/tinygrad/uop/ops.py @@ -217,8 +217,7 @@ class UOp(MathTrait, metaclass=UOpMetaClass): # determine what ranges this is in @functools.cached_property - def ranges(self) -> dict[UOp, None]: - if self.op is Ops.RANGE: return {self:None} + def _ranges(self) -> dict[UOp, None]: ret: dict[UOp, None] = {} if self.op in range_start.keys(): for s in self.src[:range_start[self.op]]: ret.update(s.ranges) @@ -228,6 +227,11 @@ class UOp(MathTrait, metaclass=UOpMetaClass): for s in self.src: ret.update(s.ranges) return ret + @property + def ranges(self) -> dict[UOp, None]: + if self.op is Ops.RANGE: return {self:None} + return self._ranges + # *** uop evaluation *** def simplify(self, tracked=False):