From d8fbbff260965042d6866b78043d6289d87afb95 Mon Sep 17 00:00:00 2001 From: George Hotz <72895+geohot@users.noreply.github.com> Date: Tue, 7 Jul 2026 11:03:06 -0700 Subject: [PATCH] EXPAND adds dims to the front (glm) (#16908) * EXPAND adds dims to the front (glm) * more read images * simpler + spec * spec should use n instead of s' * bump comma to 12 --- .github/workflows/benchmark.yml | 4 ++-- .github/workflows/test.yml | 2 +- spec/tinyspec.pdf | Bin 96655 -> 96455 bytes spec/tinyspec.tex | 4 ++-- test/null/test_viz.py | 5 ++--- tinygrad/codegen/__init__.py | 6 +++--- tinygrad/mixin/gradient.py | 2 +- tinygrad/mixin/movement.py | 13 ++++++++++--- tinygrad/schedule/indexing.py | 4 ++-- tinygrad/schedule/multi.py | 5 ++--- tinygrad/uop/ops.py | 10 +++++----- 11 files changed, 30 insertions(+), 25 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 61642bc795..1f2128737c 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -404,7 +404,7 @@ jobs: testcommalatest: name: comma Benchmark (0.11.0) runs-on: [self-hosted, Linux, comma] - timeout-minutes: 10 + timeout-minutes: 12 defaults: run: shell: bash -e -o pipefail {0} @@ -435,7 +435,7 @@ jobs: testcommaold: name: comma Benchmark (0.10.1) runs-on: [self-hosted, Linux, comma] - timeout-minutes: 10 + timeout-minutes: 12 defaults: run: shell: bash -e -o pipefail {0} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dbf55f991c..cca9359d25 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -291,7 +291,7 @@ jobs: llvm: 'true' - name: Test openpilot model kernel count and gate usage run: | - ALLOWED_KERNEL_COUNT=123 ALLOWED_READ_IMAGE=1361 ALLOWED_GATED_READ_IMAGE=55 FLOAT16=1 DEV="CL::IMAGE_PITCH_ALIGNMENT=64" IMAGE=1 python examples/openpilot/compile3.py https://gitlab.com/commaai/openpilot-lfs.git/gitlab-lfs/objects/cf6376aa9a090f0da26c280ef69eabf9bbdd51d1faac9ed392919c3db69be916 + ALLOWED_KERNEL_COUNT=123 ALLOWED_READ_IMAGE=1391 ALLOWED_GATED_READ_IMAGE=58 FLOAT16=1 DEV="CL::IMAGE_PITCH_ALIGNMENT=64" IMAGE=1 python examples/openpilot/compile3.py https://gitlab.com/commaai/openpilot-lfs.git/gitlab-lfs/objects/cf6376aa9a090f0da26c280ef69eabf9bbdd51d1faac9ed392919c3db69be916 # IMAGE_PITCH_ALIGNMENT=64 matches adreno 630 - name: Test openpilot CL compile fp32 (test correctness) run: | diff --git a/spec/tinyspec.pdf b/spec/tinyspec.pdf index 852aec4f4f57e98408e7c7dd8ab974070d03dcf5..f8b3b7cb9d9c7d038c158f242c768019f28337f9 100644 GIT binary patch delta 19080 zcmY(KQ*b2=)2%1AZF^$dwr$(SPBO8R9ox2T+qP{@l1a{6=j!{f`=YB>RoCigRd@Uo z#MlD_3WbuG1U)kYJ1oWV{mnZp3wI(4C4h@tKmgX&&Bff<9@cB~Mo-p}ybY!IO!HY; zgo&V+D6U#Ga9GGe;nm_Se?!|IeypK~BcU&^?wvAghxPGx6OWiOZuQLFk)bHyM) z?|tx|(Xd+U_ghsNAa!le_?lk8uy}5(O7Q2|=I@Z<```_2yG9gaU2YoCAar4u0YF8g zIo7N`RW+rF6oWIDj{2@g7&^MQ%0xMbZ5i{h787adXCRo1euTvK=#?Zl?@B6&bw(GS zM+%TtTxe?+`GPcUuAE~nj`g*Q!#QiL^lg46nJr7P#>zCto*{<)uh!y5`_Xf)Shm=$ z^pyM-naA`aqLzxYK4vs5Fw$eKsb2jWuL*#lI~n|Y*>XCHLf0|2!&JB9g6iO&i^qCU zvT<|*;R~r?SvhF!M0>Ls(7AU|9jKNpEL`pw#!?N0bX&u*zY*=vBQlVbF|93vx6 zl%SoU=-o?8V`(W_F-|p?)mS3IYAQ&R%HUC&%O*#8XqIW5-rd_79ln`xT--ClABsqj#t9~X zr_B`81&3oPTF28XU-fa-CYYc)`D=qg9jYTqXDWFZJ05I#tarc2IRLr!rOCl6OvZ4* z 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 delta 19309 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 should be folded into the ALU node, not shown as separate RESHAPE/EXPAND nodes - c = UOp.const(dtypes.float, 1.0, shape=(3,4)) # creates CONST->RESHAPE->EXPAND chain + # CONST->EXPAND should be folded into the ALU node, not shown as separate EXPAND nodes + c = UOp.const(dtypes.float, 1.0, shape=(3,4)) # creates CONST->EXPAND chain a = UOp.variable("a", 0.0, 10.0, dtypes.float) alu = a + c with save_viz() as viz: @@ -262,7 +262,6 @@ class TestViz(unittest.TestCase): excluded_nodes = {v["label"].split("\n")[0] for v in graph.values() if v["exclude"]} self.assertIn("CONST", excluded_nodes) self.assertIn("STACK", excluded_nodes) - self.assertIn("RESHAPE", excluded_nodes) self.assertIn("EXPAND", excluded_nodes) self.assertIn("CONST1 1", graph[id(alu)]["label"]) diff --git a/tinygrad/codegen/__init__.py b/tinygrad/codegen/__init__.py index 86a7e764cf..013e734aa3 100644 --- a/tinygrad/codegen/__init__.py +++ b/tinygrad/codegen/__init__.py @@ -172,9 +172,9 @@ devectorizer2 = pm_mops+PatternMatcher([ (UPat(Ops.RESHAPE, dtype=dtypes.void, name="x"), lambda x: x.src[0]), # reshape of a single element shaped value to scalar is an index (UPat(Ops.RESHAPE, name="x"), lambda x: x.src[0].index(UOp.const(dtypes.weakint, 0)) if x.marg == () and x.src[0].shape == (1,) else None), - # RESHAPE+EXPAND -> STACK - (UPat(Ops.EXPAND, src=(UPat(Ops.RESHAPE, src=(UPat.var("x"), UPat())), UPat()), name="out"), - lambda x,out: UOp.vectorize(*([x]*out.max_numel())) if out.shape == (out.max_numel(),) else None), + # EXPAND on scalar -> STACK + (UPat(Ops.EXPAND, src=(UPat.var("x"), UPat()), name="out"), + lambda x,out: UOp.vectorize(*([x]*out.max_numel())) if x.shape == () and out.shape == (out.max_numel(),) else None), # INDEX on INDEX is INDEX (UPat(Ops.INDEX, src=(UPat(Ops.INDEX, name="idx1", allow_any_len=True),), allow_any_len=True, name="idx2"), lambda idx1, idx2: idx1.src[0].index(*idx1.src[1:], *idx2.src[1:])), diff --git a/tinygrad/mixin/gradient.py b/tinygrad/mixin/gradient.py index aece6850d8..bbafd38b67 100644 --- a/tinygrad/mixin/gradient.py +++ b/tinygrad/mixin/gradient.py @@ -75,7 +75,7 @@ pm_gradient = PatternMatcher([ (UPat(Ops.CONTIGUOUS_BACKWARD), lambda ctx: (ctx.contiguous(),)), (UPat(Ops.RESHAPE, name="ret"), lambda ctx, ret: (ctx.reshape(ret.src[0].shape), None)), (UPat(Ops.EXPAND, name="ret"), lambda ctx, ret: - (ctx.cast(sum_acc_dtype(ctx.dtype))._rop(Ops.ADD, tuple(i for i,(s,n) in enumerate(zip(ret.src[0].shape, ret.shape)) if s!=n)) + (ctx.cast(sum_acc_dtype(ctx.dtype))._rop(Ops.ADD, tuple(range(len(ret.marg)))) .reshape(ret.src[0].shape).cast(ctx.dtype), None)), (UPat(Ops.PAD, name="ret"), lambda ctx, ret: (ctx.shrink(tuple([(p[0], s+p[0]) for s,p in zip(ret.src[0].shape, ret.marg)])), None, None)), (UPat(Ops.SHRINK, name="ret"), lambda ctx, ret: (ctx.pad(tuple([(p[0], s-p[0]-p[1]) for s,p in zip(ret.src[0].shape, ret.marg)])), None, None)), diff --git a/tinygrad/mixin/movement.py b/tinygrad/mixin/movement.py index 2562e3c330..cc36b3f392 100644 --- a/tinygrad/mixin/movement.py +++ b/tinygrad/mixin/movement.py @@ -123,9 +123,16 @@ class MovementMixin: # for each dimension, check either dim is 1, or it does not change if not all(s == ns or s == 1 for s, ns in zip(shape, new_shape)): raise ValueError(f"cannot broadcast {self.shape} to {new_shape=}") - reshaped = self.reshape(shape) - ret = reshaped._mop(Ops.EXPAND, arg=new_shape) - return reshaped if ret.shape == reshaped.shape else ret + # EXPAND only adds dims on the left. squeeze 1s that need expanding, EXPAND on left, permute back. + n_left = len(new_shape) - len(self.shape) + expand_at = tuple(i for i, s in enumerate(self.shape) if resolve(s == 1, default=False) and resolve(new_shape[n_left+i] != 1)) + kept = tuple(i for i in range(len(self.shape)) if i not in expand_at) + squeezed = self.reshape(tuple(self.shape[i] for i in kept)) + expanded = squeezed._mop(Ops.EXPAND, arg=new_shape[:n_left] + tuple(new_shape[n_left+i] for i in expand_at)) + # expanded shape = [left] + [expand_at dims] + [kept dims], permute to new_shape + perm = tuple(range(n_left)) + tuple( + n_left + (expand_at.index(i) if i in expand_at else len(expand_at) + kept.index(i)) for i in range(len(self.shape))) + return expanded.permute(perm) def expand(self, shape, *args) -> Self: """ diff --git a/tinygrad/schedule/indexing.py b/tinygrad/schedule/indexing.py index 7c69a1bc52..043fe2a7c8 100644 --- a/tinygrad/schedule/indexing.py +++ b/tinygrad/schedule/indexing.py @@ -134,7 +134,7 @@ def apply_movement_op(op:Ops, in_shape:tuple[sint,...], arg:tuple, rngs:tuple[UO case Ops.SHRINK: rngs = tuple(a if off == 0 else a+off for a,(off,_) in zip(rngs, arg)) case Ops.PERMUTE: rngs = tuple(rngs[p] for p in argsort(arg)) case Ops.FLIP: rngs = tuple(((s-1)-a) if f else a for a,s,f in zip(rngs, in_shape, arg)) - case Ops.EXPAND: rngs = tuple(a if in_sh == out_sh else a.const_like(0) for a,in_sh,out_sh in zip(rngs, in_shape, arg)) + case Ops.EXPAND: rngs = rngs[len(arg):] case Ops.PAD: # NOTE: the .where(r-s, i) is not inside the graph_rewrite so that `convert_pad_to_where_to_keep_behavior_local` # wraps the pad with only the newly added valid @@ -248,7 +248,7 @@ def run_rangeify(tsink:UOp, debug:bool=False) -> tuple[UOp, IndexingContext]: # if the EXPAND is used to inject a range, we don't mark it as ending_ranges. otherwise we do. # NOTE: this doesn't actually always end a range, but this is why convs are realized, so for now we need it if x.op is Ops.EXPAND and all(isinstance(y, int) or y.op is not Ops.RANGE for y in x.shape): - ending_ranges[x] += list(UOp.sink(*[ro for ri, ro in zip(rngs, out_rngs) if ri is not ro]).ranges.keys()) + ending_ranges[x] += list(UOp.sink(*out_rngs[:len(x.marg)]).ranges.keys()) # REDUCE creates ranges for the axes it is reducing if x.op is Ops.REDUCE and x.arg[1]: diff --git a/tinygrad/schedule/multi.py b/tinygrad/schedule/multi.py index 79cd0bee41..fbad0f8b2b 100644 --- a/tinygrad/schedule/multi.py +++ b/tinygrad/schedule/multi.py @@ -84,9 +84,8 @@ def reshape_multi(root:UOp, multi:UOp): return multi.src[0].reshape(new_shape).multi(new_axis) def expand_multi(root:UOp, multi:UOp): - if multi.axis is None: new_shape = root.marg - else: new_shape = tuple(multi.src[0].shape[multi.axis] if a == multi.axis else s for a,s in enumerate(root.marg)) - return multi.src[0].expand(new_shape).multi(multi.axis) + new_axis = None if multi.axis is None else multi.axis + len(root.marg) + return multi.src[0]._mop(Ops.EXPAND, arg=root.marg).multi(new_axis) def pad_multi(root:UOp, multi:UOp): assert multi.axis is None or root.marg[multi.axis] == (0, multi.shape[multi.axis]), f"padding not supported for {root.marg=}" diff --git a/tinygrad/uop/ops.py b/tinygrad/uop/ops.py index f697bdb6cb..f5a2e93745 100644 --- a/tinygrad/uop/ops.py +++ b/tinygrad/uop/ops.py @@ -322,9 +322,7 @@ class UOp(RandMixin, metaclass=UOpMetaClass): if prod(ps) != prod(self.marg): raise ValueError(f"bad reshape: {ps} -> {self.marg}") return self.marg case Ops.EXPAND: - if len(ps) != len(self.marg) or not all(s==ns or (s==1 and ns>=0) for s,ns in zip(ps, self.marg)): - raise ValueError(f"bad expand: {ps} -> {self.marg}") - return self.marg + return tuple(self.marg) + ps case Ops.PERMUTE: if sorted(self.marg) != list(range(len(ps))): raise ValueError(f"invalid permutation {self.marg} of len {len(ps)}") return tuple(ps[i] for i in self.marg) @@ -548,7 +546,7 @@ class UOp(RandMixin, metaclass=UOpMetaClass): ret = UOp.vectorize(*stk) else: ret = UOp(Ops.CONST, dtype, arg=dtype.const(b), src=()) - return ret.reshape((1,)*len(shape)).expand(shape) if shape is not None and shape != () and ret.shape != shape else ret + return ret._mop(Ops.EXPAND, arg=shape) if shape is not None and shape != () and ret.shape != shape else ret @staticmethod def range(end:sint, axis_id, axis_type=AxisType.LOOP, *arg, dtype=dtypes.weakint, src=(), **kwargs): return UOp(Ops.RANGE, dtype=dtype, src=(sint_to_uop(end, dtype),)+src, arg=(axis_id, axis_type)+arg, **kwargs) @@ -637,6 +635,7 @@ class UOp(RandMixin, metaclass=UOpMetaClass): if self.shape[new_axis] % len(self.device) != 0: raise RuntimeError(f"reshape {self.src[0].shape} -> {self.shape} moved items between shards") return new_axis if self.op is Ops.PERMUTE: return self.marg.index(src_axis) if src_axis is not None else None + if self.op is Ops.EXPAND: return src_axis + len(self.marg) if src_axis is not None else None return src_axis def _unshard(self, axis:int) -> UOp: @@ -688,7 +687,8 @@ class UOp(RandMixin, metaclass=UOpMetaClass): def _mop(self, op:Ops, arg) -> UOp: # early NOOP - if op in {Ops.SHRINK, Ops.PAD, Ops.EXPAND} and len(arg) == 0: + if op is Ops.EXPAND and len(arg) == 0: return self + if op in {Ops.SHRINK, Ops.PAD} and len(arg) == 0: assert len(self.shape) == 0, "0 len arg only valid on zero length shape" return self match op: