mirror of
https://github.com/tinygrad/tinygrad.git
synced 2026-08-14 10:38:28 +00:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
954f30a1c1 | ||
|
|
58e79f3fc8 | ||
|
|
aee4ebe52f | ||
|
|
cb9bcc60de | ||
|
|
92160f1cf1 | ||
|
|
2584fe8907 | ||
|
|
8dff2c1375 | ||
|
|
3cff1a6b13 |
+23
-23
@@ -870,29 +870,29 @@ jobs:
|
||||
- name: Test ONNX Runner (WEBGPU)
|
||||
run: WEBGPU=1 PYTHONPATH=. python3 test/external/external_test_onnx_runner.py
|
||||
|
||||
#osxremote:
|
||||
# name: MacOS (remote metal)
|
||||
# runs-on: macos-15
|
||||
# timeout-minutes: 10
|
||||
# env:
|
||||
# REMOTE: 1
|
||||
# REMOTEDEV: METAL
|
||||
# steps:
|
||||
# - name: Checkout Code
|
||||
# uses: actions/checkout@v4
|
||||
# - name: Setup Environment
|
||||
# uses: ./.github/actions/setup-tinygrad
|
||||
# with:
|
||||
# key: macos-remote
|
||||
# deps: testing_minimal
|
||||
# - name: Check Device.DEFAULT and print some source
|
||||
# run: |
|
||||
# python -c "from tinygrad import Device; assert Device.DEFAULT == 'REMOTE', Device.DEFAULT"
|
||||
# python -c "from tinygrad import Device; assert Device.default.properties.real_device == 'METAL', Device.default.properties.real_device"
|
||||
# DEBUG=4 python3 test/test_tiny.py TestTiny.test_plus
|
||||
# - name: Run REMOTE=1 Test
|
||||
# run: |
|
||||
# python3 -m pytest test/test_tiny.py test/test_jit.py test/test_subbuffer.py test/test_graph.py test/test_multitensor.py test/test_tensor_variable.py
|
||||
osxremote:
|
||||
name: MacOS (remote metal)
|
||||
runs-on: macos-15
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
REMOTE: 1
|
||||
REMOTEDEV: METAL
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-tinygrad
|
||||
with:
|
||||
key: macos-remote
|
||||
deps: testing_minimal
|
||||
- name: Check Device.DEFAULT and print some source
|
||||
run: |
|
||||
python -c "from tinygrad import Device; assert Device.DEFAULT == 'REMOTE', Device.DEFAULT"
|
||||
python -c "from tinygrad import Device; assert Device.default.properties.real_device == 'METAL', Device.default.properties.real_device"
|
||||
DEBUG=4 python3 test/test_tiny.py TestTiny.test_plus
|
||||
- name: Run REMOTE=1 Test
|
||||
run: |
|
||||
python3 -m pytest test/test_tiny.py test/test_jit.py test/test_subbuffer.py test/test_graph.py test/test_multitensor.py test/test_tensor_variable.py
|
||||
|
||||
amdremote:
|
||||
name: Linux (remote)
|
||||
|
||||
+1
-1
@@ -126,7 +126,7 @@ print(t_log_grad.uop)
|
||||
"""
|
||||
void E_(float* restrict data0, float* restrict data1) {
|
||||
float val0 = *(data1+0);
|
||||
*(data0+0) = (1/val0);
|
||||
*(data0+0) = (0.6931471805599453f*(1/(val0*0.6931471805599453f)));
|
||||
}
|
||||
"""
|
||||
# the derivative is close to 1/3
|
||||
|
||||
@@ -1318,47 +1318,18 @@ def train_llama3():
|
||||
if (llama_layers:=getenv("LLAMA_LAYERS")) != 0: params['n_layers'] = llama_layers
|
||||
model = Transformer(**params, max_context=SEQLEN, jit=False, disable_kv_cache=True)
|
||||
|
||||
if (DP := getenv("DP", 1)) > 1:
|
||||
device = tuple(f"{Device.DEFAULT}:{i}" for i in range(DP))
|
||||
for v in get_parameters(model):
|
||||
v.shard_(device, axis=None)
|
||||
|
||||
# TODO: MP
|
||||
# if (GPUS := getenv("GPUS", 1)) > 1:
|
||||
# device = tuple(f"{Device.DEFAULT}:{i}" for i in range(GPUS))
|
||||
# for k,v in get_state_dict(model).items():
|
||||
# if 'scale' in k: v.shard_(device, axis=None) # from quantized
|
||||
# # elif '.attention.wq' in k: v.shard_(device, axis=0)
|
||||
# # elif '.attention.wk' in k: v.shard_(device, axis=0)
|
||||
# # elif '.attention.wv' in k: v.shard_(device, axis=0)
|
||||
# # elif '.attention.wo' in k: v.shard_(device, axis=1)
|
||||
# # elif '.feed_forward.w1.' in k: v.shard_(device, axis=0)
|
||||
# # elif '.feed_forward.w2.' in k: v.shard_(device, axis=1)
|
||||
# # elif '.feed_forward.w3.' in k: v.shard_(device, axis=0)
|
||||
# # elif 'tok_embeddings.weight' in k: v.shard_(device, axis=0)
|
||||
# elif 'output.weight' in k: v.shard_(device, axis=0) # 243.32
|
||||
# else:
|
||||
# # print(k)
|
||||
# # attention_norm, ffn_norm, norm
|
||||
# v.shard_(device, axis=None)
|
||||
|
||||
optim = AdamW(get_parameters(model), lr=0.0,
|
||||
b1=opt_adamw_beta_1, b2=opt_adamw_beta_2, eps=opt_adamw_epsilon, weight_decay=opt_adamw_weight_decay)
|
||||
scheduler = CosineAnnealingLRWithWarmup(optim, opt_base_learning_rate, opt_end_learning_rate, opt_learning_rate_warmup_steps, opt_learning_rate_decay_steps)
|
||||
|
||||
@TinyJit
|
||||
@Tensor.train()
|
||||
def train_step(model, tokens:Tensor, grad_acc:int):
|
||||
def train_step(model, tokens):
|
||||
optim.zero_grad()
|
||||
# grad acc
|
||||
for batch in tokens.split(tokens.shape[0]//grad_acc):
|
||||
if (DP := getenv("DP", 1)) > 1:
|
||||
device = tuple(f"{Device.DEFAULT}:{i}" for i in range(DP))
|
||||
batch = batch.shard(device, 0)
|
||||
logits:Tensor = model(batch[:, :-1], start_pos=0, temperature=math.nan)
|
||||
loss = logits.sparse_categorical_crossentropy(batch[:, 1:])
|
||||
loss.backward()
|
||||
Tensor.realize(*[p.grad for p in optim.params])
|
||||
logits:Tensor = model(tokens[:, :-1], start_pos=0, temperature=math.nan)
|
||||
loss = logits.sparse_categorical_crossentropy(tokens[:, 1:])
|
||||
loss.backward()
|
||||
|
||||
# L2 norm grad clip
|
||||
# https://github.com/NVIDIA/NeMo/blob/3368c3fc0b4a186ab33a1d68a504315100c0b2a6/nemo/collections/nlp/modules/common/megatron/clip_grads.py#L57
|
||||
# https://docs.pytorch.org/docs/stable/generated/torch.nn.utils.clip_grad_norm_.html
|
||||
@@ -1387,12 +1358,11 @@ def train_llama3():
|
||||
iter = batch_load_llama3(GBS, SAMPLES, SEQLEN, Path(getenv("BASEDIR", "/raid/datasets/c4/")), seed=SEED, val=bool(TRAIN_ON_VAL))
|
||||
|
||||
i = 0
|
||||
for tokens in tqdm(iter, total=SAMPLES//GBS):
|
||||
t = time.perf_counter()
|
||||
for tokens in tqdm(iter, total=SAMPLES//BS):
|
||||
GlobalCounters.reset()
|
||||
loss, lr = train_step(model, tokens, grad_acc)
|
||||
loss, lr = train_step(model, tokens)
|
||||
# above as tqdm.write f-string
|
||||
tqdm.write(f"{loss.item():.4f} loss, {lr.item():.12f} LR, {GlobalCounters.mem_used / 1e9:.2f} GB used, {time.perf_counter()-t:.2f} s")
|
||||
tqdm.write(f"{loss.item():.4f} loss, {lr.item():.12f} LR, {GlobalCounters.mem_used / 1e9:.2f} GB used")
|
||||
if (fname:=getenv("LOSS_FILE", "")):
|
||||
with open(fname, "a") as f:
|
||||
f.write(f"{i} {loss.item():.4f} {lr.item():.12f} {GlobalCounters.mem_used / 1e9:.2f}\n")
|
||||
|
||||
@@ -16,7 +16,7 @@ def _do_reset_device(pci_bus): System.pci_reset(pci_bus)
|
||||
def _is_module_loaded(name: str) -> bool: return os.path.isdir(f"/sys/module/{name}")
|
||||
|
||||
def cmd_remove_module(args):
|
||||
modules = ["nvidia_drm", "nvidia_modeset", "nvidia_uvm", "nvidia", "ast"] if args.backend == "nv" else ["amdgpu"]
|
||||
modules = ["nvidia_drm", "nvidia_modeset", "nvidia_uvm", "nvidia"] if args.backend == "nv" else ["amdgpu"]
|
||||
to_unload = [m for m in modules if _is_module_loaded(m)]
|
||||
if not to_unload: print("Kernel modules are not loaded")
|
||||
else:
|
||||
|
||||
@@ -9,7 +9,7 @@ with open(directory / 'README.md', encoding='utf-8') as f:
|
||||
|
||||
testing_minimal = [
|
||||
"numpy",
|
||||
"torch==2.7.1",
|
||||
"torch",
|
||||
"pytest",
|
||||
"pytest-xdist",
|
||||
"hypothesis",
|
||||
|
||||
Vendored
+1
-1
@@ -10,7 +10,7 @@ if __name__ == "__main__":
|
||||
|
||||
model, kv = Transformer.from_gguf(Tensor.from_url(models["1B"]), max_context=4096)
|
||||
|
||||
tok = SimpleTokenizer.from_gguf_kv(kv)
|
||||
tok = SimpleTokenizer(kv["tokenizer.ggml.tokens"])
|
||||
bos_id: int = kv['tokenizer.ggml.bos_token_id']
|
||||
eos_id: int = kv['tokenizer.ggml.eos_token_id']
|
||||
|
||||
|
||||
+5
-7
@@ -1,19 +1,17 @@
|
||||
from transformers import AutoTokenizer
|
||||
from datasets import load_dataset
|
||||
from tinygrad.apps.llm import SimpleTokenizer, gpt2_decode_vocab, get_llama_re
|
||||
from tinygrad.helpers import tqdm, getenv, partition
|
||||
from tinygrad.apps.llm import SimpleTokenizer
|
||||
from tinygrad.helpers import tqdm, getenv
|
||||
|
||||
# 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")
|
||||
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)
|
||||
vocab_words = [ word for word, _ in sorted(base_tokenizer.get_vocab().items(), key=lambda t: t[1]) ]
|
||||
inv_vocab = { tid: word for word, tid in base_tokenizer.get_vocab().items() }
|
||||
simple_tokenizer = SimpleTokenizer(get_llama_re(), gpt2_decode_vocab(dict(normal_tokens)), dict(special_tokens))
|
||||
simple_tokenizer = SimpleTokenizer(vocab_words)
|
||||
|
||||
color_codes = [ 91, 92, 94, 93, 95 ]
|
||||
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"
|
||||
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"
|
||||
|
||||
ds = load_dataset("OpenAssistant/oasst1")
|
||||
allow_failed = getenv("ALLOW_FAILED", 10)
|
||||
|
||||
+2
-3
@@ -74,7 +74,6 @@ def diff(offset:int, fxns:dict[str, Callable[..., tuple|None]]) -> None:
|
||||
warnings.warn(f"detected changes in over {MAX_DIFF_PCT}%. skipping further diff generation.", ProcessReplayWarning)
|
||||
early_stop.set()
|
||||
break
|
||||
name, loc = "", ""
|
||||
try:
|
||||
name, args, kwargs, ctx_vals, loc, ret = pickle.loads(row[0])
|
||||
ctx_vars = {k:v.value for k,v in ctx_vals.items() if k != "DEBUG" and (var:=ContextVar._cache.get(k)) is not None and var.value != v.value}
|
||||
@@ -91,7 +90,7 @@ def diff(offset:int, fxns:dict[str, Callable[..., tuple|None]]) -> None:
|
||||
warnings.warn("PROCESS REPLAY DETECTED CHANGE", ProcessReplayWarning)
|
||||
except Exception as e:
|
||||
changed += 1
|
||||
warnings.warn(f"{name=} {loc=} {e=}", ProcessReplayWarning)
|
||||
warnings.warn(e, ProcessReplayWarning)
|
||||
conn.commit()
|
||||
cur.close()
|
||||
|
||||
@@ -124,5 +123,5 @@ if __name__ == "__main__":
|
||||
logging.info(f"running process replay with {ASSERT_DIFF=}")
|
||||
try: _pmap(replayers)
|
||||
except Exception as e:
|
||||
logging.info(f"process replay err: {e}")
|
||||
logging.info("process replay err", e)
|
||||
exit(int(ASSERT_DIFF))
|
||||
|
||||
+38
-1
@@ -4,7 +4,7 @@ import torch
|
||||
from typing import Any, List
|
||||
from tinygrad.device import is_dtype_supported
|
||||
from tinygrad.helpers import getenv, DEBUG, CI
|
||||
from tinygrad.dtype import DType, DTYPES_DICT, least_upper_dtype, fp8_to_float, float_to_fp8
|
||||
from tinygrad.dtype import DType, DTYPES_DICT, ImageDType, PtrDType, least_upper_dtype, to_dtype, fp8_to_float, float_to_fp8
|
||||
from tinygrad import Device, Tensor, dtypes
|
||||
from tinygrad.tensor import _to_np_dtype
|
||||
from hypothesis import assume, given, settings, strategies as strat
|
||||
@@ -384,6 +384,30 @@ class TestPtrDType(unittest.TestCase):
|
||||
self.assertEqual(dt.v, 4)
|
||||
self.assertEqual(dt.count, 4)
|
||||
|
||||
class TestImageDType(unittest.TestCase):
|
||||
def test_image_scalar(self):
|
||||
assert dtypes.imagef((10,10)).base.scalar() == dtypes.float32
|
||||
assert dtypes.imageh((10,10)).base.scalar() == dtypes.float32
|
||||
def test_image_vec(self):
|
||||
assert dtypes.imagef((10,10)).base.vec(4) == dtypes.float32.vec(4)
|
||||
assert dtypes.imageh((10,10)).base.vec(4) == dtypes.float32.vec(4)
|
||||
|
||||
class TestEqStrDType(unittest.TestCase):
|
||||
def test_image_ne(self):
|
||||
if ImageDType is None: raise unittest.SkipTest("no ImageDType support")
|
||||
assert dtypes.float == dtypes.float32, "float doesn't match?"
|
||||
assert dtypes.imagef((1,2,4)) != dtypes.imageh((1,2,4)), "different image dtype doesn't match"
|
||||
assert dtypes.imageh((1,2,4)) != dtypes.imageh((1,4,2)), "different shape doesn't match"
|
||||
assert dtypes.imageh((1,2,4)) == dtypes.imageh((1,2,4)), "same shape matches"
|
||||
assert isinstance(dtypes.imageh((1,2,4)), ImageDType)
|
||||
def test_ptr_eq(self):
|
||||
assert dtypes.float32.ptr() == dtypes.float32.ptr()
|
||||
assert not (dtypes.float32.ptr() != dtypes.float32.ptr())
|
||||
def test_strs(self):
|
||||
if PtrDType is None: raise unittest.SkipTest("no PtrDType support")
|
||||
self.assertEqual(str(dtypes.imagef((1,2,4))), "dtypes.imagef((1, 2, 4))")
|
||||
self.assertEqual(str(dtypes.float32.ptr(16)), "dtypes.float.ptr(16)")
|
||||
|
||||
class TestImplicitFunctionTypeChange(unittest.TestCase):
|
||||
def test_functions(self):
|
||||
result = []
|
||||
@@ -414,6 +438,19 @@ class TestDtypeUsage(unittest.TestCase):
|
||||
t = Tensor([[1, 2], [3, 4]], dtype=d)
|
||||
(t*t).max().item()
|
||||
|
||||
class TestToDtype(unittest.TestCase):
|
||||
def test_dtype_to_dtype(self):
|
||||
dtype = dtypes.int32
|
||||
res = to_dtype(dtype)
|
||||
self.assertIsInstance(res, DType)
|
||||
self.assertEqual(res, dtypes.int32)
|
||||
|
||||
def test_str_to_dtype(self):
|
||||
dtype = "int32"
|
||||
res = to_dtype(dtype)
|
||||
self.assertIsInstance(res, DType)
|
||||
self.assertEqual(res, dtypes.int32)
|
||||
|
||||
@unittest.skipUnless(is_dtype_supported(dtypes.bfloat16), f"no bfloat16 on {Device.DEFAULT}")
|
||||
class TestOpsBFloat16(unittest.TestCase):
|
||||
def test_cast(self):
|
||||
|
||||
+1
-2
@@ -107,9 +107,8 @@ class TestGraph(unittest.TestCase):
|
||||
helper_test_graphs(Device[d0].graph, graphs)
|
||||
|
||||
def skip_if_not_multigraph(self):
|
||||
graph = g.func if isinstance(g:=(d:=Device[Device.DEFAULT]).graph, functools.partial) else g
|
||||
graph = g.func if isinstance(g:=Device[Device.DEFAULT].graph, functools.partial) else g
|
||||
if not issubclass(graph, MultiGraphRunner): self.skipTest("graph is not supported (not MultiGraphRunner)")
|
||||
if not hasattr(d.allocator, '_transfer'): self.skipTest("device is not supported (no transfers)")
|
||||
|
||||
def test_order_copy_writed(self):
|
||||
self.skip_if_not_multigraph()
|
||||
|
||||
+2
-167
@@ -5,10 +5,9 @@ import numpy as np
|
||||
from hypothesis import given, settings, strategies as strat
|
||||
from test.helpers import assert_jit_cache_len, not_support_multi_device, REAL_DEV
|
||||
from tinygrad.tensor import Tensor
|
||||
from tinygrad.engine.jit import TinyJit, GraphRunner, MultiGraphRunner, graph_class
|
||||
from tinygrad.engine.realize import CompiledRunner, BufferCopy, BufferXfer
|
||||
from tinygrad.engine.jit import TinyJit
|
||||
from tinygrad.device import Device
|
||||
from tinygrad.helpers import Context, JIT, GlobalCounters, getenv
|
||||
from tinygrad.helpers import Context, JIT, GlobalCounters
|
||||
from tinygrad.dtype import dtypes
|
||||
from extra.models.unet import ResBlock
|
||||
|
||||
@@ -670,169 +669,5 @@ class TestJitFree(unittest.TestCase):
|
||||
out = fxn(Tensor([11,1,2,3,4]))
|
||||
self.assertEqual(out.item(), 13600)
|
||||
|
||||
class TestJitGraphSplit(unittest.TestCase):
|
||||
def compute(self, device, inp):
|
||||
assert inp.device == device, f"Input device {inp.device} does not match expected {device}"
|
||||
return (inp + 1.0).contiguous().realize()
|
||||
|
||||
def copy(self, device, to_device, inp):
|
||||
assert inp.device == device, f"Input device {inp.device} does not match expected {device}"
|
||||
return inp.to(to_device).realize()
|
||||
|
||||
def expect(self, f, *args, graph=None, multigraph=None, hcqgraph=None):
|
||||
def _numpies(tpl): return tpl.numpy() if tpl.__class__ is Tensor else tuple([t.numpy() for t in tpl])
|
||||
|
||||
expected = _numpies(f(*args))
|
||||
for i in range(4):
|
||||
res = _numpies(f(*args))
|
||||
np.testing.assert_allclose(res, expected, atol=1e-4, rtol=1e-5)
|
||||
|
||||
dev = Device[Device.DEFAULT]
|
||||
graph_t = graph_class(dev)
|
||||
if graph_t is None: return
|
||||
|
||||
got = f.jit_cache
|
||||
from tinygrad.runtime.graph.hcq import HCQGraph
|
||||
if graph_t is HCQGraph:
|
||||
validate = hcqgraph
|
||||
elif issubclass(graph_t, MultiGraphRunner):
|
||||
validate = multigraph
|
||||
else:
|
||||
validate = graph
|
||||
|
||||
assert len(got) == len(validate), f"Expected {len(validate)} operations, got {len(got)}"
|
||||
for expected, got in zip(validate, got):
|
||||
if expected["type"] == "graph":
|
||||
assert isinstance(got.prg, GraphRunner), f"Expected GraphRunner, got {type(got.prg)}"
|
||||
assert len(got.prg.jit_cache) == expected["cnt"], f"Expected {expected['cnt']} operations in graph, got {len(got.prg.jit_cache)}"
|
||||
elif expected["type"] == "comp":
|
||||
assert isinstance(got.prg, CompiledRunner), f"Expected CompiledRunner, got {type(got.prg)}"
|
||||
elif expected["type"] == "copy":
|
||||
assert isinstance(got.prg, BufferCopy), f"Expected BufferCopy, got {type(got.prg)}"
|
||||
elif expected["type"] == "xfer":
|
||||
assert isinstance(got.prg, BufferXfer), f"Expected BufferXfer, got {type(got.prg)}"
|
||||
|
||||
def ji_graph(self, cnt): return {"type": "graph", "cnt": cnt}
|
||||
def ji_comp(self): return {"type": "comp"}
|
||||
def ji_copy(self): return {"type": "copy"}
|
||||
def ji_xfer(self): return {"type": "xfer"}
|
||||
|
||||
def test_jit_split_simple(self):
|
||||
if Device.DEFAULT == "REMOTE": raise unittest.SkipTest("REMOTE gpu is broken")
|
||||
|
||||
@TinyJit
|
||||
def f(inp):
|
||||
op0 = self.compute(Device.DEFAULT, inp)
|
||||
op1 = self.compute(Device.DEFAULT, op0)
|
||||
op2 = self.compute(Device.DEFAULT, op1)
|
||||
return op2
|
||||
|
||||
inp = Tensor.randn(10, 10, device=Device.DEFAULT).realize()
|
||||
self.expect(f, inp,
|
||||
graph=[self.ji_graph(3)],
|
||||
multigraph=[self.ji_graph(3)],
|
||||
hcqgraph=[self.ji_graph(3)])
|
||||
|
||||
def test_jit_cpu_simple(self):
|
||||
if Device.DEFAULT == "CPU": raise unittest.SkipTest("CPU is not a valid default device for this test")
|
||||
|
||||
@TinyJit
|
||||
def f(inp, inp_cpu):
|
||||
op0 = self.compute(Device.DEFAULT, inp)
|
||||
op1 = self.compute(Device.DEFAULT, op0)
|
||||
op2 = self.compute("CPU", inp_cpu)
|
||||
op3 = self.compute(Device.DEFAULT, op1)
|
||||
return op2, op3
|
||||
|
||||
inp = Tensor.randn(10, 10, device=Device.DEFAULT).realize()
|
||||
inp_cpu = Tensor.randn(10, 10, device="CPU").realize()
|
||||
self.expect(f, inp, inp_cpu,
|
||||
graph=[self.ji_graph(2), self.ji_comp(), self.ji_comp()],
|
||||
multigraph=[self.ji_graph(2), self.ji_comp(), self.ji_comp()],
|
||||
hcqgraph=[self.ji_graph(4)])
|
||||
|
||||
def test_jit_cpu_several(self):
|
||||
if Device.DEFAULT == "CPU": raise unittest.SkipTest("CPU is not a valid default device for this test")
|
||||
|
||||
@TinyJit
|
||||
def f(inp, inp_cpu):
|
||||
op0 = self.compute(Device.DEFAULT, inp)
|
||||
op1 = self.compute(Device.DEFAULT, op0)
|
||||
op2 = self.compute("CPU", inp_cpu)
|
||||
op3 = self.compute("CPU", op2)
|
||||
op4 = self.compute(Device.DEFAULT, op1)
|
||||
return op3, op4
|
||||
|
||||
inp = Tensor.randn(10, 10, device=Device.DEFAULT).realize()
|
||||
inp_cpu = Tensor.randn(10, 10, device="CPU").realize()
|
||||
self.expect(f, inp, inp_cpu,
|
||||
graph=[self.ji_graph(2), self.ji_graph(2), self.ji_comp()],
|
||||
multigraph=[self.ji_graph(2), self.ji_graph(2), self.ji_comp()],
|
||||
hcqgraph=[self.ji_graph(5)])
|
||||
|
||||
def test_jit_multidev(self):
|
||||
if Device.DEFAULT == "CPU": raise unittest.SkipTest("CPU is not a valid default device for this test")
|
||||
|
||||
try: Device[f"{Device.DEFAULT}:1"]
|
||||
except Exception: raise unittest.SkipTest("no multidevice")
|
||||
|
||||
@TinyJit
|
||||
def f(inp, inp_d1):
|
||||
op0 = self.compute(Device.DEFAULT, inp)
|
||||
op1 = self.compute(Device.DEFAULT, op0)
|
||||
op2 = self.compute(f"{Device.DEFAULT}:1", inp_d1)
|
||||
op3 = self.compute(f"{Device.DEFAULT}:1", op2)
|
||||
op4 = self.compute(Device.DEFAULT, op1)
|
||||
return op3, op4
|
||||
|
||||
inp = Tensor.randn(10, 10, device=Device.DEFAULT).realize()
|
||||
inp_d1 = Tensor.randn(10, 10, device=f"{Device.DEFAULT}:1").realize()
|
||||
self.expect(f, inp, inp_d1,
|
||||
graph=[self.ji_graph(2), self.ji_graph(2), self.ji_comp()],
|
||||
multigraph=[self.ji_graph(5)],
|
||||
hcqgraph=[self.ji_graph(5)])
|
||||
|
||||
def test_jit_multidev_xfer(self):
|
||||
if Device.DEFAULT in {"CPU", "LLVM"}: raise unittest.SkipTest("CPU/LLVM is not a valid default device for this test (zero-copies)")
|
||||
|
||||
try: Device[f"{Device.DEFAULT}:1"]
|
||||
except Exception: raise unittest.SkipTest("no multidevice")
|
||||
|
||||
@TinyJit
|
||||
def f(inp, inp_d1):
|
||||
op0 = self.compute(Device.DEFAULT, inp)
|
||||
op1 = self.compute(Device.DEFAULT, op0)
|
||||
op2 = self.compute(f"{Device.DEFAULT}:1", inp_d1)
|
||||
op3 = self.copy(f"{Device.DEFAULT}:1", Device.DEFAULT, op2)
|
||||
op4 = self.compute(f"{Device.DEFAULT}:1", op2)
|
||||
op5 = self.compute(Device.DEFAULT, op3)
|
||||
return op1, op4, op5
|
||||
|
||||
inp = Tensor.randn(10, 10, device=Device.DEFAULT).realize()
|
||||
inp_d1 = Tensor.randn(10, 10, device=f"{Device.DEFAULT}:1").realize()
|
||||
self.expect(f, inp, inp_d1,
|
||||
graph=[self.ji_graph(2), self.ji_comp(), self.ji_xfer(), self.ji_comp(), self.ji_comp()],
|
||||
multigraph=[self.ji_graph(6)],
|
||||
hcqgraph=[self.ji_graph(6)])
|
||||
|
||||
@unittest.skipIf(getenv("MOCKGPU"), "MockGPU does not support parallel copies")
|
||||
def test_jit_multidev_copy(self):
|
||||
if Device.DEFAULT in {"CPU", "LLVM"}: raise unittest.SkipTest("CPU/LLVM is not a valid default device for this test (zero-copies)")
|
||||
if Device.DEFAULT == "REMOTE": raise unittest.SkipTest("REMOTE gpu is broken")
|
||||
|
||||
@TinyJit
|
||||
def f(inp):
|
||||
op0 = self.compute(Device.DEFAULT, inp)
|
||||
op1 = self.compute(Device.DEFAULT, op0)
|
||||
op2 = self.copy(Device.DEFAULT, "CPU", op1)
|
||||
op3 = self.compute("CPU", op2)
|
||||
return op3
|
||||
|
||||
inp = Tensor.randn(10, 10, device=Device.DEFAULT).realize()
|
||||
self.expect(f, inp,
|
||||
graph=[self.ji_graph(2), self.ji_copy(), self.ji_comp()],
|
||||
multigraph=[self.ji_graph(2), self.ji_copy(), self.ji_comp()],
|
||||
hcqgraph=[self.ji_graph(4)])
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ def reconstruction_helper(A:List[Tensor],B:Tensor, tolerance=1.0e-5):
|
||||
class TestLinAlg(unittest.TestCase):
|
||||
|
||||
def test_svd_general(self):
|
||||
sizes = [(2,2),(5,3),(3,5),(3,4,4),(2,2,2,2,3)]
|
||||
sizes = [(2,2),(5,3),(3,5),(2,2,2,2,3)]
|
||||
for size in sizes:
|
||||
a = Tensor.randn(size).realize()
|
||||
U,S,V = Tensor.svd(a)
|
||||
|
||||
@@ -2,7 +2,7 @@ import unittest, functools, random
|
||||
from tinygrad import Tensor, Device, nn, GlobalCounters, TinyJit, dtypes, Variable
|
||||
from tinygrad.device import is_dtype_supported
|
||||
from tinygrad.uop.ops import Ops, UOp
|
||||
from tinygrad.helpers import CI, getenv, prod, Context
|
||||
from tinygrad.helpers import CI, getenv, prod, Context, OSX
|
||||
from tinygrad.nn.state import get_parameters, get_state_dict
|
||||
from tinygrad.engine.realize import lower_schedule, BufferCopy, CompiledRunner, run_schedule
|
||||
import numpy as np
|
||||
@@ -374,6 +374,7 @@ class TestMultiTensor(unittest.TestCase):
|
||||
|
||||
# NOTE: this is failing on LLVM CI, no idea why. Works locally.
|
||||
@unittest.skipIf(CI and REAL_DEV in ("CUDA", "NV", "LLVM", "CPU"), "slow, and flaky on LLVM/CPU")
|
||||
@unittest.skipIf(REAL_DEV == "WEBGPU" and not OSX, "WEBGPU Vulkan can only run kernels with up to 10 buffers")
|
||||
def test_data_parallel_resnet(self):
|
||||
from extra.models.resnet import ResNet18
|
||||
|
||||
@@ -410,6 +411,7 @@ class TestMultiTensor(unittest.TestCase):
|
||||
np.testing.assert_allclose(grad, shard_grad, atol=1e-5, rtol=1e-5)
|
||||
|
||||
@unittest.skipIf(CI and REAL_DEV in ("CUDA", "NV", "LLVM", "CPU"), "slow, and flaky on LLVM/CPU")
|
||||
@unittest.skipIf(REAL_DEV == "WEBGPU" and not OSX, "WEBGPU Vulkan can only run kernels with up to 10 buffers")
|
||||
def test_data_parallel_resnet_train_step(self):
|
||||
from extra.models.resnet import ResNet18
|
||||
fake_image = Tensor.rand((2, 3, 224//8, 224//8))
|
||||
@@ -936,6 +938,7 @@ class TestShrinkMultiTensorShardedAxis(unittest.TestCase):
|
||||
np.testing.assert_allclose(output.numpy(), expected)
|
||||
|
||||
@unittest.skipIf(not_support_multi_device(), "no multi")
|
||||
@unittest.skipIf(REAL_DEV == "WEBGPU" and not OSX, "WEBGPU Vulkan can only run kernels with up to 10 buffers")
|
||||
class TestBatchNorm(unittest.TestCase):
|
||||
def test_unsynced_backprop_conv_bn(self):
|
||||
with Tensor.train():
|
||||
@@ -963,6 +966,7 @@ class TestBatchNorm(unittest.TestCase):
|
||||
optim.step()
|
||||
out.numpy()
|
||||
|
||||
@unittest.skipIf(REAL_DEV == "WEBGPU" and not OSX, "WEBGPU Vulkan can only run kernels with up to 10 buffers")
|
||||
def test_unsynced_backprop_standalone_bn(self):
|
||||
from extra.lr_scheduler import OneCycleLR
|
||||
GPUS = (d1, d2)
|
||||
|
||||
+125
-19
@@ -4,7 +4,7 @@ import numpy as np
|
||||
import torch
|
||||
from tinygrad import Tensor, Device, TinyJit
|
||||
from tinygrad.uop.ops import Ops
|
||||
from tinygrad.helpers import GlobalCounters, CI, Context
|
||||
from tinygrad.helpers import GlobalCounters, CI, Context, OSX
|
||||
from tinygrad.nn import Conv1d, ConvTranspose1d, Conv2d, ConvTranspose2d, Linear, Embedding
|
||||
from tinygrad.nn import BatchNorm, LayerNorm, LayerNorm2d, GroupNorm, InstanceNorm, RMSNorm, LSTMCell
|
||||
from tinygrad.nn.state import load_state_dict
|
||||
@@ -108,39 +108,105 @@ class TestNN(unittest.TestCase):
|
||||
_test_linear(Tensor.randn(BS, in_dim), in_dim, out_dim)
|
||||
_test_linear(Tensor.randn(BS, T, in_dim), in_dim, out_dim) # test with more dims
|
||||
|
||||
def _test_conv(self, tiny_conv, torch_conv, BS, C1, DIMS, C2, K, S, P, D=1):
|
||||
def test_conv1d(self):
|
||||
BS, C1, W = 4, 16, 224//4
|
||||
C2, K, S, P = 64, 7, 2, 1
|
||||
|
||||
# create in tinygrad
|
||||
layer = tiny_conv(C1, C2, kernel_size=K, stride=S, padding=P, dilation=D)
|
||||
layer = Conv1d(C1, C2, kernel_size=K, stride=S, padding=P)
|
||||
|
||||
# create in torch
|
||||
with torch.no_grad():
|
||||
torch_layer = torch_conv(C1, C2, kernel_size=K, stride=S, padding=P, dilation=D).eval()
|
||||
torch_layer = torch.nn.Conv1d(C1, C2, kernel_size=K, stride=S, padding=P).eval()
|
||||
torch_layer.weight[:] = torch.tensor(layer.weight.numpy(), dtype=torch.float32)
|
||||
torch_layer.bias[:] = torch.tensor(layer.bias.numpy(), dtype=torch.float32)
|
||||
|
||||
# test
|
||||
x = Tensor.uniform(BS, C1, *DIMS)
|
||||
x = Tensor.uniform(BS, C1, W)
|
||||
z = layer(x)
|
||||
torch_x = torch.tensor(x.numpy())
|
||||
torch_z = torch_layer(torch_x)
|
||||
np.testing.assert_allclose(z.numpy(), torch_z.detach().numpy(), atol=5e-4, rtol=1e-5)
|
||||
|
||||
def test_conv1d(self): self._test_conv(Conv1d, torch.nn.Conv1d, BS=4, C1=16, DIMS=[224//4], C2=64, K=7, S=2, P=1)
|
||||
def test_conv2d(self): self._test_conv(Conv2d, torch.nn.Conv2d, BS=4, C1=16, DIMS=[224//4, 224//4], C2=64, K=7, S=2, P=1)
|
||||
def test_conv2d(self):
|
||||
BS, C1, H, W = 4, 16, 224//4, 224//4
|
||||
C2, K, S, P = 64, 7, 2, 1
|
||||
|
||||
# create in tinygrad
|
||||
layer = Conv2d(C1, C2, kernel_size=K, stride=S, padding=P)
|
||||
|
||||
# create in torch
|
||||
with torch.no_grad():
|
||||
torch_layer = torch.nn.Conv2d(C1, C2, kernel_size=K, stride=S, padding=P).eval()
|
||||
torch_layer.weight[:] = torch.tensor(layer.weight.numpy(), dtype=torch.float32)
|
||||
torch_layer.bias[:] = torch.tensor(layer.bias.numpy(), dtype=torch.float32)
|
||||
|
||||
# test
|
||||
x = Tensor.uniform(BS, C1, H, W)
|
||||
z = layer(x)
|
||||
torch_x = torch.tensor(x.numpy())
|
||||
torch_z = torch_layer(torch_x)
|
||||
np.testing.assert_allclose(z.numpy(), torch_z.detach().numpy(), atol=5e-4, rtol=1e-5)
|
||||
|
||||
def test_conv1d_same_padding(self):
|
||||
self._test_conv(Conv1d, torch.nn.Conv1d, BS=8, C1=3, DIMS=[32], C2=16, K=3, S=1, P='same')
|
||||
BS, C1, W = 8, 3, 32
|
||||
C2, K, S, P = 16, 3, 1, 'same'
|
||||
|
||||
# create in tinygrad
|
||||
layer = Conv1d(C1, C2, kernel_size=K, stride=S, padding=P)
|
||||
|
||||
# create in torch
|
||||
with torch.no_grad():
|
||||
torch_layer = torch.nn.Conv1d(C1, C2, kernel_size=K, stride=S, padding=P).eval()
|
||||
torch_layer.weight[:] = torch.tensor(layer.weight.numpy(), dtype=torch.float32)
|
||||
torch_layer.bias[:] = torch.tensor(layer.bias.numpy(), dtype=torch.float32)
|
||||
|
||||
# test
|
||||
x = Tensor.uniform(BS, C1, W)
|
||||
z = layer(x)
|
||||
torch_x = torch.tensor(x.numpy())
|
||||
torch_z = torch_layer(torch_x)
|
||||
np.testing.assert_allclose(z.numpy(), torch_z.detach().numpy(), atol=5e-4, rtol=1e-5)
|
||||
|
||||
def _run_conv2d_same_padding_test(self, BS, C1, C2, H, W, K, S, padding='same', D=1):
|
||||
# create in tinygrad
|
||||
layer = Conv2d(C1, C2, kernel_size=K, stride=S, padding=padding, dilation=D)
|
||||
|
||||
# create in torch
|
||||
with torch.no_grad():
|
||||
torch_layer = torch.nn.Conv2d(C1, C2, kernel_size=K, stride=S, padding=padding, dilation=D).eval()
|
||||
torch_layer.weight[:] = torch.tensor(layer.weight.numpy(), dtype=torch.float32)
|
||||
torch_layer.bias[:] = torch.tensor(layer.bias.numpy(), dtype=torch.float32)
|
||||
|
||||
# test
|
||||
x = Tensor.uniform(BS, C1, H, W)
|
||||
z = layer(x)
|
||||
torch_x = torch.tensor(x.numpy())
|
||||
torch_z = torch_layer(torch_x)
|
||||
np.testing.assert_allclose(z.numpy(), torch_z.detach().numpy(), atol=5e-4, rtol=1e-5)
|
||||
|
||||
def test_conv2d_same_padding_odd_input(self):
|
||||
self._test_conv(Conv2d, torch.nn.Conv2d, BS=16, C1=16, DIMS=[29, 31], C2=32, K=5, S=1, P='same')
|
||||
BS, C1, H, W = 16, 16, 29, 31
|
||||
C2, K, S, P = 32, 5, 1, 'same'
|
||||
self._run_conv2d_same_padding_test(BS, C1, C2, H, W, K, S, P)
|
||||
|
||||
def test_conv2d_same_padding_large_kernel(self):
|
||||
self._test_conv(Conv2d, torch.nn.Conv2d, BS=16, C1=16, DIMS=[28, 33], C2=32, K=9, S=1, P='same')
|
||||
BS, C1, H, W = 16, 16, 28, 33
|
||||
C2, K, S, P = 32, 9, 1, 'same'
|
||||
self._run_conv2d_same_padding_test(BS, C1, C2, H, W, K, S, P)
|
||||
|
||||
def test_conv2d_same_padding_with_dilation(self):
|
||||
self._test_conv(Conv2d, torch.nn.Conv2d, BS=16, C1=3, DIMS=[28, 28], C2=32, K=3, S=1, P='same', D=3)
|
||||
BS, C1, H, W = 16, 3, 28, 28
|
||||
C2, K, S, P, D = 32, 3, 1, 'same', 3
|
||||
self._run_conv2d_same_padding_test(BS, C1, C2, H, W, K, S, P, D)
|
||||
|
||||
def test_conv2d_same_padding_invalid_stride(self):
|
||||
self.assertRaises(ValueError, Conv2d, in_channels=16, out_channels=32, kernel_size=2, stride=2, padding='same')
|
||||
C1, C2, K, S, P = 16, 32, 2, 2, 'same'
|
||||
self.assertRaises(ValueError, Conv2d, C1, C2, kernel_size=K, stride=S, padding=P)
|
||||
|
||||
def test_conv2d_same_padding_invalid_padding_str(self):
|
||||
self.assertRaises(ValueError, Conv2d, in_channels=16, out_channels=32, kernel_size=2, stride=1, padding='not_same')
|
||||
C1, C2, K, S, P = 16, 32, 2, 1, 'not_same'
|
||||
self.assertRaises(ValueError, Conv2d, C1, C2, kernel_size=K, stride=S, padding=P)
|
||||
|
||||
@unittest.skip("Takes too long to compile for Compiled backends")
|
||||
def test_conv2d_winograd(self):
|
||||
@@ -163,13 +229,12 @@ class TestNN(unittest.TestCase):
|
||||
with Context(WINO=1):
|
||||
z = layer(x)
|
||||
|
||||
m = z.mean()
|
||||
m.backward()
|
||||
|
||||
torch_x = torch.tensor(x.numpy(), requires_grad=True)
|
||||
torch_z = torch_layer(torch_x)
|
||||
np.testing.assert_allclose(z.numpy(), torch_z.detach().numpy(), atol=5e-4, rtol=1e-5)
|
||||
|
||||
m = z.mean()
|
||||
m.backward()
|
||||
gw = layer.weight.grad.realize()
|
||||
gb = layer.bias.grad.realize()
|
||||
gx = x.grad.realize()
|
||||
@@ -180,10 +245,46 @@ class TestNN(unittest.TestCase):
|
||||
np.testing.assert_allclose(gx.numpy(), torch_x.grad.numpy(), atol=5e-4, rtol=1e-5)
|
||||
|
||||
def test_conv_transpose1d(self):
|
||||
self._test_conv(ConvTranspose1d, torch.nn.ConvTranspose1d, BS=4, C1=16, DIMS=[224//4], C2=64, K=7, S=2, P=1)
|
||||
def test_conv_transpose2d(self):
|
||||
self._test_conv(ConvTranspose2d, torch.nn.ConvTranspose2d, BS=4, C1=16, DIMS=[224//4, 224//4], C2=64, K=7, S=2, P=1)
|
||||
BS, C1, W = 4, 16, 224//4
|
||||
C2, K, S, P = 64, 7, 2, 1
|
||||
|
||||
# create in tinygrad
|
||||
layer = ConvTranspose1d(C1, C2, kernel_size=K, stride=S, padding=P)
|
||||
|
||||
# create in torch
|
||||
with torch.no_grad():
|
||||
torch_layer = torch.nn.ConvTranspose1d(C1, C2, kernel_size=K, stride=S, padding=P).eval()
|
||||
torch_layer.weight[:] = torch.tensor(layer.weight.numpy(), dtype=torch.float32)
|
||||
torch_layer.bias[:] = torch.tensor(layer.bias.numpy(), dtype=torch.float32)
|
||||
|
||||
# test
|
||||
x = Tensor.uniform(BS, C1, W)
|
||||
z = layer(x)
|
||||
torch_x = torch.tensor(x.numpy())
|
||||
torch_z = torch_layer(torch_x)
|
||||
np.testing.assert_allclose(z.numpy(), torch_z.detach().numpy(), atol=5e-4, rtol=1e-5)
|
||||
|
||||
def test_conv_transpose2d(self):
|
||||
BS, C1, H, W = 4, 16, 224//4, 224//4
|
||||
C2, K, S, P = 64, 7, 2, 1
|
||||
|
||||
# create in tinygrad
|
||||
layer = ConvTranspose2d(C1, C2, kernel_size=K, stride=S, padding=P)
|
||||
|
||||
# create in torch
|
||||
with torch.no_grad():
|
||||
torch_layer = torch.nn.ConvTranspose2d(C1, C2, kernel_size=K, stride=S, padding=P).eval()
|
||||
torch_layer.weight[:] = torch.tensor(layer.weight.numpy(), dtype=torch.float32)
|
||||
torch_layer.bias[:] = torch.tensor(layer.bias.numpy(), dtype=torch.float32)
|
||||
|
||||
# test
|
||||
x = Tensor.uniform(BS, C1, H, W)
|
||||
z = layer(x)
|
||||
torch_x = torch.tensor(x.numpy())
|
||||
torch_z = torch_layer(torch_x)
|
||||
np.testing.assert_allclose(z.numpy(), torch_z.detach().numpy(), atol=5e-4, rtol=1e-5)
|
||||
|
||||
@unittest.skipIf(Device.DEFAULT == "WEBGPU" and not OSX, "WEBGPU Vulkan can only run kernels with up to 10 buffers")
|
||||
def test_groupnorm(self):
|
||||
BS, H, W, C, G = 20, 10, 10, 6, 3
|
||||
|
||||
@@ -210,6 +311,7 @@ class TestNN(unittest.TestCase):
|
||||
np.testing.assert_allclose(layer.weight.grad.numpy(), torch_layer.weight.grad.detach().numpy(), atol=5e-4, rtol=5e-4)
|
||||
np.testing.assert_allclose(layer.bias.grad.numpy(), torch_layer.bias.grad.detach().numpy(), atol=5e-4, rtol=5e-4)
|
||||
|
||||
@unittest.skipIf(Device.DEFAULT == "WEBGPU" and not OSX, "WEBGPU Vulkan can only run kernels with up to 10 buffers")
|
||||
def test_layernorm(self):
|
||||
N, C, H, W = 20, 5, 10, 10
|
||||
|
||||
@@ -236,6 +338,7 @@ class TestNN(unittest.TestCase):
|
||||
np.testing.assert_allclose(layer.weight.grad.numpy(), torch_layer.weight.grad.detach().numpy(), atol=5e-4, rtol=5e-4)
|
||||
np.testing.assert_allclose(layer.bias.grad.numpy(), torch_layer.bias.grad.detach().numpy(), atol=5e-4, rtol=5e-4)
|
||||
|
||||
@unittest.skipIf(Device.DEFAULT == "WEBGPU" and not OSX, "WEBGPU Vulkan can only run kernels with up to 10 buffers")
|
||||
def test_layernorm_2d(self):
|
||||
N, C, H, W = 20, 5, 10, 10
|
||||
|
||||
@@ -262,6 +365,7 @@ class TestNN(unittest.TestCase):
|
||||
np.testing.assert_allclose(layer.weight.grad.numpy(), torch_layer.weight.grad.detach().numpy(), atol=5e-4, rtol=5e-4)
|
||||
np.testing.assert_allclose(layer.bias.grad.numpy(), torch_layer.bias.grad.detach().numpy(), atol=5e-4, rtol=5e-4)
|
||||
|
||||
@unittest.skipIf(Device.DEFAULT == "WEBGPU" and not OSX, "WEBGPU Vulkan can only run kernels with up to 10 buffers")
|
||||
def test_instancenorm_2d(self):
|
||||
N, C, H, W = 20, 10, 10, 10
|
||||
|
||||
@@ -288,6 +392,7 @@ class TestNN(unittest.TestCase):
|
||||
np.testing.assert_allclose(layer.weight.grad.numpy(), torch_layer.weight.grad.detach().numpy(), atol=1e-3, rtol=1e-3)
|
||||
np.testing.assert_allclose(layer.bias.grad.numpy(), torch_layer.bias.grad.detach().numpy(), atol=1e-3, rtol=1e-3)
|
||||
|
||||
@unittest.skipIf(Device.DEFAULT == "WEBGPU" and not OSX, "WEBGPU Vulkan can only run kernels with up to 10 buffers")
|
||||
def test_instancenorm_3d(self):
|
||||
N, C, D, H, W = 20, 10, 10, 10, 10
|
||||
|
||||
@@ -314,6 +419,7 @@ class TestNN(unittest.TestCase):
|
||||
np.testing.assert_allclose(layer.weight.grad.numpy(), torch_layer.weight.grad.detach().numpy(), atol=2e-3, rtol=1e-3)
|
||||
np.testing.assert_allclose(layer.bias.grad.numpy(), torch_layer.bias.grad.detach().numpy(), atol=1e-3, rtol=1e-3)
|
||||
|
||||
@unittest.skipIf(Device.DEFAULT == "WEBGPU" and not OSX, "WEBGPU Vulkan can only run kernels with up to 10 buffers")
|
||||
def test_rmsnorm(self):
|
||||
class TorchRMSNorm(torch.nn.Module):
|
||||
# https://github.com/meta-llama/llama/blob/be327c427cc5e89cc1d3ab3d3fec4484df771245/llama/model.py#L34C1-L77C36
|
||||
|
||||
+4
-1
@@ -2,7 +2,7 @@ import time, math, unittest, functools, platform, warnings
|
||||
import numpy as np
|
||||
from typing import List, Callable
|
||||
import torch
|
||||
from tinygrad.helpers import getenv, IMAGE, DEBUG, CI, Context, TRANSCENDENTAL, AMD_LLVM
|
||||
from tinygrad.helpers import getenv, IMAGE, DEBUG, CI, Context, TRANSCENDENTAL, OSX, AMD_LLVM
|
||||
from tinygrad import Tensor, Device, dtypes
|
||||
from tinygrad.tensor import _to_np_dtype
|
||||
from tinygrad.device import is_dtype_supported
|
||||
@@ -2682,6 +2682,7 @@ class TestOps(unittest.TestCase):
|
||||
i, j, k, o, p = [Tensor(tor.detach().cpu().numpy().astype(np.int32), requires_grad=False) for tor in [a,b,c,d,e]]
|
||||
return a,b,c,d,e,i,j,k,o,p
|
||||
|
||||
@unittest.skipIf(Device.DEFAULT == "WEBGPU", "WEBGPU can only run kernels with up to 10 buffers")
|
||||
def test_slice_fancy_indexing_no_dim_collapse(self):
|
||||
a,b,c,d,e,i,j,k,o,p = self._get_index_randoms()
|
||||
# no dim collapse from int or dim injection from None
|
||||
@@ -2733,6 +2734,7 @@ class TestOps(unittest.TestCase):
|
||||
helper_test_op([(2,3)], lambda x: x[torch.tensor([[0,1,-1],[-1,-2,0]]), torch.tensor([2,1,-1])],
|
||||
lambda x: x[Tensor([[0,1,-1],[-1,-2,0]]), Tensor([2,1,-1])])
|
||||
|
||||
@unittest.skipIf(Device.DEFAULT == "WEBGPU", "WEBGPU can only run kernels with up to 10 buffers")
|
||||
def test_slice_fancy_indexing_list_indices(self):
|
||||
a,b,c,d,e,i,j,k,o,p = self._get_index_randoms()
|
||||
helper_test_op([(2,5,6,5,3,4)], lambda x: x[[[0]]], lambda x: x[[[0]]])
|
||||
@@ -2752,6 +2754,7 @@ class TestOps(unittest.TestCase):
|
||||
helper_test_op([(2,5,6,5,3,4)], lambda x: x[a,((2,),(1,),(0,)),c,(2,1,0)], lambda x: x[i,((2,),(1,),(0,)),k,(2,1,0)])
|
||||
helper_test_op([(2,5,6,5,3,4)], lambda x: x[1,(2,1,0),None,c,(2,1,0),e], lambda x: x[1,(2,1,0),None,k,(2,1,0),p])
|
||||
|
||||
@unittest.skipIf(Device.DEFAULT == "WEBGPU" and not OSX, "WEBGPU Vulkan can only run kernels with up to 10 buffers")
|
||||
def test_slice_fancy_indexing_list_with_tensors(self):
|
||||
a,b,c,d,e,i,j,k,o,p = self._get_index_randoms()
|
||||
helper_test_op([(2,5,6,5,3,4)], lambda x: x[[a]], lambda x: x[[i]])
|
||||
|
||||
@@ -3,7 +3,6 @@ import numpy as np
|
||||
from tinygrad import Tensor, Variable, Device
|
||||
from tinygrad.helpers import OSX
|
||||
|
||||
# TODO: still fails with MAX_KERNEL_BUFFERS
|
||||
@unittest.skipIf(Device.DEFAULT == "WEBGPU" and not OSX, "WEBGPU Vulkan can only run kernels with up to 10 buffers")
|
||||
class TestSample(unittest.TestCase):
|
||||
def test_sample(self):
|
||||
|
||||
@@ -86,18 +86,6 @@ class TestFuse(unittest.TestCase):
|
||||
return (arange == idx).mul(vals).sum(-2, dtype=vals.dtype)
|
||||
self._test_fuse(embedding, a, atol=1e-5)
|
||||
|
||||
def test_attention_kernel_count(self):
|
||||
wq = Tensor.empty(32, 32)
|
||||
wk = Tensor.empty(32, 32)
|
||||
wv = Tensor.empty(32, 32)
|
||||
x = Tensor.empty(2, 100, 32)
|
||||
q = (x @ wq).contiguous()
|
||||
k = (x @ wk).contiguous()
|
||||
v = (x @ wv).contiguous()
|
||||
attn = q.scaled_dot_product_attention(k, v).fuse()
|
||||
s = attn.schedule()
|
||||
self.assertEqual(len(s), 4) # 3 matmul and 1 attention
|
||||
|
||||
def test_flash_attention(self):
|
||||
BS = 4
|
||||
HEADS = 2
|
||||
@@ -133,7 +121,7 @@ class TestSoftmaxFusion(unittest.TestCase):
|
||||
out = (inp / div).reshape(32, 10)
|
||||
out.realize()
|
||||
|
||||
np.testing.assert_allclose(sout.numpy(), out.numpy(), atol=3e-7)
|
||||
np.testing.assert_allclose(sout.numpy(), out.numpy())
|
||||
|
||||
def test_softmax(self):
|
||||
# this is the softmax from scaled_dot_product_attention
|
||||
|
||||
+3
-3
@@ -892,13 +892,13 @@ class TestIdxUpcast(unittest.TestCase):
|
||||
|
||||
@unittest.skipUnless(is_dtype_supported(dtypes.long), "int64 is supported")
|
||||
def test_overflow_sym(self):
|
||||
self.do_op_then_assert(dtypes.long, 2048, 2048, UOp.variable("dim3", 1, 2048).bind(32))
|
||||
self.do_op_then_assert(dtypes.long, 2048, 2048, UOp.variable("dim3", 0, 2048).bind(32))
|
||||
|
||||
def test_regular(self):
|
||||
self.do_op_then_assert(dtypes.int, 64, 64, 64)
|
||||
|
||||
def test_regular_sym(self):
|
||||
self.do_op_then_assert(dtypes.int, 2048, 2048, UOp.variable("dim3", 1, 64).bind(32))
|
||||
self.do_op_then_assert(dtypes.int, 2048, 2048, UOp.variable("dim3", 0, 64).bind(32))
|
||||
|
||||
@unittest.skipIf(PTX, "PTX always convert Ops.INDEX to int64")
|
||||
def test_symfold(self):
|
||||
@@ -910,7 +910,7 @@ class TestIdxUpcast(unittest.TestCase):
|
||||
@unittest.skipIf(is_dtype_supported(dtypes.long), "int64 is supported")
|
||||
def test_int64_unsupported_overflow_sym(self):
|
||||
with self.assertRaises(KeyError):
|
||||
self.do_op_then_assert(dtypes.long, 2048, 2048, UOp.variable("dim3", 1, 2048).bind(32))
|
||||
self.do_op_then_assert(dtypes.long, 2048, 2048, UOp.variable("dim3", 0, 2048).bind(32))
|
||||
|
||||
@unittest.skipIf(is_dtype_supported(dtypes.long), "int64 is supported")
|
||||
def test_int64_unsupported_overflow(self):
|
||||
|
||||
@@ -33,6 +33,35 @@ class TestTiny(unittest.TestCase):
|
||||
self.assertListEqual((out:=a@b).flatten().tolist(), [1.0]*(N*N))
|
||||
if IMAGE < 2: self.assertEqual(out.dtype, out_dtype)
|
||||
|
||||
def test_double_gemm(self, N=64, BS=1):
|
||||
a = Tensor.ones(BS,N,N).contiguous().realize()
|
||||
b = Tensor.eye(N).contiguous().realize()
|
||||
c = Tensor.eye(N).contiguous().realize()
|
||||
d = Tensor.eye(N).contiguous().realize()
|
||||
e = Tensor.eye(N).contiguous().realize()
|
||||
f = Tensor.eye(N).contiguous().realize()
|
||||
g = Tensor.eye(N).contiguous().realize()
|
||||
out = ((a@b@c@d).contiguous()@e@f@g).contiguous().realize()
|
||||
self.assertListEqual(out.flatten().tolist(), [1.0]*(BS*N*N))
|
||||
|
||||
def test_double_gemm_bs(self, N=64, BS=1): self.test_double_gemm(BS=4)
|
||||
|
||||
def test_conv2d(self):
|
||||
N = 64
|
||||
a = Tensor.ones(1,4,N,N).contiguous().realize()
|
||||
w1 = Tensor.ones(16,4,3,3).contiguous().realize()
|
||||
out = a.conv2d(w1).contiguous().realize()
|
||||
|
||||
def test_double_conv2d(self):
|
||||
N = 64
|
||||
a = Tensor.ones(1,4,N,N).contiguous().realize()
|
||||
w1 = Tensor.ones(4,4,3,3).contiguous().realize()
|
||||
w2 = Tensor.ones(4,4,3,3).contiguous().realize()
|
||||
w3 = Tensor.ones(4,4,3,3).contiguous().realize()
|
||||
w4 = Tensor.ones(4,4,3,3).contiguous().realize()
|
||||
w5 = Tensor.ones(4,4,3,3).contiguous().realize()
|
||||
out = a.conv2d(w1).conv2d(w2).conv2d(w3).conv2d(w4).conv2d(w5).contiguous().realize()
|
||||
|
||||
# *** randomness ***
|
||||
|
||||
def test_random(self):
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import unittest
|
||||
from tinygrad.tensor import Tensor
|
||||
from tinygrad.dtype import dtypes, DType, ImageDType, PtrDType, to_dtype
|
||||
|
||||
class TestImageDType(unittest.TestCase):
|
||||
def test_image_scalar(self):
|
||||
assert dtypes.imagef((10,10)).base.scalar() == dtypes.float32
|
||||
assert dtypes.imageh((10,10)).base.scalar() == dtypes.float32
|
||||
def test_image_vec(self):
|
||||
assert dtypes.imagef((10,10)).base.vec(4) == dtypes.float32.vec(4)
|
||||
assert dtypes.imageh((10,10)).base.vec(4) == dtypes.float32.vec(4)
|
||||
|
||||
class TestEqStrDType(unittest.TestCase):
|
||||
def test_image_ne(self):
|
||||
if ImageDType is None: raise unittest.SkipTest("no ImageDType support")
|
||||
assert dtypes.float == dtypes.float32, "float doesn't match?"
|
||||
assert dtypes.imagef((1,2,4)) != dtypes.imageh((1,2,4)), "different image dtype doesn't match"
|
||||
assert dtypes.imageh((1,2,4)) != dtypes.imageh((1,4,2)), "different shape doesn't match"
|
||||
assert dtypes.imageh((1,2,4)) == dtypes.imageh((1,2,4)), "same shape matches"
|
||||
assert isinstance(dtypes.imageh((1,2,4)), ImageDType)
|
||||
def test_ptr_eq(self):
|
||||
assert dtypes.float32.ptr() == dtypes.float32.ptr()
|
||||
assert not (dtypes.float32.ptr() != dtypes.float32.ptr())
|
||||
def test_strs(self):
|
||||
if PtrDType is None: raise unittest.SkipTest("no PtrDType support")
|
||||
self.assertEqual(str(dtypes.imagef((1,2,4))), "dtypes.imagef((1, 2, 4))")
|
||||
self.assertEqual(str(dtypes.float32.ptr(16)), "dtypes.float.ptr(16)")
|
||||
|
||||
class TestToDtype(unittest.TestCase):
|
||||
def test_dtype_to_dtype(self):
|
||||
dtype = dtypes.int32
|
||||
res = to_dtype(dtype)
|
||||
self.assertIsInstance(res, DType)
|
||||
self.assertEqual(res, dtypes.int32)
|
||||
|
||||
def test_str_to_dtype(self):
|
||||
dtype = "int32"
|
||||
res = to_dtype(dtype)
|
||||
self.assertIsInstance(res, DType)
|
||||
self.assertEqual(res, dtypes.int32)
|
||||
|
||||
class TestCastConvenienceMethod(unittest.TestCase):
|
||||
def test_method(self):
|
||||
for input_dtype in (dtypes.float, dtypes.int):
|
||||
t = Tensor([1, 2], dtype=input_dtype)
|
||||
self.assertEqual(t.dtype, input_dtype)
|
||||
self.assertEqual(t.bool().dtype, dtypes.bool)
|
||||
self.assertEqual(t.short().dtype, dtypes.short)
|
||||
self.assertEqual(t.int().dtype, dtypes.int)
|
||||
self.assertEqual(t.long().dtype, dtypes.long)
|
||||
self.assertEqual(t.half().dtype, dtypes.half)
|
||||
self.assertEqual(t.bfloat16().dtype, dtypes.bfloat16)
|
||||
self.assertEqual(t.float().dtype, dtypes.float)
|
||||
self.assertEqual(t.double().dtype, dtypes.double)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,57 +0,0 @@
|
||||
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,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
import unittest, pickle, functools, math
|
||||
import unittest, pickle, functools
|
||||
import z3
|
||||
|
||||
from tinygrad.dtype import dtypes, ConstType
|
||||
@@ -29,17 +29,16 @@ class TestSymbolicPickle(unittest.TestCase):
|
||||
def test_pickle_variable_times_2(self): self._test_pickle_unpickle(Variable("a", 3, 8)*2)
|
||||
|
||||
class TestSymbolic(unittest.TestCase):
|
||||
def helper_test_variable(self, v, n, m, s, test_z3:bool=True):
|
||||
def helper_test_variable(self, v, n, m, s):
|
||||
rendered, nmin, nmax = render(v)
|
||||
if isinstance(s, tuple): self.assertIn(rendered, s)
|
||||
else: self.assertEqual(rendered, s)
|
||||
self.assertEqual(nmin, n)
|
||||
self.assertEqual(nmax, m)
|
||||
if test_z3:
|
||||
solver = z3.Solver()
|
||||
z3_sink = graph_rewrite(v.sink(v.simplify()), z3_renderer, ctx=(solver, {}))
|
||||
expr, epxr_simplified = z3_sink.src[0].arg, z3_sink.src[1].arg
|
||||
self.assertEqual(solver.check(expr != epxr_simplified), z3.unsat, "simplified expression not equal to original")
|
||||
solver = z3.Solver()
|
||||
z3_sink = graph_rewrite(v.sink(v.simplify()), z3_renderer, ctx=(solver, {}))
|
||||
expr, epxr_simplified = z3_sink.src[0].arg, z3_sink.src[1].arg
|
||||
self.assertEqual(solver.check(expr != epxr_simplified), z3.unsat, "simplified expression not equal to original")
|
||||
|
||||
def test_cmp_simple(self):
|
||||
self.helper_test_variable(Variable("a", 3, 8) < 4, 0, 1, "(a<4)")
|
||||
@@ -673,12 +672,6 @@ class TestSymbolic(unittest.TestCase):
|
||||
self.helper_test_variable(numerator, 3, 390, "(a*((a*4)+-1))")
|
||||
self.helper_test_variable((numerator//denominator)<=0, 1, 1, "True")
|
||||
|
||||
def test_const_reciprocal(self):
|
||||
a = Variable("a", 1, 10, dtypes.float)
|
||||
# TODO: bounds for reciprocal
|
||||
# TODO: should z3 work?
|
||||
self.helper_test_variable(2*(2*a).reciprocal(), -math.inf, math.inf, "(1/a)", test_z3=False)
|
||||
|
||||
class TestSymbolicNumeric(unittest.TestCase):
|
||||
def helper_test_numeric(self, f):
|
||||
MIN, MAX = 0, 10
|
||||
|
||||
@@ -106,12 +106,13 @@ 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,)))
|
||||
@track_rewrites(name=lambda inp,ret: TracingKey("custom_name", (inp,), fmt=f"input={inp.render()}"))
|
||||
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
|
||||
|
||||
+25
-49
@@ -1,57 +1,33 @@
|
||||
from __future__ import annotations
|
||||
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}]+"
|
||||
import sys, argparse
|
||||
from tinygrad import Tensor, nn, UOp, TinyJit, getenv
|
||||
|
||||
class SimpleTokenizer:
|
||||
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 __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 = "Ċ"
|
||||
|
||||
@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 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
|
||||
|
||||
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 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 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 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 apply_rope(x:Tensor, start_pos:int|UOp, base:int=10000):
|
||||
B, H, T, Hd = x.shape
|
||||
@@ -189,7 +165,7 @@ if __name__ == "__main__":
|
||||
model, kv = Transformer.from_gguf(Tensor.from_url(models[args.size]), args.max_context)
|
||||
|
||||
# extract some metadata
|
||||
tok = SimpleTokenizer.from_gguf_kv(kv)
|
||||
tok = SimpleTokenizer(kv["tokenizer.ggml.tokens"])
|
||||
bos_id: int = kv['tokenizer.ggml.bos_token_id']
|
||||
eos_id: int = kv['tokenizer.ggml.eos_token_id']
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ from tinygrad.codegen.devectorizer import load_store_folding, load_store_indexin
|
||||
ReduceContext, correct_load_store, pm_render
|
||||
from tinygrad.codegen.optional import get_late_rewrite_patterns
|
||||
from tinygrad.codegen.linearize import block_create, pm_blockend_merge, block_merge, pm_finalize, BlockContext
|
||||
from tinygrad.opt import pm_optimize
|
||||
|
||||
@dataclass
|
||||
class RewriteStep:
|
||||
@@ -43,10 +42,6 @@ def get_rewrites_for_renderer(opts:Renderer, linearizer:bool=True) -> list[Rewri
|
||||
def _get_rewrites_for_renderer(opts:Renderer, linearizer:bool, _QUANTIZE, _DEVECTORIZE, _TRANSCENDENTAL) -> list[RewriteStep]:
|
||||
# ** lowerer (rewrite_shapetracker_with_index) **
|
||||
ret: list[RewriteStep] = []
|
||||
|
||||
# this is kernel.py
|
||||
ret.append(RewriteStep(pm_optimize, ctx=lambda _: opts, name="optimize ast"))
|
||||
|
||||
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))
|
||||
|
||||
|
||||
@@ -90,9 +90,9 @@ class BlockContext:
|
||||
ctx.block_ctxs[u] = _sort_ctx(this_block_ctx) if u.op is not Ops.SINK else ()
|
||||
|
||||
# RANGE/IF add to the next ctx
|
||||
# STORE/ASSIGN subtract from the next ctx
|
||||
# STORE/REDUCE_AXIS subtract from the next ctx
|
||||
if u.op in {Ops.RANGE, Ops.IF}: ctx.child_ctxs[u] = _sort_ctx(ctx.block_ctxs[u] + (u,))
|
||||
elif u.op is Ops.STORE: ctx.child_ctxs[u] = tuple([y for y in ctx.block_ctxs[u] if y not in u.src])
|
||||
elif u.op in {Ops.STORE, Ops.REDUCE_AXIS, Ops.CONTIGUOUS}: ctx.child_ctxs[u] = tuple([y for y in ctx.block_ctxs[u] if y not in u.src])
|
||||
return ctx
|
||||
|
||||
# ***** make blocks *****
|
||||
|
||||
@@ -193,21 +193,6 @@ def least_upper_float(dt:DType) -> DType: return dt if dtypes.is_float(dt) else
|
||||
DTYPES_DICT = {k: v for k, v in dtypes.__dict__.items() if isinstance(v, DType) and not k.startswith(("default", "void"))}
|
||||
INVERSE_DTYPES_DICT = {**{v.name:k for k,v in DTYPES_DICT.items()}, "void": "void"}
|
||||
|
||||
@functools.cache
|
||||
def can_safe_cast(dt0:DType, dt1:DType) -> bool:
|
||||
# return if dt1 preserves value of dt0
|
||||
# https://numpy.org/doc/stable/reference/generated/numpy.can_cast.html
|
||||
if dt0 == dt1 or dt0 == dtypes.bool: return True
|
||||
match dt1:
|
||||
case dtypes.double: return dt0 in (dtypes.float, dtypes.half, dtypes.bfloat16)
|
||||
case dtypes.float: return dt0 in (dtypes.half, dtypes.bfloat16)
|
||||
case dtypes.uint64: return dt0 in (dtypes.uint32, dtypes.uint16, dtypes.uint8)
|
||||
case dtypes.uint32: return dt0 in (dtypes.uint16, dtypes.uint8)
|
||||
case dtypes.int64: return dt0 in (dtypes.uint32, dtypes.uint16, dtypes.uint8, dtypes.int32, dtypes.int16, dtypes.int8)
|
||||
case dtypes.int32: return dt0 in (dtypes.uint16, dtypes.uint8, dtypes.int16, dtypes.int8)
|
||||
case dtypes.int16: return dt0 in (dtypes.uint8, dtypes.int8)
|
||||
case _: return False
|
||||
|
||||
def sum_acc_dtype(dt:DType):
|
||||
# default acc dtype for sum
|
||||
if dtypes.is_unsigned(dt): return least_upper_dtype(dt, dtypes.uint)
|
||||
|
||||
+13
-20
@@ -21,24 +21,24 @@ def apply_graph_to_jit(jit_cache: list[ExecItem], input_rawbuffers: list[Buffer]
|
||||
# This allows the accelerator to run some batches while subsequent graphs are still being updated.
|
||||
graphed_jit_cache: list[ExecItem] = []
|
||||
current_batch: list[ExecItem] = []
|
||||
current_batch_devs: list[Compiled] = []
|
||||
current_device: Compiled|None = None
|
||||
|
||||
def flush_batch():
|
||||
nonlocal current_batch, current_batch_devs, max_batch_size
|
||||
nonlocal current_batch, current_device, max_batch_size
|
||||
try:
|
||||
if len(current_batch_devs) == 0: raise GraphException("no device for graph")
|
||||
if current_device is None: raise GraphException("no device for graph")
|
||||
if len(current_batch) <= 1 and not getenv("GRAPH_ONE_KERNEL"): raise GraphException("only one kernel doesn't graph")
|
||||
graph_runner = current_batch_devs[0].graph(current_batch, input_rawbuffers, var_vals)
|
||||
graph_runner = current_device.graph(current_batch, input_rawbuffers, var_vals)
|
||||
# clear jit inputs to allow their memory to be freed/reused
|
||||
for (j,i) in graph_runner.input_replace.keys(): graph_runner.jit_cache[j].bufs[i] = None
|
||||
graphed_jit_cache.append(ExecItem(graph_runner, cast(list[Buffer|None], input_rawbuffers)))
|
||||
max_batch_size *= 2
|
||||
if DEBUG >= 2: print(f"JIT GRAPHing batch with {len(current_batch)} kernels on device {current_batch_devs[0]}")
|
||||
if DEBUG >= 2: print(f"JIT GRAPHing batch with {len(current_batch)} kernels on device {current_device}")
|
||||
except GraphException as e:
|
||||
graphed_jit_cache.extend(current_batch)
|
||||
if DEBUG >= 2: print(f"JIT GRAPHing failed batch with {len(current_batch)} kernels on device {current_batch_devs[0]}: {e}")
|
||||
if DEBUG >= 2: print(f"JIT GRAPHing failed batch with {len(current_batch)} kernels on device {current_device}: {e}")
|
||||
current_batch = []
|
||||
current_batch_devs = []
|
||||
current_device = None
|
||||
|
||||
for ji in jit_cache:
|
||||
match ji.prg:
|
||||
@@ -48,18 +48,13 @@ def apply_graph_to_jit(jit_cache: list[ExecItem], input_rawbuffers: list[Buffer]
|
||||
case ViewOp(): continue # ViewOps are just ignored
|
||||
case _: ji_graph_dev = None # Everything else is not graphed and flushes existing graph if it's being constructed
|
||||
|
||||
# Check if this jit item can be graphed at all, so check if a new graph supports the current item.
|
||||
can_be_graphed = ji_graph_dev is not None and ji_graph_dev.graph is not None and graph_class(ji_graph_dev).supports_exec_item([ji_graph_dev], ji)
|
||||
|
||||
# Check if the current batch can be extended with this item.
|
||||
can_share_graph = can_be_graphed and len(current_batch_devs) > 0 and \
|
||||
graph_class(current_batch_devs[0]).supports_exec_item(dedup(current_batch_devs + [ji_graph_dev]), ji)
|
||||
can_be_graphed = ji_graph_dev is not None and ji_graph_dev.graph is not None and graph_class(ji_graph_dev).supports_exec_item(ji_graph_dev, ji)
|
||||
is_multigraph = can_be_graphed and issubclass(graph_class(ji_graph_dev), MultiGraphRunner)
|
||||
can_share_graph = can_be_graphed and (type(ji_graph_dev) is type(current_device) if is_multigraph else ji_graph_dev == current_device)
|
||||
can_extend_graph_batch = can_share_graph and (max_batch_size == 0 or len(current_batch) < max_batch_size)
|
||||
|
||||
# Flush the current batch if any, since it can't be extended or is full.
|
||||
if not can_extend_graph_batch and len(current_batch) > 0: flush_batch()
|
||||
(current_batch if can_be_graphed else graphed_jit_cache).append(ji)
|
||||
current_batch_devs = dedup(current_batch_devs + [ji_graph_dev]) if can_be_graphed else []
|
||||
current_device = ji_graph_dev if can_be_graphed else None
|
||||
|
||||
if len(current_batch) > 0: flush_batch()
|
||||
return graphed_jit_cache
|
||||
@@ -132,14 +127,12 @@ class GraphRunner(Runner):
|
||||
return list({id(x):x for x in wait_nodes}.values())
|
||||
|
||||
@staticmethod
|
||||
def supports_exec_item(devs:list[Compiled], ei:ExecItem) -> bool: return isinstance(ei.prg, CompiledRunner) and len(dedup(devs)) == 1
|
||||
def supports_exec_item(dev, ei:ExecItem) -> bool: return isinstance(ei.prg, CompiledRunner)
|
||||
|
||||
# a marker for your graph supporting multiple devices of the same type
|
||||
class MultiGraphRunner(GraphRunner):
|
||||
@staticmethod
|
||||
def supports_exec_item(devs:list[Compiled], ei:ExecItem) -> bool:
|
||||
# Devices must be the same type
|
||||
return isinstance(ei.prg, (CompiledRunner, BufferXfer)) and len(dedup([type(Device[b.device]) for b in ei.bufs if b]+[type(d) for d in devs]))==1
|
||||
def supports_exec_item(dev, ei:ExecItem) -> bool: return isinstance(ei.prg, (CompiledRunner, BufferXfer))
|
||||
|
||||
def get_out_buffers_for_ei(ei:ExecItem) -> list[Buffer]:
|
||||
if isinstance(ei.prg, CompiledRunner): return [cast(Buffer, ei.bufs[out]) for out in ei.prg.p.outs if out not in ei.prg.p.ins]
|
||||
|
||||
@@ -2,16 +2,18 @@ from typing import cast, Generator
|
||||
import time, pprint
|
||||
from dataclasses import dataclass, replace, field
|
||||
from tinygrad.helpers import all_same, colored, DEBUG, GlobalCounters, ansilen, BEAM, NOOPT, all_int, CAPTURING, Metadata, TRACEMETA, TracingKey
|
||||
from tinygrad.helpers import DEVECTORIZE, time_to_str, VALIDATE_WITH_CPU, getenv, cpu_profile
|
||||
from tinygrad.helpers import DEVECTORIZE, time_to_str, VALIDATE_WITH_CPU, getenv
|
||||
from tinygrad.uop.ops import Ops, PatternMatcher, UOp, UPat, Variable, sym_infer, graph_rewrite, print_uops, track_rewrites
|
||||
from tinygrad.device import Device, Buffer
|
||||
from tinygrad.renderer import Renderer, ProgramSpec, Estimates
|
||||
from tinygrad.engine.schedule import ScheduleItem
|
||||
from tinygrad.opt import get_optimized_ast
|
||||
from tinygrad.codegen import full_rewrite
|
||||
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=ret))
|
||||
@track_rewrites(name=lambda _ast,_renderer,ret: TracingKey(ret.name, (ret.function_name, ret.ast), ret.src, ret=ret))
|
||||
def get_program(ast:UOp, renderer:Renderer) -> ProgramSpec:
|
||||
"""
|
||||
Transform an AST into a ProgramSpec. May trigger BEAM search.
|
||||
@@ -25,13 +27,16 @@ 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()))
|
||||
|
||||
# linearize
|
||||
try:
|
||||
uops = full_rewrite(ast, renderer)
|
||||
uops = full_rewrite(modified_ast, renderer)
|
||||
except RuntimeError:
|
||||
print("***** LINEARIZE FAILURE *****")
|
||||
print(f"ast = {ast}")
|
||||
print(f"opts = {modified_ast.arg.applied_opts}")
|
||||
raise
|
||||
assert uops[-1].op is Ops.SINK, "last uop must be sink"
|
||||
|
||||
@@ -58,10 +63,7 @@ class CompiledRunner(Runner):
|
||||
def __init__(self, p:ProgramSpec, precompiled:bytes|None=None, prg=None):
|
||||
if DEBUG >= 4: print(p.src)
|
||||
self.p:ProgramSpec = p
|
||||
if precompiled is not None: self.lib = precompiled
|
||||
else:
|
||||
with cpu_profile(TracingKey(f"compile {p.name}", (p.function_name,), cat="compiler"), "TINY"):
|
||||
self.lib = Device[p.device].compiler.compile_cached(p.src)
|
||||
self.lib:bytes = precompiled if precompiled is not None else Device[p.device].compiler.compile_cached(p.src)
|
||||
if DEBUG >= 7: Device[p.device].compiler.disassemble(self.lib)
|
||||
self._prg = Device[p.device].runtime(p.function_name, self.lib) if prg is None else prg
|
||||
super().__init__(p.name, p.device, p.estimates)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from typing import cast
|
||||
import math, dataclasses
|
||||
from tinygrad.dtype import dtypes, sum_acc_dtype
|
||||
from tinygrad.uop.ops import UOp, PatternMatcher, UPat, Ops, all_metadata
|
||||
from tinygrad.helpers import argsort
|
||||
|
||||
@@ -7,7 +8,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].eq(to_inp_shape(ret)).cast(ctx.dtype)
|
||||
max_is_1s = ret.src[0].ne(to_inp_shape(ret)).ne(ret.src[0].const_like(1).cast(dtypes.bool)).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],)
|
||||
@@ -37,7 +38,9 @@ pm_gradient = PatternMatcher([
|
||||
(UPat(Ops.PAD, name="ret"), lambda ctx, ret: (ctx.shrink(tuple([(p[0], s+p[0]) for s,p in zip(ret.src[0].shape, ret.arg)])),)),
|
||||
(UPat(Ops.SHRINK, name="ret"), lambda ctx, ret: (ctx.pad(tuple([(p[0], s-p[1]) for s,p in zip(ret.src[0].shape, ret.arg)])),)),
|
||||
(UPat(Ops.FLIP, name="ret"), lambda ctx, ret: (ctx.flip(ret.arg),)),
|
||||
(UPat(Ops.EXPAND, name="ret"), lambda ctx, ret: (ctx.r(Ops.ADD, tuple(i for i,(si,so) in enumerate(zip(ret.src[0].shape, ret.arg)) if si!=so)),)),
|
||||
# TODO: this cast can be removed by putting the casts around the EXPAND
|
||||
(UPat(Ops.EXPAND, name="ret"), lambda ctx, ret:
|
||||
(ctx.cast(sum_acc_dtype(ctx.dtype)).r(Ops.ADD, tuple(i for i,(si,so) in enumerate(zip(ret.src[0].shape, ret.arg)) if si!=so)).cast(ctx.dtype),)),
|
||||
(UPat(Ops.MULTI, name="ret"), lambda ctx, ret: ctx.shard(ret.device, ret.axis).src),
|
||||
# there's no gradient for bitcast
|
||||
(UPat(Ops.BITCAST), lambda ctx: (None,)),
|
||||
|
||||
@@ -196,6 +196,7 @@ 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
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ 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`
|
||||
@@ -181,6 +182,7 @@ 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"
|
||||
@@ -211,6 +213,7 @@ 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"
|
||||
@@ -237,6 +240,7 @@ 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"
|
||||
@@ -283,6 +287,7 @@ 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"
|
||||
|
||||
@@ -76,6 +76,8 @@ 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)
|
||||
|
||||
@@ -83,6 +85,7 @@ 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):
|
||||
@@ -116,6 +119,7 @@ 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)
|
||||
@@ -123,6 +127,7 @@ 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)
|
||||
@@ -131,6 +136,7 @@ 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):
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
|
||||
from tinygrad.opt.kernel import Kernel
|
||||
from tinygrad.opt.heuristic import hand_coded_optimizations
|
||||
from tinygrad.uop.ops import UOp, PatternMatcher, UPat, Ops
|
||||
from tinygrad.uop.ops import UOp
|
||||
from tinygrad.helpers import NOOPT, BEAM, USE_TC, getenv
|
||||
from tinygrad.renderer import Renderer
|
||||
from tinygrad.uop.spec import type_verify
|
||||
|
||||
def get_optimized_ast(ast:UOp, renderer:Renderer) -> UOp:
|
||||
"""
|
||||
@@ -28,11 +27,4 @@ def get_optimized_ast(ast:UOp, renderer:Renderer) -> UOp:
|
||||
kb = Kernel(ast, opts=renderer)
|
||||
rawbufs = bufs_from_lin(kb, allocate=False)
|
||||
k = beam_search(kb, rawbufs, BEAM.value, bool(getenv("BEAM_ESTIMATE", 1)))
|
||||
ret = k.get_optimized_ast()
|
||||
if __debug__: type_verify(list(ret.toposort()))
|
||||
return ret
|
||||
|
||||
pm_optimize = PatternMatcher([
|
||||
(UPat(Ops.SINK, name="ast"), lambda ctx,ast:
|
||||
get_optimized_ast(ast, ctx) if (ast.arg is None or ast.arg.opts_to_apply is not None) and ast.src[0].st is not None else None),
|
||||
])
|
||||
return k.get_optimized_ast()
|
||||
|
||||
@@ -90,10 +90,11 @@ class Kernel:
|
||||
|
||||
# axis types
|
||||
global_loops = AxisType.GLOBAL if self.opts.has_local else AxisType.LOOP
|
||||
self.axis_types: list[AxisType] = [AxisType.REDUCE if resolve(x!=y) else global_loops for x,y in zip(self.output_shape, self.full_shape)]
|
||||
self.axis_types: list[AxisType] = [AxisType.REDUCE if resolve(x!=y) else global_loops for x,y in zip(self.sts[0].shape, self.sts[-1].shape)]
|
||||
|
||||
# confirm all reduce axes are at the end
|
||||
if (final_reduces := [x for x in self.axis_types if x == AxisType.REDUCE]) and final_reduces != self.axis_types[-len(final_reduces):]:
|
||||
final_reduces = [i for i,(s,n) in enumerate(zip(self.full_shape, self.output_shape)) if resolve(s != n)]
|
||||
if final_reduces != list(range(len(self.full_shape)-len(final_reduces), len(self.full_shape))):
|
||||
raise RuntimeError(f"reduces are not at the end of the shape {self.full_shape} -> {self.output_shape}")
|
||||
|
||||
def copy(self):
|
||||
@@ -200,7 +201,7 @@ class Kernel:
|
||||
if self.shape_len == 0: return
|
||||
shapes, strides = [x.shape for x in self.sts], [x.real_strides() for x in self.sts]
|
||||
# NOTE: we can't use self.first_reduce yet
|
||||
first_reduce = [resolve(x!=y) for x,y in zip(self.output_shape+(0,), self.full_shape+(1,))].index(True)
|
||||
first_reduce = [resolve(x!=y) for x,y in zip(self.sts[0].shape+(0,), self.full_shape+(1,))].index(True)
|
||||
|
||||
# if it's an image, insert fake strides such that this fusion doesn't happen across image axes
|
||||
# TODO: remove membufs
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
from tinygrad.uop.ops import UOp, Ops, GroupOp, PatternMatcher, UPat, graph_rewrite, resolve, sint
|
||||
from tinygrad.helpers import all_same, prod, unwrap
|
||||
from tinygrad.shape.shapetracker import ShapeTracker
|
||||
from tinygrad.shape.view import View, strides_for_shape, get_contraction_with_reduce
|
||||
from tinygrad.schedule.grouper import ALWAYS_CONTIGUOUS
|
||||
|
||||
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),
|
||||
])
|
||||
|
||||
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, Ops.LOAD, Ops.STORE}, 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),
|
||||
])
|
||||
@@ -141,7 +141,9 @@ class CStyleLanguage(Renderer):
|
||||
c: defaultdict[str, int] = defaultdict(int)
|
||||
name = "test"
|
||||
for u in uops:
|
||||
if u.op is Ops.NOOP: continue
|
||||
if u.op is Ops.NOOP:
|
||||
if len(u.src): r[u] = r[u.src[0]]
|
||||
continue
|
||||
if u.op is Ops.SINK:
|
||||
if u.arg is not None: name = u.arg.function_name
|
||||
continue
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import collections, time
|
||||
from typing import Any, cast
|
||||
from tinygrad.helpers import round_up, PROFILE, merge_dicts, getenv, dedup
|
||||
from tinygrad.runtime.support.hcq import HCQCompiled, HCQAllocator, HCQSignal, HCQBuffer, HWQueue, HCQArgsState, BumpAllocator, MMIOInterface
|
||||
from tinygrad.helpers import round_up, PROFILE, merge_dicts, getenv
|
||||
from tinygrad.runtime.support.hcq import HCQCompiled, HCQAllocator, HCQSignal, HCQBuffer, HWQueue, HCQArgsState, BumpAllocator
|
||||
from tinygrad.device import Buffer, BufferSpec, Compiled, Device, ProfileGraphEntry, ProfileGraphEvent
|
||||
from tinygrad.dtype import dtypes
|
||||
from tinygrad.uop.ops import UOp, Variable
|
||||
@@ -29,7 +29,7 @@ class HCQGraph(MultiGraphRunner):
|
||||
for ji in jit_cache:
|
||||
if not isinstance(ji.prg, CompiledRunner): continue
|
||||
kernargs_size[ji.prg.dev] += round_up(ji.prg._prg.kernargs_alloc_size, 16)
|
||||
self.kernargs_bufs: dict[Compiled, HCQBuffer] = {d:d.allocator._alloc(max(sz, 1), BufferSpec(cpu_access=True)) for d,sz in kernargs_size.items()}
|
||||
self.kernargs_bufs: dict[Compiled, HCQBuffer] = {dev:dev.allocator._alloc(sz, BufferSpec(cpu_access=True)) for dev,sz in kernargs_size.items()}
|
||||
|
||||
# Fill initial arguments.
|
||||
self.ji_args: dict[int, HCQArgsState] = {}
|
||||
@@ -51,8 +51,8 @@ class HCQGraph(MultiGraphRunner):
|
||||
self.comp_queues: dict[HCQCompiled, HWQueue] = {dev: dev.hw_compute_queue_t() for dev in self.devices}
|
||||
self.copy_queues: dict[HCQCompiled, HWQueue] = {} # lazy allocation
|
||||
|
||||
self.signals: dict[Any, HCQSignal] = {**{dev: dev.new_signal(value=0) for dev in self.devices if not dev._is_cpu()},
|
||||
**{"KICK": self.devices[0].new_signal(value=0)}, **{dev: self.devices[0].new_signal(value=0) for dev in self.devices if dev._is_cpu()}}
|
||||
self.signals: dict[Any, HCQSignal] = {**{dev: dev.new_signal(value=0) for dev in self.devices if dev.device != "CPU"},
|
||||
**{"KICK": self.devices[0].new_signal(value=0)}, **{dev: self.devices[0].new_signal(value=0) for dev in self.devices if dev.device == "CPU"}}
|
||||
self.kickoff_value: int = 0
|
||||
self.kickoff_var = UOp.variable("kickoff_var", 0, 0xffffffff, dtype=dtypes.uint32)
|
||||
|
||||
@@ -87,7 +87,7 @@ class HCQGraph(MultiGraphRunner):
|
||||
assert (enqueue_dev.hw_copy_queue_t is not None), "device must implement a copy queue"
|
||||
enqueue_queue = self.copy_queues.setdefault(enqueue_dev, enqueue_dev.hw_copy_queue_t())
|
||||
|
||||
out_signal = self.signals.setdefault(enqueue_queue, self.devices[0].new_signal(value=0))
|
||||
out_signal = self.signals.setdefault(enqueue_queue, enqueue_dev.new_signal(value=0))
|
||||
|
||||
# Get dependencies based on input and output buffers.
|
||||
rdeps = self._access_resources(ji.bufs, ji.prg.p.outs if is_exec_prg else [0], (enqueue_queue, j + 1)) #type:ignore
|
||||
@@ -225,17 +225,7 @@ class HCQGraph(MultiGraphRunner):
|
||||
for fdev, buf in self.kernargs_bufs.items(): fdev.allocator._free(buf, BufferSpec(cpu_access=True))
|
||||
|
||||
@staticmethod
|
||||
def supports_exec_item(devs:list[Compiled], ei:ExecItem) -> bool:
|
||||
# Check if all devices are HCQ
|
||||
all_devs = cast(list[HCQCompiled], dedup(devs + [Device[b.device] for b in ei.bufs if b]))
|
||||
if not all(issubclass(type(d), HCQCompiled) for d in all_devs): return False
|
||||
|
||||
# If all of devices are mapped into CPU address space, can use CPU inside the peer group.
|
||||
cpu_support = all(isinstance(d.timeline_signal.base_buf.view, MMIOInterface) for d in all_devs)
|
||||
|
||||
# Check if all devices are within the same peer group. If CPU is supported, don't count it as a separate peer group.
|
||||
if len(set(d.peer_group for d in all_devs if cpu_support and not d._is_cpu())) > 1: return False
|
||||
|
||||
def supports_exec_item(dev, ei:ExecItem) -> bool:
|
||||
# MOCKGPU is not supported, since it can't execute commands in parallel
|
||||
copy = (isinstance(ei.prg, BufferCopy) and cast(HCQCompiled, devs[0]).hw_copy_queue_t is not None) and not getenv("MOCKGPU")
|
||||
return isinstance(ei.prg, (CompiledRunner, BufferXfer)) or copy
|
||||
copy = (isinstance(ei.prg, BufferCopy) and cast(HCQCompiled, dev).hw_copy_queue_t is not None) and not getenv("MOCKGPU")
|
||||
return all(issubclass(type(Device[b.device]), HCQCompiled) for b in ei.bufs if b) and (isinstance(ei.prg, (CompiledRunner, BufferXfer)) or copy)
|
||||
|
||||
@@ -673,8 +673,8 @@ class PCIIface(PCIIfaceBase):
|
||||
self.dev_impl.gfx.setup_ring(ring_addr=ring.va_addr, ring_size=ring.size, rptr_addr=gart.va_addr, wptr_addr=gart.va_addr+0x10,
|
||||
eop_addr=eop_buffer.va_addr, eop_size=eop_buffer.size, doorbell=(doorbell_index:=am.AMDGPU_NAVI10_DOORBELL_MEC_RING0), pipe=0, queue=0)
|
||||
|
||||
return AMDQueueDesc(ring=ring.cpu_view().view(fmt='I'), doorbells=[self.dev_impl.doorbell64.view(doorbell_index * 8, 8, fmt='Q')],
|
||||
read_ptrs=[gart.cpu_view().view(size=8, fmt='Q')], write_ptrs=[gart.cpu_view().view(offset=0x10, size=8, fmt='Q')])
|
||||
return AMDQueueDesc(ring=MMIOInterface(ring.va_addr, ring.size, fmt='I'), read_ptrs=[MMIOInterface(gart.va_addr, 8, fmt='Q')],
|
||||
write_ptrs=[MMIOInterface(gart.va_addr+0x10, 8, fmt='Q')], doorbells=[MMIOInterface(self.doorbell_cpu_addr + doorbell_index * 8, 8, fmt='Q')])
|
||||
|
||||
def sleep(self, timeout):
|
||||
if self.pci_dev.irq_poller is not None and (events_cnt:=len(self.pci_dev.irq_poller.poll(timeout))):
|
||||
@@ -717,8 +717,16 @@ class USBIface(PCIIface):
|
||||
view=USBMMIOInterface(self.usb, self.bars[0][0] + am_mapping.paddrs[0][0], size, fmt='B') if cpu_access else None, owner=self.dev)
|
||||
|
||||
def create_queue(self, queue_type, ring, gart, eop_buffer=None, cwsr_buffer=None, ctl_stack_size=0, ctx_save_restore_size=0, xcc_id=0):
|
||||
if queue_type == kfd.KFD_IOC_QUEUE_TYPE_COMPUTE: self.usb._pci_cacheable += [(ring.cpu_view().addr, ring.size)]
|
||||
return super().create_queue(queue_type, ring, gart, eop_buffer, cwsr_buffer, ctl_stack_size, ctx_save_restore_size, xcc_id)
|
||||
if queue_type == kfd.KFD_IOC_QUEUE_TYPE_SDMA:
|
||||
self.dev_impl.sdma.setup_ring(ring_addr=ring.va_addr, ring_size=ring.size, rptr_addr=gart.va_addr, wptr_addr=gart.va_addr+0x10,
|
||||
doorbell=(doorbell_index:=am.AMDGPU_NAVI10_DOORBELL_sDMA_ENGINE0), pipe=0, queue=0)
|
||||
else:
|
||||
self.dev_impl.gfx.setup_ring(ring_addr=ring.va_addr, ring_size=ring.size, rptr_addr=gart.va_addr, wptr_addr=gart.va_addr+0x10,
|
||||
eop_addr=eop_buffer.va_addr, eop_size=eop_buffer.size, doorbell=(doorbell_index:=am.AMDGPU_NAVI10_DOORBELL_MEC_RING0), pipe=0, queue=0)
|
||||
self.usb._pci_cacheable += [(ring.cpu_view().addr, ring.size)]
|
||||
|
||||
return AMDQueueDesc(ring=ring.cpu_view().view(fmt='I'), doorbells=[self.dev_impl.doorbell64.view(doorbell_index * 8, 8, fmt='Q')],
|
||||
read_ptrs=[gart.cpu_view().view(size=8, fmt='Q')], write_ptrs=[gart.cpu_view().view(offset=0x10, size=8, fmt='Q')])
|
||||
|
||||
def sleep(self, timeout): pass
|
||||
|
||||
|
||||
+12
-30
@@ -1,5 +1,5 @@
|
||||
from __future__ import annotations
|
||||
import platform, subprocess, sys, ctypes, functools, time, mmap, threading, queue
|
||||
import platform, subprocess, sys, ctypes, functools, time, mmap
|
||||
from tinygrad.helpers import capstone_flatdump, getenv, from_mv, to_mv, OSX, mv_address, wait_cond, cpu_profile
|
||||
from tinygrad.device import Compiler, BufferSpec, DMACPURef
|
||||
from tinygrad.runtime.support.hcq import HCQCompiled, HCQAllocatorBase, HCQBuffer, HWQueue, HCQArgsState, HCQSignal, HCQProgram, MMIOInterface
|
||||
@@ -7,10 +7,6 @@ from tinygrad.runtime.support.elf import jit_loader
|
||||
from tinygrad.renderer.cstyle import ClangRenderer
|
||||
from tinygrad.uop.ops import sint
|
||||
|
||||
class CPUSignal(HCQSignal):
|
||||
def _sleep(self, time_spent_waiting_ms:int):
|
||||
if self.is_timeline and self.owner is not None: self.owner.tasks.join()
|
||||
|
||||
class ClangJITCompiler(Compiler):
|
||||
def __init__(self, cachekey="compile_clang_jit"): super().__init__(cachekey)
|
||||
|
||||
@@ -25,19 +21,6 @@ class ClangJITCompiler(Compiler):
|
||||
|
||||
def disassemble(self, lib:bytes): return capstone_flatdump(lib)
|
||||
|
||||
class CPUWorker(threading.Thread):
|
||||
def __init__(self, dev):
|
||||
super().__init__()
|
||||
self.dev, self.tasks, self.daemon = dev, dev.tasks, True
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
cmd_iter = iter(self.tasks.get())
|
||||
for cmd in cmd_iter:
|
||||
args_cnt = next(cmd_iter)
|
||||
cmd(*[next(cmd_iter) for _ in range(args_cnt)])
|
||||
self.tasks.task_done()
|
||||
|
||||
class CPUComputeQueue(HWQueue):
|
||||
def _exec(self, prg, bufs, *args):
|
||||
prg.fxn(*map(ctypes.c_uint64, args[:bufs]), *map(ctypes.c_int64 if platform.machine() == "arm64" else ctypes.c_int32, args[bufs:]))
|
||||
@@ -54,7 +37,13 @@ class CPUComputeQueue(HWQueue):
|
||||
def wait(self, signal, value=0): return self.cmd(self._wait, signal.value_addr, value)
|
||||
def timestamp(self, signal): return self.cmd(self._timestamp, signal.timestamp_addr)
|
||||
def signal(self, signal, value:sint=0): return self.cmd(self._signal, signal.value_addr, value)
|
||||
def _submit(self, dev): dev.tasks.put(self._q[:])
|
||||
|
||||
def _submit(self, dev):
|
||||
# Execute the commands in the queue: fn, argc, args...
|
||||
off = 0
|
||||
while off < len(self._q):
|
||||
self._q[off](*self._q[off + 2:off + 2 + self._q[off + 1]])
|
||||
off += self._q[off + 1] + 2
|
||||
|
||||
# NOTE: MAP_JIT is added to mmap module in python 3.13
|
||||
MAP_JIT = 0x0800
|
||||
@@ -101,23 +90,16 @@ class CPUAllocator(HCQAllocatorBase):
|
||||
elif sys.platform == "win32": addr = mv_address(buf:=mmap.mmap(-1, size, access=mmap.ACCESS_WRITE))
|
||||
else: addr = mv_address(buf:=mmap.mmap(-1, size, mmap.MAP_ANON | mmap.MAP_PRIVATE, mmap.PROT_READ | mmap.PROT_WRITE))
|
||||
return HCQBuffer(va:=addr, sz:=size, meta=buf, view=MMIOInterface(va, sz, fmt='B'), owner=self.dev)
|
||||
def _as_buffer(self, src) -> memoryview:
|
||||
self.dev.synchronize()
|
||||
return to_mv(src.va_addr, src.size)
|
||||
def _as_dmaref(self, buf):
|
||||
self.dev.synchronize()
|
||||
return DMACPURef(buf.va_addr, buf.size)
|
||||
def _as_buffer(self, src) -> memoryview: return to_mv(src.va_addr, src.size)
|
||||
def _as_dmaref(self, buf): return DMACPURef(buf.va_addr, buf.size)
|
||||
def _copyin(self, dest, src:memoryview):
|
||||
self.dev.synchronize()
|
||||
with cpu_profile('TINY -> CPU', self.dev.device, is_copy=True): ctypes.memmove(dest.va_addr, from_mv(src), len(src))
|
||||
def _copyout(self, dest:memoryview, src):
|
||||
self.dev.synchronize()
|
||||
with cpu_profile('CPU -> TINY', self.dev.device, is_copy=True): ctypes.memmove(from_mv(dest), src.va_addr, len(dest))
|
||||
def _map(self, buf:HCQBuffer):
|
||||
if buf.view is None or not isinstance(buf.view, MMIOInterface): raise RuntimeError("Cannot map buffer without view to cpu")
|
||||
|
||||
class CPUDevice(HCQCompiled):
|
||||
def __init__(self, device:str=""):
|
||||
self.tasks:queue.Queue = queue.Queue()
|
||||
CPUWorker(self).start()
|
||||
super().__init__(device, CPUAllocator(self), ClangRenderer(), ClangJITCompiler(), functools.partial(CPUProgram, self), CPUSignal, CPUComputeQueue)
|
||||
super().__init__(device, CPUAllocator(self), ClangRenderer(), ClangJITCompiler(), functools.partial(CPUProgram, self), HCQSignal, CPUComputeQueue,
|
||||
supports_graph=False)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import ctypes, platform, functools, queue
|
||||
import ctypes, platform, functools
|
||||
from tinygrad.device import Compiler
|
||||
from tinygrad.runtime.support.hcq import HCQCompiled, HCQSignal
|
||||
from tinygrad.runtime.ops_cpu import CPUAllocator, CPUProgram, CPUComputeQueue, CPUWorker
|
||||
from tinygrad.runtime.ops_cpu import CPUAllocator, CPUProgram, CPUComputeQueue
|
||||
from tinygrad.helpers import OSX, getenv, capstone_flatdump, DEBUG
|
||||
from tinygrad.renderer.llvmir import LLVMRenderer
|
||||
import tinygrad.runtime.autogen.llvm as llvm
|
||||
@@ -73,6 +73,5 @@ class HostLLVMCompiler(LLVMCompiler):
|
||||
|
||||
class LLVMDevice(HCQCompiled):
|
||||
def __init__(self, device:str=""):
|
||||
self.tasks:queue.Queue = queue.Queue()
|
||||
CPUWorker(self).start()
|
||||
super().__init__(device, CPUAllocator(self), LLVMRenderer(), HostLLVMCompiler(), functools.partial(CPUProgram, self), HCQSignal, CPUComputeQueue)
|
||||
super().__init__(device, CPUAllocator(self), LLVMRenderer(), HostLLVMCompiler(), functools.partial(CPUProgram, self), HCQSignal, CPUComputeQueue,
|
||||
supports_graph=False)
|
||||
|
||||
@@ -225,8 +225,7 @@ class RemoteHandler:
|
||||
graph_cls = graph_class(Device[self.base_device])
|
||||
rp = RemoteProperties(
|
||||
real_device=dev.device, renderer=(cls.__module__, cls.__name__, args), offset_supported=hasattr(dev.allocator, '_offset'),
|
||||
graph_supported=graph_cls is not None,
|
||||
graph_supports_multi=graph_cls is not None and issubclass(graph_cls, MultiGraphRunner) and hasattr(dev.allocator, '_transfer'),
|
||||
graph_supported=graph_cls is not None, graph_supports_multi=graph_cls is not None and issubclass(graph_cls, MultiGraphRunner),
|
||||
ib_gid=bytes(self.ib_ctx.gid_attr.raw) if self.ib_ctx is not None else None,
|
||||
)
|
||||
ret = repr(rp).encode()
|
||||
|
||||
@@ -358,14 +358,14 @@ class HCQCompiled(Compiled, Generic[SignalType]):
|
||||
peer_groups: dict[str, list[HCQCompiled]] = collections.defaultdict(list)
|
||||
signal_pages: dict[str, list[HCQBuffer]] = collections.defaultdict(list) # per peer group
|
||||
signal_pool: dict[str, list[HCQBuffer]] = collections.defaultdict(list) # per peer group
|
||||
cpu_devices: list[HCQCompiled] = []
|
||||
|
||||
def __init__(self, device:str, allocator:HCQAllocatorBase, renderer:Renderer, compiler:Compiler, runtime, signal_t:Type[SignalType],
|
||||
comp_queue_t:Callable[[], HWQueue], copy_queue_t:Callable[[], HWQueue]|None=None, kernargs_size=(16 << 20), sigalloc_size=0x1000):
|
||||
comp_queue_t:Callable[[], HWQueue], copy_queue_t:Callable[[], HWQueue]|None=None, kernargs_size=(16 << 20), sigalloc_size=0x1000,
|
||||
supports_graph=True):
|
||||
self.device_id:int = int(device.split(":")[1]) if ":" in device else 0
|
||||
|
||||
from tinygrad.runtime.graph.hcq import HCQGraph
|
||||
super().__init__(device, allocator, renderer, compiler, runtime, HCQGraph)
|
||||
super().__init__(device, allocator, renderer, compiler, runtime, HCQGraph if supports_graph else None)
|
||||
|
||||
# TODO: peer logic is determined based on device name.
|
||||
self.peer_group = device.split(":")[0]
|
||||
@@ -383,13 +383,7 @@ class HCQCompiled(Compiled, Generic[SignalType]):
|
||||
self.kernargs_buf:HCQBuffer = self.allocator.alloc(kernargs_size, BufferSpec(cpu_access=True))
|
||||
self.kernargs_offset_allocator:BumpAllocator = BumpAllocator(self.kernargs_buf.size, wrap=True)
|
||||
|
||||
if self._is_cpu(): HCQCompiled.cpu_devices.append(self)
|
||||
|
||||
def synchronize(self):
|
||||
# If we have any work on CPU devices, need to synchronize them. This is just an optimization to release GIL allowing to finish faster.
|
||||
if not self._is_cpu():
|
||||
for dev in HCQCompiled.cpu_devices: dev.synchronize()
|
||||
|
||||
try: self.timeline_signal.wait(self.timeline_value - 1)
|
||||
except RuntimeError as e:
|
||||
if hasattr(self, 'on_device_hang'): self.on_device_hang()
|
||||
|
||||
@@ -60,8 +60,7 @@ class NVRpcQueue:
|
||||
|
||||
# Handling special functions
|
||||
if hdr.function == nv.NV_VGPU_MSG_EVENT_GSP_RUN_CPU_SEQUENCER: self.gsp.run_cpu_seq(msg)
|
||||
elif hdr.function == nv.NV_VGPU_MSG_EVENT_OS_ERROR_LOG:
|
||||
print(f"nv {self.gsp.nvdev.devfmt}: GSP LOG: {msg[12:].tobytes().rstrip(bytes([0])).decode('utf-8')}")
|
||||
elif hdr.function == nv.NV_VGPU_MSG_EVENT_OS_ERROR_LOG: print(f"GSP LOG: {msg[12:].tobytes().rstrip(bytes([0])).decode('utf-8')}")
|
||||
|
||||
# Update the read pointer
|
||||
self.rx.readPtr = (self.rx.readPtr + round_up(hdr.length, self.tx.msgSize) // self.tx.msgSize) % self.tx.msgCount
|
||||
|
||||
@@ -66,7 +66,7 @@ class NVPageTableEntry:
|
||||
return self.read_fields(entry_id)[f'address{small}{sys}'] << 12
|
||||
|
||||
class NVMemoryManager(MemoryManager):
|
||||
va_allocator = TLSFAllocator((1 << 44), base=0x1000000000) # global for all devices.
|
||||
va_allocator = TLSFAllocator((1 << 44), base=1 << 30) # global for all devices.
|
||||
|
||||
def on_range_mapped(self): self.dev.NV_VIRTUAL_FUNCTION_PRIV_MMU_INVALIDATE.write((1 << 0) | (1 << 1) | (1 << 6) | (1 << 31))
|
||||
|
||||
|
||||
@@ -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.DEFINE_GLOBAL}
|
||||
Ops.CONST, Ops.BIND, Ops.DEVICE, Ops.MSELECT, Ops.MSTACK}
|
||||
|
||||
# **** Grouper decides which of the UOps realize
|
||||
|
||||
|
||||
+248
-24
@@ -1,14 +1,14 @@
|
||||
from dataclasses import dataclass
|
||||
from tinygrad.uop.ops import UOp, Ops, GroupOp, PatternMatcher, UPat, graph_rewrite, graph_rewrite_map, identity_element, resolve
|
||||
from tinygrad.uop.ops import UOp, Ops, GroupOp, PatternMatcher, UPat, graph_rewrite, graph_rewrite_map, identity_element, resolve, sint
|
||||
from tinygrad.uop.ops import track_rewrites, _substitute
|
||||
from tinygrad.uop.spec import type_verify, tensor_uop_spec
|
||||
from tinygrad.uop.symbolic import symbolic_simple
|
||||
from tinygrad.helpers import Metadata, all_int, all_same, colored, prod, dedup, unwrap, getenv, pluralize, FUSE_ARANGE, DEBUG, SPLIT_REDUCEOP
|
||||
from tinygrad.helpers import Metadata, all_int, all_same, colored, prod, dedup, unwrap, getenv, pluralize, FUSE_ARANGE, DEBUG, SPLIT_REDUCEOP, argsort
|
||||
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
|
||||
from tinygrad.opt.swizzler import merge_views, view_left, view_right, apply_swizzle, swizzle_reduceop
|
||||
|
||||
# creation can recurse a lot
|
||||
import sys
|
||||
@@ -148,13 +148,118 @@ 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
|
||||
|
||||
early_buffer_ops = PatternMatcher([
|
||||
add_buffer_ops = PatternMatcher([
|
||||
# LOAD
|
||||
(UPat(Ops.BUFFER, name="x"), lambda ctx,x: UOp(Ops.DEFINE_GLOBAL, x.dtype.ptr(x.size), (), ctx.index(x), tag=1)),
|
||||
# no SINK for meta ops
|
||||
(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).view(s.st), s) for i,x in enumerate(sink.src)])),
|
||||
# passthrough ASSIGN
|
||||
(UPat(Ops.ASSIGN, name="x"), lambda x: x.src[1]),
|
||||
# VALID
|
||||
(UPat(Ops.VIEW, src=(UPat.cvar(),), name="self"),
|
||||
lambda self: UOp.where(UOp(Ops.VALID, dtypes.bool, (UOp(Ops.VIEW, arg=self.st),)), self.const_like(self.base.arg), 0)),
|
||||
])
|
||||
|
||||
def check_load_st(glbl:UOp, view:UOp):
|
||||
@@ -168,16 +273,6 @@ def check_load_st(glbl:UOp, view:UOp):
|
||||
+colored(" - a += a.T\n", "red")+colored(" + a += a.T.contiguous()", "green"))
|
||||
|
||||
fix_kernel_ops = PatternMatcher([
|
||||
# add the LOAD
|
||||
(UPat(Ops.DEFINE_GLOBAL, name="x"), lambda x: x.replace(tag=None).view(x.st).load() if x.tag is not None else None),
|
||||
# STORE (except for meta ops)
|
||||
(UPat(Ops.SINK, src=UPat(GroupOp.All-{Ops.STORE}), name="sink"), lambda sink:
|
||||
UOp.sink(*[UOp.store(UOp(Ops.DEFINE_GLOBAL, (s:=x.base).dtype.ptr(s.st.real_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
|
||||
(UPat(Ops.VIEW, src=(UPat.cvar(),), name="self"),
|
||||
lambda self: UOp.where(UOp(Ops.VALID, dtypes.bool, (UOp(Ops.VIEW, arg=self.st),)), self.const_like(self.base.arg), 0)),
|
||||
# remove CONTIGUOUS/DEVICE from kernel AST
|
||||
(UPat((Ops.CONTIGUOUS, Ops.MSELECT), src=(UPat.var("x"),)), lambda x: x),
|
||||
(UPat(Ops.VIEW, src=(UPat(Ops.DEVICE),), name="view"), lambda view: view.replace(src=())),
|
||||
@@ -196,6 +291,10 @@ replace_globals = PatternMatcher([
|
||||
|
||||
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:
|
||||
@@ -203,15 +302,9 @@ def fix_kernel_ast(k:UOp) -> UOp|None:
|
||||
# traverse back through MSELECT and MSTACK. HACK: 0 branch of MSTACK only
|
||||
while s.op in {Ops.MSELECT, Ops.MSTACK}: s = s.src[0]
|
||||
bufs.append(s)
|
||||
# replace global memory ops with the BUFFER they write to
|
||||
ast = graph_rewrite(k.arg.ast, replace_globals, bottom_up=True, name="replace globals")
|
||||
ast = graph_rewrite(ast, early_buffer_ops, bufs, bottom_up=True, name="replace buffer early")
|
||||
ast = graph_rewrite(ast, view_left+add_buffer_ops+fix_kernel_ops, bufs, bottom_up=True, name="replace buffer")
|
||||
if ast.op is Ops.SINK and not all_same([x.device for x in k.src]):
|
||||
raise RuntimeError(f"all buffers must be on the same device: {tuple(b.buf_uop.buffer for b in k.src)}")
|
||||
# TODO: move these to codegen
|
||||
ast = graph_rewrite(ast, view_left, name="Main View Left")
|
||||
ast = graph_rewrite(ast, view_right, name="Main View Right")
|
||||
ast = graph_rewrite(ast, view_left+fix_kernel_ops, bottom_up=True, name="replace buffer")
|
||||
return k.replace(arg=Kernel(ast, k.arg.metadata))
|
||||
|
||||
create_ast = PatternMatcher([(UPat(Ops.KERNEL, name="k"), fix_kernel_ast),])
|
||||
@@ -251,7 +344,7 @@ pm_fuse = PatternMatcher([
|
||||
def do_fusion(x:UOp):
|
||||
found_contiguous = {}
|
||||
def gate_contiguous(x):
|
||||
if is_contiguous:=(x.op is Ops.CONTIGUOUS): found_contiguous[x] = x.replace(src=(UOp(Ops.VIEW, arg=x.st), UOp.unique()))
|
||||
if is_contiguous:=(x.op is Ops.CONTIGUOUS): found_contiguous[x] = x.replace(src=(UOp(Ops.VIEW, arg=x.st),))
|
||||
return not is_contiguous
|
||||
x.toposort(gate=gate_contiguous)
|
||||
del gate_contiguous
|
||||
@@ -324,6 +417,121 @@ finalize_contiguous = PatternMatcher([
|
||||
|
||||
remove_tags = PatternMatcher([(UPat(GroupOp.All, name="x"), lambda x: x.replace(tag=None) if x.tag is not None else None)])
|
||||
|
||||
def contiguous_create_ranges(ctx:list[int], x:UOp):
|
||||
if len(x.src) != 1: return None
|
||||
ranges = []
|
||||
for s in x.shape:
|
||||
if resolve(s!=1):
|
||||
ranges.append(UOp.range(dtypes.int, s, ctx[0]))
|
||||
ctx[0] += 1
|
||||
else:
|
||||
ranges.append(UOp.const(dtypes.int, 0))
|
||||
mm = UOp(Ops.MAP, dtype=x.src[0].dtype, src=(x.src[0],)+tuple(ranges))
|
||||
buf = UOp.new_buffer(x.device, prod(x.shape), x.dtype).reshape(x.shape)
|
||||
mm2 = UOp(Ops.MAP, dtype=x.src[0].dtype, src=(buf,)+tuple(ranges))
|
||||
return UOp(Ops.STORE, src=(mm2, mm)+tuple(ranges))
|
||||
#return x.replace(src=(mm,)+tuple(ranges))
|
||||
|
||||
def map_reshape(x:UOp):
|
||||
# don't push on the final buffer reshape for readable graph
|
||||
#if x.src[0].src[0].op is Ops.BUFFER: return None
|
||||
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):
|
||||
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.MAP, dtype=x.src[0].src[0].dtype, src=(x.src[0].src[0],)+ret)
|
||||
# generic
|
||||
return x.src[0].replace(src=tuple([UOp(Ops.MAP, dtype=s.dtype, src=(s,)+ret) for s in x.src[0].src]))
|
||||
|
||||
def map_reduce(ctx:list[int], x:UOp):
|
||||
rngs = list(x.src[1:])
|
||||
r = x.src[0]
|
||||
new_ranges = []
|
||||
for i,s in enumerate(r.src[0].shape):
|
||||
if i in r.arg[1]:
|
||||
assert rngs[i].op == Ops.CONST
|
||||
rngs[i] = UOp.range(dtypes.int, s, ctx[0])
|
||||
new_ranges.append(rngs[i])
|
||||
ctx[0] += 1
|
||||
mm = UOp(Ops.MAP, r.src[0].dtype, src=(r.src[0],)+tuple(rngs))
|
||||
return UOp(Ops.REDUCE_AXIS, r.dtype, src=(mm,)+tuple(new_ranges), arg=r.arg)
|
||||
|
||||
def map_permute(x:UOp):
|
||||
ret = x.src[1:]
|
||||
# argsort or not?
|
||||
perm = argsort(x.src[0].arg)
|
||||
print(perm, x.src[0].arg)
|
||||
ret = tuple([ret[p] for p in perm])
|
||||
print(x.src[0].src[0].shape, ret)
|
||||
#return UOp(Ops.MAP, dtype=x.src[0].src[0].dtype, src=(x.src[0].src[0],)+ret)
|
||||
return x.src[0].replace(src=tuple([UOp(Ops.MAP, dtype=s.dtype, src=(s,)+ret) for s in x.src[0].src]))
|
||||
|
||||
def map_expand(x:UOp):
|
||||
r = x.src[0]
|
||||
inp_shape, exp_shape = x.src[0].src[0].shape, x.src[0].shape
|
||||
ret = list(x.src[1:])
|
||||
exp_ranges = []
|
||||
for i,(x,y) in enumerate(zip(inp_shape, exp_shape)):
|
||||
if x != y:
|
||||
exp_ranges.append(ret[i])
|
||||
ret[i] = UOp.const(dtypes.int, 0)
|
||||
mm = UOp(Ops.MAP, r.dtype, src=(r.src[0],)+tuple(ret))
|
||||
return UOp(Ops.EXPAND, r.dtype, src=(mm,)+tuple(exp_ranges), arg=r.arg)
|
||||
|
||||
def map_shrink(ctx:list[int], x:UOp):
|
||||
r = x.src[0]
|
||||
ret = list(x.src[1:])
|
||||
for i,(s,(ss,se)) in enumerate(zip(r.src[0].shape, r.arg)):
|
||||
assert ss == 0, "add to range?"
|
||||
if se-ss != s and False:
|
||||
new_ret_i = [ret[i]]
|
||||
if ss != 0:
|
||||
new_ret_i = [UOp.range(dtypes.int, ss, ctx[0])] + new_ret_i
|
||||
ctx[0] += 1
|
||||
if se != s:
|
||||
new_ret_i = new_ret_i + [UOp.range(dtypes.int, s-se, ctx[0])]
|
||||
ctx[0] += 1
|
||||
ret[i] = UOp(Ops.CATRANGE, src=tuple(new_ret_i))
|
||||
mm = UOp(Ops.MAP, r.dtype, src=(r.src[0],)+tuple(ret))
|
||||
#return mm
|
||||
# TODO: put the ranges on the shrink?
|
||||
return UOp(Ops.SHRINK, r.dtype, src=(mm,), arg=r.arg)
|
||||
|
||||
index_pushing = PatternMatcher([
|
||||
(UPat(Ops.CONTIGUOUS, name="x"), contiguous_create_ranges),
|
||||
(UPat(Ops.MAP, src=(UPat(Ops.RESHAPE),), allow_any_len=True, name="x"), map_reshape),
|
||||
(UPat(Ops.MAP, src=(UPat(Ops.PERMUTE),), allow_any_len=True, name="x"), map_permute),
|
||||
(UPat(Ops.MAP, src=(UPat(Ops.EXPAND),), allow_any_len=True, name="x"), map_expand),
|
||||
(UPat(Ops.MAP, src=(UPat(Ops.SHRINK),), allow_any_len=True, name="x"), map_shrink),
|
||||
(UPat(Ops.MAP, src=(UPat(Ops.REDUCE_AXIS),), allow_any_len=True, name="x"), map_reduce),
|
||||
# move MAP through elementwise ALU
|
||||
(UPat(Ops.MAP, src=(UPat(GroupOp.Elementwise),), allow_any_len=True, name="x"),
|
||||
lambda x: x.src[0].replace(src=tuple([UOp(Ops.MAP, dtype=s.dtype, src=(s,)+x.src[1:]) for s in x.src[0].src]))),
|
||||
# MAP on STORE is NOOP
|
||||
(UPat(Ops.MAP, src=(UPat(Ops.STORE),), allow_any_len=True, name="x"), lambda x: x.src[0]),
|
||||
])
|
||||
|
||||
fix_buffers = PatternMatcher([
|
||||
(UPat(Ops.BUFFER, name="x"), lambda x: UOp(Ops.DEFINE_GLOBAL, dtype=x.dtype.ptr(x.arg), arg=x.src[0].arg)),
|
||||
(UPat(Ops.MAP, name="x"), lambda x: x.replace(op=Ops.INDEX, dtype=x.src[0].dtype).load()),
|
||||
(UPat(Ops.STORE, src=(UPat(Ops.LOAD),), name="x", allow_any_len=True), lambda x: x.replace(src=(x.src[0].src[0],)+x.src[1:])),
|
||||
(UPat((Ops.RESHAPE, Ops.SHRINK, Ops.PERMUTE), name="x"), lambda x: x.src[0]),
|
||||
# do EXPANDs need to track the ranges they end?
|
||||
(UPat(Ops.EXPAND, name="x"), lambda x: x.src[0]),
|
||||
#(UPat(Ops.EXPAND, name="x"), lambda x: x.replace(arg=None, op=Ops.NOOP)),
|
||||
(UPat(Ops.REDUCE_AXIS, name="x"), lambda x: x.replace(op=Ops.REDUCE, arg=x.arg[0])),
|
||||
])
|
||||
|
||||
@track_rewrites(name=lambda sink,ret: f"Schedule {pluralize('Kernel',len([u for u in ret[sink].toposort() if u.op is Ops.KERNEL]))}")
|
||||
def get_kernelize_map(sink:UOp) -> dict[UOp, UOp]:
|
||||
"""
|
||||
@@ -335,6 +543,20 @@ def get_kernelize_map(sink:UOp) -> dict[UOp, UOp]:
|
||||
Returns:
|
||||
Map transforming each UOp in the sink to the Ops.KERNEL graph.
|
||||
"""
|
||||
pushed = graph_rewrite(sink, index_pushing, ctx=[0], name="index pushing", bottom_up=True)
|
||||
pushed = graph_rewrite(pushed, fix_buffers, name="fix buffers")
|
||||
from tinygrad.codegen.devectorizer import pm_reduce, ReduceContext
|
||||
pushed = graph_rewrite(pushed, pm_reduce, ctx=ReduceContext(), name="remove reduce")
|
||||
from tinygrad.codegen.linearize import block_create, BlockContext, pm_blockend_merge, block_merge, pm_finalize
|
||||
pushed = graph_rewrite(pushed, block_create, ctx=BlockContext.from_sink(pushed), name="block create", bottom_up=True)
|
||||
pushed = graph_rewrite(pushed, pm_blockend_merge, name="blockend merge")
|
||||
pushed = graph_rewrite(pushed, block_merge, name="block merge")
|
||||
pushed = graph_rewrite(pushed, pm_finalize, name="finalize")
|
||||
from tinygrad.device import Device
|
||||
try:
|
||||
print(Device['CPU'].renderer.render(pushed.arg.lst))
|
||||
except Exception as e:
|
||||
print("render fail", e)
|
||||
|
||||
# multi + merge_views + simplify
|
||||
tensor_map = graph_rewrite_map(sink, multi_pm+do_fuse+merge_views+sym+replace_contiguous, ctx={}, name="merge_views")
|
||||
@@ -347,6 +569,8 @@ 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")
|
||||
|
||||
|
||||
+33
-10
@@ -2234,7 +2234,7 @@ class Tensor(MathTrait):
|
||||
"""
|
||||
def parse_formula(formula:str, *operands:Tensor):
|
||||
if "..." in (formula := formula.replace(" ", "")):
|
||||
ell_chars, ell_longest = "".join(c for c in string.ascii_letters if c not in formula), 0
|
||||
ell_chars, ell_longest = "".join(set(string.ascii_letters) - set(formula)), 0
|
||||
for i, inp in enumerate(filter(lambda x: "..." in x, inputs := formula.split("->")[0].split(","))):
|
||||
if (ell_count := max(operands[i].ndim, 1) - (len(inp) - len("..."))) > ell_longest: ell_longest = ell_count
|
||||
inputs[i] = inp.replace("...", ell_chars[-ell_count:])
|
||||
@@ -2332,6 +2332,8 @@ 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())
|
||||
@@ -2378,6 +2380,8 @@ 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())
|
||||
@@ -3006,6 +3010,8 @@ 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())
|
||||
```
|
||||
@@ -3042,6 +3048,7 @@ 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"
|
||||
@@ -3284,6 +3291,7 @@ 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"
|
||||
@@ -3296,6 +3304,7 @@ 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"
|
||||
@@ -3308,6 +3317,7 @@ 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"
|
||||
@@ -3332,6 +3342,7 @@ 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"
|
||||
@@ -3344,6 +3355,7 @@ 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"
|
||||
@@ -3356,6 +3368,7 @@ 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"
|
||||
@@ -3440,6 +3453,8 @@ 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())
|
||||
```
|
||||
@@ -3464,6 +3479,7 @@ 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"
|
||||
@@ -3476,6 +3492,8 @@ 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())
|
||||
```
|
||||
@@ -3486,6 +3504,8 @@ 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())
|
||||
```
|
||||
@@ -3499,6 +3519,7 @@ 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"
|
||||
@@ -3511,6 +3532,8 @@ 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())
|
||||
```
|
||||
@@ -3521,6 +3544,8 @@ 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())
|
||||
```
|
||||
@@ -3536,8 +3561,7 @@ class Tensor(MathTrait):
|
||||
# for each dimension, check either dim is 1, or it does not change
|
||||
if not all(resolve(s == ns) or resolve(s == 1) for s,ns in zip(shape, new_shape)):
|
||||
raise ValueError(f"cannot broadcast {self.shape} to {new_shape=}")
|
||||
# NOTE: this cast is no-op in forward and uses sum_acc_dtype in the backward sum
|
||||
return self.reshape(shape).cast(sum_acc_dtype(self.dtype))._apply_uop(UOp.expand, arg=new_shape).cast(self.dtype)
|
||||
return self.reshape(shape)._apply_uop(UOp.expand, arg=new_shape)
|
||||
|
||||
def _broadcasted(self, y:Tensor|ConstType|UOp, reverse:bool=False, match_dtype:bool=True) -> tuple[Tensor, Tensor]:
|
||||
x: Tensor = self
|
||||
@@ -3814,6 +3838,7 @@ 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"
|
||||
@@ -3832,6 +3857,7 @@ 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"
|
||||
@@ -3856,6 +3882,7 @@ 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"
|
||||
@@ -3897,6 +3924,7 @@ 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"
|
||||
@@ -4089,8 +4117,8 @@ class Tensor(MathTrait):
|
||||
#extract singular values and sort. construct U from Q
|
||||
S, indices = U.square().sum(-2).sqrt().sort(dim = -1, descending=True)
|
||||
new_indices = Tensor.arange(num).reshape((1,) * (self.ndim - 1) + (num,)).expand(b_shape + 2 * (num,)).contiguous()
|
||||
new_indices[..., :num] = indices.reshape(b_shape + (1,) + (num,)).expand(b_shape + 2 * (num,))
|
||||
U,V = U.gather(-1, new_indices[...,0:num,0:num]) / S.unsqueeze(-2), V.gather(-1, new_indices[..., 0:num, 0:num]).realize()
|
||||
new_indices[..., :num] = indices.reshape(b_shape + (1,) + (U.shape[0],)).expand(b_shape + 2 * (num,))
|
||||
U,V = U.gather(-1, new_indices[...,0:num,0:num]) / S.unsqueeze(-2), V.gather(-1, new_indices[..., 0:num, 0:num])
|
||||
|
||||
padded_u = Tensor.eye(q_num, dtype = U.dtype).reshape((1,) * (self.ndim - 2) + 2 * (q_num,)).expand(b_shape + 2 * (q_num,)).contiguous()
|
||||
padded_u[..., 0:num, 0:num] = U
|
||||
@@ -4288,11 +4316,6 @@ class Tensor(MathTrait):
|
||||
"""
|
||||
return self.cast(dtypes.bool)
|
||||
|
||||
def bfloat16(self) -> Tensor: return self.cast(dtypes.bfloat16)
|
||||
def double(self) -> Tensor: return self.cast(dtypes.double)
|
||||
def long(self) -> Tensor: return self.cast(dtypes.long)
|
||||
def short(self) -> Tensor: return self.cast(dtypes.short)
|
||||
|
||||
# *** image Tensor function replacements ***
|
||||
|
||||
def image_dot(self, w:Tensor, dtype:DTypeLike|None=None) -> Tensor:
|
||||
|
||||
@@ -10,6 +10,7 @@ class FastEnum(IntEnum):
|
||||
class Ops(FastEnum):
|
||||
# uops that aren't rendered
|
||||
NOOP = auto(); SINK = auto(); UNIQUE = auto(); DEVICE = auto(); KERNEL = auto(); PRECAST = auto() # noqa: E702
|
||||
MAP = auto(); CATRANGE = auto()
|
||||
|
||||
# buffer ops
|
||||
COPY = auto(); BUFFER = auto(); BUFFER_VIEW = auto(); MSELECT = auto(); MSTACK = auto() # noqa: E702
|
||||
@@ -82,6 +83,7 @@ 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}
|
||||
|
||||
|
||||
+3
-12
@@ -5,7 +5,7 @@ from dataclasses import dataclass, field
|
||||
from enum import Enum, auto
|
||||
from tinygrad.uop import Ops, GroupOp
|
||||
from tinygrad.uop.mathtraits import MathTrait
|
||||
from tinygrad.dtype import ConstType, ImageDType, dtypes, DType, truncate, PtrDType
|
||||
from tinygrad.dtype import ConstType, ImageDType, dtypes, DType, truncate
|
||||
from tinygrad.helpers import ContextVar, all_int, prod, getenv, all_same, Context, partition, temp, unwrap, T, argfix, Metadata, flatten
|
||||
from tinygrad.helpers import PICKLE_BUFFERS, PROFILE, dedup, cdiv, cmod, diskcache_put, to_function_name, cpu_profile, TracingKey
|
||||
if TYPE_CHECKING:
|
||||
@@ -150,12 +150,7 @@ 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}:
|
||||
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. even worse hack with tag
|
||||
if self.op is Ops.CAST and self.src[0].op is Ops.DEFINE_GLOBAL and self.src[0].tag is None: return None
|
||||
#if self.op in {Ops.DEFINE_GLOBAL, Ops.DEFINE_LOCAL, Ops.DEFINE_REG}: return ShapeTracker.from_shape((self.dtype.size,))
|
||||
|
||||
# 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
|
||||
@@ -176,9 +171,7 @@ 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, ...]:
|
||||
assert self.st is not None, f"{self.op} doesn't have a shape"
|
||||
return unwrap(self.st).shape
|
||||
def shape(self) -> tuple[sint, ...]: 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
|
||||
|
||||
@@ -642,7 +635,6 @@ 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)
|
||||
@@ -949,7 +941,6 @@ renderer = PatternMatcher([
|
||||
(UPat(Ops.BIND, src=UPat(Ops.NOOP), name="x"), lambda x: x.src[0]),
|
||||
#(UPat(Ops.BIND, src=UPat(Ops.NOOP), name="x"), lambda x: UOp(Ops.NOOP, arg=f"{x.src[0].arg}[={x.src[1].arg}]")),
|
||||
(UPat(Ops.NEG, src=UPat(Ops.NOOP), name="x"), lambda x: UOp(Ops.NOOP, arg=f"(-{x.src[0].arg})")),
|
||||
(UPat(Ops.RECIP, src=UPat(Ops.NOOP), name="x"), lambda x: UOp(Ops.NOOP, arg=f"(1/{x.src[0].arg})")),
|
||||
(UPat(Ops.MAX, src=UPat(Ops.NOOP), name="x"), lambda x: UOp(Ops.NOOP, arg=f"max({x.src[0].arg}, {x.src[1].arg})")),
|
||||
(UPat(Ops.MULACC, src=UPat(Ops.NOOP), name="x"), lambda x: UOp(Ops.NOOP, arg=f"({x.src[0].arg}*{x.src[1].arg}+{x.src[2].arg})")),
|
||||
(UPat(Ops.WHERE, src=UPat(Ops.NOOP), name="x"), lambda x: UOp(Ops.NOOP, arg=f"({x.src[1].arg} if {x.src[0].arg} else {x.src[2].arg})")),
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Any, Literal, cast
|
||||
import math, operator, struct, functools
|
||||
from collections import defaultdict
|
||||
from tinygrad.uop.ops import Ops, PatternMatcher, UPat, UOp, GroupOp, exec_alu
|
||||
from tinygrad.dtype import ConstType, dtypes, PtrDType, AddrSpace, can_safe_cast
|
||||
from tinygrad.dtype import ConstType, dtypes, PtrDType, AddrSpace
|
||||
from tinygrad.helpers import partition, all_same, prod, flatten, get_single_element, cdiv, cmod, CORRECT_DIVMOD_FOLDING
|
||||
from tinygrad.uop.transcendental import xpow
|
||||
|
||||
@@ -65,8 +65,6 @@ symbolic_simple = PatternMatcher([
|
||||
(UPat(Ops.CAST, name="root", src=(UPat.cvar("c"),)), lambda root, c: root.const_like(c.arg)),
|
||||
(UPat((Ops.CAST, Ops.BITCAST), name="root"), lambda root: root.src[0] if root.dtype == root.src[0].dtype else None),
|
||||
(UPat(Ops.BITCAST, name="root", src=(UPat.cvar("c"),)), fold_bitcast),
|
||||
# b.cast(a).cast(b) -> b if a preserves all values in b
|
||||
(UPat.var('x').cast().named('a').cast().named('b'), lambda x,a,b: x if x.dtype == b.dtype and can_safe_cast(b.dtype, a.dtype) else None),
|
||||
# ** pow **
|
||||
(UPat.var("x").alu(Ops.POW, UPat.cvar("c", vec=False)), simplify_pow),
|
||||
# positive const ** x
|
||||
@@ -429,6 +427,7 @@ sym = symbolic_flat+PatternMatcher([
|
||||
(UPat(Ops.WMMA, src=(UPat.var(), UPat.const(None, 0.0), UPat.var("acc"))), lambda acc: acc),
|
||||
# threefry + remove longs
|
||||
(UPat(Ops.THREEFRY, dtype=dtypes.uint64, src=(UPat.var("x"), UPat.var("key"))), threefry2x32),
|
||||
(UPat.var('x', dtypes.uint32).cast(dtypes.uint64).cast(dtypes.uint32), lambda x: x), # cast there and back is noop (TODO: genericize)
|
||||
((UPat.var('x', dtypes.uint64)&0xFFFFFFFF).cast(dtypes.uint32), lambda x: x.cast(dtypes.uint32)), # cast does truncation
|
||||
(((UPat.var(None, dtypes.uint64)*(1<<32)) | UPat.var('y', dtypes.uint32).cast(dtypes.uint64)).cast(dtypes.uint32), lambda y: y),
|
||||
(((UPat.var('x', dtypes.uint64)*(1<<32)) | UPat.var(None, dtypes.uint32).cast(dtypes.uint64))//(1<<32), lambda x: x),
|
||||
@@ -467,7 +466,6 @@ sym = symbolic_flat+PatternMatcher([
|
||||
if any(x.op in REMOVE_FROM_SINK for x in root.src) else None),
|
||||
((UPat.var("x") * UPat.var("x")).reciprocal(), lambda x: x.reciprocal()*x.reciprocal()), # 1/(x^c) -> (1/x)^c
|
||||
((UPat.var("x") * UPat.var("x") * UPat.var("x")).reciprocal(), lambda x: x.reciprocal()*x.reciprocal()*x.reciprocal()),
|
||||
((UPat.var("x") * UPat.cvar("c")).reciprocal(), lambda x,c: x.reciprocal()*c.reciprocal()), # 1/(x*c) -> (1/c)*(1/x)
|
||||
(UPat.var("x") * ((1+UPat.var("x")).reciprocal().named("d")), lambda x,d: 1-d), # x*/(1+x) -> 1-1/(1+x)
|
||||
(UPat.var("x") * ((1+UPat.var("x")).reciprocal().named("d")*UPat.var("y")), lambda x,y,d: y*(1-d)),
|
||||
(UPat.var("x") * ((1+UPat.var("x")).reciprocal().named("d")+UPat.var("y")), lambda x,y,d: (1-d)+x*y),
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
background-color: #1a1b26;
|
||||
border: 1px solid #4a4b56;
|
||||
color: #f0f0f5;
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
padding: 6px;
|
||||
cursor: pointer;
|
||||
height: 32px;
|
||||
@@ -184,6 +184,7 @@
|
||||
}
|
||||
.btn:hover {
|
||||
background-color: #2a2b36;
|
||||
border-color: #5a5b66;
|
||||
}
|
||||
.collapsed .container {
|
||||
display: none;
|
||||
@@ -202,6 +203,7 @@
|
||||
pre code.hljs {
|
||||
overflow-y: auto;
|
||||
max-height: 30vh;
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
}
|
||||
.progress-message {
|
||||
@@ -253,7 +255,7 @@
|
||||
font-size: 0.95em;
|
||||
}
|
||||
table td {
|
||||
border-bottom: 1px solid #4a4b56;
|
||||
border-bottom: 1px solid #2c2f40;
|
||||
vertical-align: top;
|
||||
}
|
||||
table tr:last-child > td {
|
||||
@@ -289,7 +291,7 @@
|
||||
text-align: left;
|
||||
padding: 10px 12px;
|
||||
font-weight: 600;
|
||||
border-bottom: 1px solid #4a4b56;
|
||||
border-bottom: 1px solid #3a3d52;
|
||||
font-size: 0.95em;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ function formatTime(ts, dur=ts) {
|
||||
}
|
||||
const formatUnit = (d, unit="") => d3.format(".3~s")(d)+unit;
|
||||
|
||||
const colorScheme = {TINY:["#1b5745", "#354f52", "#354f52", "#46acc2", "#1d2e62", "#63b0cd"],
|
||||
const colorScheme = {TINY:["#1b5745", "#354f52", "#354f52", "#46acc2", "#1d2e62"],
|
||||
DEFAULT:["#2b2e39", "#2c2f3a", "#31343f", "#323544", "#2d303a", "#2e313c", "#343746", "#353847", "#3c4050", "#404459", "#444862", "#4a4e65"],
|
||||
BUFFER:["#3A57B7","#5066C1","#6277CD","#7488D8","#8A9BE3","#A3B4F2"],
|
||||
CATEGORICAL:["#ff8080", "#F4A261", "#C8F9D4", "#8D99AE", "#F4A261", "#ffffa2", "#ffffc0", "#87CEEB"],}
|
||||
|
||||
+19
-28
@@ -30,13 +30,10 @@ 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)]
|
||||
ret.append(r:={"name":k.display_name, "steps":steps})
|
||||
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})
|
||||
# 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
|
||||
|
||||
@@ -132,8 +129,7 @@ def timeline_layout(events:list[tuple[int, int, float, DevEvent]]) -> dict:
|
||||
name, cat, info = e.name, None, None
|
||||
if (ref:=ref_map.get(name)) is not None:
|
||||
name = ctxs[ref]["name"]
|
||||
# TODO: support symbolic by capturing var_vals in profile events
|
||||
if isinstance(p:=contexts[0][ref].ret, ProgramSpec) and all(isinstance(es,int) for es in [p.estimates.ops, p.estimates.mem, p.estimates.lds]):
|
||||
if isinstance(p:=contexts[0][ref].ret, ProgramSpec):
|
||||
info = f"{p.estimates.ops/(t:=dur*1e3):.2f} GFLOPS {p.estimates.mem/t:4.1f}|{p.estimates.lds/t:.1f} GB/s"
|
||||
elif isinstance(e.name, TracingKey):
|
||||
name, cat = e.name.display_name, e.name.cat
|
||||
@@ -193,24 +189,6 @@ def get_runtime_stats(key) -> list[dict]:
|
||||
ret.append({"device":e.device, "data":[{"name":"Duration", "value":float(e.en-e.st), "unit":"us"}]})
|
||||
return ret
|
||||
|
||||
# ** Assembly analyzers
|
||||
|
||||
def get_llvm_mca(asm:str, mtriple:str, mcpu:str) -> dict:
|
||||
target_args = [f"-mtriple={mtriple}", f"-mcpu={mcpu}"]
|
||||
# disassembly output can include headers / metadata, skip if llvm-mca can't parse those lines
|
||||
data = json.loads(subprocess.check_output(["llvm-mca","-skip-unsupported-instructions=parse-failure","--json","-"]+target_args, input=asm.encode()))
|
||||
cr = data["CodeRegions"][0]
|
||||
rows:list = [{"data":[instr], "segs":{}} for instr in cr["Instructions"]]
|
||||
for i,info in enumerate(cr["InstructionInfoView"]["InstructionList"]): rows[i]["data"].append(info["Latency"])
|
||||
for d in cr["ResourcePressureView"]["ResourcePressureInfo"]:
|
||||
i, r = d["InstructionIndex"], d["ResourceIndex"]
|
||||
if i>len(rows)-1: continue
|
||||
rows[i]["segs"][r] = rows[i]["segs"].get(r, 0)+d["ResourceUsage"]
|
||||
# rescale segment width to 0-100
|
||||
max_usage = max([sum(x["segs"].values()) for x in rows], default=0)
|
||||
for x in rows: x["segs"] = {k:{"width":(v/max_usage)*100, "value":v} for k,v in x["segs"].items()}
|
||||
return {"rows":rows, "cols":["Opcode", "Latency", "HW Resources"], "segments":data["TargetInfo"]["Resources"]}
|
||||
|
||||
def get_disassembly(ctx:list[str]):
|
||||
if not isinstance(prg:=contexts[0][int(ctx[0])].ret, ProgramSpec): return
|
||||
lib = (compiler:=Device[prg.device].compiler).compile(prg.src)
|
||||
@@ -220,9 +198,22 @@ def get_disassembly(ctx:list[str]):
|
||||
if isinstance(compiler, LLVMCompiler):
|
||||
mtriple = ctypes.string_at(llvm.LLVMGetTargetMachineTriple(tm:=compiler.target_machine)).decode()
|
||||
mcpu = ctypes.string_at(llvm.LLVMGetTargetMachineCPU(tm)).decode()
|
||||
ret = get_llvm_mca(disasm_str, mtriple, mcpu)
|
||||
else: ret = {"src":disasm_str}
|
||||
return json.dumps(ret).encode()
|
||||
# NOTE: llvm-objdump may contain headers, skip if llvm-mca can't parse those lines
|
||||
data = json.loads(subprocess.check_output(["llvm-mca", f"-mtriple={mtriple}", f"-mcpu={mcpu}", "-skip-unsupported-instructions=parse-failure",
|
||||
"--json", "-"], input=disasm_str.encode()))
|
||||
cr = data["CodeRegions"][0]
|
||||
instrs:list = [{"data":[rep], "segs":{}} for rep in cr["Instructions"]]
|
||||
for i,info in enumerate(cr["InstructionInfoView"]["InstructionList"]): instrs[i]["data"].append(info["Latency"])
|
||||
for d in cr["ResourcePressureView"]["ResourcePressureInfo"]:
|
||||
i, r = d["InstructionIndex"], d["ResourceIndex"]
|
||||
if i>len(instrs)-1: continue
|
||||
instrs[i]["segs"][r] = instrs[i]["segs"].get(r, 0)+d["ResourceUsage"]
|
||||
# rescale segment width to 0-100
|
||||
if instrs:
|
||||
hi = max([sum(ins["segs"].values()) for ins in instrs])
|
||||
for n in instrs: n["segs"] = {k:{"width":v/hi*100, "value":v} for k,v in n["segs"].items()}
|
||||
return json.dumps({"rows":instrs, "cols":["Opcode", "Latency", "HW Resources"], "segments":data["TargetInfo"]["Resources"]}).encode()
|
||||
return json.dumps({"src":disasm_str}).encode()
|
||||
|
||||
# ** HTTP server
|
||||
|
||||
|
||||
Reference in New Issue
Block a user