Compare commits

..
Author SHA1 Message Date
George HotzandGitHub 7ae02dea19 Merge branch 'master' into moveleftright 2025-08-04 19:10:27 -07:00
George HotzandGitHub 7f6acfb0d5 give define global and friends a shape (#11502)
* give define global and friends a shape

* ignore negative size

* ptx fix
2025-08-04 19:09:39 -07:00
geohot 0e91b6fd30 bugfixes 2025-08-04 18:46:32 -07:00
geohot 823dfbde70 move view to swizzle 2025-08-04 18:27:27 -07:00
chenyuandGitHub 83385e7abc update gradient src in ramp.py (#11499)
that's simplified now
2025-08-04 18:58:03 -04:00
qazalandGitHub 846a2826ab viz: remove TracingKey.fmt (#11482)
* viz: remove TracingKey.fmt

* remove from test too
2025-08-05 00:00:03 +03:00
chenyuandGitHub 01d44e8f16 tiny reduce_gradient cleanup [pr] (#11498) 2025-08-04 16:12:53 -04:00
chenyuandGitHub 8a11af01ed remove broken paperswithcode links in doc (#11497) 2025-08-04 13:12:33 -04:00
4f0ee4e982 BPE tokenizer (#11415)
* BPE works

* refactor tok

* oops

* basic tests

* fix eval

* smaller diff

* fix error

* proper vocab decoding

* use regex for splitting

* escape ucatrange

* full compat

---------

Co-authored-by: George Hotz <[email protected]>
2025-08-04 09:52:38 -07:00
30 changed files with 275 additions and 537 deletions
+1 -1
View File
@@ -126,7 +126,7 @@ print(t_log_grad.uop)
"""
void E_(float* restrict data0, float* restrict data1) {
float val0 = *(data1+0);
*(data0+0) = (0.6931471805599453f*(1/(val0*0.6931471805599453f)));
*(data0+0) = (1/val0);
}
"""
# the derivative is close to 1/3
+1 -1
View File
@@ -10,7 +10,7 @@ if __name__ == "__main__":
model, kv = Transformer.from_gguf(Tensor.from_url(models["1B"]), max_context=4096)
tok = SimpleTokenizer(kv["tokenizer.ggml.tokens"])
tok = SimpleTokenizer.from_gguf_kv(kv)
bos_id: int = kv['tokenizer.ggml.bos_token_id']
eos_id: int = kv['tokenizer.ggml.eos_token_id']
+7 -5
View File
@@ -1,17 +1,19 @@
from transformers import AutoTokenizer
from datasets import load_dataset
from tinygrad.apps.llm import SimpleTokenizer
from tinygrad.helpers import tqdm, getenv
from tinygrad.apps.llm import SimpleTokenizer, gpt2_decode_vocab, get_llama_re
from tinygrad.helpers import tqdm, getenv, partition
# use ALLOW_FAILED=-1 to go over the entire dataset without printing.
if __name__ == "__main__":
base_tokenizer = AutoTokenizer.from_pretrained("NousResearch/Meta-Llama-3-8B-Instruct")
vocab_words = [ word for word, _ in sorted(base_tokenizer.get_vocab().items(), key=lambda t: t[1]) ]
special_tokens, normal_tokens = partition(((t, tid) for t, tid in base_tokenizer.vocab.items()),
lambda e: e[1] in base_tokenizer.all_special_ids)
inv_vocab = { tid: word for word, tid in base_tokenizer.get_vocab().items() }
simple_tokenizer = SimpleTokenizer(vocab_words)
simple_tokenizer = SimpleTokenizer(get_llama_re(), gpt2_decode_vocab(dict(normal_tokens)), dict(special_tokens))
color_codes = [ 91, 92, 94, 93, 95 ]
def color_tokens(tids): return "".join(f"\033[{color_codes[i%len(color_codes)]}m{inv_vocab[t]}" for i, t in enumerate(tids)) + "\033[0m"
def color_tokens(tids):
return "".join(f"\033[{color_codes[i%len(color_codes)]}m{base_tokenizer.decode([t])}" for i, t in enumerate(tids)) + "\033[0m"
ds = load_dataset("OpenAssistant/oasst1")
allow_failed = getenv("ALLOW_FAILED", 10)
+2 -3
View File
@@ -743,9 +743,8 @@ class TestFloat4(unittest.TestCase):
len([uop for uop in uops if uop.op is Ops.STORE and uop.src[1].dtype == dtypes.half.vec(4)]))
def test_float4_basic(self):
# NOTE: this used to fuse from (2, 8)
a = Tensor.empty(16).realize()
b = Tensor.empty(16).realize()
a = Tensor.empty(2, 8).realize()
b = Tensor.empty(2, 8).realize()
c = a + b
s = c.schedule()[0]
-1
View File
@@ -160,7 +160,6 @@ class TestOps(unittest.TestCase):
b = torch.tensor([[1,2,3],[4,5,6]], dtype=torch.int32)
helper_test_op([], lambda: torch.zeros_like(b), lambda: Tensor.zeros_like(a), forward_only=True)
@unittest.skip("this is undefined, right?")
def test_empty_0(self):
helper_test_op([], lambda: torch.empty(45,65)*0/0, lambda: Tensor.empty(45,65)*0/0, forward_only=True)
-41
View File
@@ -1,41 +0,0 @@
import unittest
from tinygrad import Tensor, Device
from tinygrad.uop.ops import KernelInfo
from tinygrad.opt.kernel import Opt, OptOps
from tinygrad.engine.realize import get_program
def with_opts(c:Tensor, opts_to_apply:list[Opt]):
s = c.schedule()[-1]
program = get_program(s.ast.replace(arg=KernelInfo(opts_to_apply=tuple(opts_to_apply))), Device.default.renderer)
print(program.src)
class TestRangeify(unittest.TestCase):
def test_dont_upcast(self):
a = Tensor.empty(4, 4)
b = Tensor.empty(4, 4)
c = a + b
with_opts(c, [])
def test_upcast(self):
a = Tensor.empty(4, 4)
b = Tensor.empty(4, 4)
c = a + b
with_opts(c, [Opt(op=OptOps.UPCAST, axis=1, arg=4)])
def test_upcast_sum(self):
a = Tensor.empty(4, 4)
b = a.sum(axis=1)
with_opts(b, [Opt(op=OptOps.UPCAST, axis=0, arg=4)])
def test_unroll_sum(self):
a = Tensor.empty(4, 4)
b = a.sum(axis=1)
with_opts(b, [Opt(op=OptOps.UNROLL, axis=0, arg=4)])
def test_both_sum(self):
a = Tensor.empty(4, 4)
b = a.sum(axis=1)
with_opts(b, [Opt(op=OptOps.UPCAST, axis=0, arg=4), Opt(op=OptOps.UNROLL, axis=0, arg=4)])
if __name__ == '__main__':
unittest.main()
+2 -1
View File
@@ -15,7 +15,8 @@ from tinygrad.shape.shapetracker import ShapeTracker
from tinygrad.uop.ops import PatternMatcher, UOp, Ops, GroupOp, UPat, graph_rewrite, track_rewrites
from tinygrad.uop.symbolic import symbolic_simple
from tinygrad.helpers import CI, DEBUG, FUSE_ARANGE, SPLIT_REDUCEOP, GlobalCounters, Context, getenv, all_same, temp
from tinygrad.schedule.kernelize import merge_views, get_kernelize_map, Kernel
from tinygrad.schedule.kernelize import get_kernelize_map, Kernel
from tinygrad.opt.swizzler import merge_views
from tinygrad.engine.schedule import ScheduleItem, create_schedule_with_vars
from tinygrad.engine.realize import CompiledRunner, run_schedule, lower_schedule
+1 -11
View File
@@ -30,19 +30,9 @@ class TestTiny(unittest.TestCase):
def test_gemm(self, N=64, out_dtype=dtypes.float):
a = Tensor.ones(N,N).contiguous()
b = Tensor.eye(N).contiguous()
self.assertListEqual((out:=a@b).contiguous().flatten().tolist(), [1.0]*(N*N))
self.assertListEqual((out:=a@b).flatten().tolist(), [1.0]*(N*N))
if IMAGE < 2: self.assertEqual(out.dtype, out_dtype)
def test_eye(self):
a = Tensor.eye(4, dtype=dtypes.int)
self.assertListEqual(a.tolist(), [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])
def test_conv(self, N=32):
a = Tensor.ones(1,4,N,N).contiguous()
w1 = Tensor.ones(16,4,3,3).contiguous()
out = a.conv2d(w1)
self.assertTrue(all([x == 36.0 for x in out.contiguous().flatten().tolist()]))
# *** randomness ***
def test_random(self):
+57
View File
@@ -0,0 +1,57 @@
import unittest, base64, functools
from tinygrad.apps.llm import SimpleTokenizer, get_llama_re
from tinygrad.helpers import fetch
class TestLLMTokenizer(unittest.TestCase):
@functools.cached_property
def basic_tok(self): return SimpleTokenizer(".*", { b"a": 0, b"b": 1, b"c": 2, b"ab": 3, b"bc": 4 }, { "<x>": 5, "<y>": 6, "<z>": 7 })
@functools.cached_property
def llama_tok(self):
# from https://github.com/tinygrad/tinygrad/blob/e0106b6b257ebc003eb3694144e3e198f7d8cc37/examples/llama3.py#L14
model_file = fetch("https://huggingface.co/bofenghuang/Meta-Llama-3-8B/resolve/main/original/tokenizer.model")
with open(model_file, "rt") as fd:
str_vocab = [ line.split(maxsplit=1) for line in fd.read().splitlines() if line ]
normal_tokens = { base64.b64decode(stok): int(srank) for stok, srank in str_vocab }
special_tokens = [
"<|begin_of_text|>",
"<|end_of_text|>",
"<|reserved_special_token_0|>",
"<|reserved_special_token_1|>",
"<|reserved_special_token_2|>",
"<|reserved_special_token_3|>",
"<|start_header_id|>",
"<|end_header_id|>",
"<|reserved_special_token_4|>",
"<|eot_id|>",
] + [ f"<|reserved_special_token_{i}|>" for i in range(5, 256 - 5) ]
return SimpleTokenizer(get_llama_re(), normal_tokens, { token: len(normal_tokens) + i for i, token in enumerate(special_tokens) })
def _test_coding(self, tok: SimpleTokenizer, text: str, expected_tokens: list[int]):
self.assertEqual(tok.encode(text), expected_tokens)
self.assertEqual(tok.decode(expected_tokens), text)
def test_abc(self): self._test_coding(self.basic_tok, "abc", [ 3, 2 ])
def test_abbc(self): self._test_coding(self.basic_tok, "abbc", [ 3, 4 ])
def test_aabbbcc(self): self._test_coding(self.basic_tok, "aabbbcc", [ 0, 3, 1, 4, 2 ])
def test_specials1(self): self._test_coding(self.basic_tok, "a<x>a<y>a<z>a", [ 0, 5, 0, 6, 0, 7, 0 ])
def test_specials2(self): self._test_coding(self.basic_tok, "<x>a<y>a<z>", [ 5, 0, 6, 0, 7 ])
def test_invalid_token(self):
with self.assertRaises(RuntimeError): self._test_coding(self.basic_tok, "L", [])
def test_no_specials(self): self._test_coding(SimpleTokenizer(".*", { bytes([i]): i for i in range(256) }, {}), "abc", [97, 98, 99])
# NOTE: the correct tokenization for this can only be found by looking up the text chunk in the vocab, not by applying merges
def test_llama_early_tokenize(self): self._test_coding(self.llama_tok, " например", [ 111797 ])
def test_llama_basic(self): self._test_coding(self.llama_tok, "hello world", [ 15339, 1917 ])
def test_llama_control_char(self): self._test_coding(self.llama_tok, " \x850", [ 220, 116360, 15 ])
def test_llama_bytes(self): self._test_coding(self.llama_tok, " \xec\x8b\xa4\xed", [ 1717, 105, 116174, 82638, 2483 ])
def test_llama_special1(self): self._test_coding(self.llama_tok, "hello <|end_of_text|>", [ 15339, 220, 128001 ])
def test_llama_special2(self): self._test_coding(self.llama_tok, "<|start_header_id|>user<|end_header_id|>\n\n", [ 128006, 882, 128007, 271 ])
def test_llama_repeat(self): self._test_coding(self.llama_tok, "00000000000000000", [ 931, 931, 931, 931, 931, 410 ])
def test_llama_pat(self): self._test_coding(self.llama_tok, "today\n \n", [ 31213, 14211 ])
if __name__ == '__main__':
unittest.main()
+1 -2
View File
@@ -106,13 +106,12 @@ class TestViz(BaseTestViz):
# name can also come from a function that returns a TracingKey
def test_tracing_key(self):
@track_rewrites(name=lambda inp,ret: TracingKey("custom_name", (inp,), fmt=f"input={inp.render()}"))
@track_rewrites(name=lambda inp,ret: TracingKey("custom_name", (inp,)))
def test(s:UOp): return graph_rewrite(s, PatternMatcher([]))
test(UOp.variable("a", 1, 10)+1)
lst = get_viz_list()
# NOTE: names from TracingKey do not get deduped
self.assertEqual(lst[0]["name"], "custom_name")
self.assertEqual(lst[0]["fmt"], "input=(a+1)")
def test_colored_label(self):
# NOTE: dataclass repr prints literal escape codes instead of unicode chars
+49 -25
View File
@@ -1,33 +1,57 @@
from __future__ import annotations
import sys, argparse
from tinygrad import Tensor, nn, UOp, TinyJit, getenv
import sys, argparse, typing, re, itertools, unicodedata
from tinygrad import Tensor, nn, UOp, TinyJit, getenv, helpers
def gpt2_decode_vocab(voc: dict[str, int]): # https://github.com/openai/gpt-2/blob/9b63575ef42771a015060c964af2c3da4cf7c8ab/src/encoder.py#L9
c2b = { chr(cp): cp for cp in itertools.chain(range(ord("!"), ord("~")+1), range(ord("¡"), ord("¬")+1), range(ord("®"), ord("ÿ")+1)) }
c2b.update({ chr(256+off): cp for off, cp in enumerate(cp for cp in range(256) if chr(cp) not in c2b) })
return { bytes(c2b[c] for c in tok): tid for tok, tid in voc.items() }
def get_llama_re():
def ucat_range(pre: str): return "".join(re.escape(chr(cp)) for cp in range(sys.maxunicode + 1) if unicodedata.category(chr(cp)).startswith(pre))
r_ws, r_p_N, r_p_L = r"\t\n\x0b\x0c\r\x85" + ucat_range("Z"), ucat_range("N"), ucat_range("L")
# https://github.com/ggml-org/llama.cpp/blob/94933c8c2eeaa9a7983e3f6c08af76bd86724094/src/llama-vocab.cpp#L286
return "(?i:'s|'t|'re|'ve|'m|'ll|'d)|" + \
f"[^\\r\\n{r_p_N}{r_p_L}]?[{r_p_L}]+|[{r_p_N}]{{1,3}}| ?[^{r_ws}{r_p_N}{r_p_L}]+[\\r\\n]*|[{r_ws}]*[\\r\\n]+|[{r_ws}]+(?![^{r_ws}])|[{r_ws}]+"
class SimpleTokenizer:
def __init__(self, vocab: list[str]):
self.vocab: list[str] = vocab
self.biggest_token: int = max(map(len, vocab))
self.token_to_id: dict[str, int] = {tok: i for i, tok in enumerate(vocab)}
self.replace_space = "Ġ"
self.replace_newline = "Ċ"
def __init__(self, pat: str, normal_tokens: dict[bytes, int], special_tokens: dict[str, int]):
self._normal_tokens, self._special_tokens, self._pat = normal_tokens, special_tokens, re.compile(pat)
self._tok2str = { tid: tok.encode() for tok, tid in special_tokens.items() } | { tid: tok for tok, tid in normal_tokens.items() }
self._special_re = re.compile("|".join(re.escape(tok) for tok in self._special_tokens.keys()) if special_tokens else r"(?!)")
def encode(self, text:str) -> list[int]:
s = text.replace(" ", self.replace_space).replace("\n", self.replace_newline)
out: list[int] = []
i = 0
while i < len(s):
j = min(i+self.biggest_token, len(s))
while i < j and (tid:=self.token_to_id.get(s[i:j])) is None: j -= 1
if tid is None: raise RuntimeError(f"token not found in {s}")
assert tid is not None, f"token not found in {s}"
out.append(tid)
i = j
return out
@staticmethod
def from_gguf_kv(kv: dict):
# https://github.com/ggml-org/llama.cpp/blob/94933c8c2eeaa9a7983e3f6c08af76bd86724094/src/llama-vocab.cpp#L1818-L1820
if kv["tokenizer.ggml.pre"] not in ("llama3","llama-v3","llama-bpe"): raise ValueError(f"Invalid tokenizer preset '{kv['tokenizer.ggml.pre']}'")
vocab: typing.Iterable[tuple[str, int]] = ((tok, idx) for idx, tok in enumerate(kv["tokenizer.ggml.tokens"]))
normal_tokens, special_tokens = helpers.partition(vocab, lambda e: kv["tokenizer.ggml.token_type"][e[1]] == 1)
return SimpleTokenizer(get_llama_re(), gpt2_decode_vocab(dict(normal_tokens)), dict(special_tokens))
def decode(self, ids: list[int]) -> str:
return ''.join(self.vocab[tid] for tid in ids).replace(self.replace_space, " ").replace(self.replace_newline, "\n")
def encode(self, text: str):
tokens: list[int] = []
pos = 0
for match in self._special_re.finditer(text):
tokens.extend(self._encode_sentence(text[pos:match.start(0)]) + [self._special_tokens[text[match.start(0):match.end(0)]]])
pos = match.end(0)
return tokens + self._encode_sentence(text[pos:])
def role(self, role:str):
return [t for x in ["<|start_header_id|>", role, "<|end_header_id|>\n\n"] for t in self.encode(x)] # llama style
def decode(self, ids: list[int]) -> str: return b''.join(self._tok2str[tid] for tid in ids).decode()
def role(self, role:str): return self.encode("<|start_header_id|>" + role + "<|end_header_id|>\n\n")
def _encode_sentence(self, chunk: str): return [ tok for word in self._pat.findall(chunk) for tok in self._encode_word(word.encode()) ]
def _encode_word(self, word: bytes):
if (early_token:=self._normal_tokens.get(word)) is not None: return [early_token]
parts = [word[i:i+1] for i in range(len(word))]
while True:
min_tid, min_idx = 2**32, -1
for idx, (p1, p2) in enumerate(zip(parts[:-1], parts[1:])):
tid = self._normal_tokens.get(p1 + p2, min_tid)
if tid < min_tid: min_tid, min_idx = tid, idx
if min_idx == -1: break
parts = parts[:min_idx] + [parts[min_idx] + parts[min_idx+1]] + parts[min_idx+2:]
try: return [ self._normal_tokens[p] for p in parts ]
except KeyError: raise RuntimeError("token not found")
def apply_rope(x:Tensor, start_pos:int|UOp, base:int=10000):
B, H, T, Hd = x.shape
@@ -165,7 +189,7 @@ if __name__ == "__main__":
model, kv = Transformer.from_gguf(Tensor.from_url(models[args.size]), args.max_context)
# extract some metadata
tok = SimpleTokenizer(kv["tokenizer.ggml.tokens"])
tok = SimpleTokenizer.from_gguf_kv(kv)
bos_id: int = kv['tokenizer.ggml.bos_token_id']
eos_id: int = kv['tokenizer.ggml.eos_token_id']
+1 -4
View File
@@ -7,7 +7,6 @@ from tinygrad.uop.spec import type_verify
from tinygrad.renderer import Renderer
# import all pattern matchers here
from tinygrad.codegen.rangeify import pm_rangeify, pm_name, RangeifyContext
from tinygrad.codegen.lowerer import pm_lowerer, get_index
from tinygrad.codegen.quantize import pm_quant
from tinygrad.codegen.gpudims import pm_add_gpudims
@@ -44,9 +43,7 @@ def _get_rewrites_for_renderer(opts:Renderer, linearizer:bool, _QUANTIZE, _DEVEC
# ** lowerer (rewrite_shapetracker_with_index) **
ret: list[RewriteStep] = []
if _QUANTIZE and opts.device in {"CPU", "DSP"}: ret.append(RewriteStep(pm_quant, name="quantize"))
#ret.append(RewriteStep(pm_lowerer, get_index, name="lowerer", bottom_up=True))
ret.append(RewriteStep(pm_rangeify, lambda _: RangeifyContext(), name="rangeify", bottom_up=True))
ret.append(RewriteStep(pm_name, lambda _: [0], name="name"))
ret.append(RewriteStep(pm_lowerer, get_index, name="lowerer", bottom_up=True))
# ** expander (expand_rewrite) **
ret.append(RewriteStep(sym+migrate_indexing, name="initial symbolic"))
+4 -6
View File
@@ -260,8 +260,6 @@ pm_render = PatternMatcher([
(UPat(Ops.STORE, src=(UPat(src=(UPat(), UPat(), UPat(dtype=dtypes.bool)), name="idx").or_casted(), UPat()), name="store", allow_any_len=True),
lambda store,idx: UOp(Ops.STORE, dtype=store.dtype, src=store.src[:2]+(UOp(Ops.IF, src=(idx.src[2],)),)+store.src[2:]) if \
len(store.src) <= 2 or store.src[2].op != Ops.IF else None),
# TODO: CONST shouldn't have src
(UPat(Ops.CONST, name="c"), lambda c: c.replace(src=()) if len(c.src) else None),
])
# *** Ops.REDUCE -> Ops.DEFINE_ACC ***
@@ -279,7 +277,7 @@ def horizontal_reduce(inp:UOp, out_dtype:DType) -> list[UOp]:
return [inp]
def reduce_to_acc(ctx:ReduceContext, red:UOp):
inp, acc, reduce_range = red.src[0], red.src[1], red.src[2:]
inp, reduce_range = red.src[0], red.src[1:]
lst = horizontal_reduce(inp, red.dtype)
assert all(x.dtype == red.dtype for x in lst), f"horizontal reduction mismatch {lst[0].dtype} != {red.dtype}"
# if we have a range
@@ -287,8 +285,8 @@ def reduce_to_acc(ctx:ReduceContext, red:UOp):
topo = inp.toposort()
stored_ranges = flatten([x.src[2:] for x in topo if x.op is Ops.STORE])
input_ranges = tuple([x for x in topo if x.op is Ops.RANGE and x not in reduce_range and x not in stored_ranges])
identity = UOp.const(red.dtype, identity_element(red.arg, red.dtype.scalar()))
#acc = UOp(Ops.DEFINE_REG, red.dtype.ptr(size=1, addrspace=AddrSpace.REG), arg=(ctx.acc_num,)).index(UOp.const(dtypes.int, 0))
identity = red.const_like(identity_element(red.arg, red.dtype.scalar()))
acc = UOp(Ops.DEFINE_REG, red.dtype.ptr(size=1, addrspace=AddrSpace.REG), arg=(ctx.acc_num,)).index(UOp.const(dtypes.int, 0))
do_store = acc.store(identity, UOp(Ops.NOOP, src=input_ranges)) if len(input_ranges) else acc.store(identity)
lst = [acc.load(do_store, *reduce_range)] + lst # put acc as the first element
ctx.acc_num += 1
@@ -372,7 +370,7 @@ def reduce_collapse(red:UOp):
def reduce_unparented(red:UOp):
if red.arg not in {Ops.ADD, Ops.MAX}: return None
reduce_parented, reduce_unparented = partition(red.src[2:], lambda x: x in red.src[0].sparents)
reduce_parented, reduce_unparented = partition(red.src[1:], lambda x: x in red.src[0].sparents)
if len(reduce_unparented) == 0: return None
ret = red.replace(src=(red.src[0],)+tuple(reduce_parented)) if len(reduce_parented) or red.dtype != red.src[0].dtype else red.src[0]
if red.arg is Ops.ADD:
-249
View File
@@ -1,249 +0,0 @@
from tinygrad.uop.ops import PatternMatcher, UPat, Ops, UOp, resolve, KernelInfo, GroupOp, AxisType, TRACK_MATCH_STATS, identity_element
from tinygrad.opt.kernel import axis_colors, Opt, OptOps
from dataclasses import dataclass
from tinygrad.dtype import dtypes, AddrSpace
from tinygrad.helpers import argsort, colored, prod, all_same, getenv
@dataclass
class RangeifyContext:
idx: int = 0
regs: int = 0
opts: tuple[Opt, ...] = ()
def map_store(ctx:RangeifyContext, x:UOp):
if x.tag == 1: return None
ranges = []
for i,s in enumerate(x.shape):
upcast_amount = prod([o.arg if o.arg != 0 else s for o in ctx.opts if o.axis == i and o.op == OptOps.UPCAST])
if resolve(s!=1):
if upcast_amount != 1:
assert s%upcast_amount == 0
rng = UOp.range(dtypes.int, s//upcast_amount, (ctx.idx, AxisType.LOOP)) * upcast_amount
rng = rng + UOp.range(dtypes.int, upcast_amount, (ctx.idx+1, AxisType.UPCAST))
ranges.append(rng)
ctx.idx += 2
else:
ranges.append(UOp.range(dtypes.int, s, (ctx.idx, AxisType.LOOP)))
ctx.idx += 1
else:
ranges.append(UOp.const(dtypes.int, 0))
mm = UOp(Ops.INDEX, dtype=x.src[0].dtype, src=(x.src[0],)+tuple(ranges))
mm2 = UOp(Ops.INDEX, dtype=x.src[0].dtype, src=(x.src[1],)+tuple(ranges))
return UOp(Ops.STORE, src=(mm, mm2)+tuple([x for x in UOp.sink(*ranges).toposort() if x.op is Ops.RANGE]), tag=1)
def map_load(ctx:RangeifyContext, idx:UOp, load:UOp):
out_ranges = idx.src[1:]
idx_sink = UOp.sink(*out_ranges)
upcast_ranges = [x for x in idx_sink.toposort() if x.op is Ops.RANGE and x.arg[1] in (AxisType.UPCAST, AxisType.UNROLL)]
upcast_shape = tuple([x.vmax+1 for x in upcast_ranges])
if len(upcast_ranges):
buf = UOp(Ops.DEFINE_REG, load.dtype.ptr(size=prod([x.vmax+1 for x in upcast_ranges]), addrspace=AddrSpace.REG), arg=(ctx.regs,))
buf = buf.reshape(upcast_shape)
ctx.regs += 1
replace_ranges = {}
for r in upcast_ranges:
replace_ranges[r] = UOp.range(dtypes.int, r.vmax+1, (ctx.idx, AxisType.UPCAST))
ctx.idx += 1
replace_ranges_v = list(replace_ranges.values())
out_ranges = idx_sink.substitute(replace_ranges).src
ret = load.src[0].index(*out_ranges).load()
ret = buf.index(*upcast_ranges).load(buf.index(*replace_ranges_v).store(ret, *replace_ranges_v, tag=1))
return ret
else:
return UOp(Ops.INDEX, load.src[0].dtype, src=(load.src[0],)+out_ranges).load()
def map_reduce(ctx:RangeifyContext, idx:UOp, red:UOp):
rngs = list(idx.src[1:])
input_ranges = [x for x in UOp.sink(*rngs).toposort() if x.op is Ops.RANGE and x.arg[1] != AxisType.UPCAST]
upcast_ranges = [x for x in UOp.sink(*rngs).toposort() if x.op is Ops.RANGE and x.arg[1] == AxisType.UPCAST]
upcast_shape = tuple([x.vmax+1 for x in upcast_ranges])
acc = UOp(Ops.DEFINE_REG, red.dtype.ptr(size=prod([x.vmax+1 for x in upcast_ranges]), addrspace=AddrSpace.REG),
arg=(ctx.regs,)).reshape(upcast_shape)
ctx.regs += 1
# create reduce dims (before new upcast dims)
new_ranges = []
reduce_axis = 0
for i,s in enumerate(red.src[0].shape):
if i in red.arg[1]:
unroll_amount = prod([o.arg if o.arg != 0 else s for o in ctx.opts if o.axis == reduce_axis and o.op == OptOps.UNROLL])
reduce_axis += 1
assert rngs[i].op == Ops.CONST
#rngs[i] = UOp.range(dtypes.int, s, (ctx.idx, AxisType.REDUCE))
#ctx.idx += 1
if unroll_amount != 1:
assert s%unroll_amount == 0
rngs[i] = UOp.range(dtypes.int, s//unroll_amount, (ctx.idx, AxisType.REDUCE)) * unroll_amount
rngs[i] = rngs[i] + UOp.range(dtypes.int, unroll_amount, (ctx.idx+1, AxisType.UNROLL))
ctx.idx += 2
new_ranges.extend(list(rngs[i].src))
else:
rngs[i] = UOp.range(dtypes.int, s, (ctx.idx, AxisType.REDUCE))
ctx.idx += 1
new_ranges.append(rngs[i])
# create new upcast dims
replace_ranges = {}
for r in upcast_ranges:
replace_ranges[r] = UOp.range(dtypes.int, r.vmax+1, (ctx.idx, AxisType.UPCAST))
ctx.idx += 1
replace_ranges_v = list(replace_ranges.values())
rngs = list(UOp.sink(*rngs).substitute(replace_ranges).src)
# identity store
identity_ranges = []
for r in upcast_ranges:
identity_ranges.append(UOp.range(dtypes.int, r.vmax+1, (ctx.idx, AxisType.LOOP)))
ctx.idx += 1
identity = UOp.const(red.dtype, identity_element(red.arg[0], red.dtype.scalar()))
do_identity_store = acc.index(*identity_ranges).store(identity, *identity_ranges, UOp(Ops.NOOP, src=tuple(input_ranges)), tag=1)
mm = UOp(Ops.INDEX, red.src[0].dtype, src=(red.src[0],)+tuple(rngs))
rbufidx = acc.index(*replace_ranges_v)
loaded = rbufidx.load(do_identity_store, *new_ranges)
reduce_store = rbufidx.store(loaded.alu(red.arg[0], mm), *new_ranges, *replace_ranges_v, tag=1)
return acc.index(*replace_ranges.keys()).load(reduce_store)
#return UOp(Ops.REDUCE, red.dtype, src=(mm, loaded)+tuple(replace_ranges_v)+tuple(new_ranges), arg=red.arg[0])
def map_reshape(x:UOp, r:UOp):
acc = 1
to_sum = []
for s,src in list(zip(x.shape, x.src[1:]))[::-1]:
to_sum.append(acc*src)
acc *= s
mish = sum(to_sum)
ret = []
for s in x.src[0].src[0].shape[::-1]:
if resolve(s!=1):
# this MOD should limit any ranges outside s
ret.append(mish % s)
mish //= s
else:
ret.append(UOp.const(dtypes.int, 0))
ret = UOp.sink(*ret).simplify().src[::-1] if len(ret) else ()
return UOp(Ops.INDEX, r.dtype, src=(r.src[0],)+tuple(ret))
def map_pad(x:UOp, r:UOp):
ret = list(x.src[1:])
bigwhere = UOp.const(dtypes.bool, True)
for i,(sh,(s,e)) in enumerate(zip(r.shape, r.arg)):
if s == 0 and e == 0: continue
where = UOp.const(dtypes.bool, True)
if e > 0: where = where & (ret[i] < (sh-e))
if s > 0: where = where & (ret[i] >= s)
bigwhere = bigwhere & where
# this is safe but dumb
ret[i] = (ret[i] - s).maximum(0).minimum(r.src[0].shape[i]-1)
# mask the load
#ret[i] = where.where(ret[i], UOp(Ops.INVALID, dtype=ret[i].dtype))
# PAD is with 0
return bigwhere.simplify().where(UOp(Ops.INDEX, r.dtype, src=(r.src[0],)+tuple(ret)), UOp.const(r.dtype, 0))
def capture_sink(ctx:RangeifyContext, x: UOp):
if x.tag == 1:
late_subs = {}
for k,v in x.get_children_map().items():
if k.op is Ops.CHILDREN and all([vi.op is Ops.INDEX for vi in v]):
idxs = list(zip(*[vi.src[1:] for vi in v]))
new_idxs = []
only_new_idxs = []
save_shape = []
full_shape = []
for idx in idxs:
if all_same(idx):
new_idxs.append(idx[0])
save_shape.append(1)
full_shape.append(idx[0].vmax+1)
else:
ll = [z.vmax+1 for z in idx]
assert all_same(ll), f"mismatch shapes {ll}"
save_shape.append(ll[0])
full_shape.append(ll[0])
new_idxs.append(UOp.range(dtypes.int, ll[0], (ctx.idx, AxisType.LOOP)))
only_new_idxs.append(new_idxs[-1])
ctx.idx += 1
new_idxs = tuple(new_idxs)
inp = k.src[0]
print(save_shape, full_shape)
if len(save_shape):
buf = UOp(Ops.DEFINE_REG, inp.dtype.ptr(size=prod(save_shape), addrspace=AddrSpace.REG), arg=(ctx.regs,))
ctx.regs += 1
buf = buf.reshape(tuple(save_shape)).expand(tuple(full_shape))
store = UOp(Ops.INDEX, buf.dtype, (buf,)+new_idxs).store(UOp(Ops.INDEX, inp.dtype, (inp,)+new_idxs), *only_new_idxs, tag=1)
for vi in v:
late_subs[vi] = UOp(Ops.INDEX, buf.dtype, (buf,)+vi.src[1:]).load(store)
else:
print("no replace")
for vi in v:
assert new_idxs == vi.src[1:]
late_subs[vi] = UOp(Ops.INDEX, inp.dtype, (inp,)+new_idxs)
if not len(late_subs): return None
return x.substitute(late_subs)
if x.arg is not None and x.arg.opts_to_apply is not None: ctx.opts = x.arg.opts_to_apply
replace_children = {}
for k,v in x.get_children_map().items():
if k.op not in {Ops.CHILDREN, Ops.DEVICE} and len(v) > 1:
replace_children[k] = UOp(Ops.CHILDREN, dtype=k.dtype, src=(k.replace(tag=len(v)),))
if getenv("FUSE") and TRACK_MATCH_STATS > 0: x = x.substitute(replace_children)
return x.replace(arg=None, tag=1)
pm_rangeify = PatternMatcher([
(UPat(Ops.SINK, name="x"), capture_sink),
# TODO: handle INDEX on STORE
(UPat(Ops.STORE, name="x"), map_store),
(UPat(Ops.INDEX, src=(UPat(Ops.REDUCE_AXIS, name="red"),), allow_any_len=True, name="idx"), map_reduce),
# this is like the definitions of these
(UPat(Ops.INDEX, src=(UPat(Ops.PERMUTE, name="r"),), allow_any_len=True, name="x"),
lambda r,x: UOp(Ops.INDEX, r.dtype, src=(r.src[0],)+tuple([x.src[1+p] for p in argsort(x.src[0].arg)]))),
(UPat(Ops.INDEX, src=(UPat(Ops.SHRINK, name="r"),), allow_any_len=True, name="x"),
lambda r,x: UOp(Ops.INDEX, r.dtype, src=(r.src[0],)+tuple([a+ss if resolve(ss != 0) else a for a,(ss,_) in zip(x.src[1:], r.arg)]))),
(UPat(Ops.INDEX, src=(UPat(Ops.FLIP, name="r"),), allow_any_len=True, name="x"),
lambda r,x: UOp(Ops.INDEX, r.dtype, src=(r.src[0],)+tuple([((s-1)-a) if f else a for a,s,f in zip(x.src[1:], r.shape, r.arg)]))),
(UPat(Ops.INDEX, src=(UPat(Ops.EXPAND, name="r"),), allow_any_len=True, name="x"),
lambda r,x: UOp(Ops.INDEX, r.dtype, src=(r.src[0],)+
tuple([a.const_like(0) if resolve(x!=y, False) else a for a,x,y in zip(x.src[1:], r.src[0].shape, r.shape)]))),
(UPat(Ops.INDEX, src=(UPat(Ops.RESHAPE, name="r"),), allow_any_len=True, name="x"), map_reshape),
(UPat(Ops.INDEX, src=(UPat(Ops.PAD, name="r"),), allow_any_len=True, name="x"), map_pad),
# bring where to the front
#(UPat(GroupOp.Binary, name="base", src=(UPat.var("c").where(UPat.var("x"), UPat(Ops.INVALID, name="inv")), UPat.var("a"))),
# lambda c,x,a,base,inv: c.where(UOp(base.op, base.dtype, (x,a)), inv)),
#(UPat(GroupOp.Binary, name="base", src=(UPat.var("c").where(UPat(Ops.INVALID, name="inv"), UPat.var("x")), UPat.var("a"))),
# lambda c,x,a,base,inv: c.where(inv, UOp(base.op, base.dtype, (x,a)))),
#(UPat(GroupOp.Binary, name="base", src=(UPat.var("a"), UPat.var("c").where(UPat.var("x"), UPat(Ops.INVALID, name="inv")))),
# lambda c,x,a,base,inv: c.where(UOp(base.op, base.dtype, (a,x)), inv)),
#(UPat(GroupOp.Binary, name="base", src=(UPat.var("a"), UPat.var("c").where(UPat(Ops.INVALID, name="inv"), UPat.var("x")))),
# lambda c,x,a,base,inv: c.where(inv, UOp(base.op, base.dtype, (a,x)))),
# move MAP through elementwise ALU
(UPat(Ops.INDEX, src=(UPat(GroupOp.Elementwise.union({Ops.STORE})),), allow_any_len=True, name="x"),
lambda x: x.src[0].replace(src=tuple([UOp(Ops.INDEX, dtype=s.dtype, src=(s,)+x.src[1:]) for s in x.src[0].src]))),
# map load
(UPat(Ops.INDEX, src=(UPat(Ops.LOAD, name="load"),), allow_any_len=True, name="idx"), map_load),
# INDEX without ranges on a DEFINE is just index 0
(UPat(Ops.INDEX, src=(UPat(GroupOp.Defines),), name="x"), lambda x: x.replace(src=x.src+(UOp.const(dtypes.int, 0),))),
# CONST can't have axes
(UPat(Ops.INDEX, src=(UPat(Ops.CONST,name="c"),)), lambda c: c),
# unbind...but this is too late
(UPat(Ops.BIND, src=(UPat(Ops.DEFINE_VAR, name="v"), UPat(Ops.CONST))), lambda v: v),
])
def name_the_sink(x:UOp):
if x.arg is not None: return None
ranges = sorted([u for u in x.toposort() if u.op is Ops.RANGE], key=lambda y: y.arg)
return x.replace(arg=KernelInfo(name='k_'+'_'.join([colored(str(u.src[0].arg), axis_colors[u.arg[1]]) for u in ranges])))
pm_name = PatternMatcher([
(UPat(Ops.SINK, name="x"), name_the_sink),
])
+4 -5
View File
@@ -13,7 +13,7 @@ from tinygrad.uop.spec import type_verify
# **************** Program Creation ****************
@track_rewrites(name=lambda _ast,_renderer,ret: TracingKey(ret.name, (ret.function_name, ret.ast), ret.src, ret=ret))
@track_rewrites(name=lambda _ast,_renderer,ret: TracingKey(ret.name, (ret.function_name, ret.ast), ret=ret))
def get_program(ast:UOp, renderer:Renderer) -> ProgramSpec:
"""
Transform an AST into a ProgramSpec. May trigger BEAM search.
@@ -27,9 +27,8 @@ def get_program(ast:UOp, renderer:Renderer) -> ProgramSpec:
"""
if getenv("VIZ"): graph_rewrite(ast, PatternMatcher([]), name="View Base AST")
#modified_ast = get_optimized_ast(ast, renderer) if ast.arg is None or ast.arg.opts_to_apply is not None else ast
#if __debug__: type_verify(list(modified_ast.toposort()))
modified_ast = ast
modified_ast = get_optimized_ast(ast, renderer) if ast.arg is None or ast.arg.opts_to_apply is not None else ast
if __debug__: type_verify(list(modified_ast.toposort()))
# linearize
try:
@@ -37,7 +36,7 @@ def get_program(ast:UOp, renderer:Renderer) -> ProgramSpec:
except RuntimeError:
print("***** LINEARIZE FAILURE *****")
print(f"ast = {ast}")
#print(f"opts = {modified_ast.arg.applied_opts}")
print(f"opts = {modified_ast.arg.applied_opts}")
raise
assert uops[-1].op is Ops.SINK, "last uop must be sink"
+1 -2
View File
@@ -1,6 +1,5 @@
from typing import cast
import math, dataclasses
from tinygrad.dtype import dtypes
from tinygrad.uop.ops import UOp, PatternMatcher, UPat, Ops, all_metadata
from tinygrad.helpers import argsort
@@ -8,7 +7,7 @@ def reduce_gradient(ctx:UOp, ret:UOp):
def to_inp_shape(x): return x.reshape(x.shape+(1,)*(len(ret.src[0].shape)-len(x.shape))).expand(ret.src[0].shape)
if ret.arg[0] == Ops.ADD: return (to_inp_shape(ctx),)
if ret.arg[0] == Ops.MAX:
max_is_1s = ret.src[0].ne(to_inp_shape(ret)).ne(ret.src[0].const_like(1).cast(dtypes.bool)).cast(ctx.dtype)
max_is_1s = ret.src[0].eq(to_inp_shape(ret)).cast(ctx.dtype)
div = to_inp_shape(max_is_1s.r(Ops.ADD, ret.arg[1]))
return ((max_is_1s/div) * to_inp_shape(ctx),)
if ret.arg[0] == Ops.MUL: return (to_inp_shape(ctx * ret) / ret.src[0],)
-1
View File
@@ -196,7 +196,6 @@ class Profiling(contextlib.ContextDecorator):
class TracingKey:
display_name:str # display name of this trace event
keys:tuple[str, ...]=() # optional keys to search for related traces
fmt:str|None=None # optional detailed formatting
cat:str|None=None # optional category to color this by
ret:Any=None
-5
View File
@@ -10,7 +10,6 @@ class BatchNorm:
"""
Applies Batch Normalization over a 2D or 3D input.
- Described: https://paperswithcode.com/method/batch-normalization
- Paper: https://arxiv.org/abs/1502.03167v3
See: `Tensor.batchnorm`
@@ -182,7 +181,6 @@ class GroupNorm:
"""
Applies Group Normalization over a mini-batch of inputs.
- Described: https://paperswithcode.com/method/group-normalization
- Paper: https://arxiv.org/abs/1803.08494v3
```python exec="true" source="above" session="tensor" result="python"
@@ -213,7 +211,6 @@ class InstanceNorm:
"""
Applies Instance Normalization over a mini-batch of inputs.
- Described: https://paperswithcode.com/method/instance-normalization
- Paper: https://arxiv.org/abs/1607.08022v3
```python exec="true" source="above" session="tensor" result="python"
@@ -240,7 +237,6 @@ class LayerNorm:
"""
Applies Layer Normalization over a mini-batch of inputs.
- Described: https://paperswithcode.com/method/layer-normalization
- Paper: https://arxiv.org/abs/1607.06450v1
```python exec="true" source="above" session="tensor" result="python"
@@ -287,7 +283,6 @@ class RMSNorm:
"""
Applies Root Mean Square Normalization to input.
- Described: https://paperswithcode.com/method/rmsnorm
- Paper: https://arxiv.org/abs/1910.07467
```python exec="true" source="above" session="tensor" result="python"
-6
View File
@@ -76,8 +76,6 @@ def SGD(params: list[Tensor], lr=0.001, momentum=0.0, weight_decay=0.0, nesterov
Stochastic Gradient Descent (SGD) optimizer with optional momentum and weight decay.
`classic` is a boolean flag that determines whether to use the popular momentum update rule or the classic momentum update rule.
- Described: https://paperswithcode.com/method/sgd
"""
return LARS(params, lr, momentum, weight_decay, nesterov, classic, tcoef=0.0, fused=fused)
@@ -85,7 +83,6 @@ class LARS(Optimizer):
"""
Layer-wise Adaptive Rate Scaling (LARS) optimizer with optional momentum and weight decay.
- Described: https://paperswithcode.com/method/lars
- Paper: https://arxiv.org/abs/1708.03888v3
"""
def __init__(self, params:list[Tensor], lr=0.001, momentum=0.9, weight_decay=1e-4, nesterov=False, classic=True, tcoef=0.001, fused=FUSE_OPTIM):
@@ -119,7 +116,6 @@ def AdamW(params: list[Tensor], lr=0.001, b1=0.9, b2=0.999, eps=1e-8, weight_dec
"""
AdamW optimizer with optional weight decay.
- Described: https://paperswithcode.com/method/adamw
- Paper: https://arxiv.org/abs/1711.05101v3
"""
return LAMB(params, lr, b1, b2, eps, weight_decay, adam=True, fused=fused)
@@ -127,7 +123,6 @@ def Adam(params: list[Tensor], lr=0.001, b1=0.9, b2=0.999, eps=1e-8, fused=FUSE_
"""
Adam optimizer.
- Described: https://paperswithcode.com/method/adam
- Paper: https://arxiv.org/abs/1412.6980
"""
return LAMB(params, lr, b1, b2, eps, 0.0, adam=True, fused=fused)
@@ -136,7 +131,6 @@ class LAMB(Optimizer):
"""
LAMB optimizer with optional weight decay.
- Described: https://paperswithcode.com/method/lamb
- Paper: https://arxiv.org/abs/1904.00962
"""
def __init__(self, params: list[Tensor], lr=0.001, b1=0.9, b2=0.999, eps=1e-6, weight_decay=0.0, adam=False, fused=FUSE_OPTIM):
+4 -2
View File
@@ -14,7 +14,7 @@ from tinygrad.dtype import ImageDType, AddrSpace
from tinygrad.helpers import all_same, colored, ansilen, dedup, prod, round_up, to_function_name, unwrap, argfix, DEBUG, TC_SELECT, TC_OPT, AMX
from tinygrad.shape.shapetracker import ShapeTracker
from tinygrad.shape.view import strides_for_shape, get_contraction
from tinygrad.schedule.kernelize import view_left
from tinygrad.opt.swizzler import view_left, view_right
class OptOps(Enum):
TC = auto(); UPCAST = auto(); UNROLL = auto(); LOCAL = auto() # noqa: E702
@@ -52,6 +52,8 @@ class TensorCoreOptions:
class Kernel:
def __init__(self, ast:UOp, opts:Renderer|None=None):
assert ast.op is Ops.SINK, ast.op
ast = graph_rewrite(ast, view_left, name="Main View Left")
ast = graph_rewrite(ast, view_right, name="Main View Right")
self.ast = ast
self.opts = opts if opts is not None else Device[Device.DEFAULT].renderer
@@ -73,7 +75,7 @@ class Kernel:
self.sts.append(unwrap(x.src[0].st))
# add a shapetracker to the end to track the full shape, with 0 strides so it can merge
full_shape = ast.full_shape
full_shape = self.ast.full_shape
self.sts.append(ShapeTracker.from_shape(full_shape, (0,)*len(full_shape)))
# parameters for optimization
+108
View File
@@ -0,0 +1,108 @@
from tinygrad.uop.ops import UOp, Ops, GroupOp, PatternMatcher, UPat, graph_rewrite, resolve, sint
from tinygrad.shape.view import View, strides_for_shape, get_contraction_with_reduce
from tinygrad.helpers import unwrap, prod, all_same
from tinygrad.shape.shapetracker import ShapeTracker
from tinygrad.schedule.grouper import ALWAYS_CONTIGUOUS
# **** swizzler
merge_views = PatternMatcher([
# merge adjacent views
(UPat(Ops.VIEW, src=(UPat(Ops.VIEW, name="v1"),), name="v2"), lambda v1,v2: v1.replace(arg=v1.arg+v2.arg)),
# replace MovementOps with VIEW
(UPat(GroupOp.Movement, src=(UPat.var("x"),), name="mop"), lambda mop,x: x.base.view(mop.st)),
# remove NOOP views
(UPat.var("x").view(name="view"),
lambda x,view: x if x.st is not None and x.op not in GroupOp.Defines and view.st.contiguous and view.shape == x.shape else None),
(UPat(GroupOp.All-{Ops.DEFINE_GLOBAL}).view(name="view"),
lambda view: view.const_like(0) if (mask:=view.st.views[-1].mask) is not None and any((x[1]-x[0]) == 0 for x in mask) else None),
# only unmaksed VIEW on CONST replaces the ShapeTracker
(UPat(Ops.VIEW, src=(UPat((Ops.CONST, Ops.DEFINE_VAR), name="x"),), name="view"),
lambda x,view: x.replace(src=(x.src[0].replace(arg=x.st+view.st),)) if all(v.mask is None for v in (x.st+view.st).views) else None),
# VIEW on SINK is SINK
(UPat(Ops.VIEW, name="v").sink(), lambda v: v.src[0].sink()),
])
def reduce_push_add_ones(src:UOp, r:UOp, view:UOp):
# contiguous, expand, and the same with ones removed
if unwrap(view.st).contiguous and len(r.shape) < len(view.shape) and \
tuple(x for x in r.shape if resolve(x != 1)) == tuple(x for x in view.shape if resolve(x != 1)):
new_shape: list[sint] = []
new_reduce_axis = []
if (contraction:=get_contraction_with_reduce(view.shape, r.shape, r.arg[1])) is None: return None
for i,pairs in enumerate(contraction):
new_shape_chunk = [view.shape[p] for p in pairs]
if i in r.arg[1]:
# if this is a reduce axis, we need a 1 in the view here to put it
assert len(new_shape_chunk) > 0
new_shape += [1]*(len(pairs)-1) + [src.shape[i]]
new_reduce_axis.append(len(new_shape)-1)
else:
# otherwise, pass through the new_shape_chunk
new_shape += new_shape_chunk
ret = r.replace(src=(src.reshape(tuple(new_shape)),), arg=(r.arg[0], tuple(new_reduce_axis))+r.arg[2:])
assert ret.shape == view.shape, f"shape mismatch on reduce_push_add_ones, {ret.shape} != {view.shape}"
return ret
return None
view_left = merge_views+PatternMatcher([
# view before elementwise and buffer ops
(UPat(Ops.VIEW, src=(UPat({*GroupOp.ALU, Ops.CAST, Ops.BITCAST, Ops.BIND, Ops.LOAD, Ops.STORE, Ops.VALID, Ops.SINK}, name="e"),), name="view"),
lambda e,view: e.replace(src=tuple(s.view(view.st) for s in e.src))),
# if there's ones added after reduce, put this before the reduce
(UPat(Ops.VIEW, src=(UPat(Ops.REDUCE_AXIS, src=(UPat.var("src"),), name="r"),), name="view"), reduce_push_add_ones),
])
def apply_swizzle(u:UOp) -> UOp: return graph_rewrite(u, view_left, name="Sub View Left")
# change reduceop axes and input ShapeTrackers, view gets replaced with a reshape.
def swizzle_reduceop(r:UOp, src:UOp, view:UOp, fuse=False):
# contiguous and same size can push to children
# if there's a reduce child, shapes match with ones removed
if unwrap(view.st).contiguous and view.size == r.size and \
(not (len(r.arg) == 3 and r.arg[2]) or # arg[2] = True is fuse marker
tuple((i,x) for i,x in enumerate(r.shape) if resolve(x != 1)) == tuple((i,x) for i,x in enumerate(view.shape) if resolve(x != 1))):
return None
# swizzle the input
input_st = ShapeTracker.from_shape(src.shape)
tmp = input_st.permute(tuple(i for i in range(len(input_st.shape)) if i not in r.axis_arg)+r.axis_arg)
prshape = prod(rshape:=tmp.shape[-len(r.axis_arg):])
strides = strides_for_shape(rshape)
nv = [View.create(v.shape+rshape, tuple(x*prshape for x in v.strides)+strides,
v.offset*prshape, v.mask+tuple((0,s) for s in rshape) if v.mask is not None else None) for v in unwrap(view.st).views]
new_view = tmp + ShapeTracker(tuple(nv))
swizzled_input = apply_swizzle(src.view(new_view))
# create a new reduceop
new_axis = tuple(range(len(view.shape), len(view.shape) + len(r.axis_arg)))
if fuse: red = UOp(Ops.REDUCE_AXIS, r.dtype, (swizzled_input.fuse(),), (r.arg[0], new_axis, True))
else: red = UOp(Ops.REDUCE_AXIS, r.dtype, (swizzled_input,), (r.arg[0], new_axis))
return red.reshape(view.shape)
def reduceop_view_right(src:UOp, v:UOp, r:UOp):
assert unwrap(v.st).contiguous and v.size == src.size, f"can't compute new axis for {src.shape} -> {r.shape}"
new_axis = [i for i,(s,u) in enumerate(zip(src.shape, r.shape)) if s != u]
return src.r(r.arg[0], tuple(new_axis)).reshape(r.shape)
def elementwise_view_right(root:UOp):
if not (swizzles:=[x for x in root.src if x.op is Ops.VIEW and x.base.op not in ALWAYS_CONTIGUOUS]): return None
assert all_same([x.base.size for x in swizzles]), f"swizzle inputs must have the same size {swizzles}"
# place view after applying the elementwise op
new_st = ShapeTracker.from_shape(swizzles[0].base.shape)
new_src = [x.base if x.base.shape==new_st.shape else apply_swizzle(x.view(new_st)) for x in root.src]
# reshape to match downstream shapes
return root.replace(src=tuple(new_src)).reshape(root.shape)
# push VIEW to children
view_right = merge_views+PatternMatcher([
# push a non contiguous ShapeTracker through reduceop
(UPat(Ops.VIEW, src=(UPat(Ops.REDUCE_AXIS, src=(UPat.var("src"),), name="r"),), name="view"), swizzle_reduceop),
# apply view after reduceops
(UPat(Ops.REDUCE_AXIS, src=(UPat(Ops.VIEW, src=(UPat(GroupOp.All-ALWAYS_CONTIGUOUS, name="src"),), name="v"),), name="r"), reduceop_view_right),
# apply view after elementwise ops
(UPat(GroupOp.All-{Ops.SINK, Ops.REDUCE_AXIS}, name="root"), elementwise_view_right),
# merge axes for double reduce (invert of SPLIT_REDUCEOP=1)
(UPat(Ops.REDUCE_AXIS, src=(UPat(Ops.REDUCE_AXIS, name="r1"),), name="r2"),
lambda r1,r2: r1.replace(arg=(r1.arg[0], r2.arg[1]+r1.arg[1])) if r1.arg[0] is r2.arg[0] else None),
# add VIEW to any DEFINE_GLOBAL that somehow lost its view
(UPat(Ops.STORE, src=(UPat(Ops.DEFINE_GLOBAL, name="d"),), name="x", allow_any_len=True), lambda d,x: x.replace(src=(d.view(d.st),)+x.src[1:])),
])
+1 -1
View File
@@ -158,7 +158,7 @@ class CStyleLanguage(Renderer):
# naming
prefix = None
if u.op is Ops.SPECIAL: r[u] = u.arg[0]
elif u.op is Ops.RANGE: r[u] = f"ridx{u.arg[0]}"
elif u.op is Ops.RANGE: r[u] = f"ridx{u.arg}"
else:
prefix = {Ops.WMMA: "wmma", Ops.DEFINE_LOCAL: "temp", Ops.CONST: "const",
Ops.CAST: "cast", Ops.BITCAST: "cast", Ops.GEP: "gep", Ops.VECTORIZE: "cast", Ops.PRECAST: "precast",
+1 -1
View File
@@ -3,7 +3,7 @@ from tinygrad.helpers import all_int, prod, unwrap, dedup, DONT_REALIZE_EXPAND,
from tinygrad.shape.shapetracker import ShapeTracker
ALWAYS_CONTIGUOUS = {Ops.CONTIGUOUS, Ops.ASSIGN, Ops.COPY, Ops.BUFFER, Ops.BUFFER_VIEW,
Ops.CONST, Ops.BIND, Ops.DEVICE, Ops.MSELECT, Ops.MSTACK}
Ops.CONST, Ops.BIND, Ops.DEVICE, Ops.MSELECT, Ops.MSTACK, Ops.DEFINE_GLOBAL}
# **** Grouper decides which of the UOps realize
+3 -106
View File
@@ -7,7 +7,6 @@ from tinygrad.helpers import Metadata, all_int, all_same, colored, prod, dedup,
from tinygrad.dtype import ImageDType, dtypes
from tinygrad.schedule.multi import multi_pm
from tinygrad.shape.shapetracker import ShapeTracker
from tinygrad.shape.view import View, strides_for_shape, get_contraction_with_reduce
from tinygrad.schedule.grouper import group_realizes, ALWAYS_CONTIGUOUS
# creation can recurse a lot
@@ -148,113 +147,15 @@ create_kernels = PatternMatcher([
lambda ms: UOp(Ops.MSTACK, ms.dtype, tuple(x.src[0] for x in ms.src)).reshape(ms.src[0].arg)),
])
# **** swizzler
merge_views = PatternMatcher([
# merge adjacent views
(UPat(Ops.VIEW, src=(UPat(Ops.VIEW, name="v1"),), name="v2"), lambda v1,v2: v1.replace(arg=v1.arg+v2.arg)),
# replace MovementOps with VIEW
(UPat(GroupOp.Movement, src=(UPat.var("x"),), name="mop"), lambda mop,x: x.base.view(mop.st)),
# remove NOOP views
(UPat.var("x").view(name="view"), lambda x,view: x if x.st is not None and view.st.contiguous and view.shape == x.shape else None),
(UPat(GroupOp.All-{Ops.DEFINE_GLOBAL}).view(name="view"),
lambda view: view.const_like(0) if (mask:=view.st.views[-1].mask) is not None and any((x[1]-x[0]) == 0 for x in mask) else None),
# only unmaksed VIEW on CONST replaces the ShapeTracker
(UPat(Ops.VIEW, src=(UPat((Ops.CONST, Ops.DEFINE_VAR), name="x"),), name="view"),
lambda x,view: x.replace(src=(x.src[0].replace(arg=x.st+view.st),)) if all(v.mask is None for v in (x.st+view.st).views) else None),
])
def reduce_push_add_ones(src:UOp, r:UOp, view:UOp):
# contiguous, expand, and the same with ones removed
if unwrap(view.st).contiguous and len(r.shape) < len(view.shape) and \
tuple(x for x in r.shape if resolve(x != 1)) == tuple(x for x in view.shape if resolve(x != 1)):
new_shape: list[sint] = []
new_reduce_axis = []
if (contraction:=get_contraction_with_reduce(view.shape, r.shape, r.arg[1])) is None: return None
for i,pairs in enumerate(contraction):
new_shape_chunk = [view.shape[p] for p in pairs]
if i in r.arg[1]:
# if this is a reduce axis, we need a 1 in the view here to put it
assert len(new_shape_chunk) > 0
new_shape += [1]*(len(pairs)-1) + [src.shape[i]]
new_reduce_axis.append(len(new_shape)-1)
else:
# otherwise, pass through the new_shape_chunk
new_shape += new_shape_chunk
ret = r.replace(src=(src.reshape(tuple(new_shape)),), arg=(r.arg[0], tuple(new_reduce_axis))+r.arg[2:])
assert ret.shape == view.shape, f"shape mismatch on reduce_push_add_ones, {ret.shape} != {view.shape}"
return ret
return None
view_left = merge_views+PatternMatcher([
# view before elementwise and buffer ops
(UPat(Ops.VIEW, src=(UPat({*GroupOp.ALU, Ops.CAST, Ops.BITCAST, Ops.BIND, Ops.LOAD, Ops.STORE, Ops.VALID, Ops.SINK}, name="e"),), name="view"),
lambda e,view: e.replace(src=tuple(s.view(view.st) for s in e.src))),
# if there's ones added after reduce, put this before the reduce
(UPat(Ops.VIEW, src=(UPat(Ops.REDUCE_AXIS, src=(UPat.var("src"),), name="r"),), name="view"), reduce_push_add_ones),
])
def apply_swizzle(u:UOp) -> UOp: return graph_rewrite(u, view_left, name="Sub View Left")
# change reduceop axes and input ShapeTrackers, view gets replaced with a reshape.
def swizzle_reduceop(r:UOp, src:UOp, view:UOp, fuse=False):
# contiguous and same size can push to children
# if there's a reduce child, shapes match with ones removed
if unwrap(view.st).contiguous and view.size == r.size and \
(not (len(r.arg) == 3 and r.arg[2]) or # arg[2] = True is fuse marker
tuple((i,x) for i,x in enumerate(r.shape) if resolve(x != 1)) == tuple((i,x) for i,x in enumerate(view.shape) if resolve(x != 1))):
return None
# swizzle the input
input_st = ShapeTracker.from_shape(src.shape)
tmp = input_st.permute(tuple(i for i in range(len(input_st.shape)) if i not in r.axis_arg)+r.axis_arg)
prshape = prod(rshape:=tmp.shape[-len(r.axis_arg):])
strides = strides_for_shape(rshape)
nv = [View.create(v.shape+rshape, tuple(x*prshape for x in v.strides)+strides,
v.offset*prshape, v.mask+tuple((0,s) for s in rshape) if v.mask is not None else None) for v in unwrap(view.st).views]
new_view = tmp + ShapeTracker(tuple(nv))
swizzled_input = apply_swizzle(src.view(new_view))
# create a new reduceop
new_axis = tuple(range(len(view.shape), len(view.shape) + len(r.axis_arg)))
if fuse: red = UOp(Ops.REDUCE_AXIS, r.dtype, (swizzled_input.fuse(),), (r.arg[0], new_axis, True))
else: red = UOp(Ops.REDUCE_AXIS, r.dtype, (swizzled_input,), (r.arg[0], new_axis))
return red.reshape(view.shape)
def reduceop_view_right(src:UOp, v:UOp, r:UOp):
assert unwrap(v.st).contiguous and v.size == src.size, f"can't compute new axis for {src.shape} -> {r.shape}"
new_axis = [i for i,(s,u) in enumerate(zip(src.shape, r.shape)) if s != u]
return src.r(r.arg[0], tuple(new_axis)).reshape(r.shape)
def elementwise_view_right(root:UOp):
if not (swizzles:=[x for x in root.src if x.op is Ops.VIEW and x.base.op not in ALWAYS_CONTIGUOUS]): return None
assert all_same([x.base.size for x in swizzles]), f"swizzle inputs must have the same size {swizzles}"
# place view after applying the elementwise op
new_st = ShapeTracker.from_shape(swizzles[0].base.shape)
new_src = [x.base if x.base.shape==new_st.shape else apply_swizzle(x.view(new_st)) for x in root.src]
# reshape to match downstream shapes
return root.replace(src=tuple(new_src)).reshape(root.shape)
# push VIEW to children
view_right = merge_views+PatternMatcher([
# push a non contiguous ShapeTracker through reduceop
(UPat(Ops.VIEW, src=(UPat(Ops.REDUCE_AXIS, src=(UPat.var("src"),), name="r"),), name="view"), swizzle_reduceop),
# apply view after reduceops
(UPat(Ops.REDUCE_AXIS, src=(UPat(Ops.VIEW, src=(UPat(GroupOp.All-ALWAYS_CONTIGUOUS, name="src"),), name="v"),), name="r"), reduceop_view_right),
# apply view after elementwise ops
(UPat(GroupOp.All-{Ops.SINK, Ops.REDUCE_AXIS}, name="root"), elementwise_view_right),
# merge axes for double reduce (invert of SPLIT_REDUCEOP=1)
(UPat(Ops.REDUCE_AXIS, src=(UPat(Ops.REDUCE_AXIS, name="r1"),), name="r2"),
lambda r1,r2: r1.replace(arg=(r1.arg[0], r2.arg[1]+r1.arg[1])) if r1.arg[0] is r2.arg[0] else None),
])
# **** fix kernel AST
add_buffer_ops = PatternMatcher([
# LOAD
(UPat(Ops.BUFFER, name="x"), lambda ctx,x: UOp.load(UOp(Ops.DEFINE_GLOBAL, x.dtype.ptr(x.size), (), ctx.index(x)).reshape(x.shape),)),
(UPat(Ops.BUFFER, name="x"), lambda ctx,x: UOp.load(UOp(Ops.DEFINE_GLOBAL, x.dtype.ptr(x.size), (), ctx.index(x)).view(x.st),)),
# STORE (except for meta ops)
(UPat(Ops.SINK, src=(UPat(Ops.CONTIGUOUS, src=(UPat(GroupOp.Meta, name="x"),),))), lambda x:x),
(UPat(Ops.SINK, src=UPat(GroupOp.All-{Ops.STORE}), name="sink"), lambda ctx,sink:
UOp.sink(*[UOp.store(UOp(Ops.DEFINE_GLOBAL, (s:=x.base).dtype.ptr(ctx[i].size), (), i).reshape(s.shape), s) for i,x in enumerate(sink.src)])),
UOp.sink(*[UOp.store(UOp(Ops.DEFINE_GLOBAL, (s:=x.base).dtype.ptr(ctx[i].size), (), i).view(s.st), s) for i,x in enumerate(sink.src)])),
# passthrough ASSIGN
(UPat(Ops.ASSIGN, name="x"), lambda x: x.src[1]),
# VALID
@@ -293,8 +194,6 @@ def fix_kernel_ast(k:UOp) -> UOp|None:
if k.arg.ast.op in GroupOp.Meta or all(s.op is Ops.STORE for s in k.arg.ast.src): return None
# replace global memory ops with the BUFFER they write to
ast = graph_rewrite(k.arg.ast, replace_globals, bottom_up=True, name="replace globals")
# push views to edges
#ast = graph_rewrite(graph_rewrite(ast, view_left, name="Main View Left"), view_right, name="Main View Right")
# replace buffer with define_global + add load/store last
bufs = []
for s in k.src:
@@ -382,7 +281,7 @@ def fuse_arange(root:UOp):
return root.substitute(fuse_rep, name="fuse_arange") if fuse_rep else None
do_fuse = PatternMatcher([
(UPat(Ops.FUSE, name="x"), do_fusion),
#(UPat(Ops.FUSE, name="x"), do_fusion),
(UPat(Ops.REDUCE_AXIS, name="root"), fuse_arange),
])
@@ -446,8 +345,6 @@ def get_kernelize_map(sink:UOp) -> dict[UOp, UOp]:
tensor_map = graph_rewrite_map(tensor_map[sink], add_contiguous, ctx=realize_map, bottom_up=True, input_map=tensor_map, name="add_contiguous")
tensor_map = graph_rewrite_map(tensor_map[sink], finalize_contiguous+remove_tags, input_map=tensor_map, name="finalize_contiguous")
# TODO: move view_left/view_right here
# group into kernels (this is context-free)
tensor_map = graph_rewrite_map(tensor_map[sink], create_kernels, input_map=tensor_map, name="create_kernels")
-29
View File
@@ -2332,8 +2332,6 @@ class Tensor(MathTrait):
NOTE: unlike PyTorch, this implementation is not limited to only 2d pooling and instead works for any number of dimensions.
See: https://paperswithcode.com/method/average-pooling
```python exec="true" source="above" session="tensor" result="python"
t = Tensor.arange(25).reshape(1, 1, 5, 5)
print(t.avg_pool2d().numpy())
@@ -2380,8 +2378,6 @@ class Tensor(MathTrait):
NOTE: unlike PyTorch, this implementation is not limited to only 2d pooling and instead works for any number of dimensions.
See: https://paperswithcode.com/method/max-pooling
```python exec="true" source="above" session="tensor" result="python"
t = Tensor.arange(25).reshape(1, 1, 5, 5)
print(t.max_pool2d().numpy())
@@ -3010,8 +3006,6 @@ class Tensor(MathTrait):
"""
Applies the Rectified Linear Unit (ReLU) function element-wise.
- Described: https://paperswithcode.com/method/relu
```python exec="true" source="above" session="tensor" result="python"
print(Tensor([-3., -2., -1., 0., 1., 2., 3.]).relu().numpy())
```
@@ -3048,7 +3042,6 @@ class Tensor(MathTrait):
Applies the Hardsigmoid function element-wise.
NOTE: default `alpha` and `beta` values are taken from torch
- Described: https://paperswithcode.com/method/hard-sigmoid
- See: https://pytorch.org/docs/stable/generated/torch.nn.functional.hardsigmoid.html
```python exec="true" source="above" session="tensor" result="python"
@@ -3291,7 +3284,6 @@ class Tensor(MathTrait):
"""
Applies the Exponential Linear Unit (ELU) function element-wise.
- Described: https://paperswithcode.com/method/elu
- Paper: https://arxiv.org/abs/1511.07289v5
```python exec="true" source="above" session="tensor" result="python"
@@ -3304,7 +3296,6 @@ class Tensor(MathTrait):
"""
Applies the Continuously differentiable Exponential Linear Unit (CELU) function element-wise.
- Described: https://paperswithcode.com/method/celu
- Paper: https://arxiv.org/abs/1704.07483
```python exec="true" source="above" session="tensor" result="python"
@@ -3317,7 +3308,6 @@ class Tensor(MathTrait):
"""
Applies the Scaled Exponential Linear Unit (SELU) function element-wise.
- Described: https://paperswithcode.com/method/selu
- Paper: https://arxiv.org/abs/1706.02515v5
```python exec="true" source="above" session="tensor" result="python"
@@ -3342,7 +3332,6 @@ class Tensor(MathTrait):
"""
Applies the Sigmoid Linear Unit (SiLU) function element-wise.
- Described: https://paperswithcode.com/method/silu
- Paper: https://arxiv.org/abs/1606.08415
```python exec="true" source="above" session="tensor" result="python"
@@ -3355,7 +3344,6 @@ class Tensor(MathTrait):
"""
Applies the ReLU6 function element-wise.
- Described: https://paperswithcode.com/method/relu6
- Paper: https://arxiv.org/abs/1704.04861v1
```python exec="true" source="above" session="tensor" result="python"
@@ -3368,7 +3356,6 @@ class Tensor(MathTrait):
"""
Applies the Hardswish function element-wise.
- Described: https://paperswithcode.com/method/hard-swish
- Paper: https://arxiv.org/abs/1905.02244v5
```python exec="true" source="above" session="tensor" result="python"
@@ -3453,8 +3440,6 @@ class Tensor(MathTrait):
"""
Applies the Hardtanh function element-wise.
- Described: https://paperswithcode.com/method/hardtanh-activation
```python exec="true" source="above" session="tensor" result="python"
print(Tensor([-1.5, -1.0, -0.5, 0., 0.5, 1.0, 1.5]).hardtanh().numpy())
```
@@ -3479,7 +3464,6 @@ class Tensor(MathTrait):
"""
Applies the Gaussian Error Linear Unit (GELU) function element-wise.
- Described: https://paperswithcode.com/method/gelu
- Paper: https://arxiv.org/abs/1606.08415v5
```python exec="true" source="above" session="tensor" result="python"
@@ -3492,8 +3476,6 @@ class Tensor(MathTrait):
"""
Applies the Sigmoid GELU approximation element-wise.
- Described: https://paperswithcode.com/method/gelu
```python exec="true" source="above" session="tensor" result="python"
print(Tensor([-3., -2., -1., 0., 1., 2., 3.]).quick_gelu().numpy())
```
@@ -3504,8 +3486,6 @@ class Tensor(MathTrait):
"""
Applies the Leaky ReLU function element-wise.
- Described: https://paperswithcode.com/method/leaky-relu
```python exec="true" source="above" session="tensor" result="python"
print(Tensor([-3., -2., -1., 0., 1., 2., 3.]).leaky_relu().numpy())
```
@@ -3519,7 +3499,6 @@ class Tensor(MathTrait):
"""
Applies the Mish function element-wise.
- Described: https://paperswithcode.com/method/mish
- Paper: https://arxiv.org/abs/1908.08681v3
```python exec="true" source="above" session="tensor" result="python"
@@ -3532,8 +3511,6 @@ class Tensor(MathTrait):
"""
Applies the Softplus function element-wise.
- Described: https://paperswithcode.com/method/softplus
```python exec="true" source="above" session="tensor" result="python"
print(Tensor([-3., -2., -1., 0., 1., 2., 3.]).softplus().numpy())
```
@@ -3544,8 +3521,6 @@ class Tensor(MathTrait):
"""
Applies the Softsign function element-wise.
- Described: https://paperswithcode.com/method/softsign
```python exec="true" source="above" session="tensor" result="python"
print(Tensor([-3., -2., -1., 0., 1., 2., 3.]).softsign().numpy())
```
@@ -3839,7 +3814,6 @@ class Tensor(MathTrait):
"""
Applies Layer Normalization over a mini-batch of inputs.
- Described: https://paperswithcode.com/method/layer-normalization
- Paper: https://arxiv.org/abs/1607.06450v1
```python exec="true" source="above" session="tensor" result="python"
@@ -3858,7 +3832,6 @@ class Tensor(MathTrait):
"""
Applies Batch Normalization over a mini-batch of inputs.
- Described: https://paperswithcode.com/method/batch-normalization
- Paper: https://arxiv.org/abs/1502.03167
```python exec="true" source="above" session="tensor" result="python"
@@ -3883,7 +3856,6 @@ class Tensor(MathTrait):
NOTE: dropout is only applied when `Tensor.training` is `True`.
- Described: https://paperswithcode.com/method/dropout
- Paper: https://jmlr.org/papers/v15/srivastava14a.html
```python exec="true" source="above" session="tensor" result="python"
@@ -3925,7 +3897,6 @@ class Tensor(MathTrait):
Computes scaled dot-product attention.
`self` is the query tensor, `key` is the key tensor, and `value` is the value tensor.
- Described: https://paperswithcode.com/method/scaled
- Paper: https://arxiv.org/abs/1706.03762v7
```python exec="true" source="above" session="tensor" result="python"
-3
View File
@@ -13,7 +13,6 @@ class Ops(FastEnum):
# buffer ops
COPY = auto(); BUFFER = auto(); BUFFER_VIEW = auto(); MSELECT = auto(); MSTACK = auto() # noqa: E702
CHILDREN = auto()
# ops that adjust the behavior of the scheduler
CONTIGUOUS = auto(); CONTIGUOUS_BACKWARD = auto(); DETACH = auto(); FUSE = auto() # noqa: E702
@@ -24,7 +23,6 @@ class Ops(FastEnum):
# 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
INVALID = auto()
# view is what all movement ops become
VIEW = auto()
@@ -84,7 +82,6 @@ class GroupOp:
Ops.XOR, Ops.SHL, Ops.SHR, Ops.OR, Ops.AND, Ops.THREEFRY, Ops.SUB, Ops.FDIV, Ops.POW}
Ternary = {Ops.WHERE, Ops.MULACC}
ALU = set.union(Unary, Binary, Ternary)
Elementwise = set.union(ALU, {Ops.CAST, Ops.BITCAST})
Defines = {Ops.DEFINE_GLOBAL, Ops.DEFINE_LOCAL, Ops.DEFINE_REG}
+18 -20
View File
@@ -136,10 +136,8 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
@functools.cached_property
def st(self) -> ShapeTracker|None:
if self.op in GroupOp.Block: return None
if self.op in GroupOp.Block or self.op is Ops.INDEX: return None
from tinygrad.shape.shapetracker import ShapeTracker
if self.op in {Ops.DEFINE_GLOBAL, Ops.DEFINE_LOCAL, Ops.DEFINE_REG}:
return ShapeTracker.from_shape((cast(PtrDType, self.dtype).size,))
# VIEW and MovementOps define a new ShapeTracker from the arg
if self.op is Ops.VIEW: return self.arg
if self.op in GroupOp.Movement: return unwrap(self.src[0].st).mop(self.op, self.arg)
@@ -152,11 +150,16 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
# BUFFER/BUFFER_VIEW and KERNEL only have a size
if self.op in {Ops.BUFFER, Ops.BUFFER_VIEW}: return ShapeTracker.from_shape((self.size,))
if self.op is Ops.KERNEL: return ShapeTracker.from_shape((self.arg.ast.size,))
#if self.op in {Ops.DEFINE_GLOBAL, Ops.DEFINE_LOCAL, Ops.DEFINE_REG}: return ShapeTracker.from_shape((self.dtype.size,))
if self.op in {Ops.DEFINE_GLOBAL, Ops.DEFINE_LOCAL, Ops.DEFINE_REG}:
sz = cast(PtrDType, self.dtype).size
return ShapeTracker.from_shape((sz,)) if sz > 0 else None
# hack for PTX, CASTing the ptr loses the shape
if self.op is Ops.CAST and self.src[0].op is Ops.DEFINE_GLOBAL: return None
# otherwise we get the shape from sources
if not (src_sts := [x.st for x in self.src if x.st is not None]): return None
if not all_same([x.shape for x in src_sts]): raise RuntimeError(f"UOp sources must have the same shape {self} {[x.shape for x in src_sts]}")
assert all_same([x.shape for x in src_sts]), f"UOp sources must have the same shape {self} {[x.shape for x in src_sts]}"
match self.op:
case Ops.MULTI: shape = tuple(self.src[0].shape[a]*len(self.device) if a == self.axis else s for a,s in enumerate(self.src[0].shape))
case Ops.BITCAST:
@@ -173,7 +176,9 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
parent_shapes = [x.full_shape for x in self.src]
return tuple(smax(x) for x in itertools.zip_longest(*parent_shapes, fillvalue=1))
@property
def shape(self) -> tuple[sint, ...]: return unwrap(self.st).shape
def shape(self) -> tuple[sint, ...]:
assert self.st is not None, f"{self.op} doesn't have a shape"
return unwrap(self.st).shape
@property
def size(self) -> int: return self.arg[0] if self.op is Ops.BUFFER_VIEW else self.arg if self.op is Ops.BUFFER else unwrap(self.st).size
@@ -214,15 +219,11 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
return ret
def sink(self, *srcs:UOp|None, **kwargs): return UOp(Ops.SINK, dtypes.void, (self,)+tuple([x for x in srcs if x is not None]), **kwargs)
def detach(self): return UOp(Ops.DETACH, self.dtype, (self,))
def index(self, *srcs:UOp|None): return UOp(Ops.INDEX, self.dtype, (self,)+tuple([x for x in srcs if x is not None]))
def index(self, idx:UOp, valid:UOp|None=None): return UOp(Ops.INDEX, self.dtype, (self,idx,valid) if valid is not None else (self,idx))
def __getitem__(self, idx): return self.index(idx)
def const_like(self, b:ConstLike):
# constants can optionally have a DEVICE source
try:
st = self.st
except RuntimeError:
st = None
return UOp.const(self.dtype, b, device=self._device, shape=st.shape if st is not None else None)
return UOp.const(self.dtype, b, device=self._device, shape=self.shape if self.st is not None else None)
def broadcast(self, count:int):
assert self.dtype.count == 1
if count == 1: return self
@@ -254,15 +255,11 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
if isinstance(b, UOp): return b.unbind()[0] if b.op is Ops.BIND else b
if isinstance(b, tuple) and all_same(b): b = b[0] # doesn't have to be a VCONST if they are all the same
ret = UOp(Ops.VCONST if isinstance(b, tuple) else Ops.CONST, dtype, arg=dtypes.as_const(b, dtype))
#if shape is not None:
#from tinygrad.shape.shapetracker import ShapeTracker
#ret = ret.replace(src=(UOp(Ops.VIEW, dtypes.void, (), ShapeTracker.from_shape(shape, (0,)*len(shape))),))
if shape is not None:
from tinygrad.shape.shapetracker import ShapeTracker
ret = ret.replace(src=(UOp(Ops.VIEW, dtypes.void, (), ShapeTracker.from_shape(shape, (0,)*len(shape))),))
if device is not None:
ret = ret.replace(src=(UOp(Ops.DEVICE, arg=device),))
#ret = ret.replace(src=(UOp(Ops.DEVICE, arg=device).view(unwrap(ret.st)),))
# only has shape if it has device?
if shape is not None:
ret = ret.reshape((1,)*len(shape)).expand(shape)
ret = ret.replace(src=(UOp(Ops.DEVICE, arg=device).view(unwrap(ret.st)),))
return ret
@staticmethod
def range(dtype:DType, end:sint, idx:int): return UOp(Ops.RANGE, dtype=dtype, src=(sint_to_uop(end),), arg=idx)
@@ -645,6 +642,7 @@ class UPat(MathTrait):
def const(dtype:DType|tuple[DType, ...]|None, b:ConstType): return UPat(Ops.CONST, dtype=dtype, arg=b)
# copied from UOp
def sink(self, *srcs:UPat|None, **kwargs): return UPat(Ops.SINK, dtypes.void, (self,)+tuple([x for x in srcs if x is not None]), **kwargs)
def index(self, idx:UPat, valid:UPat|None=None): return UPat(Ops.INDEX, self.dtype, (self,idx,valid) if valid is not None else (self,idx))
def view(self, st=None, **kwargs): return UPat(Ops.VIEW, self.dtype, (self,), st, **kwargs)
def cast(self, dtype=None, **kwargs): return UPat(Ops.CAST, dtype, (self,), **kwargs)
+2 -2
View File
@@ -22,7 +22,7 @@ try:
(UPat(Ops.SPECIAL, src=(), name="x"), lambda x: UOp(Ops.SPECIAL, arg=x.arg[0], src=(x.ufix(x.arg[1]),))),
(UPat(Ops.SPECIAL, src=UPat(Ops.NOOP), name="x"), lambda x,ctx: UOp(Ops.NOOP, arg=create_bounded(x.arg, 0, x.src[0].arg-1, ctx[0]))),
(UPat(Ops.DEFINE_VAR, name="x"), lambda x,ctx: UOp(Ops.NOOP, arg=create_bounded(x.arg[0], x.arg[1], x.arg[2], ctx[0]))),
(UPat(Ops.RANGE, name="x"), lambda x,ctx: UOp(Ops.NOOP, arg=create_bounded(f"ridx{x.arg[0]}", 0, x.src[0].arg-1, ctx[0]))),
(UPat(Ops.RANGE, name="x"), lambda x,ctx: UOp(Ops.NOOP, arg=create_bounded(f"ridx{x.arg}", 0, x.src[0].arg-1, ctx[0]))),
(UPat(Ops.LOAD, name="x"), lambda x,ctx: UOp(Ops.NOOP, arg=create_bounded(f"load{ctx[1].setdefault(x, len(ctx[1]))}", x.vmin, x.vmax, ctx[0]))),
(UPat(Ops.CONST, name="x"), lambda x,ctx: UOp(Ops.NOOP, arg=(z3.BoolVal if dtypes.is_bool(x.dtype) else z3.IntVal)(x.arg, ctx=ctx[0].ctx))),
(UPat(Ops.CAST, name="x"), lambda x: x.src[0]),
@@ -134,7 +134,7 @@ spec = PatternMatcher([
(UPat(Ops.DEFINE_REG, src=()), lambda: True),
(UPat(Ops.DEFINE_VAR, name="x"), lambda x: isinstance(x.arg[1], int) and isinstance(x.arg[2], int)),
(UPat(Ops.RANGE, src=(UPat.var("x"),), name="rng"), lambda rng,x: rng.dtype == x.dtype and isinstance(rng.arg[0], int)),
(UPat(Ops.RANGE, src=(UPat.var("x"),), name="rng"), lambda rng,x: rng.dtype == x.dtype and isinstance(rng.arg, int)),
(UPat(Ops.SPECIAL, src=()), lambda: True),
(UPat(Ops.VIEW, dtypes.void, src=(), name="x"), lambda x: isinstance(x.arg, ShapeTracker)),
+1 -1
View File
@@ -474,5 +474,5 @@ sym = symbolic_flat+PatternMatcher([
# move const multiply after REDUCE (NOTE: the mul chain can do this, but only if it's a same dtype reduce)
((UPat.var("x")*UPat.cvar("c", vec=False)).reduce(arg=Ops.ADD, name="r", allow_any_len=True), lambda x,c,r: r.replace(src=(x,)+r.src[1:])*c.arg),
# reduce mul chain, move muls after the reduce
#(UPat(Ops.MUL).reduce(name="r", allow_any_len=True), reduce_mul_chain),
(UPat(Ops.MUL).reduce(name="r", allow_any_len=True), reduce_mul_chain),
])
+6 -3
View File
@@ -30,10 +30,13 @@ def get_metadata(keys:list[TracingKey], contexts:list[list[TrackedGraphRewrite]]
for i,(k,v) in enumerate(zip(keys, contexts)):
steps = [{"name":s.name, "loc":s.loc, "depth":s.depth, "match_count":len(s.matches), "code_line":printable(s.loc),
"query":f"/ctxs?ctx={i}&idx={j}"} for j,s in enumerate(v)]
if isinstance(k.ret, ProgramSpec): steps.append({"name":"View Disassembly", "query":f"/disasm?ctx={i}"})
ret.append(r:={"name":k.display_name, "fmt":k.fmt, "steps":steps})
ret.append(r:={"name":k.display_name, "steps":steps})
# use the first key to get runtime profiling data about this context
if getenv("PROFILE_VALUE") >= 2 and k.keys: r["runtime_stats"] = get_runtime_stats(k.keys[0])
# program spec metadata
if isinstance(k.ret, ProgramSpec):
steps.append({"name":"View Disassembly", "query":f"/disasm?ctx={i}"})
r["fmt"] = k.ret.src
for key in k.keys: ref_map[key] = i
return ret
@@ -55,7 +58,7 @@ def uop_to_json(x:UOp) -> dict[int, dict]:
excluded: set[UOp] = set()
for u in (toposort:=x.toposort()):
# always exclude DEVICE/CONST/UNIQUE
if u.op in {Ops.DEVICE, Ops.CONST, Ops.UNIQUE, Ops.INVALID} and u is not x: excluded.add(u)
if u.op in {Ops.DEVICE, Ops.CONST, Ops.UNIQUE} and u is not x: excluded.add(u)
# only exclude CONST VIEW source if it has no other children in the graph
if u.op is Ops.CONST and len(u.src) != 0 and all(cr.op is Ops.CONST for c in u.src[0].children if (cr:=c()) is not None and cr in toposort):
excluded.update(u.src)