Compare commits

..
Author SHA1 Message Date
geohot f215f84241 use end range count in priority 2025-11-06 10:17:35 -08:00
George HotzandGitHub 290441dd44 do loads early (#13131)
* do loads early

* local and reg
2025-11-06 09:57:09 -08:00
George HotzandGitHub 097264853d very simple priority (#13130)
* very simple priority

* still simple
2025-11-06 09:25:28 -08:00
George HotzandGitHub 07b415e831 fixup op order (#13128)
* fixup op order

* more order

* move a few more

* more

* DEBUG_LINEARIZE
2025-11-06 08:50:04 -08:00
6 changed files with 104 additions and 52 deletions
+6
View File
@@ -38,6 +38,12 @@ class TestLinearizer(unittest.TestCase):
np.testing.assert_equal(a.numpy(), ta)
np.testing.assert_equal(b.numpy(), tb)
def test_late_bias_load(self):
img = Tensor.empty(1, 3, 16, 16)
w = Tensor.empty(16, 3, 3, 3)
b = Tensor.empty(16)
img.conv2d(w, b).realize()
def _test_no_nested_ranges(self, lins, skip=None):
for l in lins:
range_in_acc = flatten([[x for x in u.src if x.op is Ops.RANGE] for u in l.uops if u.op is Ops.DEFINE_REG])
+38 -6
View File
@@ -1,14 +1,15 @@
import heapq
from collections import defaultdict
from tinygrad.uop.ops import PatternMatcher, UOp, Ops, UPat
from tinygrad.helpers import prod
from tinygrad.uop.ops import PatternMatcher, UOp, Ops, UPat, multirange_str
from tinygrad.helpers import prod, getenv
def linearize(u:UOp) -> list[UOp]:
# this is a toposort with priority
lst = list(u.toposort())
consumers: defaultdict[UOp, list[UOp]] = defaultdict(list)
in_degree:dict[UOp, int] = {}
priorities:dict[UOp, tuple[int, int]] = {}
priorities:dict[UOp, tuple[int, int, int, int]] = {}
ended_ranges:dict[UOp, dict[UOp, None]] = {}
# get consumers and assign priorities
# NOTE: this requires the lst be locally toposorted
@@ -16,15 +17,42 @@ def linearize(u:UOp) -> list[UOp]:
for s in u.src: consumers[s].append(u)
in_degree[u] = len(u.src)
# we place UOps upstream of more end ranges earlier
ended_ranges[u] = {}
for x in consumers[u]:
if x.op is Ops.END: ended_ranges[u][x] = None
ended_ranges[u].update(ended_ranges[x])
# we place UOps with higher run_counts later
# this will cause ranges to be placed late and ends to be placed early
run_count = prod([int(r.vmax)+1 for r in u.ranges])
# simple priority
priorities[u] = (run_count, 0)
# simple op priority
match u.op:
# the order and placement of these is important. they end the loop early
case Ops.DEFINE_GLOBAL | Ops.DEFINE_VAR | Ops.DEFINE_LOCAL | Ops.DEFINE_REG:
priorities[u] = (-20, 0, 0, 0)
continue
# early consts
case Ops.CONST: op_priority = -10
# place END as soon as you can
case Ops.END: op_priority = -100
# nothing else has op_priority
case _: op_priority = 0
# load priority
match u.op:
# place loads early
case Ops.LOAD: load_priority = -1
# control flow resets priority
case Ops.RANGE|Ops.IF|Ops.ENDIF: load_priority = 0
# prevent priority inversion
case _: load_priority = min([0]+[priorities[x][-1] for x in consumers[u]])
priorities[u] = (op_priority, -len(ended_ranges[u]), run_count, load_priority)
# number the uops in "ideal" order
nkey = {u:i for i,u in enumerate(sorted(lst, key=lambda x: (priorities[x],)+x.tuplize))}
nkey = {u:i for i,u in enumerate(sorted(lst, key=lambda x: priorities[x]+x.tuplize))}
# then force then to be toposorted in as close to the ideal order as possible
heapq.heapify(heap:=[(nkey[u],u) for u in lst if in_degree[u] == 0])
@@ -35,6 +63,10 @@ def linearize(u:UOp) -> list[UOp]:
in_degree[v] -= 1
if in_degree[v] == 0: heapq.heappush(heap, (nkey[v],v))
assert len(newlst) == len(lst), f"len mismatch {len(newlst)} != {len(lst)}"
if getenv("DEBUG_LINEARIZE"):
for i,u in enumerate(newlst):
print(f"{i:4d} {str(u.op):20s} {multirange_str(u.ranges, color=True, pad=10)} {priorities[u]}")
return newlst
class CFGContext:
+48 -38
View File
@@ -1,3 +1,5 @@
# flake8: noqa: E702
# allow semicolons to put multiple ops on one line
from enum import auto, IntEnum, Enum
# wrapper around IntEnum that preserves Enum.__str__ and makes auto() unique across all FastEnum subclasses
@@ -9,16 +11,13 @@ class FastEnum(IntEnum):
# the order of these Ops controls the order of the toposort
class Ops(FastEnum):
# ** 1 -- defines/consts **
# ** 1 -- defines/special **
# TODO: unify these ops into the levels of the memory hierarchy. depends on ASSIGN is STORE
DEFINE_GLOBAL = auto(); DEFINE_LOCAL = auto(); DEFINE_REG = auto() # noqa: E702
# TODO: unify these ops into the levels of the memory hierarchy
DEFINE_GLOBAL = auto(); DEFINE_LOCAL = auto(); DEFINE_REG = auto()
# this is for symbolic shapes
DEFINE_VAR = auto(); BIND = auto() # noqa: E702
# consts. VCONST is a vectorized const
VCONST = auto(); CONST = auto() # noqa: E702
DEFINE_VAR = auto(); BIND = auto()
# this is a RANGE for GPU dimensions, similar to symbolic shapes but not exactly
SPECIAL = auto()
@@ -26,8 +25,7 @@ class Ops(FastEnum):
# ** 2 -- non op uops **
# uops that aren't rendered
NOOP = auto(); SINK = auto(); UNIQUE = auto(); DEVICE = auto(); KERNEL = auto(); PRECAST = auto(); REWRITE_ERROR = auto() # noqa: E702
SENTINEL = auto()
NOOP = auto(); SINK = auto(); PRECAST = auto()
# AFTER passes src[0] through and promises in the toposort that any consumers of the AFTER run after src[1:]
AFTER = auto()
@@ -35,24 +33,8 @@ class Ops(FastEnum):
# GROUP is a NOOP that just merges things together
GROUP = auto()
# buffer ops
COPY = auto(); BUFFER = auto(); BUFFER_VIEW = auto(); MSELECT = auto(); MSTACK = auto() # noqa: E702
# create buffer
BUFFERIZE = auto()
# ops that adjust the behavior of the scheduler
CONTIGUOUS = auto(); CONTIGUOUS_BACKWARD = auto(); DETACH = auto() # noqa: E702
# movement ops! these only exist in the tensor graph
RESHAPE = auto(); PERMUTE = auto(); EXPAND = auto(); PAD = auto(); SHRINK = auto(); FLIP = auto() # noqa: E702
MULTI = auto() # MULTI is really a movement op
# reduce (movement)
REDUCE_AXIS = auto(); REDUCE = auto(); ALLREDUCE = auto() # noqa: E702
# optimization helper ops
UNROLL = auto(); CONTRACT = auto(); GEP = auto(); VECTORIZE = auto(); CAT = auto(); PTRCAT = auto() # noqa: E702
# vector creation / item selection
GEP = auto(); VECTORIZE = auto()
# ** 3 -- load/store **
@@ -60,8 +42,7 @@ class Ops(FastEnum):
INDEX = auto()
# load/store before math
LOAD = auto(); STORE = auto() # noqa: E702
ASSIGN = auto() # TODO: ASSIGN is STORE, remove ASSIGN
LOAD = auto(); STORE = auto()
# ** 4 -- math **
@@ -69,24 +50,53 @@ class Ops(FastEnum):
WMMA = auto()
# UnaryOps
CAST = auto(); BITCAST = auto(); EXP2 = auto(); LOG2 = auto(); SIN = auto(); SQRT = auto(); RECIPROCAL = auto(); NEG = auto(); TRUNC = auto() # noqa: E702
CAST = auto(); BITCAST = auto(); EXP2 = auto(); LOG2 = auto(); SIN = auto()
SQRT = auto(); RECIPROCAL = auto(); NEG = auto(); TRUNC = auto()
# BinaryOps
ADD = auto(); MUL = auto(); SHL = auto(); SHR = auto(); IDIV = auto(); MAX = auto(); MOD = auto() # noqa: E702
CMPLT = auto(); CMPNE = auto(); CMPEQ = auto() # noqa: E702
XOR = auto(); OR = auto(); AND = auto() # noqa: E702
THREEFRY = auto(); SUB = auto(); FDIV = auto(); POW = auto() # noqa: E702
ADD = auto(); MUL = auto(); SHL = auto(); SHR = auto(); IDIV = auto(); MAX = auto(); MOD = auto()
CMPLT = auto(); CMPNE = auto(); CMPEQ = auto()
XOR = auto(); OR = auto(); AND = auto()
THREEFRY = auto(); SUB = auto(); FDIV = auto(); POW = auto()
# TernaryOps
WHERE = auto(); MULACC = auto() # noqa: E702
WHERE = auto(); MULACC = auto()
# ** 5 -- control flow / other **
# ** 5 -- control flow / consts / custom **
# control flow ops
BARRIER = auto(); RANGE = auto(); IF = auto(); END = auto(); ENDIF = auto() # noqa: E702
BARRIER = auto(); RANGE = auto(); IF = auto(); END = auto(); ENDIF = auto()
# consts. VCONST is a vectorized const
VCONST = auto(); CONST = auto()
# CUSTOM/CUSTOMI are used to output strings into codegen. the I makes the string inline
CUSTOM = auto(); CUSTOMI = auto() # noqa: E702
CUSTOM = auto(); CUSTOMI = auto()
# ** 6 -- ops that don't exist in programs **
# tensor graph ops
UNIQUE = auto(); DEVICE = auto(); KERNEL = auto()
ASSIGN = auto()
# buffer ops
BUFFERIZE = auto(); COPY = auto(); BUFFER = auto(); BUFFER_VIEW = auto(); MSELECT = auto(); MSTACK = auto()
# ops that adjust the behavior of the scheduler
CONTIGUOUS = auto(); CONTIGUOUS_BACKWARD = auto(); DETACH = auto()
# movement ops! these only exist in the tensor graph
RESHAPE = auto(); PERMUTE = auto(); EXPAND = auto(); PAD = auto(); SHRINK = auto(); FLIP = auto()
MULTI = auto() # MULTI is really a movement op
# reduce
REDUCE_AXIS = auto(); REDUCE = auto(); ALLREDUCE = auto()
# errors/placeholders
REWRITE_ERROR = auto(); SENTINEL = auto()
# expander ops
UNROLL = auto(); CONTRACT = auto(); CAT = auto(); PTRCAT = auto()
class GroupOp:
Unary = {Ops.EXP2, Ops.LOG2, Ops.SIN, Ops.SQRT, Ops.RECIPROCAL, Ops.NEG, Ops.TRUNC}
+6 -2
View File
@@ -48,6 +48,11 @@ def range_str(u:UOp, color=False) -> str:
ret = '_'.join([str(x) if x >= 0 else "m"+str(-x) for x in u.arg[0:-1]])
return colored(ret, axis_colors[u.arg[-1]]) if color else ret
def multirange_str(rngs:Iterable[UOp], color=False, pad=None) -> str:
ret = ','.join([range_str(x, color=color) for x in sorted(rngs, key=lambda x: x.arg)])
if pad is not None: ret += " " * (pad-ansilen(ret))
return ret
def consumer_map_from_toposort(lst:Iterable[UOp]):
ret: dict[UOp, dict[UOp, None]] = {}
for u in lst:
@@ -853,8 +858,7 @@ def exec_alu(op:Ops, dtype:DType, operands, truncate_output=True):
def print_uops(uops:list[UOp]):
for i,u in enumerate(uops):
formatted_srcs = [(uops.index(x) if x.op is not Ops.CONST else f"{x.arg}") if x in uops else "--" for x in u.src]
formatted_range = ','.join([range_str(r, color=True) for r in sorted(u.ranges, key=lambda x: x.arg)])
print(f"{i:4d} {str(u.op):20s}: {(formatted_range)+' '*(10-ansilen(formatted_range))} {str(u.dtype):40s} " f"{str(formatted_srcs):32s} {u.arg}")
print(f"{i:4d} {str(u.op):20s}: {multirange_str(u.ranges, color=True, pad=10)} {str(u.dtype):40s} " f"{str(formatted_srcs):32s} {u.arg}")
# ***** pattern matcher *****
+4 -4
View File
@@ -134,10 +134,6 @@ shared_codegen_spec = PatternMatcher([
# WMMA has a <a, b, acc>
(UPat(Ops.WMMA, src=(UPat(), UPat(), UPat()), name="x"), lambda x: isinstance(x.arg, tuple) and len(x.arg) == 8),
# UNROLL/CONTRACT is used here for WMMA
(UPat(Ops.CONTRACT, name="x"), lambda x: x.dtype.count == prod(y[1] for y in x.arg)),
(UPat(Ops.UNROLL, name="x"), lambda x: x.src[0].dtype.count == prod(y[1] for y in x.arg)),
# VECTORIZE/GEP
(UPat(Ops.VECTORIZE, name="x"), lambda x: len(x.src)>1 and len(x.src) == x.dtype.vcount and all(x.dtype == y.dtype.vec(len(x.src)) for y in x.src)),
(UPat(Ops.GEP, src=(UPat.var("src"),), name="gep"), lambda gep,src: gep.dtype == src.dtype.scalar()),
@@ -166,6 +162,10 @@ kernel_spec = PatternMatcher([
# index is allowed here
(UPat(GroupOp.Elementwise|{Ops.CONST, Ops.RANGE, Ops.DEFINE_VAR}, dtype=dtypes.index), lambda: True),
# UNROLL/CONTRACT is used here for WMMA
(UPat(Ops.CONTRACT, name="x"), lambda x: x.dtype.count == prod(y[1] for y in x.arg)),
(UPat(Ops.UNROLL, name="x"), lambda x: x.src[0].dtype.count == prod(y[1] for y in x.arg)),
# END can end multiple axes here
(UPat(Ops.END, src=(UPat(), UPat()), allow_any_len=True, dtype=dtypes.void), lambda: True),
+2 -2
View File
@@ -8,7 +8,7 @@ from urllib.parse import parse_qs, urlparse
from typing import Any, TypedDict, TypeVar, Generator, Callable
from tinygrad.helpers import colored, getenv, tqdm, unwrap, word_wrap, TRACEMETA, ProfileEvent, ProfileRangeEvent, TracingKey, ProfilePointEvent, temp
from tinygrad.uop.ops import TrackedGraphRewrite, RewriteTrace, UOp, Ops, printable, GroupOp, srender, sint, sym_infer, range_str, pyrender
from tinygrad.uop.ops import print_uops, range_start
from tinygrad.uop.ops import print_uops, range_start, multirange_str
from tinygrad.device import ProfileDeviceEvent, ProfileGraphEvent, ProfileGraphEntry, Device
from tinygrad.renderer import ProgramSpec
from tinygrad.dtype import dtypes
@@ -78,7 +78,7 @@ def uop_to_json(x:UOp) -> dict[int, dict]:
label += f"\n{x.op.name}{idx} {arg}" + (f" {x.src[0].op}" if len(x.src) else "")
try:
if len(rngs:=u.ranges):
label += f"\n({','.join([range_str(x, color=True) for x in sorted(rngs, key=lambda x: x.arg[0:-1])])})"
label += f"\n({multirange_str(rngs, color=True)})"
if u.op not in {Ops.BUFFER, Ops.KERNEL, Ops.ASSIGN, Ops.COPY, Ops.SINK, *GroupOp.Buffer} and u._shape is not None:
label += f"\n{shape_to_str(u.shape)}"
if u.op in {Ops.INDEX, Ops.BUFFERIZE}: