mirror of
https://github.com/tinygrad/tinygrad.git
synced 2026-08-29 11:16:07 +00:00
delete the fake buffer from const (#8355)
* delete the fake buffer from const * fix test_sink_childless_const_alt * it should be CONST(VIEW(DEVICE))
This commit is contained in:
@@ -1987,7 +1987,7 @@ class TestBigGraph(unittest.TestCase):
|
||||
|
||||
def test_sink_childless_const_alt(self):
|
||||
x = UOp.const(dtypes.int, 0)
|
||||
y = UOp(Ops.VIEW, dtypes.int, (UOp(Ops.BUFFER, dtypes.int, (), 0), UOp.const(dtypes.int, 0)), ShapeTracker.from_shape(()))
|
||||
y = UOp(Ops.VIEW, dtypes.int, (UOp(Ops.DEVICE, arg=Device.DEFAULT), UOp.const(dtypes.int, 0)), ShapeTracker.from_shape(()))
|
||||
big_graph = big_graph_rewrite(UOp.sink(x, y), ctx:=ScheduleContext())
|
||||
self.assertIs(big_graph, UOp(Ops.NOOP))
|
||||
self.assertEqual(len(ctx.realizes), 0)
|
||||
@@ -2001,8 +2001,8 @@ class TestBigGraph(unittest.TestCase):
|
||||
self.assertEqual(len(ctx.realizes), 1)
|
||||
|
||||
tensor_const_pm = PatternMatcher([
|
||||
(UPat(Ops.VIEW, src=(UPat(Ops.BUFFER), UPat(Ops.CONST, src=()))), lambda: True),
|
||||
(UPat(Ops.VIEW, src=(UPat(Ops.BUFFER), UPat(Ops.BIND, src=(UPat(Ops.DEFINE_VAR), UPat(Ops.CONST))))), lambda: True),
|
||||
(UPat(Ops.VIEW, src=(UPat(Ops.DEVICE), UPat(Ops.CONST, src=()))), lambda: True),
|
||||
(UPat(Ops.VIEW, src=(UPat(Ops.DEVICE), UPat(Ops.BIND, src=(UPat(Ops.DEFINE_VAR), UPat(Ops.CONST))))), lambda: True),
|
||||
])
|
||||
class TestConst(unittest.TestCase):
|
||||
# ** part 1: basic functionality of a tensor directly created from CONST
|
||||
|
||||
@@ -3,7 +3,7 @@ from tinygrad import Tensor
|
||||
from tinygrad.ops import UPat, Ops
|
||||
|
||||
realized_pattern = UPat(Ops.VIEW, src=(UPat(Ops.BUFFER),))
|
||||
const_pattern = UPat(Ops.VIEW, src=(UPat(Ops.BUFFER), UPat(Ops.CONST)))
|
||||
const_pattern = UPat(Ops.VIEW, src=(UPat(Ops.DEVICE), UPat(Ops.CONST)))
|
||||
def is_pattern(ten:Tensor, pat:UPat): assert pat.match(ten.lazydata, {})
|
||||
|
||||
class TestTensorUopRepresentation(unittest.TestCase):
|
||||
@@ -51,7 +51,6 @@ class TestTensorUopRepresentation(unittest.TestCase):
|
||||
# UOp(Ops.VIEW, dtypes.float, arg=ShapeTracker(views=(View(shape=(), strides=(), offset=0, mask=None, contiguous=True),)), src=(
|
||||
# UOp(Ops.CONST, dtypes.float, arg=1.0, src=(
|
||||
# UOp(Ops.DEVICE, dtypes.void, arg="METAL", src=()),)),)),))
|
||||
@unittest.expectedFailure
|
||||
def test_consts_dont_have_buffers(self):
|
||||
a = Tensor.ones(10, 10)
|
||||
print(a.lazydata)
|
||||
|
||||
+20
-16
@@ -73,10 +73,9 @@ tensor_uop_spec = PatternMatcher([
|
||||
(UPat(Ops.VIEW, name="view", src=(UPat(Ops.BUFFER, name="buf"), UPat(GroupOp.Meta, name="uop"))),
|
||||
lambda view,buf,uop: view.dtype == buf.dtype == uop.dtype and view.size == buf.size),
|
||||
|
||||
# Tensor const has a ShapeTracker of shape=() and fake buffer of size 1
|
||||
(UPat(Ops.VIEW, name="view", arg=ShapeTracker.from_shape(()), src=(UPat(Ops.BUFFER, name="fake", arg=(-1, 1)),
|
||||
UPat({Ops.CONST, Ops.BIND}, name="const_uop"))),
|
||||
lambda view,fake,const_uop: view.dtype == fake.dtype == const_uop.dtype),
|
||||
# Tensor const has a ShapeTracker of shape=() and a device
|
||||
(UPat(Ops.VIEW, name="view", arg=ShapeTracker.from_shape(()), src=(UPat(Ops.DEVICE), UPat({Ops.CONST, Ops.BIND}, name="const_uop"))),
|
||||
lambda view,const_uop: view.dtype == const_uop.dtype),
|
||||
|
||||
# NOTE: EMPTY just ensures the source BUFFER is allocated before children run
|
||||
# TODO: this should be EMPTY(VIEW(BUFFER))
|
||||
@@ -126,11 +125,16 @@ class ScheduleContext:
|
||||
contiguous: dict[UOp, UOp] = field(default_factory=dict) # this maps roots to places they are made contiguous
|
||||
children: defaultdict[UOp, dict[UOp, None]] = field(default_factory=lambda: defaultdict(dict))
|
||||
|
||||
# TODO: delete this once CONST has a VIEW source
|
||||
# currently tensor uop is VIEW(DEVICE, CONST)
|
||||
def is_constant(u:UOp): return u.op is Ops.VIEW and len(u.src) == 2 and u.src[1].op in {Ops.CONST, Ops.BIND}
|
||||
|
||||
def to_uop(buf:UOp, ctx:ScheduleContext, cache:dict[UOp, UOp]) -> UOp:
|
||||
if (r:=cache.get(buf)) is not None: return r
|
||||
# shapeless op is passthrough
|
||||
# realized is passthrough
|
||||
if buf.st is None or buf.base.is_realized: return buf
|
||||
# constants are passthrough
|
||||
if buf.st is None or buf.base.is_realized or is_constant(buf.base): return buf
|
||||
# view is passthrough
|
||||
if buf is not buf.base:
|
||||
cache[buf] = ret = to_uop(buf.base, ctx, cache).view(buf.st)
|
||||
@@ -228,7 +232,6 @@ def _append_st_vars(ctx:ScheduleItemContext, x:UOp) -> UOp|None:
|
||||
return st.to_uop() if st != x.st else None
|
||||
|
||||
def _append_buf(ctx:ScheduleItemContext, x:UOp) -> UOp:
|
||||
assert x.arg[0] != -1, "fake -1 BUFFERS should not make it here"
|
||||
ctx.bufs.append(x)
|
||||
return UOp(Ops.DEFINE_GLOBAL, x.dtype.ptr(size=x.arg[1]), (), len(ctx.bufs)-1)
|
||||
append_bufs = PatternMatcher([(UPat(Ops.BUFFER, name="x"), _append_buf)])
|
||||
@@ -290,7 +293,7 @@ if getenv("RUN_PROCESS_REPLAY"):
|
||||
|
||||
# **** Schedule grouping
|
||||
|
||||
def is_scheduled(u:UOp) -> bool: return u.op is Ops.VIEW and len(u.src) == 2
|
||||
def is_scheduled(u:UOp) -> bool: return u.op is Ops.VIEW and len(u.src) == 2 and u.src[0].op is Ops.BUFFER
|
||||
def uval(u:UOp) -> UOp:
|
||||
assert is_scheduled(u), f"must be a scheduled op {u}"
|
||||
return r.src[0] if (r:=u.src[1]).op is Ops.CONTIGUOUS and not (r.src[0].base.op is Ops.VIEW and len(r.src[0].base.src) == 2) else r
|
||||
@@ -372,7 +375,7 @@ def group_realizes(ctx:ScheduleContext) -> list[list[UOp]]:
|
||||
group = {tr: None}
|
||||
ctx.realizes[tr] = tr
|
||||
reduce_for_op.update((tr, r) for tr in group)
|
||||
if FUSE_ARANGE and r_uop.arg[0] is Ops.ADD and uval(r_uop.src[0].base).op is Ops.CONST: reduce_of_const.append(r)
|
||||
if FUSE_ARANGE and r_uop.arg[0] is Ops.ADD and r_uop.src[0].base.is_unrealized_const(): reduce_of_const.append(r)
|
||||
# fuse double reduces with no other child
|
||||
for reduceop in double_reduces:
|
||||
top_reduce = uval(ctx.allbufs[reduceop]).src[0].base.buf_uop
|
||||
@@ -445,6 +448,8 @@ def replace_contiguous(ctx:ScheduleContext, alu:UOp):
|
||||
ops_folding = PatternMatcher([
|
||||
# op with size 0 is zero
|
||||
(UPatScheduled(), lambda b,to_store,base: _as_const(base, 0) if base.size == 0 else None),
|
||||
# if the uop folded to a CONST we can delete the BUFFER
|
||||
(UPatScheduled(Ops.CONST, name="const"), lambda b,base,const: base.replace(src=(UOp(Ops.DEVICE, arg=base.device), const))),
|
||||
# DETACH is a NOOP here
|
||||
(UPat(Ops.DETACH, name="detach"), lambda detach: detach.src[0]),
|
||||
# elementwise const folding
|
||||
@@ -499,8 +504,7 @@ merge_bufs = PatternMatcher([
|
||||
|
||||
# ** this decides which ops get realized
|
||||
|
||||
def realize(ctx:ScheduleContext, b:UOp, to_store:UOp, **kwargs) -> None:
|
||||
if to_store.op not in {Ops.CONST, Ops.BIND}: ctx.realizes.update([(b, to_store)])
|
||||
def realize(ctx:ScheduleContext, b:UOp, to_store:UOp, **kwargs) -> None: ctx.realizes[b] = to_store
|
||||
|
||||
def realize_view(ctx:ScheduleContext, view:UOp, src:UOp, b:UOp, **kwargs) -> None:
|
||||
if src.st is None: return None
|
||||
@@ -519,7 +523,7 @@ def fold_img_cast(ctx:ScheduleContext, xb:UOp, view:UOp, b:UOp, to_cast:UOp, **k
|
||||
return to_cast.view(unwrap(view.st))
|
||||
|
||||
def init_big_graph(sink:UOp) -> UOp|None:
|
||||
new_src = tuple(x.base for x in sink.src if is_scheduled(x.base) and x.base.src[1].op is not Ops.CONST)
|
||||
new_src = tuple(x.base for x in sink.src if is_scheduled(x.base))
|
||||
return None if new_src == sink.src else UOp(Ops.NOOP) if len(new_src) == 0 else UOp.sink(*new_src)
|
||||
|
||||
do_realize = PatternMatcher([
|
||||
@@ -539,9 +543,9 @@ do_realize = PatternMatcher([
|
||||
|
||||
# ** this breaks down realized ops into STOREs and rewrites the ops to LOADs
|
||||
|
||||
def generate_valid(ctx:ScheduleContext, b:UOp, to_store:UOp, base:UOp) -> UOp:
|
||||
if to_store.op is Ops.BIND: ctx.var_vals.update([to_store.unbind()])
|
||||
return UOp.const_with_shape(base.dtype, to_store if to_store.op is Ops.BIND else to_store.arg, unwrap(base.st).shape)
|
||||
def generate_valid(ctx:ScheduleContext, const:UOp, st:UOp) -> UOp:
|
||||
if const.op is Ops.BIND: ctx.var_vals.update([const.unbind()])
|
||||
return UOp.const_with_shape(const.dtype.base, const if const.op is Ops.BIND else const.arg, unwrap(st.st).shape)
|
||||
|
||||
def append_realize(ctx:ScheduleContext, b:UOp, to_store:UOp, base:UOp) -> UOp:
|
||||
ctx.realizes[b] = UOp.store(b, ShapeTracker.from_shape(base.shape).to_uop(), append_op(ctx, b, to_store))
|
||||
@@ -554,7 +558,7 @@ def append_op(ctx:ScheduleContext, b:UOp, to_store:UOp) -> UOp:
|
||||
|
||||
break_sched = PatternMatcher([
|
||||
# consts are always fused and generated
|
||||
(UPatScheduled({Ops.CONST, Ops.BIND}), generate_valid),
|
||||
(UPat(Ops.VIEW, name="st", src=(UPat(Ops.DEVICE), UPat({Ops.CONST, Ops.BIND}, name="const"))), generate_valid),
|
||||
# view of realized buffer just loads
|
||||
(UPat(Ops.BUFFER, name="b").view(name="v"), lambda ctx,b,v: UOp(Ops.PRELOAD if b in ctx.assigns else Ops.LOAD, b.dtype.base, (b, v.st.to_uop()))),
|
||||
# all other views either fold or realize with a store
|
||||
@@ -579,7 +583,7 @@ remove_movement_ops = PatternMatcher([(UPat(GroupOp.Movement, name="x"), lambda
|
||||
@track_rewrites(named=True)
|
||||
def create_schedule_with_vars(outs:list[UOp], skip_check:bool=not __debug__) -> tuple[list[ScheduleItem], dict[Variable, int]]:
|
||||
if not skip_check: type_verify(list(UOp.sink(*outs).toposort), extra_spec=tensor_uop_spec)
|
||||
if len(outs:=dedup(x.base for x in outs if x.base.realized is None and x.base.op is not Ops.CONST)) == 0: return [], {}
|
||||
if len(outs:=dedup(x.base for x in outs if x.base.realized is None and not x.base.is_unrealized_const())) == 0: return [], {}
|
||||
# create the big graph
|
||||
ctx = ScheduleContext()
|
||||
cache: dict[UOp, UOp] = {}
|
||||
|
||||
+1
-3
@@ -437,11 +437,9 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
|
||||
def metaop(op:Ops, shape:tuple[sint, ...], dtype:DType, device:str, arg=None, src:tuple[UOp, ...]=()) -> UOp:
|
||||
from tinygrad.shape.shapetracker import ShapeTracker
|
||||
if op is Ops.CONST:
|
||||
# NOTE: we embed device on CONST with a fake BUFFER uop
|
||||
fake = UOp(Ops.BUFFER, dtype, (UOp(Ops.DEVICE, arg=device),), (-1, 1))
|
||||
# NOTE: BIND stays BIND, UOp.const unbinds here
|
||||
const_uop = arg if isinstance(arg, UOp) else UOp.const(dtype, unwrap(arg))
|
||||
return UOp(Ops.VIEW, dtype, (fake, const_uop), ShapeTracker.from_shape(())).reshape((1,)*len(shape)).expand(shape)
|
||||
return UOp(Ops.VIEW, dtype, (UOp(Ops.DEVICE, arg=device), const_uop), ShapeTracker.from_shape(())).reshape((1,)*len(shape)).expand(shape)
|
||||
# otherwise it's a contiguous st
|
||||
return UOp(Ops.VIEW, dtype, (UOp.new_buffer(device, (st:=ShapeTracker.from_shape(shape)).size, dtype), UOp(op, dtype, src, arg)), st)
|
||||
def copy_to_device(self, device:str, force=False, clone:bool=False) -> UOp:
|
||||
|
||||
@@ -64,8 +64,7 @@ def uop_to_json(x:UOp) -> dict[int, tuple[str, str, list[int], str, str]]:
|
||||
graph: dict[int, tuple[str, str, list[int], str, str]] = {}
|
||||
excluded = set()
|
||||
for u in x.toposort:
|
||||
# NOTE: we are hiding the BUFFERs on consts. they should at least be devices
|
||||
if u.op in {Ops.CONST, Ops.DEVICE} or (u.op is Ops.BUFFER and u.arg[0] == -1):
|
||||
if u.op in {Ops.CONST, Ops.DEVICE}:
|
||||
excluded.add(u)
|
||||
continue
|
||||
argst = ("\n".join([f"{v.shape} / {v.strides}"+(f" / {v.offset}" if v.offset else "") for v in u.arg.views])) if u.op is Ops.VIEW else str(u.arg)
|
||||
|
||||
Reference in New Issue
Block a user