Compare commits

...
Author SHA1 Message Date
geohot a8dca47fbc fix 2025-10-03 10:52:18 +08:00
geohot 769db23df6 that 2025-10-03 10:45:47 +08:00
geohot 1cd40941c8 delete junk 2025-10-03 10:35:12 +08:00
George HotzandGitHub 9a607e69e1 Merge branch 'master' into support_opts_in_contig 2025-10-03 10:32:08 +08:00
George HotzandGitHub 5b05bf4ab4 Merge branch 'master' into support_opts_in_contig 2025-10-03 10:03:14 +08:00
geohot e59e0aadc1 opts 2025-10-02 18:25:38 +08:00
geohot 3688afa513 fix swap 2025-10-02 18:17:03 +08:00
geohot dae164ffb1 warp 2025-10-02 18:04:45 +08:00
geohot 3fb3dd4c06 flash attention with two gemms 2025-10-02 17:48:48 +08:00
geohot e5028d58e9 flash attention sort of works 2025-10-02 17:29:23 +08:00
geohot 5a602e6c36 double wmma works 2025-10-02 16:06:46 +08:00
geohot 6640514555 demote works on both matmuls 2025-10-02 15:42:22 +08:00
geohot 2fbd7d21f9 tc passes 2025-10-02 14:27:52 +08:00
geohot f32a497f08 bug 2025-10-02 13:10:07 +08:00
geohot 3fd25a425b fix gfr 2025-10-02 13:06:31 +08:00
George HotzandGitHub 3da569c20b Merge branch 'master' into support_opts_in_contig 2025-10-02 12:57:43 +08:00
George HotzandGitHub 9d5d4b248c Merge branch 'master' into support_opts_in_contig 2025-10-02 12:39:50 +08:00
geohot c449e8eb17 don't change that 2025-10-01 17:47:43 +08:00
geohot 3dc1b2e98e broken 2025-10-01 17:27:17 +08:00
George HotzandGitHub 8e6126160f Merge branch 'master' into support_opts_in_contig 2025-10-01 17:20:51 +08:00
geohot 115810d13e dual tc 2025-10-01 16:51:04 +08:00
geohot 215c3e19d9 always print that 2025-10-01 16:47:59 +08:00
geohot eedf570e7b late locals 2025-10-01 16:36:49 +08:00
geohot 09ec0f7464 tag TC 2025-10-01 16:18:14 +08:00
geohot 481ffa9aed support opts in rangeify contig 2025-10-01 15:39:23 +08:00
6 changed files with 101 additions and 24 deletions
+50 -7
View File
@@ -1,7 +1,8 @@
import unittest
from tinygrad import Tensor, nn
from tinygrad.helpers import RANGEIFY, Context, GlobalCounters
from tinygrad.uop.ops import UOp
from tinygrad.helpers import RANGEIFY, Context, GlobalCounters, getenv
from tinygrad.uop.ops import UOp, Ops
from tinygrad.codegen.opt import OptOps, Opt
@unittest.skipIf(RANGEIFY<1, "tests only for RANGEIFY")
class TestRangeifyAssign(unittest.TestCase):
@@ -18,7 +19,7 @@ class TestRangeifyAssign(unittest.TestCase):
self.assertListEqual(lst, lst3)
self.assertListEqual(lst2, B.permute(1, 0).tolist())
N = 256
N = getenv("N", 256)
class TestRangeifyOpt(unittest.TestCase):
def test_randperm(self):
@@ -93,6 +94,29 @@ class TestRangeify(unittest.TestCase):
C = Tensor.empty(N, N)
(A@B@C).realize()
#@unittest.skip("gemm tc")
def test_double_gemm_tc(self):
with Context(DEBUG=0):
A, B, C = [Tensor.randn(N, N) for _ in range(3)]
Tensor.realize(A, B, C)
args = ()
args += (Opt(OptOps.DEMOTE, 2, 8),)
# NOTE: these axes are poorly sorted
args += (Opt(OptOps.TC, 0, (0,0,1,1)),)
args += (Opt(OptOps.TC, 0, (0,0,1,0)),)
args += (Opt(OptOps.UPCAST, 0, 2),)
args += (Opt(OptOps.UPCAST, 1, 2),)
args += (Opt(OptOps.UNROLL, 0, 2),)
args += (Opt(OptOps.UNROLL, 1, 2),)
tst = (A@B@C).contiguous(arg=args).realize()
assert tst.uop.base.op is Ops.BUFFER, "buffer"
with Context(RANGEIFY=0, DEBUG=2):
GlobalCounters.reset()
mse = ((A@B@C)-tst).square().mean().item()
print(mse)
def test_double_gemm_exp(self):
A = Tensor.empty(N, N)
B = Tensor.empty(N, N)
@@ -202,7 +226,10 @@ class TestRangeify(unittest.TestCase):
out.realize()
def test_flash_attention(self):
BS, HEADS, SEQLEN, EMB = 4, 2, 16, 8
#BS, HEADS, SEQLEN, EMB = 4, 2, 16, 8
# lil bigger
BS, HEADS, SEQLEN, EMB = 4, 32, 128, 64
# bigger
#BS, HEADS, SEQLEN, EMB = 4, 32, 1024, 64
@@ -213,15 +240,31 @@ class TestRangeify(unittest.TestCase):
def fa():
Tensor.manual_seed(1337)
with Context(DEBUG=0): q,k,v = [Tensor.rand(BS, HEADS, SEQLEN, EMB).contiguous().realize() for _ in range(3)]
return q.scaled_dot_product_attention(k, v).realize()
return q.scaled_dot_product_attention(k, v)
with Context(DEBUG=4):
GlobalCounters.reset()
ret = fa()
args = ()
args += (Opt(OptOps.DEMOTE, 5, 8),)
args += (Opt(OptOps.TC, 0, (0,0,1,3)),)
args += (Opt(OptOps.TC, 0, (0,0,1,0)),)
args += (Opt(OptOps.SWAP, 1, 4),)
args += (Opt(OptOps.SWAP, 2, 5),)
args += (Opt(OptOps.WARP, 4, 32),)
args += (Opt(OptOps.WARP, 5, 32),)
args += (Opt(OptOps.UNROLL, 1, 8),)
args += (Opt(OptOps.UNROLL, 2, 8),)
args += (Opt(OptOps.UPCAST, 0, 4),)
args += (Opt(OptOps.UPCAST, 1, 4),)
args += (Opt(OptOps.UPCAST, 2, 4),)
args += (Opt(OptOps.UPCAST, 3, 4),)
args += (Opt(OptOps.UNROLL, 0, 4),)
args += (Opt(OptOps.UNROLL, 3, 4),)
ret = fa().contiguous(arg=args).realize()
with Context(RANGEIFY=0):
with Context(DEBUG=2):
GlobalCounters.reset()
cmp = fa()
cmp = fa().realize()
with Context(DEBUG=0):
mse = ((cmp-ret)**2).sum().item()
print(f"mse: {mse}")
+4 -4
View File
@@ -75,14 +75,14 @@ def _get_rewrites_for_renderer(opts:Renderer, optimize:bool, linearizer:bool, _Q
ret.append(RewriteStep(pm_postrange_opt, ctx=lambda _: opts, name="post optimize ast"))
# ** expander (expand_rewrite) **
ret.append(RewriteStep(sym+migrate_indexing, name="postopt symbolic"))
ret.append(RewriteStep(sym+migrate_indexing+pm_group_for_reduce, name="postopt symbolic"))
# add locals
ret.append(RewriteStep(pm_add_buffers+rangeify_codegen+pm_flatten_range, name="add local buffers"))
# expand
ret.append(RewriteStep(sym+pm_pre_expander+pm_group_for_reduce+expander, name="expander"))
# add locals
ret.append(RewriteStep(pm_add_buffers+rangeify_codegen, name="add local buffers"))
# ** devectorizer (full_graph_rewrite) **
# remove reduce
ret.append(RewriteStep(pm_reduce+gep_pushing, lambda _: ReduceContext(), name="remove_reduce"))
+1
View File
@@ -7,6 +7,7 @@ from tinygrad.uop.ops import AxisType
class OptOps(Enum):
TC = auto(); UPCAST = auto(); UNROLL = auto(); LOCAL = auto(); THREAD = auto() # noqa: E702
GROUP = auto(); GROUPTOP = auto(); NOLOCALS = auto(); PADTO = auto(); SWAP = auto() # noqa: E702
DEMOTE = auto(); WARP = auto() # noqa: E702
def __lt__(self, x:OptOps): return self.value < x.value
@dataclass(frozen=True, order=True)
+32 -8
View File
@@ -68,7 +68,7 @@ class Scheduler:
# filter any not in local stores
local_store_rngs = [x.ranges for x in self.ast.toposort() if (x.op is Ops.STORE and x.src[0].ptrdtype.addrspace == AddrSpace.LOCAL) \
or (x.op is Ops.BUFFERIZE and x.arg == AddrSpace.LOCAL)]
or (x.op is Ops.BUFFERIZE and x.arg.addrspace == 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
@@ -172,14 +172,33 @@ class Scheduler:
check(not self.dont_use_locals, "can't use locals")
check(rng.arg[-1] == AxisType.REDUCE, "group is for reduce")
ret = self.shift_to(rng, amt, opt_to_at[opt.op], top=opt.op in {OptOps.GROUPTOP, OptOps.THREAD})
elif opt.op is OptOps.WARP:
warp = UOp.range(cast(int, opt.arg), -1, AxisType.WARP)
ret = self.shift_to(rng, cast(int, opt.arg), AxisType.WARP, input_new_rng=warp)
elif opt.op is OptOps.DEMOTE:
_, rr = self.shift_to(rng, cast(int, opt.arg), AxisType.LOOP)
def do_demote(ctx, x:UOp):
if x.tag is not None: return None
mr = ctx[0]
nr = mr.replace(arg=ctx[0].arg[0:-2]+(mr.arg[-2]+1, mr.arg[-1]))
ctx[0] = nr
buf = x.replace(src=(x.src[0], mr)+x.src[1:], tag=1).substitute({mr:nr})
return UOp(Ops.APPENDINDEX, dtypes.void, (buf,mr))
# do the demotion
pm_demote = PatternMatcher([
(UPat(Ops.BUFFERIZE, name="x"), do_demote),
(UPat(Ops.INDEX, src=(UPat(Ops.APPENDINDEX, name="x"),), name="y", allow_any_len=True),
lambda x,y: y.replace(src=(x.src[0],)+x.src[1:]+y.src[1:])),
])
self.ast = graph_rewrite(self.ast, pm_demote, ctx=[rr], bottom_up=True, name="demote")
elif opt.op is OptOps.TC:
check(len(self.applied_opts) == 0, "tensor core opts must be first") # TODO: remove the need for this by having warps
#check(len(self.applied_opts) == 0, "tensor core opts must be first") # TODO: remove the need for this by having warps
check(opt.axis is not None, "tensor core opts must have an axis")
check(opt.arg is not None and isinstance(opt.arg, tuple) and len(opt.arg) == 3, "tensor core opts must have valid arg")
check(opt.arg is not None and isinstance(opt.arg, tuple) and len(opt.arg) >= 3, "tensor core opts must have valid arg")
check(-1 <= (tc_select:=cast(tuple, opt.arg)[0]) < len(self.opts.tensor_cores), "tensor core opts must have valid tc_select")
check(0 <= (tc_opt:=cast(tuple, opt.arg)[1]) <= 2, "tensor core opts must have valid tc_opt")
check(0 < (use_tensor_cores:=cast(tuple, opt.arg)[2]) <= 2, "use_tensor_cores value is not valid")
try: ret = self._apply_tc_opt(use_tensor_cores, cast(int, opt.axis), tc_select, tc_opt)
try: ret = self._apply_tc_opt(use_tensor_cores, cast(int, opt.axis), tc_select, tc_opt, opt.arg[3] if len(opt.arg) > 3 else 0)
except ValueError as e: raise KernelOptError(str(e))
check(ret is not None, "no tensor core available")
elif opt.op is OptOps.PADTO:
@@ -203,7 +222,7 @@ class Scheduler:
altrng = self.rngs[opt.arg]
except IndexError:
raise KernelOptError
check(rng.arg[-1] == AxisType.GLOBAL and altrng.arg[-1] == AxisType.GLOBAL, "swap only for globals")
check(rng.arg[-1] == AxisType.LOOP and altrng.arg[-1] == AxisType.LOOP, "swap only for globals")
self.ast = self.ast.substitute({rng:rng.replace(arg=(*altrng.arg[0:-1], rng.arg[-1]), tag=1),
altrng:altrng.replace(arg=(*rng.arg[0:-1], altrng.arg[-1]), tag=1)})
self.ast = graph_rewrite(self.ast, remove_tags)
@@ -213,10 +232,10 @@ class Scheduler:
if append_opt: self.applied_opts.append(opt)
return ret
def _apply_tc_opt(self, use_tensor_cores:int, axis:int, tc_select:int, opt_level:int) -> None|list[UOp]:
def _apply_tc_opt(self, use_tensor_cores:int, axis:int, tc_select:int, opt_level:int, reduce_choice:int) -> None|list[UOp]:
reduceops = [x for x in self.ast.toposort() if x.op is Ops.REDUCE]
if not len(reduceops): raise KernelOptError("no reduce ops for TensorCore")
reduceop = reduceops[0]
reduceop = reduceops[reduce_choice]
if use_tensor_cores and reduceop is not None and reduceop.arg is Ops.ADD:
mul = reduceop.src[0] if reduceop.src[0].op is not Ops.CAST else reduceop.src[0].src[0]
if mul.op is not Ops.MUL: return None
@@ -261,7 +280,7 @@ class Scheduler:
ne: list[UOp] = []
for opt in tc.opts:
if opt[0] == "l":
axes[int(opt[1])], new_range = self.shift_to(axes[int(opt[1])], 2, AxisType.LOCAL, input_new_rng=warp%2)
axes[int(opt[1])], new_range = self.shift_to(axes[int(opt[1])], 2, AxisType.WARP, input_new_rng=warp%2)
warp //= 2
elif opt[0] == "u":
axes[int(opt[1])], new_range = self.shift_to(axes[int(opt[1])], 2, AxisType.UPCAST)
@@ -288,6 +307,11 @@ class Scheduler:
# axes to range number (was done in lowerer)
tc_upcast_axes = tuple([tuple([(self.rngs[a].arg[0], sz) for a,sz in v]) for v in tc_upcast_axes])
tc_reduce_axes = tuple([self.rngs[a].arg[0] for a in tc_reduce_axes])
#print("ne", [x.arg for x in ne])
#print(tc_upcast_axes)
#print(tc_reduce_axes)
tc_upcast_axes = (((ne[0].arg[0], 2),), ((ne[0].arg[0], 2),), ((ne[0].arg[0], 2),))
#print("hack", tc_upcast_axes)
# construct the op
# TODO: remove tc_upcast_axes from the arg
+13 -4
View File
@@ -519,7 +519,7 @@ pm_limit_bufs = PatternMatcher([(UPat(set.union(GroupOp.Binary, GroupOp.Ternary)
# BUFFERIZE returns the BUFFER ready for INDEXing (doing this will make splitting a lot easier)
# NOTE: this has been fixed up a bit
def bufferize_to_store(x:UOp):
def bufferize_to_store(x:UOp, allow_locals=True):
rngs = x.src[1:]
shape = tuple([int(r.vmax+1) for r in rngs])
size = prod(shape)
@@ -552,6 +552,7 @@ def bufferize_to_store(x:UOp):
return ret.replace(tag=x.tag)
# handle locals
if not allow_locals: return None
tag = x.arg.device
if tag is None: tag = UOp.unique().arg # TODO: hack
buf = UOp(Ops.DEFINE_LOCAL, sdtype, arg=tag)
@@ -559,14 +560,22 @@ def bufferize_to_store(x:UOp):
# TODO: how is this unified?
return buf.reshape(shape).index(*rngs, dtype=sdtype).store(x.src[0], *rngs, dtype=sdtype).forced_reshape(shape, dtype=x.dtype)
pm_add_buffers = pm_mops+to_bufferview+PatternMatcher([
(UPat(Ops.BUFFERIZE, name="x"), bufferize_to_store),
_pm_add_buffers = pm_mops+to_bufferview+PatternMatcher([
# move RESHAPEs through MSELECT/MSTACK
(UPat((Ops.MSELECT, Ops.MSTACK), src=UPat(Ops.RESHAPE), name="m"),
lambda m: m.replace(src=tuple([x.src[0] for x in m.src]), tag=None).reshape(m.src[0].arg).rtag(m.tag)),
])
pm_add_buffers_nolocals = PatternMatcher([
(UPat(Ops.BUFFERIZE, name="x"), lambda x: bufferize_to_store(x, allow_locals=False)),
])+_pm_add_buffers
# all local bufferization should happen later
pm_add_buffers = PatternMatcher([
(UPat(Ops.BUFFERIZE, name="x"), bufferize_to_store),
])+_pm_add_buffers
# *****************
# 5. split into kernels
@@ -741,7 +750,7 @@ def get_rangeify_map(sink:UOp) -> dict[UOp, UOp]:
if getenv("VIZ"): graph_rewrite(tsink, PatternMatcher([]), name="View Tagged Rangeify")
# bufferize -> store
tsink = graph_rewrite(tsink, pm_add_buffers, bottom_up=True, name="bufferize to store")
tsink = graph_rewrite(tsink, pm_add_buffers_nolocals, bottom_up=True, name="bufferize to store")
tsink = graph_rewrite(tsink, split_kernels, ctx=uop_list, name="split kernels")
# if a kernel depends on a buffer, and that buffer is later assigned to, make the assign depend on the kernel's assign
+1 -1
View File
@@ -18,7 +18,7 @@ class Ops(FastEnum):
COPY = auto(); BUFFER = auto(); BUFFER_VIEW = auto(); MSELECT = auto(); MSTACK = auto() # noqa: E702
# create buffer
BUFFERIZE = auto()
BUFFERIZE = auto(); APPENDINDEX = auto() # noqa: E702
# ops that adjust the behavior of the scheduler
CONTIGUOUS = auto(); CONTIGUOUS_BACKWARD = auto(); DETACH = auto(); FUSE = auto() # noqa: E702