From be25207a7acaa027d3c7ed88b1f5d261e7fbfa78 Mon Sep 17 00:00:00 2001 From: Christopher Milan Date: Wed, 5 Aug 2026 17:59:09 -0700 Subject: [PATCH] scope variable names inside CALLs (#17424) --- test/unit/test_call.py | 12 ++++++++++++ tinygrad/schedule/__init__.py | 8 ++++++-- tinygrad/uop/ops.py | 2 +- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/test/unit/test_call.py b/test/unit/test_call.py index 02a1be038e..3abe561c8c 100644 --- a/test/unit/test_call.py +++ b/test/unit/test_call.py @@ -212,6 +212,18 @@ class TestCallSchedule(unittest.TestCase): out = f(a, v.bind(5)) np.testing.assert_allclose(out.numpy(), [5., 10., 15.]) + def test_precompile_scoped_bind_arg(self): + @function(precompile=True) + def f(x:Tensor, scale:UOp) -> Tensor: return x * scale + a = Tensor.ones(3) + x = f(a, UOp.variable("scale_a", 1, 100).bind(2)) + y = f(a, UOp.variable("scale_b", 1, 100).bind(3)) + fx = next(u for u in x.uop.toposort() if u.op is Ops.FUNCTION) + fy = next(u for u in y.uop.toposort() if u.op is Ops.FUNCTION) + self.assertEqual(fx.src[0].key, fy.src[0].key) + np.testing.assert_equal(x.numpy(), [2, 2, 2]) + np.testing.assert_equal(y.numpy(), [3, 3, 3]) + def test_precompile_schedule_cache_hit(self): """two instances of the same @function should produce identical function body keys (schedule cache hit)""" @function(precompile=True) diff --git a/tinygrad/schedule/__init__.py b/tinygrad/schedule/__init__.py index 4b4d84baca..3941c281fd 100644 --- a/tinygrad/schedule/__init__.py +++ b/tinygrad/schedule/__init__.py @@ -98,10 +98,14 @@ pm_post_sched_cache = PatternMatcher([ create_new_buffer(ctx, b) if isinstance(b.arg, ParamArg) and b.addrspace is AddrSpace.GLOBAL else None), ]) +def resolve_linear_call(linear_call:UOp): + linear = graph_rewrite(linear_call.src[0], pm_post_sched_cache, ctx=({}, linear_call.src[1:]), walk=True, name="params to buffers") + binds = {f"p{i}":x.src[0] for i,x in enumerate(linear_call.src[1:]) if x.op is Ops.BIND} + return linear.substitute({v:binds[v.expr] for v in linear.variables() if v.expr in binds}, enter_calls=True, name="resolve scalar params") + pm_resolve_linear_call = PatternMatcher([ # call LINEAR is resolved here - (UPat(Ops.CALL, src=(UPat(Ops.LINEAR),), name="linear_call", allow_any_len=True), lambda linear_call: - graph_rewrite(linear_call.src[0], pm_post_sched_cache, ctx=({}, linear_call.src[1:]), walk=True, name="params to buffers")), + (UPat(Ops.CALL, src=(UPat(Ops.LINEAR),), name="linear_call", allow_any_len=True), resolve_linear_call), ])+pm_flatten_linear schedule_cache: dict[bytes, UOp] = {} diff --git a/tinygrad/uop/ops.py b/tinygrad/uop/ops.py index 2fc9690b64..e68b879e7c 100644 --- a/tinygrad/uop/ops.py +++ b/tinygrad/uop/ops.py @@ -1166,8 +1166,8 @@ class UOp(RandMixin, metaclass=UOpMetaClass): src: tuple[UOp, ...] = (UOp(Ops.NOOP) if shape is None else shape_to_shape_arg(shape),) return UOp(Ops.PARAM, src=src, arg=ParamArg(slot, dtype, vmin_vmax, multiple_of, name, addrspace, axis, device, volatile)) def param_like(self, slot:int): + if self.op is Ops.BIND: return self.src[0].replace(arg=replace(self.src[0].arg, slot=slot, name=f"p{slot}")) addrspace = self.addrspace if self.addrspace is not None else AddrSpace.GLOBAL - if self.op is Ops.BIND: return self.src[0].replace(arg=replace(self.src[0].arg, slot=slot, addrspace=addrspace)) return UOp.param(slot, self.dtype, self.shard_shape if self.axis is not None else self._shape, self.device, addrspace=addrspace, axis=self.axis) @staticmethod