forked from tinygrad/tinygrad
fix n^2 in limit_bufs by memoizing reachable loads [PR] (#17017)
* fix n^2 in limit_bufs by memoizing reachable loads [pr] * Update test_schedule.py --------- Co-authored-by: Jacob Kitchen <[email protected]>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
# schedule confirms the right things are capable of fusing
|
||||
# NOTE: this has overlap with external_test_opt.py
|
||||
|
||||
import unittest
|
||||
import unittest, time
|
||||
import numpy as np
|
||||
|
||||
from tinygrad import nn, dtypes, Device, Tensor, Variable
|
||||
@@ -197,6 +197,20 @@ class TestLimitBufs(unittest.TestCase):
|
||||
base = (idx >= i).where(a + b, base)
|
||||
assert all(x > 0 for x in base.tolist())
|
||||
|
||||
def test_limit_bufs_linear_scaling(self):
|
||||
def sched_time(n):
|
||||
with Context(TRACK_MATCH_STATS=0, DEBUG=0):
|
||||
bufs = [Tensor.ones(16).contiguous().realize() for _ in range(4)]
|
||||
root = bufs[0]
|
||||
for i in range(n): root = root + bufs[i % 4]
|
||||
with Context(MAX_KERNEL_BUFFERS=8, SCACHE=0):
|
||||
st = time.perf_counter()
|
||||
root.schedule_linear()
|
||||
return time.perf_counter() - st
|
||||
sched_time(400)
|
||||
t1, t2 = min(sched_time(400) for _ in range(3)), min(sched_time(1600) for _ in range(3))
|
||||
self.assertLess(t2/t1, 8, f"{t1*1e3:.1f}ms -> {t2*1e3:.1f}ms")
|
||||
|
||||
class TestSwizzle(unittest.TestCase):
|
||||
def test_swizzle_simple(self):
|
||||
Tensor.manual_seed(0)
|
||||
|
||||
@@ -45,6 +45,8 @@ class BufferizeOpts:
|
||||
class IndexingContext:
|
||||
realize_map: dict[UOp, None|list[int]] = field(default_factory=dict)
|
||||
range_map: dict[UOp, tuple[tuple[UOp, ...], tuple[UOp, ...]]] = field(default_factory=dict)
|
||||
# loads reachable from each UOp memoized across matches
|
||||
buf_cache: dict[UOp, frozenset[UOp]] = field(default_factory=dict)
|
||||
|
||||
# create ranges
|
||||
range_idx: Iterator[int] = field(default_factory=itertools.count)
|
||||
|
||||
@@ -329,12 +329,11 @@ def limit_bufs(ctx:IndexingContext, root:UOp):
|
||||
device = device if isinstance(device, str) else device[0].split(":")[0]
|
||||
if not (MAX_BUFS:=MAX_KERNEL_BUFFERS.value or DEVICE_MAX_BUFS.get(device, 0)): return None
|
||||
|
||||
bufs: set[UOp] = set()
|
||||
def gate_input(u:UOp):
|
||||
# TODO: add cache to fix n^2
|
||||
if is_load:=(u.op in {Ops.STAGE, Ops.AFTER, Ops.PARAM, Ops.MSELECT, Ops.MSTACK}): bufs.add(u)
|
||||
return not is_load
|
||||
root.toposort(gate=gate_input)
|
||||
def visitor(u:UOp) -> frozenset[UOp]:
|
||||
if u.op in {Ops.STAGE, Ops.AFTER, Ops.PARAM, Ops.MSELECT, Ops.MSTACK}: return frozenset((u,))
|
||||
if len(u.src) == 1: return ctx.buf_cache[u.src[0]]
|
||||
return frozenset().union(*[ctx.buf_cache[s] for s in u.src])
|
||||
bufs = root.topovisit(visitor, ctx.buf_cache)
|
||||
|
||||
if len(bufs) > MAX_BUFS - 1: # NOTE: this -1 is for the output buffer
|
||||
srcs = []
|
||||
|
||||
Reference in New Issue
Block a user