forked from tinygrad/tinygrad
Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2aec58654a | ||
|
|
339dadf056 | ||
|
|
4edaaf19e5 | ||
|
|
7f1d41c9f9 | ||
|
|
b31373ca70 | ||
|
|
27d899ce97 | ||
|
|
39d962106f | ||
|
|
389f01c7f4 | ||
|
|
df0f9d6860 | ||
|
|
81d9053013 | ||
|
|
d299d30f2c | ||
|
|
f6bda6ae4e | ||
|
|
6237bd86f6 | ||
|
|
3000b8d762 | ||
|
|
5cb827f7bf | ||
|
|
75a6a03664 | ||
|
|
29ef0809bb | ||
|
|
ed1fd7023b | ||
|
|
9839838fdd | ||
|
|
e523971028 | ||
|
|
09e060eab5 | ||
|
|
dc660c9fc0 |
@@ -13,7 +13,7 @@ There's also a [doc describing speed](../developer/speed.md)
|
||||
|
||||
Everything in [Tensor](../tensor/index.md) is syntactic sugar around constructing a graph of [UOps](../developer/uop.md).
|
||||
|
||||
The `UOp` graph specifies the compute in terms of low level tinygrad ops. Not all UOps will actually become realized. There's two types of UOps, base and view. base contains compute into a contiguous buffer, and view is a view (specified by a ShapeTracker). Inputs to a base can be either base or view, inputs to a view can only be a single base.
|
||||
The `UOp` graph specifies the compute in terms of low level tinygrad ops. Not all UOps will actually become realized. There's two types of UOps, base and view. base contains compute into a contiguous buffer, and view is a view. Inputs to a base can be either base or view, inputs to a view can only be a single base.
|
||||
|
||||
## Scheduling
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import globals from "globals";
|
||||
import pluginJs from "@eslint/js";
|
||||
import pluginHtml from "eslint-plugin-html";
|
||||
|
||||
export default [
|
||||
{files: ["**/*.html"], plugins: {html: pluginHtml}, rules:{"max-len": ["error", {"code": 150}]}},
|
||||
{languageOptions: {globals: globals.browser}},
|
||||
pluginJs.configs.recommended,
|
||||
];
|
||||
@@ -1438,28 +1438,34 @@ def train_llama3():
|
||||
iter = get_train_iter()
|
||||
i, sequences_seen = resume_ckpt, 0
|
||||
for tokens in tqdm(iter, total=SAMPLES//GBS):
|
||||
t = time.perf_counter()
|
||||
GlobalCounters.reset()
|
||||
loss, lr = train_step(model, tokens)
|
||||
loss = loss.float().item()
|
||||
if getenv("TRAIN", 1):
|
||||
t = time.perf_counter()
|
||||
loss, lr = train_step(model, tokens)
|
||||
loss = loss.float().item()
|
||||
lr = lr.item()
|
||||
|
||||
i += 1
|
||||
sequences_seen += tokens.shape[0]
|
||||
i += 1
|
||||
sequences_seen += tokens.shape[0]
|
||||
|
||||
tqdm.write(f"{loss:.4f} loss, {lr.item():.12f} LR, {GlobalCounters.mem_used / 1e9:.2f} GB used, {time.perf_counter()-t:.2f} s")
|
||||
if (fname:=getenv("LOSS_FILE", "")):
|
||||
with open(fname, "a") as f:
|
||||
f.write(f"{i} {loss:.4f} {lr.item():.12f} {GlobalCounters.mem_used / 1e9:.2f}\n")
|
||||
sec = time.perf_counter()-t
|
||||
tqdm.write(
|
||||
f"{i:5} {sec:.2f} s run, {loss:.4f} loss, {lr:.12f} LR, {GlobalCounters.mem_used / 1e9:.2f} GB used, "
|
||||
f"{GlobalCounters.global_ops * 1e-9 / sec:9.2f} GFLOPS")
|
||||
|
||||
if (ckpt_freq := getenv("CKPT")) and (i % ckpt_freq == 0 and (i != 1 or ckpt_freq == 1)):
|
||||
tqdm.write("saving checkpoint")
|
||||
if not os.path.exists(ckpt_dir := "./ckpts"): os.mkdir(ckpt_dir)
|
||||
fn = f"{ckpt_dir}/llama3_{i}.safe"
|
||||
safe_save(get_state_dict(model), fn)
|
||||
if (fname:=getenv("LOSS_FILE", "")):
|
||||
with open(fname, "a") as f:
|
||||
f.write(f"{i} {loss:.4f} {lr.item():.12f} {GlobalCounters.mem_used / 1e9:.2f}\n")
|
||||
|
||||
tqdm.write("saving optim checkpoint")
|
||||
fn = f"{ckpt_dir}/llama3_{i}_optim.safe"
|
||||
safe_save(get_state_dict(scheduler), fn)
|
||||
if (ckpt_freq := getenv("CKPT")) and (i % ckpt_freq == 0 and (i != 1 or ckpt_freq == 1)):
|
||||
tqdm.write("saving checkpoint")
|
||||
if not os.path.exists(ckpt_dir := "./ckpts"): os.mkdir(ckpt_dir)
|
||||
fn = f"{ckpt_dir}/llama3_{i}.safe"
|
||||
safe_save(get_state_dict(model), fn)
|
||||
|
||||
tqdm.write("saving optim checkpoint")
|
||||
fn = f"{ckpt_dir}/llama3_{i}_optim.safe"
|
||||
safe_save(get_state_dict(scheduler), fn)
|
||||
|
||||
if sequences_seen % EVAL_FREQ == 0 and (i != 1 or EVAL_FREQ == 1):
|
||||
tqdm.write(f"evaluating after {sequences_seen} sequences")
|
||||
|
||||
@@ -184,7 +184,7 @@ class SMICtx:
|
||||
if compact: return {k: temps[k] for k in ("Hotspot", "HBM") if temps.get(k, 0) != 0}
|
||||
return {k: v for k, v in temps.items() if v != 0}
|
||||
case _:
|
||||
temps_keys = [(k, name) for k, name in dev.smu.smu_mod.c__EA_TEMP_e__enumvalues.items()
|
||||
temps_keys = [(k, name) for k, name in dev.smu.smu_mod.TEMP_e.items()
|
||||
if k < dev.smu.smu_mod.TEMP_COUNT and metrics.SmuMetrics.AvgTemperature[k] != 0]
|
||||
if compact: temps_keys = [(k, name) for k, name in temps_keys if k in (dev.smu.smu_mod.TEMP_HOTSPOT, dev.smu.smu_mod.TEMP_MEM)]
|
||||
return {name: metrics.SmuMetrics.AvgTemperature[k] for k, name in temps_keys}
|
||||
@@ -193,7 +193,7 @@ class SMICtx:
|
||||
match dev.ip_ver[am.MP1_HWIP]:
|
||||
case (13,0,6): return {}
|
||||
case _:
|
||||
voltage_keys = [(k, name) for k, name in dev.smu.smu_mod.c__EA_SVI_PLANE_e__enumvalues.items()
|
||||
voltage_keys = [(k, name) for k, name in dev.smu.smu_mod.SVI_PLANE_e.items()
|
||||
if k < dev.smu.smu_mod.SVI_PLANE_COUNT and metrics.SmuMetrics.AvgVoltage[k] != 0]
|
||||
return {name: metrics.SmuMetrics.AvgVoltage[k] for k, name in voltage_keys}
|
||||
|
||||
|
||||
@@ -245,6 +245,11 @@ def convert_from_huggingface(weights:dict[str, Tensor], n_layers: int, n_heads:
|
||||
continue
|
||||
sd[keymap[k]] = v
|
||||
for k,v in experts.items(): sd[k] = Tensor.stack(*[v[i] for i in range(len(v))])
|
||||
|
||||
# Handle tied embeddings (e.g., Llama 3.2 1B Instruct where lm_head shares weights with embed_tokens)
|
||||
if "output.weight" not in sd and "tok_embeddings.weight" in sd:
|
||||
sd["output.weight"] = sd["tok_embeddings.weight"]
|
||||
|
||||
return sd
|
||||
|
||||
def convert_from_gguf(weights:dict[str, Tensor], n_layers:int):
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
import os, sys, sqlite3, pickle, random
|
||||
from tqdm import tqdm, trange
|
||||
from copy import deepcopy
|
||||
from tinygrad.nn import Linear
|
||||
from tinygrad.tensor import Tensor
|
||||
from tinygrad.nn.optim import Adam
|
||||
from tinygrad.nn.state import get_parameters, get_state_dict, safe_save, safe_load, load_state_dict
|
||||
from tinygrad.codegen.opt.search import actions
|
||||
from extra.optimization.helpers import load_worlds, ast_str_to_lin, lin_to_feats, assert_same_lin
|
||||
from tinygrad.codegen.opt.kernel import Kernel
|
||||
from tinygrad.helpers import getenv
|
||||
|
||||
# stuff needed to unpack a kernel
|
||||
from tinygrad.uop.ops import LazyOp, TernaryOps, BinaryOps, UnaryOps, ReduceOps, BufferOps, MemBuffer, ConstBuffer
|
||||
from tinygrad.dtype import dtypes
|
||||
from tinygrad.shape.shapetracker import ShapeTracker
|
||||
from tinygrad.shape.view import View
|
||||
from tinygrad.uop.ops import Variable
|
||||
inf, nan = float('inf'), float('nan')
|
||||
from tinygrad.codegen.opt.kernel import Opt, OptOps
|
||||
|
||||
INNER = 256
|
||||
class PolicyNet:
|
||||
def __init__(self):
|
||||
self.l1 = Linear(1021,INNER)
|
||||
self.l2 = Linear(INNER,INNER)
|
||||
self.l3 = Linear(INNER,1+len(actions))
|
||||
def __call__(self, x):
|
||||
x = self.l1(x).relu()
|
||||
x = self.l2(x).relu().dropout(0.9)
|
||||
return self.l3(x).log_softmax()
|
||||
|
||||
def dataset_from_cache(fn):
|
||||
conn = sqlite3.connect(fn)
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT * FROM beam_search")
|
||||
X,A = [], []
|
||||
for f in tqdm(cur.fetchall()):
|
||||
Xs,As = [], []
|
||||
try:
|
||||
lin = Kernel(eval(f[0]))
|
||||
opts = pickle.loads(f[-1])
|
||||
for o in opts:
|
||||
Xs.append(lin_to_feats(lin, use_sts=True))
|
||||
As.append(actions.index(o))
|
||||
lin.apply_opt(o)
|
||||
Xs.append(lin_to_feats(lin, use_sts=True))
|
||||
As.append(0)
|
||||
except Exception:
|
||||
pass
|
||||
X += Xs
|
||||
A += As
|
||||
return X,A
|
||||
|
||||
if __name__ == "__main__":
|
||||
if getenv("REGEN"):
|
||||
X,V = dataset_from_cache(sys.argv[1] if len(sys.argv) > 1 else "/tmp/tinygrad_cache")
|
||||
safe_save({"X": Tensor(X), "V": Tensor(V)}, "/tmp/dataset_policy")
|
||||
else:
|
||||
ld = safe_load("/tmp/dataset_policy")
|
||||
X,V = ld['X'].numpy(), ld['V'].numpy()
|
||||
|
||||
print(X.shape, V.shape)
|
||||
order = list(range(X.shape[0]))
|
||||
random.shuffle(order)
|
||||
X, V = X[order], V[order]
|
||||
|
||||
ratio = -256
|
||||
X_test, V_test = Tensor(X[ratio:]), Tensor(V[ratio:])
|
||||
X,V = X[:ratio], V[:ratio]
|
||||
print(X.shape, V.shape)
|
||||
|
||||
net = PolicyNet()
|
||||
#if os.path.isfile("/tmp/policynet.safetensors"): load_state_dict(net, safe_load("/tmp/policynet.safetensors"))
|
||||
optim = Adam(get_parameters(net))
|
||||
|
||||
def get_minibatch(X,Y,bs):
|
||||
xs, ys = [], []
|
||||
for _ in range(bs):
|
||||
sel = random.randint(0, len(X)-1)
|
||||
xs.append(X[sel])
|
||||
ys.append(Y[sel])
|
||||
return Tensor(xs), Tensor(ys)
|
||||
|
||||
Tensor.training = True
|
||||
losses = []
|
||||
test_losses = []
|
||||
test_accuracy = 0
|
||||
test_loss = float('inf')
|
||||
for i in (t:=trange(500)):
|
||||
x,y = get_minibatch(X,V,bs=256)
|
||||
out = net(x)
|
||||
loss = out.sparse_categorical_crossentropy(y)
|
||||
optim.zero_grad()
|
||||
loss.backward()
|
||||
optim.step()
|
||||
cat = out.argmax(axis=-1)
|
||||
accuracy = (cat == y).mean()
|
||||
t.set_description(f"loss {loss.numpy():7.2f} accuracy {accuracy.numpy()*100:7.2f}%, test loss {test_loss:7.2f} test accuracy {test_accuracy*100:7.2f}%")
|
||||
|
||||
losses.append(loss.numpy().item())
|
||||
test_losses.append(test_loss)
|
||||
if i % 10:
|
||||
out = net(X_test)
|
||||
test_loss = out.sparse_categorical_crossentropy(V_test).square().mean().numpy().item()
|
||||
cat = out.argmax(axis=-1)
|
||||
test_accuracy = (cat == y).mean().numpy()
|
||||
|
||||
safe_save(get_state_dict(net), "/tmp/policynet.safetensors")
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
plt.plot(losses[10:])
|
||||
plt.plot(test_losses[10:])
|
||||
plt.show()
|
||||
@@ -1,129 +0,0 @@
|
||||
import sys, sqlite3, pickle, math
|
||||
from collections import defaultdict
|
||||
from tqdm import tqdm, trange
|
||||
import numpy as np
|
||||
|
||||
# stuff needed to unpack a kernel
|
||||
from tinygrad.uop.ops import LazyOp, TernaryOps, BinaryOps, UnaryOps, ReduceOps, BufferOps, MemBuffer, ConstBuffer
|
||||
from tinygrad.dtype import dtypes
|
||||
from tinygrad.shape.shapetracker import ShapeTracker
|
||||
from tinygrad.shape.view import View
|
||||
from tinygrad.uop.ops import Variable
|
||||
inf, nan = float('inf'), float('nan')
|
||||
from tinygrad.codegen.opt.kernel import Opt, OptOps
|
||||
|
||||
# more stuff
|
||||
from tinygrad.codegen.opt.kernel import Kernel
|
||||
from tinygrad.codegen.opt.search import actions
|
||||
from extra.optimization.helpers import lin_to_feats
|
||||
from extra.optimization.pretrain_valuenet import ValueNet
|
||||
from tinygrad.nn.optim import Adam
|
||||
from tinygrad.nn.state import get_parameters, get_state_dict, safe_save, safe_load, load_state_dict
|
||||
import random
|
||||
from tinygrad.tensor import Tensor
|
||||
from tinygrad.helpers import getenv
|
||||
|
||||
def dataset_from_cache(fn):
|
||||
conn = sqlite3.connect(fn)
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT * FROM time_linearizer")
|
||||
grouped = defaultdict(dict)
|
||||
for f in tqdm(cur.fetchall()): grouped[f[0]][f[1:-1]] = pickle.loads(f[-1])
|
||||
|
||||
opts_to_outcome = {}
|
||||
|
||||
for ast,sk in grouped.items():
|
||||
cnts = defaultdict(int)
|
||||
for sks,tm in sk.items():
|
||||
if sks[1] != 1: continue
|
||||
opts = eval(sks[0])
|
||||
cnts[(len(opts), sks[1])] += 1
|
||||
opts_to_outcome[(ast, tuple(opts))] = tm
|
||||
#print(cnts)
|
||||
|
||||
S,A,V = [], [], []
|
||||
for ast,k in tqdm(opts_to_outcome):
|
||||
if len(k) == 0: continue
|
||||
old_tm = min(opts_to_outcome[(ast,k[:-1])])
|
||||
new_tm = min(opts_to_outcome[(ast,k)])
|
||||
if math.isinf(old_tm) or math.isinf(new_tm) or old_tm < 1e-9 or new_tm < 1e-9: continue
|
||||
try:
|
||||
lin = Kernel(eval(ast))
|
||||
except Exception:
|
||||
continue
|
||||
lin.apply_opts(k[:-1])
|
||||
act = k[-1]
|
||||
log_ratio = math.log(old_tm/new_tm)
|
||||
#print(f"ratio: {old_tm/new_tm:6.2f}x (log {log_ratio:5.2f}) from {str(act):50s} on {lin.colored_shape()}")
|
||||
S.append(lin_to_feats(lin, use_sts=True))
|
||||
A.append(actions.index(act))
|
||||
V.append([log_ratio]) # NOTE: i have written the bug many times with this having the wrong dim
|
||||
|
||||
S, A, V = np.array(S), np.array(A), np.array(V, dtype=np.float32)
|
||||
X = np.zeros((S.shape[0], S.shape[1]+len(actions)), dtype=np.float32)
|
||||
X[:, :S.shape[1]] = S
|
||||
X[range(S.shape[0]), S.shape[1]+A] = 1.0
|
||||
return X, V
|
||||
|
||||
def log_likelihood(x:Tensor, mu:Tensor, log_sigma:Tensor):
|
||||
#print(x.shape, mu.shape, log_sigma.shape)
|
||||
#return (x-mu).abs() * (-log_sigma).exp() + log_sigma
|
||||
return (x-mu).square() * (-2*log_sigma).exp() / 2 + log_sigma
|
||||
|
||||
if __name__ == "__main__":
|
||||
if getenv("REGEN"):
|
||||
X,V = dataset_from_cache(sys.argv[1] if len(sys.argv) > 1 else "/tmp/tinygrad_cache")
|
||||
safe_save({"X": Tensor(X), "V": Tensor(V)}, "/tmp/dataset")
|
||||
else:
|
||||
ld = safe_load("/tmp/dataset")
|
||||
X,V = ld['X'].numpy(), ld['V'].numpy()
|
||||
|
||||
print(X.shape, V.shape)
|
||||
order = list(range(X.shape[0]))
|
||||
random.shuffle(order)
|
||||
X, V = X[order], V[order]
|
||||
|
||||
ratio = -512
|
||||
X_test, V_test = Tensor(X[ratio:]), Tensor(V[ratio:])
|
||||
X,V = X[:ratio], V[:ratio]
|
||||
print(X.shape, V.shape)
|
||||
|
||||
#print(X[0], V[0])
|
||||
#print(X[-1], V[-1])
|
||||
print(X.shape)
|
||||
|
||||
net = ValueNet(X.shape[1], 2)
|
||||
optim = Adam(get_parameters(net))
|
||||
|
||||
def get_minibatch(X,Y,bs):
|
||||
xs, ys = [], []
|
||||
#random.seed(1337)
|
||||
for _ in range(bs):
|
||||
sel = random.randint(0, len(X)-1)
|
||||
xs.append(X[sel])
|
||||
ys.append(Y[sel])
|
||||
return Tensor(xs), Tensor(ys)
|
||||
|
||||
Tensor.training = True
|
||||
losses = []
|
||||
test_losses = []
|
||||
test_loss = float('inf')
|
||||
for i in (t:=trange(2000)):
|
||||
x,y = get_minibatch(X,V,bs=256)
|
||||
out = net(x)
|
||||
#loss = (out-y).square().mean()
|
||||
loss = log_likelihood(y, out[:, 0:1], out[:, 1:2]).mean()
|
||||
optim.zero_grad()
|
||||
loss.backward()
|
||||
optim.step()
|
||||
t.set_description(f"loss {loss.numpy():7.2f}, test loss {test_loss:7.2f}")
|
||||
losses.append(loss.numpy().item())
|
||||
test_losses.append(test_loss)
|
||||
if i % 10: test_loss = (net(X_test)[:, 0:1]-V_test).square().mean().numpy().item()
|
||||
|
||||
safe_save(get_state_dict(net), "/tmp/qnet.safetensors")
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
plt.plot(losses[20:])
|
||||
plt.plot(test_losses[20:])
|
||||
plt.show()
|
||||
@@ -1,124 +0,0 @@
|
||||
# stuff needed to unpack a kernel
|
||||
from tinygrad import Variable
|
||||
from tinygrad.codegen.opt import Opt, OptOps
|
||||
from tinygrad.uop.ops import UOp, Ops, KernelInfo
|
||||
from tinygrad.dtype import dtypes, PtrDType
|
||||
from tinygrad.shape.shapetracker import ShapeTracker
|
||||
from tinygrad.shape.view import View
|
||||
from tinygrad.helpers import getenv
|
||||
from tinygrad.engine.realize import get_program
|
||||
inf, nan = float('inf'), float('nan')
|
||||
UOps = Ops
|
||||
|
||||
# kernel unpacker
|
||||
from tinygrad.codegen.opt.kernel import Kernel
|
||||
def ast_str_to_ast(ast_str:str) -> UOp: return eval(ast_str)
|
||||
def ast_str_to_lin(ast_str:str, opts=None): return Kernel(ast_str_to_ast(ast_str), opts=opts)
|
||||
def kern_str_to_lin(kern_str:str, opts=None):
|
||||
(ast, applied_opts,) = eval(kern_str)
|
||||
k = Kernel(ast, opts=opts)
|
||||
k.apply_opts(applied_opts)
|
||||
return k
|
||||
|
||||
# load worlds, a dataset of about 12k kernels
|
||||
import gzip
|
||||
from pathlib import Path
|
||||
import random
|
||||
from tinygrad.helpers import dedup, DEBUG
|
||||
def load_worlds(filter_reduce=True, filter_noimage=True, filter_novariable=True):
|
||||
fn = Path(__file__).parent.parent / "datasets/sops.gz"
|
||||
ast_strs = dedup(gzip.open(fn).read().decode('utf-8').strip().split("\n"))
|
||||
assert len(ast_strs) >= getenv("MIN_ASTS", 1000), f"dataset size = {len(ast_strs)} is too small"
|
||||
if DEBUG >= 1: print(f"loaded {len(ast_strs)=} before filters")
|
||||
if filter_reduce: ast_strs = [x for x in ast_strs if "REDUCE_AXIS" in x]
|
||||
if filter_noimage: ast_strs = [x for x in ast_strs if "dtypes.image" not in x]
|
||||
if filter_novariable: ast_strs = [x for x in ast_strs if "DEFINE_VAR" not in x]
|
||||
if DEBUG >= 1: print(f"loaded {len(ast_strs)=} after filters {filter_reduce=}, {filter_noimage=}, {filter_novariable=}")
|
||||
random.seed(1337)
|
||||
random.shuffle(ast_strs)
|
||||
return ast_strs
|
||||
|
||||
def assert_same_lin(l1, l2):
|
||||
assert l1.colored_shape() == l2.colored_shape()
|
||||
assert all(x==y for x,y in zip(l1.sts, l2.sts))
|
||||
|
||||
# get features
|
||||
import math
|
||||
|
||||
MAX_DIMS = 16
|
||||
MAX_BUFS = 9
|
||||
def lin_to_feats(lin:Kernel, use_sts=True):
|
||||
assert lin.shape_len < MAX_DIMS, "too many dims"
|
||||
|
||||
all_colors = ["blue", "cyan", "white", "green", "red", "magenta", "yellow"]
|
||||
lc = [all_colors.index(x) for x in lin.colors()]
|
||||
|
||||
ret = []
|
||||
# before, some generic linearizer stuff
|
||||
ret.append(lin.upcasted)
|
||||
ret.append(lin.local_dims)
|
||||
|
||||
# first, the full shape, including the colors
|
||||
for s,os,c in zip(lin.full_shape,lin.output_shape,lc):
|
||||
if isinstance(s, UOp):
|
||||
ret.append(False)
|
||||
ret += [0]*9
|
||||
else:
|
||||
ret.append(True)
|
||||
ret.append(math.log2(s))
|
||||
ret.append(min(33, s))
|
||||
ret.append(math.log2(os))
|
||||
ret.append(min(33, os))
|
||||
ret.append(s%2 == 0)
|
||||
ret.append(s%3 == 0)
|
||||
ret.append(s%4 == 0)
|
||||
ret.append(s%8 == 0)
|
||||
ret.append(s%16 == 0)
|
||||
cc = [0]*7
|
||||
cc[c] = 1
|
||||
ret += cc
|
||||
ret += [0] * (17*(MAX_DIMS-len(lin.full_shape)))
|
||||
ret = [float(x) for x in ret]
|
||||
|
||||
if use_sts:
|
||||
my_sts = dedup([(x.shape == lin.full_shape, x.is_expanded(), any(v.mask is not None for v in x.views), len(x.views)) for x in lin.sts])
|
||||
assert len(my_sts) < MAX_BUFS
|
||||
sts_len = 3 + 5*MAX_DIMS
|
||||
for s in my_sts:
|
||||
ret.append(s[0]) # reduce
|
||||
ret.append(s[2]) # has mask
|
||||
ret.append(s[3]) # len views
|
||||
for d in s[1]:
|
||||
ret.append(d is None)
|
||||
ret.append(d == 0)
|
||||
ret.append(d == 1)
|
||||
ret.append(min(33, d) if d is not None else -1)
|
||||
if d is not None and d >= 1: ret.append(math.log2(d))
|
||||
else: ret.append(-1)
|
||||
ret += [0] * (5*(MAX_DIMS - len(s[1])))
|
||||
ret += [0] * (sts_len*(MAX_BUFS - len(my_sts)))
|
||||
assert len(ret) == 1021, f"wrong len {len(ret)}"
|
||||
else:
|
||||
assert len(ret) == 274, f"wrong len {len(ret)}"
|
||||
return ret
|
||||
|
||||
from tinygrad.device import Device, Buffer
|
||||
from tinygrad.codegen.opt.search import _ensure_buffer_alloc, _time_program
|
||||
from tinygrad.helpers import to_function_name, CACHELEVEL, diskcache_get, diskcache_put
|
||||
|
||||
def time_linearizer(lin:Kernel, rawbufs:list[Buffer], allow_test_size=True, max_global_size=65536, cnt=3, disable_cache=False, clear_l2=False) -> float: # noqa: E501
|
||||
key = {"ast": lin.ast.key, "opts": str(lin.applied_opts), "allow_test_size": allow_test_size,
|
||||
"max_global_size": max_global_size, "clear_l2": clear_l2, "device": lin.opts.device, "suffix": lin.opts.suffix}
|
||||
if not disable_cache and CACHELEVEL >= 2 and (val:=diskcache_get("time_linearizer", key)) is not None: return min(val)
|
||||
|
||||
dev = Device[lin.opts.device]
|
||||
assert dev.compiler is not None
|
||||
|
||||
rawbufs = _ensure_buffer_alloc(rawbufs)
|
||||
var_vals: dict[str, int] = {k.expr:int(k.vmax+k.vmin)//2 for k in lin.ast.variables()}
|
||||
p = get_program(lin.get_optimized_ast(), lin.opts)
|
||||
tms = _time_program(p, dev.compiler.compile(p.src), var_vals, rawbufs,
|
||||
max_global_size=max_global_size if allow_test_size else None, clear_l2=clear_l2, cnt=cnt, name=to_function_name(lin.name))
|
||||
|
||||
if CACHELEVEL >= 2: diskcache_put("time_linearizer", key, tms)
|
||||
return min(tms)
|
||||
@@ -1,88 +0,0 @@
|
||||
from tinygrad.codegen.opt.kernel import Kernel
|
||||
from tqdm import tqdm, trange
|
||||
import math
|
||||
import random
|
||||
from tinygrad.tensor import Tensor
|
||||
from tinygrad.nn import Linear
|
||||
from tinygrad.nn.optim import Adam
|
||||
from tinygrad.nn.state import get_parameters, get_state_dict, safe_save, safe_load, load_state_dict
|
||||
|
||||
# stuff needed to unpack a kernel
|
||||
from tinygrad.uop.ops import LazyOp, TernaryOps, BinaryOps, UnaryOps, ReduceOps, BufferOps, MemBuffer, ConstBuffer
|
||||
from tinygrad.dtype import dtypes
|
||||
from tinygrad.shape.shapetracker import ShapeTracker
|
||||
from tinygrad.shape.view import View
|
||||
from tinygrad.uop.ops import Variable
|
||||
inf, nan = float('inf'), float('nan')
|
||||
from tinygrad.codegen.opt.kernel import Opt, OptOps
|
||||
|
||||
from extra.optimization.helpers import lin_to_feats, MAX_DIMS
|
||||
|
||||
# NOTE: this is not real value of the state, it's just a prediction of the runtime
|
||||
INNER = 512
|
||||
class ValueNet:
|
||||
def __init__(self, feats=240, out=1):
|
||||
self.l1 = Linear(feats,INNER)
|
||||
self.l2 = Linear(INNER,INNER)
|
||||
self.l3 = Linear(INNER,INNER)
|
||||
self.l4 = Linear(INNER,out)
|
||||
def __call__(self, x):
|
||||
x = self.l1(x).relu()
|
||||
x = self.l2(x).relu()
|
||||
x = self.l3(x).relu().dropout(0.8)
|
||||
return self.l4(x)
|
||||
|
||||
if __name__ == "__main__":
|
||||
net = ValueNet()
|
||||
optim = Adam(get_parameters(net))
|
||||
|
||||
TEST_SIZE = 256
|
||||
|
||||
dset = open("/tmp/logtm").read().strip().split("\n")
|
||||
random.seed(1337)
|
||||
random.shuffle(dset)
|
||||
|
||||
X,Y = [], []
|
||||
for i,x in enumerate(tqdm(dset)):
|
||||
ast, opts, tms = eval(x)
|
||||
lin = Kernel(ast)
|
||||
for o in opts: lin.apply_opt(o)
|
||||
if lin.shape_len >= MAX_DIMS: continue
|
||||
if min(tms) == float('inf'): continue
|
||||
X.append(lin_to_feats(lin))
|
||||
Y.append([math.log(min(tms))])
|
||||
print(f"got {len(X)} samples")
|
||||
|
||||
X_test,Y_test = Tensor(X[-TEST_SIZE:]), Tensor(Y[-TEST_SIZE:])
|
||||
X,Y = X[:-TEST_SIZE], Y[:-TEST_SIZE]
|
||||
|
||||
def get_minibatch(X,Y,bs):
|
||||
xs, ys = [], []
|
||||
for _ in range(bs):
|
||||
sel = random.randint(0, len(X)-1)
|
||||
xs.append(X[sel])
|
||||
ys.append(Y[sel])
|
||||
return Tensor(xs), Tensor(ys)
|
||||
|
||||
Tensor.training = True
|
||||
losses = []
|
||||
test_losses = []
|
||||
test_loss = float('inf')
|
||||
for i in (t:=trange(2000)):
|
||||
x,y = get_minibatch(X,Y,bs=256)
|
||||
out = net(x)
|
||||
loss = (out-y).square().mean()
|
||||
optim.zero_grad()
|
||||
loss.backward()
|
||||
optim.step()
|
||||
t.set_description(f"loss {loss.numpy():7.2f}, test loss {test_loss:7.2f}")
|
||||
losses.append(loss.numpy().item())
|
||||
test_losses.append(test_loss)
|
||||
if i % 10: test_loss = (net(X_test)-Y_test).square().mean().numpy().item()
|
||||
|
||||
safe_save(get_state_dict(net), "/tmp/valuenet.safetensors")
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
plt.plot(losses[200:])
|
||||
plt.plot(test_losses[200:])
|
||||
plt.show()
|
||||
+1
-23
@@ -5,29 +5,7 @@ from tinygrad.device import ProfileEvent, ProfileDeviceEvent, ProfileProgramEven
|
||||
from tinygrad.runtime.ops_amd import ProfileSQTTEvent, ProfilePMCEvent
|
||||
from tinygrad.runtime.autogen import llvm, rocprof
|
||||
from tinygrad.runtime.support.elf import elf_loader
|
||||
|
||||
# to pass NULL to callbacks
|
||||
llvm.LLVMCreateDisasmCPUFeatures.argtypes = tuple(llvm.LLVMCreateDisasmCPUFeatures.argtypes[:5]) + (ctypes.c_void_p, ctypes.c_void_p)
|
||||
def llvm_disasm(arch:str, lib:bytes) -> dict[int, tuple[str, int]]:
|
||||
llvm.LLVMInitializeAMDGPUTargetInfo()
|
||||
llvm.LLVMInitializeAMDGPUTargetMC()
|
||||
llvm.LLVMInitializeAMDGPUAsmParser()
|
||||
llvm.LLVMInitializeAMDGPUDisassembler()
|
||||
ctx = llvm.LLVMCreateDisasmCPUFeatures("amdgcn-amd-amdhsa".encode(), arch.encode(), "".encode(), None, 0, None, None)
|
||||
|
||||
image, sections, relocs = elf_loader(lib)
|
||||
text = next((sh.header for sh in sections if sh.name == ".text"), None)
|
||||
off, sz = unwrap(text).sh_addr, unwrap(text).sh_size
|
||||
|
||||
addr_table:dict[int, tuple[str, int]] = {}
|
||||
out = ctypes.create_string_buffer(128)
|
||||
cur_off = off
|
||||
while cur_off < sz + off:
|
||||
view = (ctypes.c_ubyte * ((sz + off) - cur_off)).from_buffer_copy(memoryview(image)[cur_off:])
|
||||
instr_sz = llvm.LLVMDisasmInstruction(ctx, view, ctypes.c_uint64(len(view)), ctypes.c_uint64(0), out, ctypes.c_size_t(128))
|
||||
addr_table[cur_off] = (out.value.decode("utf-8", "replace").strip(), instr_sz)
|
||||
cur_off += instr_sz
|
||||
return addr_table
|
||||
from tinygrad.viz.serve import llvm_disasm
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class InstExec:
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
from extra.optimization.helpers import load_worlds, ast_str_to_ast
|
||||
from tinygrad.helpers import tqdm
|
||||
from tinygrad.uop.ops import pyrender, UOp, Ops
|
||||
from tinygrad import dtypes
|
||||
from tinygrad.shape.shapetracker import ShapeTracker, View
|
||||
inf, nan = float('inf'), float('nan')
|
||||
|
||||
if __name__ == "__main__":
|
||||
ast_strs = load_worlds()
|
||||
for i, ast_str in enumerate(tqdm(ast_strs)):
|
||||
good_ast = ast_str_to_ast(ast_str)
|
||||
code = '\n'.join(pyrender(good_ast))
|
||||
print("\n***************\n\n"+code)
|
||||
exec(code)
|
||||
if str(good_ast) != str(ast):
|
||||
print(code)
|
||||
print("MISMATCH")
|
||||
print(good_ast)
|
||||
print(ast)
|
||||
break
|
||||
File diff suppressed because one or more lines are too long
Vendored
-60
@@ -1,60 +0,0 @@
|
||||
import unittest, struct, array, ctypes
|
||||
from tinygrad import Device, dtypes, Tensor
|
||||
from tinygrad.helpers import to_mv
|
||||
from tinygrad.runtime.ops_nv import NVDevice, HWQueue
|
||||
from tinygrad.codegen.opt.search import Opt, OptOps
|
||||
from tinygrad.engine.realize import get_runner, CompiledRunner, get_program
|
||||
from test.external.fuzz_linearizer import get_fuzz_rawbufs
|
||||
|
||||
from tinygrad.codegen.opt.kernel import Kernel
|
||||
from tinygrad.uop.ops import LazyOp, Ops, ReduceOps, BufferOps, MemBuffer
|
||||
from tinygrad.shape.shapetracker import ShapeTracker
|
||||
from tinygrad.shape.view import View
|
||||
|
||||
@unittest.skipUnless(Device.DEFAULT == "NV", "NV specific tests/fixes")
|
||||
class TestNV(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(self):
|
||||
TestNV.d0: NVDevice = Device["NV"]
|
||||
TestNV.a = Tensor([0.,1.], device="NV").realize()
|
||||
TestNV.b = self.a + 1
|
||||
si = self.b.schedule()[-1]
|
||||
TestNV.d0_runner = get_runner(TestNV.d0.device, si.ast)
|
||||
TestNV.b.uop.buffer.allocate()
|
||||
TestNV.addr = struct.pack("QQ", TestNV.b.uop.buffer._buf.va_addr, TestNV.a.uop.buffer._buf.va_addr)
|
||||
|
||||
def test_error_on_huge_dims(self):
|
||||
ast = LazyOp(op=BufferOps.STORE, src=(LazyOp(op=ReduceOps.SUM, src=(LazyOp(op=Ops.CAST, src=(LazyOp(op=Ops.MUL, src=(LazyOp(op=BufferOps.LOAD, src=(), arg=MemBuffer(idx=1, dtype=dtypes.half, st=ShapeTracker(views=(View(shape=(1, 1, 1024, 683), strides=(0, 0, 0, 1), offset=0, mask=None, contiguous=False),)))), LazyOp(op=BufferOps.LOAD, src=(), arg=MemBuffer(idx=2, dtype=dtypes.half, st=ShapeTracker(views=(View(shape=(1, 1, 1024, 683), strides=(0, 0, 683, 1), offset=0, mask=None, contiguous=True),))))), arg=None),), arg=dtypes.float),), arg=(3,)),), arg=MemBuffer(idx=0, dtype=dtypes.float, st=ShapeTracker(views=(View(shape=(1, 1, 1024, 1), strides=(0, 0, 1, 0), offset=0, mask=None, contiguous=True),)))) # noqa: E501
|
||||
opts = [Opt(op=OptOps.GROUP, axis=0, arg=0), Opt(op=OptOps.PADTO, axis=1, arg=32), Opt(op=OptOps.UNROLL, axis=0, arg=4), Opt(op=OptOps.LOCAL, axis=0, arg=2), Opt(op=OptOps.LOCAL, axis=0, arg=2)] # noqa: E501
|
||||
with self.assertRaises(RuntimeError) as cm:
|
||||
lin = Kernel(ast)
|
||||
lin.apply_opts(opts)
|
||||
rawbufs = get_fuzz_rawbufs(lin)
|
||||
prg = CompiledRunner(get_program(lin.get_optimized_ast(), lin.opts))
|
||||
prg(rawbufs, {}, wait=True)
|
||||
self.assertEqual(str(cm.exception), "This is a runtime error message")
|
||||
|
||||
def test_buf4_usage(self):
|
||||
TestNV.along = Tensor([105615], device="NV").realize()
|
||||
ast = LazyOp(op=BufferOps.STORE, src=(LazyOp(op=Ops.SIN, src=(LazyOp(op=Ops.CAST, src=(LazyOp(op=BufferOps.LOAD, src=(), arg=MemBuffer(idx=1, dtype=dtypes.ulong, st=ShapeTracker(views=(View(shape=(3,), strides=(1,), offset=0, mask=None, contiguous=True),)))),), arg=dtypes.float),), arg=None),), arg=MemBuffer(idx=0, dtype=dtypes.float, st=ShapeTracker(views=(View(shape=(3,), strides=(1,), offset=0, mask=None, contiguous=True),)))) # noqa: E501
|
||||
temp_runner = get_runner(TestNV.d0.device, (ast,))
|
||||
temp_runner([TestNV.b.uop.buffer, TestNV.along.uop.buffer], var_vals={})
|
||||
val = TestNV.b.uop.buffer.as_buffer().cast("f")[0]
|
||||
assert abs(val - 0.80647) < 0.001, f"got val {val}"
|
||||
|
||||
def test_kernargs_no_oob_access(self):
|
||||
kernargs_start = TestNV.d0._gpu_alloc((2 << 20), map_to_cpu=True).va_addr
|
||||
kernargs = kernargs_start + ((2 << 20) - TestNV.d0_runner._prg.kernargs_alloc_size)
|
||||
to_mv(kernargs, 0x160).cast('I')[:] = array.array('I', TestNV.d0_runner._prg.constbuffer_0)
|
||||
ctypes.memmove(kernargs + TestNV.d0_runner._prg.kernargs_offset, TestNV.addr, len(TestNV.addr))
|
||||
|
||||
q = HWQueue()
|
||||
q.exec(TestNV.d0_runner._prg, kernargs, TestNV.d0_runner.global_size, TestNV.d0_runner.local_size)
|
||||
q.signal(TestNV.d0.timeline_signal, TestNV.d0.timeline_value).submit(TestNV.d0)
|
||||
TestNV.d0._wait_signal(TestNV.d0.timeline_signal, TestNV.d0.timeline_value)
|
||||
TestNV.d0.timeline_value += 1
|
||||
val = TestNV.b.uop.buffer.as_buffer().cast("f")[0]
|
||||
assert val == 1.0, f"got val {val}"
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Vendored
-61
@@ -1,61 +0,0 @@
|
||||
import random
|
||||
from tinygrad.helpers import DEBUG, getenv
|
||||
from test.unit.test_shapetracker import CheckingShapeTracker
|
||||
|
||||
def do_permute(st):
|
||||
perm = list(range(0, len(st.shape)))
|
||||
random.shuffle(perm)
|
||||
perm = tuple(perm)
|
||||
if DEBUG >= 1: print("st.permute(", perm, ")")
|
||||
st.permute(perm)
|
||||
|
||||
def do_pad(st):
|
||||
c = random.randint(0, len(st.shape)-1)
|
||||
pad = tuple((random.randint(0,2), random.randint(0,2)) if i==c else (0,0) for i in range(len(st.shape)))
|
||||
if DEBUG >= 1: print("st.pad(", pad, ")")
|
||||
st.pad(pad)
|
||||
|
||||
def do_reshape_split_one(st):
|
||||
c = random.randint(0, len(st.shape)-1)
|
||||
poss = [n for n in [1,2,3,4,5] if st.shape[c]%n == 0]
|
||||
spl = random.choice(poss)
|
||||
shp = st.shape[0:c] + (st.shape[c]//spl, spl) + st.shape[c+1:]
|
||||
if DEBUG >= 1: print("st.reshape(", shp, ")")
|
||||
st.reshape(shp)
|
||||
|
||||
def do_reshape_combine_two(st):
|
||||
if len(st.shape) < 2: return
|
||||
c = random.randint(0, len(st.shape)-2)
|
||||
shp = st.shape[:c] + (st.shape[c] * st.shape[c+1], ) + st.shape[c+2:]
|
||||
if DEBUG >= 1: print("st.reshape(", shp, ")")
|
||||
st.reshape(shp)
|
||||
|
||||
def do_shrink(st):
|
||||
c = random.randint(0, len(st.shape)-1)
|
||||
while 1:
|
||||
shrink = tuple((random.randint(0,s), random.randint(0,s)) if i == c else (0,s) for i,s in enumerate(st.shape))
|
||||
if all(x<y for (x,y) in shrink): break
|
||||
if DEBUG >= 1: print("st.shrink(", shrink, ")")
|
||||
st.shrink(shrink)
|
||||
|
||||
def do_flip(st):
|
||||
flip = tuple(random.random() < 0.5 for _ in st.shape)
|
||||
if DEBUG >= 1: print("st.flip(", flip, ")")
|
||||
st.flip(flip)
|
||||
|
||||
def do_expand(st):
|
||||
c = [i for i,s in enumerate(st.shape) if s==1]
|
||||
if len(c) == 0: return
|
||||
c = random.choice(c)
|
||||
expand = tuple(random.choice([2,3,4]) if i==c else s for i,s in enumerate(st.shape))
|
||||
if DEBUG >= 1: print("st.expand(", expand, ")")
|
||||
st.expand(expand)
|
||||
|
||||
shapetracker_ops = [do_permute, do_pad, do_shrink, do_reshape_split_one, do_reshape_combine_two, do_flip, do_expand]
|
||||
|
||||
if __name__ == "__main__":
|
||||
random.seed(42)
|
||||
for _ in range(getenv("CNT", 200)):
|
||||
st = CheckingShapeTracker((random.randint(2, 10), random.randint(2, 10), random.randint(2, 10)))
|
||||
for i in range(8): random.choice(shapetracker_ops)(st)
|
||||
st.assert_same()
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
import random
|
||||
from tinygrad.helpers import getenv, DEBUG, colored, trange
|
||||
from tinygrad.shape.shapetracker import ShapeTracker
|
||||
from test.external.fuzz_shapetracker import shapetracker_ops
|
||||
from test.unit.test_shapetracker_math import st_equal, MultiShapeTracker
|
||||
|
||||
def fuzz_plus() -> tuple[ShapeTracker, ShapeTracker]:
|
||||
m = MultiShapeTracker([ShapeTracker.from_shape((random.randint(1, 10), random.randint(1, 10), random.randint(1, 10)))])
|
||||
for _ in range(4): random.choice(shapetracker_ops)(m)
|
||||
backup = m.sts[0]
|
||||
m.sts.append(ShapeTracker.from_shape(m.sts[0].shape))
|
||||
for _ in range(4): random.choice(shapetracker_ops)(m)
|
||||
st_sum = backup + m.sts[1]
|
||||
return m.sts[0], st_sum
|
||||
|
||||
if __name__ == "__main__":
|
||||
if seed:=getenv("SEED"): random.seed(seed)
|
||||
total = getenv("CNT", 1000)
|
||||
for fuzz in [globals()[f'fuzz_{x}'] for x in getenv("FUZZ", "plus").split(",")]:
|
||||
same_but_neq = 0
|
||||
for _ in trange(total, desc=f"{fuzz}"):
|
||||
st1, st2 = fuzz()
|
||||
eq = st_equal(st1, st2)
|
||||
if getenv("CHECK_NEQ") and eq and st1.simplify() != st2.simplify():
|
||||
print(colored("same but unequal", "yellow"))
|
||||
print(st1.simplify())
|
||||
print(st2.simplify())
|
||||
same_but_neq += 1
|
||||
if DEBUG >= 1:
|
||||
print(f"EXP: {st1}")
|
||||
print(f"GOT: {st2}")
|
||||
print(colored("****", "green" if eq else "red"))
|
||||
if not eq: exit(0)
|
||||
if getenv("CHECK_NEQ"): print(f"same but unequal {same_but_neq}/{total} = {(same_but_neq/total)*100:.2f}%")
|
||||
@@ -26,14 +26,14 @@ def helper_tc_ensure_uops_and_opts_count(N: int, M:int, K:int, dtype_in:DType, d
|
||||
opts_to_apply = [Opt(OptOps.TC, axis, (tc_select, tc_opt, 1))]
|
||||
|
||||
if ensure_triggered:
|
||||
program = get_program(realized_ast, Device[Device.DEFAULT].renderer, opts=opts_to_apply)
|
||||
program = get_program(realized_ast, Device[Device.DEFAULT].renderer, Device.DEFAULT, opts=opts_to_apply)
|
||||
wmmas = len([uop for uop in program.uops if uop.op is Ops.WMMA])
|
||||
tcs = len([x for x in program.applied_opts if x.op is OptOps.TC])
|
||||
assert wmmas > 0, "tensor core not triggered"
|
||||
assert tcs == 1, "tensor core opt not included"
|
||||
else:
|
||||
try:
|
||||
program = get_program(realized_ast, Device[Device.DEFAULT].renderer, opts=opts_to_apply)
|
||||
program = get_program(realized_ast, Device[Device.DEFAULT].renderer, Device.DEFAULT, opts=opts_to_apply)
|
||||
assert False, "OptOps.TC triggered, expected KernelOptError"
|
||||
except KernelOptError: pass
|
||||
|
||||
@@ -44,7 +44,7 @@ def helper_tc_allclose(N:int, M:int, K:int, dtype_in:DType, dtype_out:DType, axi
|
||||
if dtype_in == dtypes.bfloat16: r = r.float()
|
||||
realized_ast, bufs = helper_realized_ast(r)
|
||||
opts = [Opt(op=OptOps.TC, axis=axis, arg=(tc_select, tc_opt, use_tensor_cores))]
|
||||
prg = CompiledRunner(replace(get_program(realized_ast, Device[Device.DEFAULT].renderer, opts=opts), device=Device.DEFAULT))
|
||||
prg = CompiledRunner(replace(get_program(realized_ast, Device[Device.DEFAULT].renderer, Device.DEFAULT, opts=opts), device=Device.DEFAULT))
|
||||
if use_tensor_cores == 1: assert len([uop for uop in prg.p.uops if uop.op is Ops.WMMA]) > 0, "wmma not triggered"
|
||||
assert len([x for x in prg.p.uops[-1].arg.applied_opts if x.op is OptOps.TC]) == 1, "tensor core opt not included"
|
||||
prg.exec(bufs)
|
||||
|
||||
@@ -23,8 +23,9 @@ def _test_uop_result(inputs:list[Tensor], stores:list[UOp], local_size=None):
|
||||
initial_value=np.zeros(sz, dtype=_to_np_dtype(dtype)).data) for u in uops if u.op is Ops.STORE]
|
||||
inbufs = [x.uop.base.buffer for x in inputs]
|
||||
src = Device[Device.DEFAULT].renderer.render(uops)
|
||||
lib = Device[Device.DEFAULT].compiler.compile_cached(src)
|
||||
ei = CompiledRunner(ProgramSpec(uops[-1].arg.name if uops[-1].arg is not None else "test",
|
||||
src, Device.DEFAULT, uops[-1], uops=uops, local_size=local_size))
|
||||
src, Device.DEFAULT, uops[-1], uops=uops, lib=lib, local_size=local_size))
|
||||
ei.exec(outbufs+inbufs)
|
||||
return [np.frombuffer(x.as_buffer(), _to_np_dtype(x.dtype)) for x in outbufs]
|
||||
|
||||
|
||||
+1
-6
@@ -7,7 +7,6 @@ from tinygrad.dtype import dtypes, DType, AddrSpace
|
||||
from tinygrad.device import Buffer, Device
|
||||
from tinygrad.uop.ops import Ops, UOp, UPat, KernelInfo, exec_alu, AxisType
|
||||
from tinygrad.uop.spec import shared_spec
|
||||
from tinygrad.renderer import ProgramSpec
|
||||
from tinygrad.renderer.cstyle import CStyleLanguage
|
||||
from tinygrad.engine.realize import CompiledRunner, get_program, get_runner
|
||||
from tinygrad.engine.schedule import ExecItem
|
||||
@@ -26,11 +25,7 @@ def to_uops_list(u:list[UOp], ren=None) -> list[UOp]:
|
||||
return ret[:-1]
|
||||
|
||||
def _uops_to_prg(uops_list):
|
||||
uops = full_rewrite(ast:=UOp.sink(*uops_list), ren=Device[Device.DEFAULT].renderer)
|
||||
src = Device[Device.DEFAULT].renderer.render(uops)
|
||||
has_local = Device[Device.DEFAULT].renderer.has_local
|
||||
return CompiledRunner(ProgramSpec(uops[-1].arg.name if uops[-1].arg is not None else "test", src, Device.DEFAULT, ast, uops=uops,
|
||||
global_size=[1,1,1] if has_local else None, local_size=[1,1,1] if has_local else None))
|
||||
return CompiledRunner(get_program(UOp.sink(*uops_list), Device[Device.DEFAULT].renderer, Device.DEFAULT))
|
||||
|
||||
def uop(uops:list[UOp], uop:Ops, dtype:Optional[DType], src:tuple[UOp, ...], arg:Any=None) -> UOp:
|
||||
uops.append(UOp(uop, dtype, tuple(src), arg))
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
import unittest
|
||||
import textwrap
|
||||
|
||||
from tinygrad import Device, Tensor
|
||||
from tinygrad.uop.ops import UOp, Ops, track_rewrites
|
||||
from tinygrad.renderer import ProgramSpec
|
||||
from tinygrad.helpers import TracingKey
|
||||
from tinygrad.engine.realize import ExecItem, CompiledRunner
|
||||
|
||||
# TODO: use the RDNA3 renderer when it's in master
|
||||
template = """.text
|
||||
.globl fn_name
|
||||
.p2align 8
|
||||
.type fn_name,@function
|
||||
fn_name:
|
||||
INSTRUCTION
|
||||
|
||||
.rodata
|
||||
.p2align 6
|
||||
.amdhsa_kernel fn_name
|
||||
.amdhsa_user_sgpr_kernarg_segment_ptr 1
|
||||
.amdhsa_next_free_vgpr .amdgcn.next_free_vgpr
|
||||
.amdhsa_next_free_sgpr .amdgcn.next_free_sgpr
|
||||
.amdhsa_wavefront_size32 1
|
||||
.end_amdhsa_kernel
|
||||
|
||||
.amdgpu_metadata
|
||||
---
|
||||
amdhsa.version:
|
||||
- 1
|
||||
- 0
|
||||
amdhsa.kernels:
|
||||
- .name: fn_name
|
||||
.symbol: fn_name.kd
|
||||
.group_segment_fixed_size: 0
|
||||
.private_segment_fixed_size: 0
|
||||
.wavefront_size: 32
|
||||
.sgpr_count: 8
|
||||
.vgpr_count: 8
|
||||
.max_flat_workgroup_size: 1024
|
||||
.kernarg_segment_align: 8
|
||||
.kernarg_segment_size: 8
|
||||
.args:
|
||||
- .address_space: global
|
||||
.name: a
|
||||
.offset: 0
|
||||
.size: 8
|
||||
.type_name: 'float*'
|
||||
.value_kind: global_buffer
|
||||
...
|
||||
.end_amdgpu_metadata
|
||||
"""
|
||||
|
||||
@track_rewrites(name=lambda *args,ret,**kwargs: TracingKey(ret.name, ret=ret))
|
||||
def run_asm(name:str, src:str) -> ProgramSpec:
|
||||
prg = ProgramSpec(name, template.replace("fn_name", name).replace("INSTRUCTION", textwrap.dedent(src)), Device.DEFAULT, UOp(Ops.SINK))
|
||||
ei = ExecItem(UOp(Ops.SINK), [Tensor.empty(1).uop.buffer.ensure_allocated()], prg=CompiledRunner(prg))
|
||||
ei.run()
|
||||
return prg
|
||||
|
||||
@unittest.skipUnless(Device.DEFAULT == "AMD", "only on AMD")
|
||||
class TestCfg(unittest.TestCase):
|
||||
def setUp(self):
|
||||
arch = Device["AMD"].arch
|
||||
if not any(arch.startswith(a) for a in {"gfx11", "gfx12"}):
|
||||
self.skipTest(f"tests written for RDNA, got arch {arch}")
|
||||
|
||||
def test_simple(self):
|
||||
run_asm("simple", """
|
||||
entry:
|
||||
s_branch bb1
|
||||
bb1:
|
||||
s_endpgm
|
||||
""")
|
||||
|
||||
def test_diamond(self):
|
||||
run_asm("diamond", """
|
||||
entry:
|
||||
s_cmp_eq_i32 s0, 0
|
||||
s_cbranch_scc1 if
|
||||
s_branch else
|
||||
if:
|
||||
s_nop 1
|
||||
s_branch end
|
||||
else:
|
||||
s_nop 0
|
||||
end:
|
||||
s_endpgm
|
||||
""")
|
||||
|
||||
def test_loop(self):
|
||||
run_asm("simple_loop", """
|
||||
entry:
|
||||
s_mov_b32 s1, 4
|
||||
loop:
|
||||
s_add_u32 s1, s1, -1
|
||||
s_cmp_eq_i32 s1, 0
|
||||
s_cbranch_scc0 loop
|
||||
s_endpgm
|
||||
""")
|
||||
|
||||
def test_loop_branch(self):
|
||||
run_asm("loop_if", """
|
||||
entry:
|
||||
s_mov_b32 s1, 4
|
||||
loop:
|
||||
s_add_u32 s1, s1, -1
|
||||
s_cmp_eq_i32 s1, 2
|
||||
s_cbranch_scc1 cond
|
||||
s_branch cont
|
||||
cond:
|
||||
s_add_u32 s1, s1, -2
|
||||
cont:
|
||||
s_cmp_eq_i32 s1, 0
|
||||
s_cbranch_scc0 loop
|
||||
s_endpgm
|
||||
""")
|
||||
|
||||
def test_loop_break(self):
|
||||
run_asm("loop_break", """
|
||||
entry:
|
||||
s_mov_b32 s1, 8
|
||||
loop:
|
||||
s_add_u32 s1, s1, -1
|
||||
s_cmp_eq_i32 s1, 5
|
||||
s_cbranch_scc1 break
|
||||
s_cmp_eq_i32 s1, 0
|
||||
s_cbranch_scc0 loop
|
||||
break:
|
||||
s_endpgm
|
||||
""")
|
||||
|
||||
def test_switch(self):
|
||||
run_asm("switch_case", """
|
||||
entry:
|
||||
s_cmp_eq_i32 s0, 0
|
||||
s_cbranch_scc1 case0
|
||||
s_cmp_eq_i32 s0, 1
|
||||
s_cbranch_scc1 case1
|
||||
s_branch case2
|
||||
case0:
|
||||
s_nop 0
|
||||
s_branch join
|
||||
case1:
|
||||
s_nop 1
|
||||
s_branch join
|
||||
case2:
|
||||
s_nop 2
|
||||
s_branch join
|
||||
join:
|
||||
s_endpgm
|
||||
""")
|
||||
|
||||
def test_ping_pong(self):
|
||||
run_asm("ping_pong", """
|
||||
entry:
|
||||
s_cmp_eq_i32 s0, 0
|
||||
s_cbranch_scc1 ping
|
||||
s_branch pong
|
||||
ping:
|
||||
s_cmp_eq_i32 s1, 0
|
||||
s_cbranch_scc1 pong
|
||||
s_branch end
|
||||
pong:
|
||||
s_cmp_eq_i32 s2, 0
|
||||
s_cbranch_scc1 ping
|
||||
end:
|
||||
s_endpgm
|
||||
""")
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -66,5 +66,21 @@ class TestDtypeTolist(unittest.TestCase):
|
||||
# 57344
|
||||
self.assertEqual(Tensor([-30000, 1.5, 3.1, 30000], device="PYTHON", dtype=dtypes.fp8e5m2).tolist(), [-28672.0, 1.5, 3.0, 28672.0])
|
||||
|
||||
class TestCanLosslessCast(unittest.TestCase):
|
||||
def test_can_lossless_cast(self):
|
||||
from tinygrad.dtype import can_lossless_cast
|
||||
# signed -> unsigned is NOT lossless (negative values wrap)
|
||||
self.assertFalse(can_lossless_cast(dtypes.int8, dtypes.uint64))
|
||||
self.assertFalse(can_lossless_cast(dtypes.int32, dtypes.uint32))
|
||||
# unsigned -> larger signed is lossless
|
||||
self.assertTrue(can_lossless_cast(dtypes.uint8, dtypes.int16))
|
||||
self.assertTrue(can_lossless_cast(dtypes.uint32, dtypes.int64))
|
||||
# large ints don't fit in floats
|
||||
self.assertFalse(can_lossless_cast(dtypes.int32, dtypes.float))
|
||||
self.assertFalse(can_lossless_cast(dtypes.int64, dtypes.double))
|
||||
# half has more mantissa bits
|
||||
self.assertTrue(can_lossless_cast(dtypes.int8, dtypes.half))
|
||||
self.assertFalse(can_lossless_cast(dtypes.int8, dtypes.bfloat16))
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import unittest
|
||||
import numpy as np
|
||||
from tinygrad import Tensor
|
||||
|
||||
class TestMoEFeedForward(unittest.TestCase):
|
||||
def test_moe_feed_forward(self):
|
||||
from tinygrad.apps.llm import TransformerBlock
|
||||
dim, hidden, n_heads = 8, 16, 2
|
||||
num_experts, k = 4, 2
|
||||
|
||||
block = TransformerBlock(dim, hidden, n_heads, n_heads, norm_eps=1e-5, head_dim=dim//n_heads,
|
||||
rope_theta=10000, max_context=16, num_experts=num_experts, num_experts_per_tok=k)
|
||||
|
||||
# set up weights: gate scales by (expert_id+1), up/down are identity-ish, router picks experts 0,2
|
||||
block.ffn_gate_exps.weight = Tensor.stack(*[Tensor.eye(hidden, dim) * (i + 1) for i in range(num_experts)])
|
||||
block.ffn_up_exps.weight = Tensor.stack(*[Tensor.eye(hidden, dim) for _ in range(num_experts)])
|
||||
block.ffn_down_exps.weight = Tensor.stack(*[Tensor.eye(dim, hidden) for _ in range(num_experts)])
|
||||
block.ffn_gate_inp.weight = Tensor([[1, 0, 1, 0]] * dim).T # router strongly prefers experts 0 and 2
|
||||
block.ffn_norm.weight = Tensor.ones(dim) # identity norm
|
||||
|
||||
# input of ones -> after norm still ~ones -> experts 0,2 selected -> weighted sum of silu outputs
|
||||
h = Tensor.ones(1, 1, dim)
|
||||
out = block._feed_forward(h)
|
||||
|
||||
# expected: residual + moe_output ≈ 1 + avg(silu(1), silu(3))
|
||||
expected = 1 + (Tensor([1.0]).silu().item() + Tensor([3.0]).silu().item()) / 2
|
||||
np.testing.assert_allclose(out.numpy()[0, 0, 0], expected, rtol=1e-2)
|
||||
|
||||
def test_moe_feed_forward_batched(self):
|
||||
from tinygrad.apps.llm import TransformerBlock
|
||||
dim, hidden, n_heads = 8, 16, 2
|
||||
num_experts, k = 4, 2
|
||||
|
||||
block = TransformerBlock(dim, hidden, n_heads, n_heads, norm_eps=1e-5, head_dim=dim//n_heads,
|
||||
rope_theta=10000, max_context=16, num_experts=num_experts, num_experts_per_tok=k)
|
||||
|
||||
# same setup as BS=1 test
|
||||
block.ffn_gate_exps.weight = Tensor.stack(*[Tensor.eye(hidden, dim) * (i + 1) for i in range(num_experts)])
|
||||
block.ffn_up_exps.weight = Tensor.stack(*[Tensor.eye(hidden, dim) for _ in range(num_experts)])
|
||||
block.ffn_down_exps.weight = Tensor.stack(*[Tensor.eye(dim, hidden) for _ in range(num_experts)])
|
||||
block.ffn_gate_inp.weight = Tensor([[1, 0, 1, 0]] * dim).T
|
||||
block.ffn_norm.weight = Tensor.ones(dim)
|
||||
|
||||
# test with BS=2, T=3
|
||||
h = Tensor.ones(2, 3, dim)
|
||||
out = block._feed_forward(h)
|
||||
|
||||
# all outputs should match the BS=1 expected value
|
||||
expected = 1 + (Tensor([1.0]).silu().item() + Tensor([3.0]).silu().item()) / 2
|
||||
np.testing.assert_allclose(out.numpy(), expected, rtol=1e-2)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -1,35 +0,0 @@
|
||||
const { spawn } = require("child_process");
|
||||
const puppeteer = require("puppeteer");
|
||||
|
||||
async function main() {
|
||||
// ** start viz server
|
||||
const proc = spawn("python", ["-u", "-c", "from tinygrad import Tensor; Tensor.arange(4).realize()"], { env: { ...process.env, VIZ:"1" },
|
||||
stdio: ["inherit", "pipe", "inherit"]});
|
||||
await new Promise(resolve => proc.stdout.on("data", r => {
|
||||
if (r.includes("ready")) resolve();
|
||||
}));
|
||||
|
||||
// ** run browser tests
|
||||
let browser, page;
|
||||
try {
|
||||
browser = await puppeteer.launch({ headless: true });
|
||||
page = await browser.newPage();
|
||||
const res = await page.goto("http://localhost:8000", { waitUntil:"domcontentloaded" });
|
||||
if (res.status() !== 200) throw new Error("Failed to load page");
|
||||
const scheduleSelector = await page.waitForSelector("ul:nth-of-type(2)");
|
||||
scheduleSelector.click();
|
||||
await page.waitForSelector("rect");
|
||||
await page.waitForFunction(() => {
|
||||
const nodes = document.querySelectorAll("#nodes > g").length;
|
||||
const edges = document.querySelectorAll("#edges > path").length;
|
||||
return nodes > 0 && edges > 0;
|
||||
});
|
||||
} finally {
|
||||
// ** cleanups
|
||||
if (page != null) await page.close();
|
||||
if (browser != null) await browser.close();
|
||||
proc.kill();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
+50
-19
@@ -5,7 +5,7 @@ from tinygrad.helpers import partition, TCPServerWithReuse, HTTPRequestHandler,
|
||||
|
||||
class SimpleTokenizer:
|
||||
def __init__(self, normal_tokens:dict[str, int], special_tokens:dict[str, int], preset:str="llama3"):
|
||||
if preset not in ("llama3","llama-v3","llama-bpe","qwen2"): raise ValueError(f"Invalid tokenizer preset '{preset}'")
|
||||
if preset not in ("llama3","llama-v3","llama-bpe","qwen2","olmo"): raise ValueError(f"Invalid tokenizer preset '{preset}'")
|
||||
# https://github.com/openai/gpt-2/blob/9b63575ef42771a015060c964af2c3da4cf7c8ab/src/encoder.py#L9
|
||||
bs = [*range(33, 127), *range(161, 173), *range(174, 256)] # bytes that map to themselves
|
||||
self._byte_decoder = {chr(b): b for b in bs} | {chr(256+i): b for i,b in enumerate(b for b in range(256) if b not in bs)}
|
||||
@@ -52,9 +52,13 @@ class SimpleTokenizer:
|
||||
|
||||
def decode(self, ids:list[int]) -> str: return b''.join(self._tok2bytes[tid] for tid in ids).decode(errors='replace')
|
||||
def role(self, role:str):
|
||||
if self.preset == 'olmo': return self.encode("<|" + role + "|>\n") # OLMoE Instruct format
|
||||
if self.preset == 'qwen2': return self.encode("<|im_start|>" + role + "\n")
|
||||
return self.encode("<|start_header_id|>" + role + "<|end_header_id|>\n\n")
|
||||
def end_turn(self, eos_id:int): return [eos_id] + self.encode("\n") if self.preset == 'qwen2' else [eos_id]
|
||||
def end_turn(self, eos_id:int):
|
||||
if self.preset == 'olmo': return self.encode("\n")
|
||||
if self.preset == 'qwen2': return [eos_id] + self.encode("\n")
|
||||
return [eos_id]
|
||||
|
||||
@functools.cache
|
||||
def precompute_freqs_cis(dim: int, end: int, theta: float = 10000.0) -> Tensor:
|
||||
@@ -62,6 +66,14 @@ def precompute_freqs_cis(dim: int, end: int, theta: float = 10000.0) -> Tensor:
|
||||
freqs = Tensor.arange(end).unsqueeze(dim=1) * freqs.unsqueeze(dim=0)
|
||||
return freqs.cos().cat(freqs.sin(), dim=-1).contiguous()
|
||||
|
||||
class ExpertWeights:
|
||||
"""Like nn.Linear but with num_experts dimension. Weight shape: (num_experts, out_features, in_features)."""
|
||||
def __init__(self, num_experts:int, in_features:int, out_features:int):
|
||||
self.weight = Tensor.zeros(num_experts, out_features, in_features)
|
||||
def __call__(self, sel:Tensor, x:Tensor) -> Tensor:
|
||||
# sel: (B, T, k), x: (B, T, 1, in) or (B, T, k, in) -> output: (B, T, k, out)
|
||||
return (x.unsqueeze(-2) @ self.weight[sel].transpose(-1, -2)).squeeze(-2)
|
||||
|
||||
def apply_rope(x:Tensor, freqs_cis:Tensor) -> Tensor:
|
||||
assert x.shape[-1] % 2 == 0
|
||||
cos, sin = freqs_cis.reshape(1, 1, x.shape[2], -1).chunk(2, dim=-1)
|
||||
@@ -70,12 +82,13 @@ def apply_rope(x:Tensor, freqs_cis:Tensor) -> Tensor:
|
||||
|
||||
class TransformerBlock:
|
||||
def __init__(self, dim:int, hidden_dim:int, n_heads:int, n_kv_heads:int, norm_eps:float, head_dim:int, rope_theta:float,
|
||||
max_context:int=0, qk_norm:bool=False):
|
||||
max_context:int=0, qk_norm:int=0, num_experts:int=0, num_experts_per_tok:int=0):
|
||||
self.n_heads = n_heads
|
||||
self.n_kv_heads = n_kv_heads
|
||||
self.head_dim = head_dim
|
||||
self.max_context = max_context
|
||||
self.rope_theta = rope_theta
|
||||
self.max_context = max_context
|
||||
self.qk_norm = qk_norm
|
||||
|
||||
# --- attention projections (all linear, bias-free) ------------------
|
||||
q_proj_out = self.head_dim * n_heads
|
||||
@@ -88,23 +101,30 @@ class TransformerBlock:
|
||||
# --- RMSNorms --------------------------------------------------------
|
||||
self.attn_norm = nn.RMSNorm(dim, norm_eps)
|
||||
self.ffn_norm = nn.RMSNorm(dim, norm_eps)
|
||||
if qk_norm: self.attn_q_norm, self.attn_k_norm = nn.RMSNorm(self.head_dim, norm_eps), nn.RMSNorm(self.head_dim, norm_eps)
|
||||
if qk_norm: self.attn_q_norm, self.attn_k_norm = nn.RMSNorm(qk_norm, norm_eps), nn.RMSNorm(qk_norm, norm_eps)
|
||||
|
||||
# --- feed-forward ----------------------------------------------------
|
||||
self.ffn_gate = nn.Linear(dim, hidden_dim, bias=False)
|
||||
self.ffn_up = nn.Linear(dim, hidden_dim, bias=False)
|
||||
self.ffn_down = nn.Linear(hidden_dim, dim, bias=False)
|
||||
# --- feed-forward (MoE or dense) -------------------------------------
|
||||
if num_experts > 0:
|
||||
self.num_experts_per_tok = num_experts_per_tok
|
||||
self.ffn_gate_inp = nn.Linear(dim, num_experts, bias=False) # router
|
||||
self.ffn_gate_exps = ExpertWeights(num_experts, dim, hidden_dim)
|
||||
self.ffn_up_exps = ExpertWeights(num_experts, dim, hidden_dim)
|
||||
self.ffn_down_exps = ExpertWeights(num_experts, hidden_dim, dim)
|
||||
else:
|
||||
self.ffn_gate = nn.Linear(dim, hidden_dim, bias=False)
|
||||
self.ffn_up = nn.Linear(dim, hidden_dim, bias=False)
|
||||
self.ffn_down = nn.Linear(hidden_dim, dim, bias=False)
|
||||
|
||||
def _attention(self, x:Tensor, start_pos:int|UOp) -> Tensor:
|
||||
x_norm = self.attn_norm(x) # (B,T,D)
|
||||
q, k, v = self.attn_q(x_norm), self.attn_k(x_norm), self.attn_v(x_norm)
|
||||
if self.qk_norm and self.qk_norm != self.head_dim: q, k = self.attn_q_norm(q), self.attn_k_norm(k)
|
||||
|
||||
B, T, _ = x.shape
|
||||
q = q.reshape(B, T, self.n_heads, self.head_dim).transpose(1, 2) # (B,H,T,Hd)
|
||||
k = k.reshape(B, T, self.n_kv_heads, self.head_dim).transpose(1, 2) # (B,KvH,T,Hd)
|
||||
v = v.reshape(B, T, self.n_kv_heads, self.head_dim).transpose(1, 2) # (B,KvH,T,Hd)
|
||||
|
||||
if hasattr(self, 'attn_q_norm'): q, k = self.attn_q_norm(q), self.attn_k_norm(k)
|
||||
if self.qk_norm == self.head_dim: q, k = self.attn_q_norm(q), self.attn_k_norm(k)
|
||||
|
||||
# TODO: make UOp have SupportsIndex
|
||||
freqs_cis = precompute_freqs_cis(self.head_dim, self.max_context, self.rope_theta)[start_pos:start_pos+T] # type: ignore
|
||||
@@ -127,6 +147,11 @@ class TransformerBlock:
|
||||
|
||||
def _feed_forward(self, h: Tensor) -> Tensor:
|
||||
h_norm = self.ffn_norm(h)
|
||||
if hasattr(self, 'ffn_gate_exps'):
|
||||
x = h_norm.unsqueeze(2) # (B, T, 1, D) - add expert dim for broadcasting
|
||||
probs, sel = self.ffn_gate_inp(h_norm).softmax(-1).topk(self.num_experts_per_tok) # (B, T, k) each
|
||||
x_down = self.ffn_down_exps(sel, self.ffn_gate_exps(sel, x).silu() * self.ffn_up_exps(sel, x)) # (B, T, k, D)
|
||||
return h + (x_down * probs.unsqueeze(-1)).sum(axis=2) # (B, T, D)
|
||||
# TODO: remove the need for this contiguous
|
||||
gated = self.ffn_gate(h_norm).silu().contiguous() * self.ffn_up(h_norm)
|
||||
return h + self.ffn_down(gated)
|
||||
@@ -136,9 +161,9 @@ class TransformerBlock:
|
||||
|
||||
class Transformer:
|
||||
def __init__(self, *, num_blocks, dim, hidden_dim, n_heads, n_kv_heads, norm_eps, vocab_size, head_dim:int, rope_theta:float,
|
||||
max_context:int=0, qk_norm:bool=False):
|
||||
self.blk = [TransformerBlock(dim, hidden_dim, n_heads, n_kv_heads, norm_eps, head_dim, rope_theta, max_context, qk_norm)
|
||||
for _ in range(num_blocks)]
|
||||
max_context:int=0, qk_norm:int=0, num_experts:int=0, num_experts_per_tok:int=0):
|
||||
self.blk = [TransformerBlock(dim, hidden_dim, n_heads, n_kv_heads, norm_eps, head_dim, rope_theta, max_context, qk_norm,
|
||||
num_experts, num_experts_per_tok) for _ in range(num_blocks)]
|
||||
self.token_embd = nn.Embedding(vocab_size, dim)
|
||||
self.output_norm = nn.RMSNorm(dim, norm_eps)
|
||||
self.output = nn.Linear(dim, vocab_size, bias=False)
|
||||
@@ -170,16 +195,20 @@ class Transformer:
|
||||
max_context = min(max_context, kv[f'{arch}.context_length']) if max_context is not None else kv[f'{arch}.context_length']
|
||||
n_heads, n_kv_heads = kv[f'{arch}.attention.head_count'], kv[f'{arch}.attention.head_count_kv']
|
||||
|
||||
# permute Q/K weights from interleaved to half-split RoPE layout: [0,1,2,3,4,5...] -> [0,2,4,...,1,3,5,...]
|
||||
if arch != 'qwen3':
|
||||
# Permute Q/K weights from interleaved to half-split RoPE layout (llama-style models only)
|
||||
if arch == 'llama':
|
||||
for name in state_dict:
|
||||
if 'attn_q.weight' in name: state_dict[name] = state_dict[name].rearrange("(n h two) d -> (n two h) d", n=n_heads, two=2)
|
||||
if 'attn_k.weight' in name: state_dict[name] = state_dict[name].rearrange("(n h two) d -> (n two h) d", n=n_kv_heads, two=2)
|
||||
|
||||
model = Transformer(num_blocks=kv[f'{arch}.block_count'], dim=kv[f'{arch}.embedding_length'], hidden_dim=kv[f'{arch}.feed_forward_length'],
|
||||
model = Transformer(num_blocks=kv[f'{arch}.block_count'], dim=kv[f'{arch}.embedding_length'],
|
||||
hidden_dim=kv.get(f'{arch}.expert_feed_forward_length', kv[f'{arch}.feed_forward_length']),
|
||||
n_heads=n_heads, n_kv_heads=n_kv_heads, norm_eps=kv[f'{arch}.attention.layer_norm_rms_epsilon'],
|
||||
vocab_size=len(kv['tokenizer.ggml.tokens']), head_dim=kv[f'{arch}.attention.key_length'],
|
||||
rope_theta=kv[f'{arch}.rope.freq_base'], max_context=max_context, qk_norm='blk.0.attn_q_norm.weight' in state_dict)
|
||||
vocab_size=len(kv['tokenizer.ggml.tokens']),
|
||||
head_dim=kv.get(f'{arch}.attention.key_length', kv[f'{arch}.embedding_length'] // n_heads),
|
||||
rope_theta=kv[f'{arch}.rope.freq_base'], max_context=max_context,
|
||||
qk_norm=int(state_dict['blk.0.attn_q_norm.weight'].shape[0]) if 'blk.0.attn_q_norm.weight' in state_dict else 0,
|
||||
num_experts=kv.get(f'{arch}.expert_count', 0), num_experts_per_tok=kv.get(f'{arch}.expert_used_count', 0))
|
||||
nn.state.load_state_dict(model, state_dict, verbose=False, consume=True, realize=False) # NOTE: rope_freqs.weight (32,) is unused
|
||||
# NOTE: without this contiguous, it unpacks the weights from the model every time. we shouldn't need this, but for now it's faster
|
||||
for s in (params:=nn.state.get_parameters(model)): s.replace(s.contiguous())
|
||||
@@ -207,6 +236,8 @@ models = {
|
||||
"qwen3:0.6b": "https://huggingface.co/Qwen/Qwen3-0.6B-GGUF/resolve/main/Qwen3-0.6B-Q8_0.gguf",
|
||||
"qwen3:1.7b": "https://huggingface.co/unsloth/Qwen3-1.7B-GGUF/resolve/main/Qwen3-1.7B-Q4_K_M.gguf",
|
||||
"qwen3:8b": "https://huggingface.co/Qwen/Qwen3-8B-GGUF/resolve/main/Qwen3-8B-Q4_K_M.gguf",
|
||||
"qwen3:30b-a3b": "https://huggingface.co/Qwen/Qwen3-30B-A3B-GGUF/resolve/main/Qwen3-30B-A3B-Q4_K_M.gguf",
|
||||
"olmoe": "https://huggingface.co/allenai/OLMoE-1B-7B-0924-Instruct-GGUF/resolve/main/olmoe-1b-7b-0924-instruct-q4_k_m.gguf",
|
||||
}
|
||||
|
||||
# *** simple OpenAI compatible server on 11434 to match ollama ***
|
||||
|
||||
@@ -18,6 +18,7 @@ from tinygrad.codegen.opt.postrange import apply_opts
|
||||
from tinygrad.codegen.simplify import pm_simplify_ranges, pm_flatten_range, pm_split_ranges, pm_load_collapse, pm_split_store
|
||||
from tinygrad.schedule.rangeify import pm_add_buffers_local, rangeify_codegen, pm_mops
|
||||
from tinygrad.codegen.late.linearizer import CFGContext, pm_split_ends, pm_add_control_flow, linearize
|
||||
from tinygrad.device import Compiler
|
||||
|
||||
pm_syntactic_sugar = PatternMatcher([
|
||||
# INDEX on ptr INDEX concats them
|
||||
@@ -123,6 +124,30 @@ def line_rewrite(lst:list[UOp], pm:PatternMatcher) -> list[UOp]:
|
||||
newlst.extend(ret[1])
|
||||
return newlst
|
||||
|
||||
def do_linearize(prg:UOp, sink:UOp) -> UOp:
|
||||
lst = line_rewrite(linearize(sink), pm_linearize_cleanups)
|
||||
if SPEC: type_verify(lst, program_spec)
|
||||
return prg.replace(src=prg.src + (UOp(Ops.LINEAR, src=tuple(lst)),))
|
||||
|
||||
def do_render(ctx:tuple[Renderer, Compiler], prg:UOp, lin:UOp) -> UOp:
|
||||
src = ctx[0].render(list(lin.src))
|
||||
return prg.replace(src=prg.src + (UOp(Ops.SOURCE, arg=src),))
|
||||
|
||||
def do_compile(ctx:tuple[Renderer, Compiler], prg:UOp, src:UOp) -> UOp:
|
||||
lib = ctx[1].compile_cached(src.arg)
|
||||
return prg.replace(src=prg.src + (UOp(Ops.BINARY, arg=lib),))
|
||||
|
||||
pm_to_program = PatternMatcher([
|
||||
(UPat(Ops.PROGRAM, src=(UPat(Ops.SINK, name="sink"),), name="prg"), do_linearize),
|
||||
(UPat(Ops.PROGRAM, src=(UPat(), UPat(Ops.LINEAR, name="lin")), name="prg"), do_render),
|
||||
(UPat(Ops.PROGRAM, src=(UPat(), UPat(), UPat(Ops.SOURCE, name="src")), name="prg"), do_compile),
|
||||
])
|
||||
|
||||
def full_rewrite_to_program(sink:UOp, ren:Renderer, compiler:Compiler) -> UOp:
|
||||
full_sink = full_rewrite_to_sink(sink, ren, optimize=sink.tag is None)
|
||||
sink = UOp(Ops.PROGRAM, src=(full_sink,))
|
||||
return graph_rewrite(sink, pm_to_program, ctx=(ren, compiler), name="linearize/render/compile")
|
||||
|
||||
def full_rewrite(sink:UOp, ren:Renderer|None=None) -> list[UOp]:
|
||||
"""
|
||||
Function to transform the Kernel UOp graph into a linearized program.
|
||||
|
||||
@@ -40,7 +40,7 @@ def _time_program(p:ProgramSpec, lib:bytes, var_vals:dict[str, int], rawbufs:lis
|
||||
if allow_test_size and p.global_size is not None and max_global_size is not None:
|
||||
global_size, factor = get_test_global_size(p.global_size, max_global_size, var_vals)
|
||||
p = replace(p, global_size=global_size)
|
||||
try: car = CompiledRunner(p, precompiled=lib)
|
||||
try: car = CompiledRunner(replace(p, lib=lib))
|
||||
except AssertionError: return [math.inf] * cnt
|
||||
tms = []
|
||||
input_bufs = [rawbufs[i] for i in car.p.globals]
|
||||
|
||||
+6
-7
@@ -218,17 +218,19 @@ DTYPES_DICT = {k: v for k, v in dtypes.__dict__.items() if isinstance(v, DType)
|
||||
INVERSE_DTYPES_DICT = {**{v.name:k for k,v in DTYPES_DICT.items()}, "void": "void", "index":"index"}
|
||||
|
||||
@functools.cache
|
||||
def can_safe_cast(dt0:DType, dt1:DType) -> bool:
|
||||
def can_lossless_cast(dt0:DType, dt1:DType) -> bool:
|
||||
# return if dt1 preserves value of dt0
|
||||
# https://numpy.org/doc/stable/reference/generated/numpy.can_cast.html
|
||||
# similar to https://numpy.org/doc/stable/reference/generated/numpy.can_cast.html
|
||||
if dt0 == dt1 or dt0 == dtypes.bool: return True
|
||||
match dt1:
|
||||
case dtypes.index: return dt0 in dtypes.ints
|
||||
case dtypes.double: return dt0 in (dtypes.float, dtypes.half, dtypes.bfloat16, *dtypes.fp8s,
|
||||
dtypes.uint32, dtypes.uint16, dtypes.uint8, dtypes.int32, dtypes.int16, dtypes.int8)
|
||||
case dtypes.float: return dt0 in (dtypes.half, dtypes.bfloat16, *dtypes.fp8s, dtypes.uint16, dtypes.uint8, dtypes.int16, dtypes.int8)
|
||||
case dtypes.half: return dt0 in (*dtypes.fp8s, dtypes.uint8, dtypes.int8)
|
||||
case dtypes.uint64: return dt0 in (dtypes.uint32, dtypes.uint16, dtypes.uint8)
|
||||
case dtypes.uint32: return dt0 in (dtypes.uint16, dtypes.uint8)
|
||||
case dtypes.uint16: return dt0 in (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)
|
||||
@@ -319,11 +321,8 @@ def fp8_to_float(x: int, dtype: DType) -> float:
|
||||
truncate: dict[DType, Callable] = {dtypes.bool: bool,
|
||||
dtypes.float16: float_to_fp16, dtypes.bfloat16: lambda x: float_to_bf16(float(x)),
|
||||
**{fp8: (lambda x, dtype=fp8: fp8_to_float(float_to_fp8(x, dtype), dtype)) for fp8 in dtypes.fp8s},
|
||||
dtypes.float32: lambda x: ctypes.c_float(x).value, dtypes.float64: lambda x: ctypes.c_double(x).value,
|
||||
dtypes.uint8: lambda x: ctypes.c_uint8(x).value, dtypes.uint16: lambda x: ctypes.c_uint16(x).value,
|
||||
dtypes.uint32: lambda x: ctypes.c_uint32(x).value, dtypes.uint64: lambda x: ctypes.c_uint64(x).value,
|
||||
dtypes.int8: lambda x: ctypes.c_int8(x).value, dtypes.int16: lambda x: ctypes.c_int16(x).value, dtypes.int32: lambda x: ctypes.c_int32(x).value,
|
||||
dtypes.int64: lambda x: ctypes.c_int64(x).value}
|
||||
**{getattr(dtypes, n): (lambda x, c=getattr(ctypes, f'c_{n}'): c(x).value)
|
||||
for n in ('float', 'double', 'int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64')}}
|
||||
|
||||
# numpy and torch dtype interop
|
||||
|
||||
|
||||
+19
-26
@@ -2,28 +2,30 @@ from typing import cast, Callable
|
||||
import time, pprint, random, itertools, math
|
||||
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, PROFILE, ProfilePointEvent, cpu_events, prod, Context
|
||||
from tinygrad.helpers import DEVECTORIZE, time_to_str, VALIDATE_WITH_CPU, getenv, PROFILE, ProfilePointEvent, cpu_events, prod, Context
|
||||
from tinygrad.helpers import unwrap
|
||||
from tinygrad.uop.ops import Ops, PatternMatcher, UOp, UPat, sym_infer, graph_rewrite, print_uops, track_rewrites, KernelInfo, pyrender
|
||||
from tinygrad.uop.ops import Ops, PatternMatcher, UOp, UPat, sym_infer, graph_rewrite, track_rewrites, KernelInfo, pyrender
|
||||
from tinygrad.device import Device, Buffer
|
||||
from tinygrad.renderer import Renderer, ProgramSpec, Estimates
|
||||
from tinygrad.codegen import full_rewrite
|
||||
from tinygrad.codegen import full_rewrite_to_program
|
||||
from tinygrad.codegen.opt import Opt
|
||||
|
||||
# **************** Program Creation ****************
|
||||
|
||||
@track_rewrites(name=lambda *args,ret,**kwargs: TracingKey(ret.name, (ret.function_name, ret.ast), ret=ret), replay=True)
|
||||
def get_program(ast:UOp, renderer:Renderer, opts:list[Opt]|None=None) -> ProgramSpec:
|
||||
def get_program(ast:UOp, renderer:Renderer, device:str|None=None, opts:list[Opt]|None=None) -> ProgramSpec:
|
||||
"""
|
||||
Transform an AST into a ProgramSpec. May trigger BEAM search.
|
||||
|
||||
Args:
|
||||
ast: The Ops.SINK rooted AST
|
||||
renderer: The renderer used to generate the code
|
||||
device: The device to compile for (defaults to renderer.device)
|
||||
|
||||
Returns:
|
||||
The ProgramSpec of the program.
|
||||
"""
|
||||
device = device or renderer.device
|
||||
|
||||
if getenv("VIZ"): graph_rewrite(ast, PatternMatcher([]), name="View Base AST")
|
||||
if DEBUG >= 5: print(pyrender(ast))
|
||||
@@ -32,20 +34,14 @@ def get_program(ast:UOp, renderer:Renderer, opts:list[Opt]|None=None) -> Program
|
||||
if opts is not None:
|
||||
assert ast.arg is None, "can't apply opts if sink has an arg"
|
||||
ast = ast.replace(arg=KernelInfo(opts_to_apply=tuple(opts)))
|
||||
try:
|
||||
uops = full_rewrite(ast, renderer)
|
||||
except RuntimeError as e:
|
||||
print("***** LINEARIZE FAILURE *****")
|
||||
print(e)
|
||||
print(pyrender(ast))
|
||||
raise
|
||||
assert uops[-1].op is Ops.SINK, "last uop must be sink"
|
||||
if ast.arg is None: ast = ast.replace(arg=KernelInfo())
|
||||
|
||||
# print and render
|
||||
if DEBUG >= 6: print_uops(uops)
|
||||
src = renderer.render(uops)
|
||||
prg = full_rewrite_to_program(ast, renderer, Device[device].compiler)
|
||||
# SINK/LINEAR/SOURCE/BINARY
|
||||
sink, linear, source, binary = prg.src
|
||||
|
||||
return ProgramSpec(uops[-1].arg.name if uops[-1].arg is not None else "test", src, renderer.device, ast, uops,
|
||||
# legacy
|
||||
return ProgramSpec(sink.arg.name, source.arg, device, sink, list(linear.src), binary.arg,
|
||||
global_size=[1,1,1] if renderer.has_local or renderer.has_threads else None,
|
||||
local_size=[1,1,1] if renderer.has_local else None)
|
||||
|
||||
@@ -76,19 +72,16 @@ def optimize_local_size(_prg:Callable, global_size:list[int], rawbufs:list[Buffe
|
||||
return ret[1]
|
||||
|
||||
class CompiledRunner(Runner):
|
||||
def __init__(self, p:ProgramSpec, precompiled:bytes|None=None, prg=None):
|
||||
def __init__(self, p:ProgramSpec, prg=None):
|
||||
if DEBUG >= 3: print(p.applied_opts)
|
||||
if DEBUG >= 4: print(p.src)
|
||||
assert p.lib is not None, "lib must be provided"
|
||||
self.p:ProgramSpec = p
|
||||
if precompiled is not None: self.lib = precompiled
|
||||
else:
|
||||
with cpu_profile(TracingKey(f"compile {p.name}", (p.function_name,)), "TINY"):
|
||||
self.lib = 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
|
||||
if DEBUG >= 7: Device[p.device].compiler.disassemble(p.lib)
|
||||
self._prg = Device[p.device].runtime(p.function_name, p.lib) if prg is None else prg
|
||||
super().__init__(p.name, p.device, p.estimates)
|
||||
|
||||
def __reduce__(self): return self.__class__, (self.p, self.lib)
|
||||
def __reduce__(self): return self.__class__, (self.p,)
|
||||
|
||||
def __call__(self, rawbufs:list[Buffer], var_vals:dict[str, int]|None=None, wait=False) -> float|None:
|
||||
if var_vals is None: var_vals = {}
|
||||
@@ -162,9 +155,9 @@ def get_runner(device:str, ast:UOp) -> CompiledRunner:
|
||||
if cret:=method_cache.get(ckey): return cret
|
||||
bkey = (device.split(":")[0], type(Device[device].compiler), ast.key, context, True)
|
||||
if bret:=method_cache.get(bkey):
|
||||
method_cache[ckey] = ret = CompiledRunner(replace(bret.p, device=device), bret.lib)
|
||||
method_cache[ckey] = ret = CompiledRunner(replace(bret.p, device=device))
|
||||
else:
|
||||
prg: ProgramSpec = get_program(ast, Device[device].renderer)
|
||||
prg: ProgramSpec = get_program(ast, Device[device].renderer, device)
|
||||
method_cache[ckey] = method_cache[bkey] = ret = CompiledRunner(replace(prg, device=device))
|
||||
return ret
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ class ProgramSpec:
|
||||
device:str
|
||||
ast:UOp # save the base ast (this is method cache key)
|
||||
uops:list[UOp]|None=None
|
||||
lib:bytes|None=None # compiled binary
|
||||
|
||||
# filled in from uops (if we have uops)
|
||||
global_size:list[int]|None=None
|
||||
|
||||
@@ -830,8 +830,8 @@ class PCIIface(PCIIfaceBase):
|
||||
doorbell=(doorbell_index:=am.AMDGPU_NAVI10_DOORBELL_sDMA_ENGINE0), pipe=0, queue=0)
|
||||
else:
|
||||
pv = self.dev_impl.gfx.setup_ring(ring_addr=ring.va_addr, ring_size=ring.size, rptr_addr=gart.va_addr+rptr, wptr_addr=gart.va_addr+wptr,
|
||||
eop_addr=eop_buffer.va_addr, eop_size=eop_buffer.size, doorbell=(doorbell_index:=am.AMDGPU_NAVI10_DOORBELL_MEC_RING0), pipe=0, queue=0,
|
||||
aql=(queue_type==kfd.KFD_IOC_QUEUE_TYPE_COMPUTE_AQL))
|
||||
eop_addr=eop_buffer.va_addr, eop_size=eop_buffer.size, doorbell=(doorbell_index:=am.AMDGPU_NAVI10_DOORBELL_MEC_RING0), pipe=0,
|
||||
queue=int(is_aql:=(queue_type==kfd.KFD_IOC_QUEUE_TYPE_COMPUTE_AQL)), aql=is_aql)
|
||||
|
||||
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(offset=rptr, size=8, fmt='Q')], write_ptrs=[gart.cpu_view().view(offset=wptr, size=8, fmt='Q')], put_value=pv)
|
||||
|
||||
@@ -142,7 +142,7 @@ class AMMemoryManager(MemoryManager):
|
||||
self.dev.gmc.flush_tlb(ip='MM', vmid=0)
|
||||
|
||||
class AMDev(PCIDevImplBase):
|
||||
Version = 0xA0000006
|
||||
Version = 0xA0000007
|
||||
|
||||
def __init__(self, pci_dev:PCIDevice, dma_regions:list[tuple[int, MMIOInterface]]|None=None, reset_mode=False):
|
||||
self.pci_dev, self.devfmt, self.dma_regions = pci_dev, pci_dev.pcibus, dma_regions
|
||||
|
||||
@@ -212,14 +212,19 @@ class AM_SMU(AM_IP):
|
||||
return (self.adev.mmMP1_SMN_C2PMSG_82 if not debug else self.adev.mmMP1_SMN_C2PMSG_53).read() if read_back_arg else None
|
||||
|
||||
class AM_GFX(AM_IP):
|
||||
def init_sw(self): self.xccs = len(self.adev.regs_offset[am.GC_HWIP])
|
||||
def init_sw(self):
|
||||
self.xccs = len(self.adev.regs_offset[am.GC_HWIP])
|
||||
self.mqd_paddr = [self.adev.mm.palloc(0x1000 * self.xccs, zero=False, boot=True) for i in range(2)]
|
||||
self.mqd_mc = [self.adev.paddr2mc(mqd_paddr) for mqd_paddr in self.mqd_paddr]
|
||||
|
||||
def init_hw(self):
|
||||
# Wait for RLC autoload to complete
|
||||
while self.adev.regCP_STAT.read() != 0 and self.adev.regRLC_RLCS_BOOTLOAD_STATUS.read_bitfields()['bootload_complete'] != 0: pass
|
||||
|
||||
self._config_gfx_rs64()
|
||||
self.adev.gmc.init_hub("GC", inst_cnt=self.xccs)
|
||||
if self.adev.partial_boot: return
|
||||
|
||||
self._config_gfx_rs64()
|
||||
|
||||
# NOTE: Golden reg for gfx11. No values for this reg provided. The kernel just ors 0x20000000 to this reg.
|
||||
for xcc in range(self.xccs): self.adev.regTCP_CNTL.write(self.adev.regTCP_CNTL.read() | 0x20000000, inst=xcc)
|
||||
@@ -265,49 +270,61 @@ class AM_GFX(AM_IP):
|
||||
if self.xccs > 1 and not self.adev.partial_boot: self.adev.psp._spatial_partition_cmd(1)
|
||||
|
||||
def fini_hw(self):
|
||||
for xcc in range(self.xccs):
|
||||
self._grbm_select(me=1, pipe=0, queue=0, inst=xcc)
|
||||
if self.adev.regCP_HQD_ACTIVE.read(inst=xcc) & 1: self.adev.regCP_HQD_DEQUEUE_REQUEST.write(0x2, inst=xcc) # 1 - DRAIN_PIPE; 2 - RESET_WAVES
|
||||
self._grbm_select(inst=xcc)
|
||||
# NOTE: For aqls with xccs (queue=1), will continue from the saved state.
|
||||
for q in range(2 if self.xccs == 1 else 1):
|
||||
for xcc in range(self.xccs):
|
||||
self._grbm_select(me=1, pipe=0, queue=q, inst=xcc)
|
||||
if self.adev.regCP_HQD_ACTIVE.read(inst=xcc) & 1: self.adev.regCP_HQD_DEQUEUE_REQUEST.write(0x2, inst=xcc) # 1 - DRAIN_PIPE; 2 - RESET_WAVES
|
||||
self._grbm_select(inst=xcc)
|
||||
for xcc in range(self.xccs): self.adev.regGCVM_CONTEXT0_CNTL.write(0, inst=xcc)
|
||||
|
||||
def setup_ring(self, ring_addr:int, ring_size:int, rptr_addr:int, wptr_addr:int, eop_addr:int, eop_size:int, doorbell:int, pipe:int, queue:int,
|
||||
aql:bool) -> int:
|
||||
for xcc in range(self.xccs if aql else 1):
|
||||
mqd = self.adev.mm.valloc(0x1000, uncached=True, contiguous=True)
|
||||
self._grbm_select(me=1, pipe=pipe, queue=queue, inst=0)
|
||||
restore_queue = aql and self.xccs > 1 and self.adev.partial_boot and (self.adev.regCP_HQD_ACTIVE.read(inst=0) & 1)
|
||||
restore_ptr = (self.adev.regCP_HQD_PQ_WPTR_LO.read(inst=0) | (self.adev.regCP_HQD_PQ_WPTR_HI.read(inst=0) << 32)) if restore_queue else 0
|
||||
if DEBUG >= 2 and restore_queue: print(f"am {self.adev.devfmt}: GFX queue already active, continuing from saved state {restore_ptr=:#x}.")
|
||||
|
||||
for xcc in range(self.xccs if aql else 1):
|
||||
struct_t = getattr(am, f"struct_v{self.adev.ip_ver[am.GC_HWIP][0]}{'_compute' if self.adev.ip_ver[am.GC_HWIP][0] >= 10 else ''}_mqd")
|
||||
mqd_struct = struct_t(header=0xC0310800, cp_mqd_base_addr_lo=lo32(mqd.va_addr), cp_mqd_base_addr_hi=hi32(mqd.va_addr),
|
||||
mqd_struct = struct_t(header=0xC0310800, cp_mqd_base_addr_lo=lo32(self.mqd_mc[queue] + 0x1000*xcc),
|
||||
cp_mqd_base_addr_hi=hi32(self.mqd_mc[queue] + 0x1000*xcc), cp_hqd_pipe_priority=0x2, cp_hqd_queue_priority=0xf, cp_hqd_quantum=0x111,
|
||||
cp_hqd_persistent_state=self.adev.regCP_HQD_PERSISTENT_STATE.encode(preload_size=0x55, preload_req=1),
|
||||
cp_hqd_pipe_priority=0x2, cp_hqd_queue_priority=0xf, cp_hqd_quantum=0x111,
|
||||
cp_hqd_pq_base_lo=lo32(ring_addr>>8), cp_hqd_pq_base_hi=hi32(ring_addr>>8),
|
||||
cp_hqd_pq_rptr_report_addr_lo=lo32(rptr_addr), cp_hqd_pq_rptr_report_addr_hi=hi32(rptr_addr),
|
||||
cp_hqd_pq_wptr_poll_addr_lo=lo32(wptr_addr), cp_hqd_pq_wptr_poll_addr_hi=hi32(wptr_addr),
|
||||
cp_hqd_pq_doorbell_control=self.adev.regCP_HQD_PQ_DOORBELL_CONTROL.encode(doorbell_offset=doorbell*2, doorbell_en=1),
|
||||
cp_hqd_pq_control=self.adev.regCP_HQD_PQ_CONTROL.encode(rptr_block_size=5, unord_dispatch=0, queue_size=(ring_size//4).bit_length()-2,
|
||||
**({'queue_full_en':1, 'slot_based_wptr':2, 'wpp_clamp_en':1, 'no_update_rptr':xcc!=0 or self.xccs==1} if aql else {})),
|
||||
**({'queue_full_en':1, 'slot_based_wptr':2, 'no_update_rptr':xcc!=0 or self.xccs==1} if aql else {})),
|
||||
cp_hqd_ib_control=self.adev.regCP_HQD_IB_CONTROL.encode(min_ib_avail_size=0x3), cp_hqd_hq_status0=0x20004000,
|
||||
cp_mqd_control=self.adev.regCP_MQD_CONTROL.encode(priv_state=1), cp_hqd_vmid=0, cp_hqd_aql_control=int(aql),
|
||||
cp_hqd_eop_base_addr_lo=lo32(eop_addr>>8), cp_hqd_eop_base_addr_hi=hi32(eop_addr>>8),
|
||||
cp_hqd_eop_control=self.adev.regCP_HQD_EOP_CONTROL.encode(eop_size=(eop_size//4).bit_length()-2),
|
||||
**({'compute_tg_chunk_size':1, 'compute_current_logic_xcc_id':xcc} if aql and self.xccs > 1 else {}))
|
||||
**({'compute_tg_chunk_size':1, 'compute_current_logic_xcc_id':xcc, 'cp_mqd_stride_size':0x1000} if aql and self.xccs > 1 else {}))
|
||||
for se in range(8 if self.adev.ip_ver[am.GC_HWIP][0] >= 10 else 4): setattr(mqd_struct, f'compute_static_thread_mgmt_se{se}', 0xffffffff)
|
||||
|
||||
# Copy mqd into memory
|
||||
self.adev.vram.view(mqd.paddrs[0][0], ctypes.sizeof(mqd_struct))[:] = memoryview(mqd_struct).cast('B')
|
||||
self.adev.gmc.flush_hdp()
|
||||
|
||||
self._grbm_select(me=1, pipe=pipe, queue=queue, inst=xcc)
|
||||
|
||||
mqd_st_mv = to_mv(ctypes.addressof(mqd_struct), ctypes.sizeof(mqd_struct)).cast('I')
|
||||
for i, reg in enumerate(range(self.adev.regCP_MQD_BASE_ADDR.addr[xcc], self.adev.regCP_HQD_PQ_WPTR_HI.addr[xcc] + 1)):
|
||||
self.adev.wreg(reg, mqd_st_mv[0x80 + i])
|
||||
self.adev.regCP_HQD_ACTIVE.write(0x1, inst=xcc)
|
||||
if restore_queue:
|
||||
for r in [self.adev.regCP_HQD_PQ_RPTR_REPORT_ADDR, self.adev.regCP_HQD_EOP_BASE_ADDR, self.adev.regCP_HQD_EOP_BASE_ADDR_HI,
|
||||
self.adev.regCP_HQD_PQ_RPTR_REPORT_ADDR_HI, self.adev.regCP_HQD_PQ_WPTR_POLL_ADDR, self.adev.regCP_HQD_PQ_WPTR_POLL_ADDR_HI]:
|
||||
val = memoryview(bytes(mqd_struct)).cast('I')[0x80 + (off:=r.addr[xcc] - self.adev.regCP_MQD_BASE_ADDR.addr[xcc])]
|
||||
self.adev.vram.view(self.mqd_paddr[queue] + 0x1000*xcc, ctypes.sizeof(mqd_struct), fmt='I')[0x80 + off] = val
|
||||
r.write(val, inst=xcc)
|
||||
else:
|
||||
self.adev.vram.view(self.mqd_paddr[queue] + 0x1000*xcc, ctypes.sizeof(mqd_struct))[:] = memoryview(mqd_struct).cast('B')
|
||||
|
||||
mqd_st_mv = to_mv(ctypes.addressof(mqd_struct), ctypes.sizeof(mqd_struct)).cast('I')
|
||||
for i, reg in enumerate(range(self.adev.regCP_MQD_BASE_ADDR.addr[xcc], self.adev.regCP_HQD_PQ_WPTR_HI.addr[xcc] + 1)):
|
||||
self.adev.wreg(reg, mqd_st_mv[0x80 + i])
|
||||
self.adev.regCP_HQD_ACTIVE.write(0x1, inst=xcc)
|
||||
|
||||
self.adev.gmc.flush_hdp()
|
||||
self._grbm_select(inst=xcc)
|
||||
|
||||
self.adev.reg(f"regCP_ME1_PIPE{pipe}_INT_CNTL").update(time_stamp_int_enable=1, generic0_int_enable=1, inst=xcc)
|
||||
return 0
|
||||
return restore_ptr // 16
|
||||
|
||||
def set_clockgating_state(self):
|
||||
if hasattr(self.adev, 'regMM_ATC_L2_MISC_CG'): self.adev.regMM_ATC_L2_MISC_CG.write(enable=1, mem_ls_enable=1)
|
||||
|
||||
@@ -27,6 +27,10 @@ class Ops(FastEnum):
|
||||
# uops that aren't rendered
|
||||
NOOP = auto(); REWRITE_ERROR = auto()
|
||||
|
||||
# renderer/compiler
|
||||
# LINEAR is a list of UOps, SOURCE has a str arg that's human readable, BINARY has a bytes arg that's not
|
||||
PROGRAM = auto(); LINEAR = auto(); SOURCE = auto(); BINARY = auto()
|
||||
|
||||
# AFTER passes src[0] through and promises in the toposort that any consumers of the AFTER run after src[1:]
|
||||
# GROUP is a NOOP that just merges things together
|
||||
SINK = auto(); AFTER = auto(); GROUP = auto()
|
||||
|
||||
+2
-1
@@ -218,7 +218,8 @@ class UOp(OpMixin, metaclass=UOpMetaClass):
|
||||
match self.op:
|
||||
# late ops don't have shape
|
||||
case Ops.UNIQUE | Ops.LUNIQUE | Ops.DEVICE | Ops.RANGE | Ops.LOAD | Ops.IF | Ops.BARRIER | Ops.CUSTOM | Ops.CUSTOMI | \
|
||||
Ops.VECTORIZE | Ops.VCONST | Ops.GEP | Ops.SPECIAL | Ops.UNROLL | Ops.CONTRACT | Ops.CUSTOM_KERNEL:
|
||||
Ops.VECTORIZE | Ops.VCONST | Ops.GEP | Ops.SPECIAL | Ops.UNROLL | Ops.CONTRACT | Ops.CUSTOM_KERNEL | \
|
||||
Ops.LINEAR | Ops.PROGRAM | Ops.SOURCE | Ops.BINARY:
|
||||
return None
|
||||
|
||||
case Ops.INDEX:
|
||||
|
||||
@@ -249,6 +249,16 @@ full_spec = PatternMatcher([
|
||||
# in progress MSTACK may lose device
|
||||
(UPat((Ops.MSELECT, Ops.MSTACK), name="x"), lambda x: True),
|
||||
|
||||
# codegen: PROGRAM with progressive sources through the pipeline
|
||||
(UPat(Ops.PROGRAM, dtypes.void, src=(UPat(Ops.SINK),)), lambda: True),
|
||||
(UPat(Ops.PROGRAM, dtypes.void, src=(UPat(Ops.SINK), UPat(Ops.LINEAR))), lambda: True),
|
||||
(UPat(Ops.PROGRAM, dtypes.void, src=(UPat(Ops.SINK), UPat(Ops.LINEAR), UPat(Ops.SOURCE))), lambda: True),
|
||||
(UPat(Ops.PROGRAM, dtypes.void, src=(UPat(Ops.SINK), UPat(Ops.LINEAR), UPat(Ops.SOURCE), UPat(Ops.BINARY))), lambda: True),
|
||||
# codegen: standalone LINEAR/SOURCE/BINARY
|
||||
(UPat(Ops.LINEAR, dtypes.void), lambda: True),
|
||||
(UPat(Ops.SOURCE, dtypes.void, src=()), lambda: True),
|
||||
(UPat(Ops.BINARY, dtypes.void, src=()), lambda: True),
|
||||
|
||||
# temp VECTORIZE/INDEX during rewrite have the wrong dtype
|
||||
(UPat(Ops.VECTORIZE), lambda: True),
|
||||
(UPat(Ops.INDEX), lambda: True),
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
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, can_safe_cast, Invalid
|
||||
from tinygrad.dtype import ConstType, dtypes, PtrDType, can_lossless_cast, Invalid
|
||||
from tinygrad.helpers import partition, all_same, prod, flatten, get_single_element, unwrap, IMAGE, dedup
|
||||
from tinygrad.uop.decompositions import xpow
|
||||
from tinygrad.uop.divandmod import div_and_mod_symbolic
|
||||
@@ -68,6 +68,7 @@ symbolic_simple = propagate_invalid + PatternMatcher([
|
||||
# ** zero folding **
|
||||
(UPat.var("x") < UPat.var("x"), lambda x: x.const_like(False).cast(dtypes.bool.vec(x.dtype.count))), # x < x -> False
|
||||
(UPat.var("x") % UPat.var("x"), lambda x: x.const_like(0)), # x%x -> 0
|
||||
(UPat.var("x") ^ UPat.var("x"), lambda x: x.const_like(0)), # x^x -> 0
|
||||
(UPat.var("x", dtype=dtypes.ints+(dtypes.bool, dtypes.index)) != UPat.var("x"),
|
||||
lambda x: x.const_like(False).cast(dtypes.bool.vec(x.dtype.count))), # x != x -> False (only ints)
|
||||
# ** constant folding **
|
||||
@@ -97,7 +98,7 @@ symbolic_simple = propagate_invalid + PatternMatcher([
|
||||
(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(name="a").cast(name="b"), lambda x,a,b: x if x.dtype == b.dtype and can_safe_cast(b.dtype, a.dtype) else None),
|
||||
(UPat.var('x').cast(name="a").cast(name="b"), lambda x,a,b: x if x.dtype == b.dtype and can_lossless_cast(b.dtype, a.dtype) else None),
|
||||
# ** pow **
|
||||
(UPat.var("x").alu(Ops.POW, UPat.cvar("c", vec=False)), simplify_pow),
|
||||
# positive const ** x
|
||||
@@ -242,7 +243,7 @@ symbolic = symbolic_simple+commutative+PatternMatcher([
|
||||
(UPat(Ops.RANGE, src=UPat.var("end"), name="r")//UPat.var("end"), lambda r,end: r.const_like(0)),
|
||||
# cast/long folding
|
||||
# if the intermediate cast doesnt narrow we can do it in one cast
|
||||
(UPat.var('x').cast(name="a").cast(name="b"), lambda x,a,b: x.cast(b.dtype) if can_safe_cast(x.dtype, a.dtype) else None),
|
||||
(UPat.var('x').cast(name="a").cast(name="b"), lambda x,a,b: x.cast(b.dtype) if can_lossless_cast(x.dtype, a.dtype) else None),
|
||||
(UPat.var('x', dtypes.ints+(dtypes.index,)).cast(dtypes.ints+(dtypes.index,), name="a").cast(name="b"),
|
||||
lambda x,a,b: x.cast(b.dtype) if a.dtype.min<=x.vmin and x.vmax<=a.dtype.max else None),
|
||||
# try to do math in int instead of long
|
||||
|
||||
+2
-12
@@ -124,7 +124,6 @@
|
||||
fill: rgba(26, 27, 38, 0.5);
|
||||
}
|
||||
.edgePath {
|
||||
stroke: #4a4b57;
|
||||
fill: none;
|
||||
stroke-width: 1.4px;
|
||||
}
|
||||
@@ -322,14 +321,6 @@
|
||||
td.Instruction {
|
||||
font-family: monospace;
|
||||
}
|
||||
td.pct-row > div {
|
||||
height: 12px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
td.pct-row > div > div {
|
||||
height: 100%;
|
||||
}
|
||||
thead {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
@@ -350,12 +341,11 @@
|
||||
tr.nested-row table tr.main-row:hover {
|
||||
background-color: unset;
|
||||
}
|
||||
tr.main-row.has-children > td:first-child {
|
||||
white-space: pre;
|
||||
tr.main-row.has-children > td:first-child > p {
|
||||
display: inline-block;
|
||||
}
|
||||
tr.main-row.has-children > td:first-child::before {
|
||||
content: "▸ ";
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
margin-left: -0.25em;
|
||||
}
|
||||
|
||||
+17
-31
@@ -76,23 +76,19 @@ const drawGraph = (data) => {
|
||||
.attr("x", d => -d.width/2).attr("y", d => -d.height/2);
|
||||
const STROKE_WIDTH = 1.4;
|
||||
const labels = nodes.selectAll("g.label").data(d => [d]).join("g").attr("class", "label");
|
||||
const hasLabelDims = data.nodes[0]?.value.labelWidth != null;
|
||||
if (hasLabelDims) labels.attr("transform", d => `translate(-${d.labelWidth/2}, -${d.labelHeight/2+STROKE_WIDTH*2})`);
|
||||
labels.attr("transform", d => `translate(-${d.labelWidth/2}, -${d.labelHeight/2+STROKE_WIDTH*2})`);
|
||||
labels.selectAll("text").data(d => {
|
||||
if (Array.isArray(d.label)) return [d.label];
|
||||
const ret = [[]];
|
||||
for (const { st, color } of parseColors(d.label, defaultColor="initial")) {
|
||||
const lines = st.split("\n");
|
||||
for (const s of parseColors(d.label, defaultColor="initial")) {
|
||||
const color = darkenHex(s.color, 25);
|
||||
const lines = s.st.split("\n");
|
||||
ret.at(-1).push({ st:lines[0], color });
|
||||
for (let i=1; i<lines.length; i++) ret.push([{ st:lines[i], color }]);
|
||||
}
|
||||
return [ret];
|
||||
}).join("text").selectAll("tspan").data(d => d).join("tspan").attr("x", "0").attr("dy", 14).selectAll("tspan").data(d => d).join("tspan")
|
||||
.attr("fill", d => darkenHex(d.color, 25)).text(d => d.st).attr("xml:space", "preserve");
|
||||
// recenter after drawing texts if needed
|
||||
if (!hasLabelDims) labels.attr("transform", (_,i,els) => {
|
||||
const b = els[i].getBBox();
|
||||
return `translate(${-b.x-b.width/2}, ${-b.y-b.height/2})`
|
||||
});
|
||||
.attr("fill", d => d.color).text(d => d.st).attr("xml:space", "preserve").style("font-family", g.graph().font);
|
||||
addTags(nodes.selectAll("g.tag").data(d => d.tag != null ? [d] : []).join("g").attr("class", "tag")
|
||||
.attr("transform", d => `translate(${-d.width/2+8}, ${-d.height/2+8})`).datum(e => e.tag));
|
||||
// draw edges
|
||||
@@ -103,7 +99,7 @@ const drawGraph = (data) => {
|
||||
points.unshift(intersectRect(g.node(e.v), points[0]));
|
||||
points.push(intersectRect(g.node(e.w), points[points.length-1]));
|
||||
return line(points);
|
||||
}).attr("marker-end", "url(#arrowhead)");
|
||||
}).attr("marker-end", "url(#arrowhead)").attr("stroke", e => g.edge(e).color || "#4a4b57");
|
||||
}
|
||||
|
||||
// ** UOp graph
|
||||
@@ -114,12 +110,12 @@ async function initWorker() {
|
||||
workerUrl = URL.createObjectURL(new Blob([(await Promise.all(resp.map((r) => r.text()))).join("\n")], { type: "application/javascript" }));
|
||||
}
|
||||
|
||||
function renderDag(graph, additions, recenter, layoutOpts) {
|
||||
function renderDag(layoutSpec, { recenter }) {
|
||||
// start calculating the new layout (non-blocking)
|
||||
updateProgress(Status.STARTED, "Rendering new graph...");
|
||||
if (worker != null) worker.terminate();
|
||||
worker = new Worker(workerUrl);
|
||||
worker.postMessage({graph, additions, opts:layoutOpts });
|
||||
worker.postMessage(layoutSpec);
|
||||
worker.onmessage = (e) => {
|
||||
displaySelection("#graph");
|
||||
updateProgress(Status.COMPLETE);
|
||||
@@ -158,8 +154,7 @@ const formatUnit = (d, unit="") => d3.format(".3~s")(d)+unit;
|
||||
|
||||
const colorScheme = {TINY:new Map([["Schedule","#1b5745"],["get_program","#1d2e62"],["compile","#63b0cd"],["DEFAULT","#354f52"]]),
|
||||
DEFAULT:["#2b2e39", "#2c2f3a", "#31343f", "#323544", "#2d303a", "#2e313c", "#343746", "#353847", "#3c4050", "#404459", "#444862", "#4a4e65"],
|
||||
BUFFER:["#342483", "#3E2E94", "#4938A4", "#5442B4", "#5E4CC2", "#674FCA"], SE:new Map([["OCC", "#101725"], ["INST", "#0A2042"]]),
|
||||
CATEGORICAL:["#ff8080", "#F4A261", "#C8F9D4", "#8D99AE", "#F4A261", "#ffffa2", "#ffffc0", "#87CEEB"],}
|
||||
BUFFER:["#342483", "#3E2E94", "#4938A4", "#5442B4", "#5E4CC2", "#674FCA"], SE:new Map([["OCC", "#101725"], ["INST", "#0A2042"]]),}
|
||||
const cycleColors = (lst, i) => lst[i%lst.length];
|
||||
|
||||
const rescaleTrack = (source, tid, k) => {
|
||||
@@ -635,9 +630,6 @@ hljs.registerLanguage("cpp", (hljs) => ({
|
||||
...hljs.getLanguage('cpp'),
|
||||
contains: [{ begin: '\\b(?:float|half)[0-9]+\\b', className: 'type' }, ...hljs.getLanguage('cpp').contains]
|
||||
}));
|
||||
hljs.registerLanguage("amdgpu", (hljs) => ({
|
||||
contains: [hljs.COMMENT("//", "$"), { begin:/\b(?:s_|v_|global_|buffer_|scratch_|flat_|ds_)[a-z0-9_]*\b/, className:"code" }]
|
||||
}));
|
||||
|
||||
async function fetchValue(path) {
|
||||
const res = await fetch(path);
|
||||
@@ -799,23 +791,17 @@ async function main() {
|
||||
}
|
||||
const td = tr.append("td").classed(ret.cols[i], true);
|
||||
// string format scalar values
|
||||
if (!Array.isArray(value)) { td.text(typeof value === "string" ? value : ret.cols[i] === "Duration" ? formatMicroseconds(value) : formatUnit(value)); continue; }
|
||||
// display arrays in a bar graph
|
||||
td.classed("pct-row", true);
|
||||
const bar = td.append("div");
|
||||
value.forEach(([k, v, width]) => bar.append("div").style("width", width+"%").attr("title", `${ret.cols[i].labels[k]} ${v}`)
|
||||
.style("background", cycleColors(colorScheme.CATEGORICAL, parseInt(k))))
|
||||
td.append(() => typeof value === "string" ? colored(value) : d3.create("p").text(ret.cols[i] === "Duration" ? formatMicroseconds(value) : formatUnit(value)).node());
|
||||
}
|
||||
}
|
||||
return table;
|
||||
}
|
||||
if (ret.cols != null) {
|
||||
renderTable(root, ret);
|
||||
} else root.append(() => codeBlock(ret.src, ret.lang));
|
||||
if (ret.cols != null) renderTable(root, ret);
|
||||
else if (ret.data != null) renderDag(ret, { recenter:true });
|
||||
else if (ret.src != null) root.append(() => codeBlock(ret.src, ret.lang));
|
||||
ret.metadata?.forEach(m => {
|
||||
if (Array.isArray(m)) return metadata.appendChild(tabulate(m.map(({ label, value, idx }) => {
|
||||
const div = d3.create("div").style("background", cycleColors(colorScheme.CATEGORICAL, idx)).style("width", "100%").style("height", "100%");
|
||||
return [label.trim(), div.text(typeof value === "string" ? value : formatUnit(value)).node()];
|
||||
if (Array.isArray(m)) return metadata.appendChild(tabulate(m.map(({ label, value }) => {
|
||||
return [label.trim(), typeof value === "string" ? value : formatUnit(value)];
|
||||
})).node());
|
||||
metadata.appendChild(codeBlock(m.src)).classList.add("full-height")
|
||||
});
|
||||
@@ -842,7 +828,7 @@ async function main() {
|
||||
if (ret.length === 0) return;
|
||||
// ** center graph
|
||||
const data = ret[currentRewrite];
|
||||
const render = (opts) => renderDag(data.graph, data.changed_nodes ?? [], currentRewrite === 0, opts);
|
||||
const render = (opts) => renderDag({ data, opts }, { recenter:currentRewrite === 0 });
|
||||
render({ showIndexing:toggle.checked });
|
||||
toggle.onchange = (e) => render({ showIndexing:e.target.checked });
|
||||
// ** right sidebar metadata
|
||||
|
||||
+41
-13
@@ -1,27 +1,57 @@
|
||||
const NODE_PADDING = 10;
|
||||
const rectDims = (lw, lh) => ({ width:lw+NODE_PADDING*2, height:lh+NODE_PADDING*2, labelWidth:lw, labelHeight:lh });
|
||||
|
||||
const LINE_HEIGHT = 14;
|
||||
const canvas = new OffscreenCanvas(0, 0);
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.font = `350 ${LINE_HEIGHT}px sans-serif`;
|
||||
|
||||
onmessage = (e) => {
|
||||
const { graph, additions, opts } = e.data;
|
||||
const g = new dagre.graphlib.Graph({ compound: true });
|
||||
g.setGraph({ rankdir: "LR" }).setDefaultEdgeLabel(function() { return {}; });
|
||||
if (additions.length !== 0) g.setNode("addition", {label:"", labelWidth:0, labelHeight:0, className:"overlay"});
|
||||
for (let [k, {label, src, ref, ...rest }] of Object.entries(graph)) {
|
||||
const { data, opts } = e.data;
|
||||
const g = new dagre.graphlib.Graph({ compound: true }).setDefaultEdgeLabel(function() { return {}; });
|
||||
(data.blocks != null ? layoutCfg : layoutUOp)(g, data, opts);
|
||||
postMessage(dagre.graphlib.json.write(g));
|
||||
self.close();
|
||||
}
|
||||
|
||||
const layoutCfg = (g, { blocks, paths, pc_table, colors }) => {
|
||||
g.setGraph({ rankdir:"TD", font:"monospace" });
|
||||
ctx.font = `350 ${LINE_HEIGHT}px ${g.graph().font}`;
|
||||
// basic blocks render the assembly in nodes
|
||||
for (const [lead, members] of Object.entries(blocks)) {
|
||||
let [width, height, label] = [0, 0, []];
|
||||
for (const m of members) {
|
||||
const text = pc_table[m][0];
|
||||
width = Math.max(width, ctx.measureText(text).width);
|
||||
height += LINE_HEIGHT;
|
||||
const [inst, ...operands] = text.split(" ");
|
||||
label.push([{st:inst+" ", color:"#7aa2f7"}, {st:operands.join(" "), color:"#9aa5ce"}]);
|
||||
}
|
||||
g.setNode(lead, { ...rectDims(width, height), label, id:lead, color:"#1a1b26" });
|
||||
}
|
||||
// paths become edges between basic blocks
|
||||
for (const [lead, value] of Object.entries(paths)) {
|
||||
for (const [id, color] of Object.entries(value)) g.setEdge(lead, id, {label:{type:"port", text:""}, color:colors[color]});
|
||||
}
|
||||
dagre.layout(g);
|
||||
}
|
||||
|
||||
const layoutUOp = (g, { graph, change }, opts) => {
|
||||
g.setGraph({ rankdir: "LR", font:"sans-serif" });
|
||||
ctx.font = `350 ${LINE_HEIGHT}px ${g.graph().font}`;
|
||||
if (change?.length) g.setNode("overlay", {label:"", labelWidth:0, labelHeight:0, className:"overlay"});
|
||||
for (const [k, {label, src, ref, color, tag }] of Object.entries(graph)) {
|
||||
// adjust node dims by label size (excluding escape codes) + add padding
|
||||
let [width, height] = [0, 0];
|
||||
for (line of label.replace(/\u001B\[(?:K|.*?m)/g, "").split("\n")) {
|
||||
width = Math.max(width, ctx.measureText(line).width);
|
||||
height += LINE_HEIGHT;
|
||||
}
|
||||
g.setNode(k, {width:width+NODE_PADDING*2, height:height+NODE_PADDING*2, label, labelHeight:height, labelWidth:width, ref, id:k, ...rest});
|
||||
g.setNode(k, {...rectDims(width, height), label, ref, id:k, color, tag});
|
||||
// add edges
|
||||
const edgeCounts = {}
|
||||
const edgeCounts = {};
|
||||
for (const [_, s] of src) edgeCounts[s] = (edgeCounts[s] || 0)+1;
|
||||
for (const [port, s] of src) g.setEdge(s, k, { label: edgeCounts[s] > 1 ? {type:"tag", text:edgeCounts[s]} : {type:"port", text:port}});
|
||||
if (additions.includes(parseInt(k))) g.setParent(k, "addition");
|
||||
if (change?.includes(parseInt(k))) g.setParent(k, "overlay");
|
||||
}
|
||||
// optionally hide nodes from the layuot
|
||||
if (!opts.showIndexing) {
|
||||
@@ -31,8 +61,6 @@ onmessage = (e) => {
|
||||
}
|
||||
}
|
||||
dagre.layout(g);
|
||||
// remove additions overlay if it's empty
|
||||
if (!g.node("addition")?.width) g.removeNode("addition");
|
||||
postMessage(dagre.graphlib.json.write(g));
|
||||
self.close();
|
||||
// remove overlay node if it's empty
|
||||
if (!g.node("overlay")?.width) g.removeNode("overlay");
|
||||
}
|
||||
|
||||
+84
-44
@@ -6,7 +6,7 @@ from decimal import Decimal
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
from typing import Any, TypedDict, TypeVar, Generator, Callable
|
||||
from tinygrad.helpers import colored, getenv, tqdm, unwrap, word_wrap, TRACEMETA, ProfileEvent, ProfileRangeEvent, TracingKey, ProfilePointEvent, temp
|
||||
from tinygrad.helpers import printable, system, TCPServerWithReuse, HTTPRequestHandler
|
||||
from tinygrad.helpers import printable, TCPServerWithReuse, HTTPRequestHandler
|
||||
from tinygrad.uop.ops import TrackedGraphRewrite, RewriteTrace, UOp, Ops, GroupOp, srender, sint, sym_infer, range_str, pyrender
|
||||
from tinygrad.uop.ops import print_uops, range_start, multirange_str
|
||||
from tinygrad.device import ProfileDeviceEvent, ProfileGraphEvent, ProfileGraphEntry, Device, ProfileProgramEvent
|
||||
@@ -53,7 +53,7 @@ class GraphRewriteDetails(TypedDict):
|
||||
graph: dict # JSON serialized UOp for this rewrite step
|
||||
uop: str # strigified UOp for this rewrite step
|
||||
diff: list[str]|None # diff of the single UOp that changed
|
||||
changed_nodes: list[int]|None # the changed UOp id + all its parents ids
|
||||
change: list[int]|None # the new UOp id + all its parents ids
|
||||
upat: tuple[tuple[str, int], str]|None # [loc, source_code] of the matched UPat
|
||||
|
||||
def shape_to_str(s:tuple[sint, ...]): return "(" + ','.join(srender(x) for x in s) + ")"
|
||||
@@ -115,14 +115,14 @@ def _reconstruct(a:int):
|
||||
def get_full_rewrite(ctx:TrackedGraphRewrite) -> Generator[GraphRewriteDetails, None, None]:
|
||||
next_sink = _reconstruct(ctx.sink)
|
||||
# in the schedule graph we don't show indexing ops (unless it's in a kernel AST or rewriting dtypes.index sink)
|
||||
yield {"graph":uop_to_json(next_sink), "uop":pystr(next_sink), "changed_nodes":None, "diff":None, "upat":None}
|
||||
yield {"graph":uop_to_json(next_sink), "uop":pystr(next_sink), "change":None, "diff":None, "upat":None}
|
||||
replaces: dict[UOp, UOp] = {}
|
||||
for u0_num,u1_num,upat_loc,dur in tqdm(ctx.matches):
|
||||
replaces[u0:=_reconstruct(u0_num)] = u1 = _reconstruct(u1_num)
|
||||
try: new_sink = next_sink.substitute(replaces)
|
||||
except RuntimeError as e: new_sink = UOp(Ops.NOOP, arg=str(e))
|
||||
match_repr = f"# {dur*1e6:.2f} us\n"+printable(upat_loc)
|
||||
yield {"graph":(sink_json:=uop_to_json(new_sink)), "uop":pystr(new_sink), "changed_nodes":[id(x) for x in u1.toposort() if id(x) in sink_json],
|
||||
yield {"graph":(sink_json:=uop_to_json(new_sink)), "uop":pystr(new_sink), "change":[id(x) for x in u1.toposort() if id(x) in sink_json],
|
||||
"diff":list(difflib.unified_diff(pystr(u0).splitlines(), pystr(u1).splitlines())), "upat":(upat_loc, match_repr)}
|
||||
if not ctx.bottom_up: next_sink = new_sink
|
||||
|
||||
@@ -248,23 +248,25 @@ def load_counters(profile:list[ProfileEvent]) -> None:
|
||||
durations.setdefault(str(e.name), []).append(float(e.en-e.st))
|
||||
if isinstance(e, ProfileProgramEvent): prg_events[str(e.name)] = e
|
||||
if isinstance(e, ProfileDeviceEvent): dev_events[e.device] = e
|
||||
ctxs.append({"name":"All Counters", "steps":[create_step("PMC", ("/all-pmc", len(ctxs), 0), \
|
||||
(durations, {k:v[ProfilePMCEvent][0] for k,v in counter_events.items()}))]})
|
||||
if len(counter_events) == 0: return None
|
||||
ctxs.append({"name":"All Counters", "steps":[create_step("PMC", ("/all-pmc", len(ctxs), 0), (durations, all_counters:={}))]})
|
||||
run_number = {n:0 for n,_ in counter_events}
|
||||
for k,v in counter_events.items():
|
||||
prg = trace.keys[r].ret if (r:=ref_map.get(k[0])) else None
|
||||
name = prg.name if prg is not None else k[0]
|
||||
run_number[k[0]] += 1
|
||||
for (k, tag),v in counter_events.items():
|
||||
# use the colored name if it exists
|
||||
name = trace.keys[r].ret.name if (r:=ref_map.get(k)) is not None else k
|
||||
run_number[k] += 1
|
||||
steps:list[dict] = []
|
||||
if (pmc:=v.get(ProfilePMCEvent)): steps.append(create_step("PMC", ("/prg-pmc", len(ctxs), len(steps)), pmc))
|
||||
if (pmc:=v.get(ProfilePMCEvent)):
|
||||
steps.append(create_step("PMC", ("/prg-pmc", len(ctxs), len(steps)), pmc))
|
||||
all_counters[(name, run_number[k], k)] = pmc[0]
|
||||
if (sqtt:=v.get(ProfileSQTTEvent)):
|
||||
# to decode a SQTT trace, we need the raw stream, program binary and device properties
|
||||
steps.append(create_step("SQTT", ("/prg-sqtt", len(ctxs), len(steps)), (k, [*sqtt, prg_events[k[0]], dev_events[sqtt[0].device]])))
|
||||
steps.append(create_step("SQTT", ("/prg-sqtt", len(ctxs), len(steps)), ((k, tag), [*sqtt, prg_events[k], dev_events[sqtt[0].device]])))
|
||||
if getenv("SQTT_PARSE"):
|
||||
# run our decoder on startup, we don't use this since it only works on gfx11
|
||||
from extra.sqtt.attempt_sqtt_parse import parse_sqtt_print_packets
|
||||
for e in sqtt: parse_sqtt_print_packets(e.blob)
|
||||
ctxs.append({"name":f"Exec {name} n{run_number[k[0]]}", "steps":steps})
|
||||
ctxs.append({"name":f"Exec {name} n{run_number[k]}", "steps":steps})
|
||||
|
||||
# ** SQTT OCC only unpacks wave start, end time and SIMD location
|
||||
|
||||
@@ -337,26 +339,6 @@ def get_profile(profile:list[ProfileEvent], sort_fn:Callable[[str], Any]=device_
|
||||
|
||||
# ** Assembly static analyzers
|
||||
|
||||
def get_llvm_mca(asm:str, mtriple:str, mcpu:str) -> dict:
|
||||
target_args = f"-mtriple={mtriple} -mcpu={mcpu}"
|
||||
# disassembly output can include headers / metadata, skip if llvm-mca can't parse those lines
|
||||
data = json.loads(system("llvm-mca -skip-unsupported-instructions=parse-failure --json -"+target_args, input=asm.encode()))
|
||||
cr = data["CodeRegions"][0]
|
||||
resource_labels = [repr(x)[1:-1] for x in data["TargetInfo"]["Resources"]]
|
||||
rows:list = [[instr] for instr in cr["Instructions"]]
|
||||
# add scheduler estimates
|
||||
for info in cr["InstructionInfoView"]["InstructionList"]: rows[info["Instruction"]].append(info["Latency"])
|
||||
# map per instruction resource usage
|
||||
instr_usage:dict[int, dict[int, int]] = {}
|
||||
for d in cr["ResourcePressureView"]["ResourcePressureInfo"]:
|
||||
instr_usage.setdefault(i:=d["InstructionIndex"], {}).setdefault(r:=d["ResourceIndex"], 0)
|
||||
instr_usage[i][r] += d["ResourceUsage"]
|
||||
# last row is the usage summary
|
||||
summary = [{"idx":k, "label":resource_labels[k], "value":v} for k,v in instr_usage.pop(len(rows), {}).items()]
|
||||
max_usage = max([sum(v.values()) for i,v in instr_usage.items() if i<len(rows)], default=0)
|
||||
for i,usage in instr_usage.items(): rows[i].append([[k, v, (v/max_usage)*100] for k,v in usage.items()])
|
||||
return {"rows":rows, "cols":["Instruction", "Latency", {"title":"HW Resources", "labels":resource_labels}], "metadata":[summary]}
|
||||
|
||||
def get_stdout(f: Callable) -> str:
|
||||
buf = io.StringIO()
|
||||
try:
|
||||
@@ -376,6 +358,67 @@ def amd_readelf(lib:bytes) -> list[dict]:
|
||||
".group_segment_fixed_size":"LDS size", ".private_segment_fixed_size":"Scratch size"}
|
||||
return [{"label":label, "value":v} for k,label in keys.items() if (v:=notes["amdhsa.kernels"][0][k]) > 0]
|
||||
|
||||
def llvm_disasm(arch:str, lib:bytes) -> dict[int, tuple[str, int]]:
|
||||
from tinygrad.runtime.autogen import llvm
|
||||
from tinygrad.runtime.support.elf import elf_loader
|
||||
llvm.LLVMInitializeAMDGPUTargetInfo()
|
||||
llvm.LLVMInitializeAMDGPUTargetMC()
|
||||
llvm.LLVMInitializeAMDGPUAsmParser()
|
||||
llvm.LLVMInitializeAMDGPUDisassembler()
|
||||
# pass NULL to callbacks
|
||||
cbs = [ctypes.cast(0, llvm.LLVMCreateDisasmCPUFeatures.argtypes[i]) for i in {5,6}]
|
||||
ctx = llvm.LLVMCreateDisasmCPUFeatures("amdgcn-amd-amdhsa".encode(), arch.encode(), "".encode(), None, 0, *cbs)
|
||||
image, sections, _ = elf_loader(lib)
|
||||
text = next((sh.header for sh in sections if sh.name == ".text"), None)
|
||||
assert text is not None, "no .text section found in ELF"
|
||||
off, sz = text.sh_addr, text.sh_size
|
||||
addr_table:dict[int, tuple[str, int]] = {}
|
||||
out = ctypes.create_string_buffer(128)
|
||||
cur_off = off
|
||||
while cur_off < sz + off:
|
||||
view = (ctypes.c_ubyte * ((sz + off) - cur_off)).from_buffer_copy(memoryview(image)[cur_off:])
|
||||
instr_sz = llvm.LLVMDisasmInstruction(ctx, view, ctypes.c_uint64(len(view)), ctypes.c_uint64(0), out, ctypes.c_size_t(128))
|
||||
addr_table[cur_off] = (out.value.decode("utf-8", "replace").strip(), instr_sz)
|
||||
cur_off += instr_sz
|
||||
return addr_table
|
||||
|
||||
SOPP_INSTS = {"s_branch", "s_cbranch_scc0", "s_cbranch_scc1", "s_cbranch_vccz", "s_cbranch_vccnz", "s_cbranch_execz", "s_cbranch_execnz"}
|
||||
def parse_branch(asm:str) -> int|None:
|
||||
inst, *operands = asm.split(" ")
|
||||
if inst in SOPP_INSTS:
|
||||
x = int(operands[0]) & 0xffff
|
||||
return (x - 0x10000 if x & 0x8000 else x)*4
|
||||
return None
|
||||
|
||||
COND_TAKEN, COND_NOT_TAKEN, UNCOND = range(3)
|
||||
cfg_colors = {COND_TAKEN: "#3f7564", COND_NOT_TAKEN: "#7a4540", UNCOND: "#3b5f7e"}
|
||||
def amdgpu_cfg(lib:bytes, arch:str) -> dict:
|
||||
# disassemble
|
||||
pc_table = llvm_disasm(arch, lib)
|
||||
# get leaders
|
||||
leaders:set[int] = {next(iter(pc_table))}
|
||||
for pc, (asm, sz) in pc_table.items():
|
||||
if (offset:=parse_branch(asm)) is not None: leaders.update((pc+sz+offset, pc+sz))
|
||||
# build the cfg
|
||||
curr:int|None = None
|
||||
blocks:dict[int, list[int]] = {}
|
||||
paths:dict[int, dict[int, int]] = {}
|
||||
for pc, (asm, sz) in pc_table.items():
|
||||
if pc in leaders:
|
||||
paths[curr:=pc] = {}
|
||||
blocks[pc] = []
|
||||
else: assert curr is not None, f"no basic block found for {pc}"
|
||||
blocks[curr].append(pc)
|
||||
# control flow ends in endpgm
|
||||
if asm == "s_endpgm": break
|
||||
# otherwise a basic block can have exactly one or two paths
|
||||
nx = pc+sz
|
||||
if (offset:=parse_branch(asm)) is not None:
|
||||
if asm.startswith("s_branch"): paths[curr][nx+offset] = UNCOND
|
||||
else: paths[curr].update([(nx+offset, COND_TAKEN), (nx, COND_NOT_TAKEN)])
|
||||
elif nx in leaders: paths[curr][nx] = UNCOND
|
||||
return {"blocks":blocks, "paths":paths, "pc_table":pc_table, "colors":cfg_colors}
|
||||
|
||||
# ** Main render function to get the complete details about a trace event
|
||||
|
||||
def get_render(i:int, j:int, fmt:str) -> dict:
|
||||
@@ -386,22 +429,19 @@ def get_render(i:int, j:int, fmt:str) -> dict:
|
||||
if fmt == "asm":
|
||||
compiler = Device[data.device].compiler
|
||||
disasm_str = get_stdout(lambda: compiler.disassemble(compiler.compile(data.src)))
|
||||
from tinygrad.runtime.support.compiler_cpu import llvm, LLVMCompiler
|
||||
if isinstance(compiler, LLVMCompiler):
|
||||
return get_llvm_mca(disasm_str, ctypes.string_at(llvm.LLVMGetTargetMachineTriple(tm:=compiler.target_machine)).decode(),
|
||||
ctypes.string_at(llvm.LLVMGetTargetMachineCPU(tm)).decode())
|
||||
metadata:list = []
|
||||
ret:dict = {"src":disasm_str}
|
||||
if data.device.startswith("AMD"):
|
||||
with soft_err(lambda err: metadata.append(err)):
|
||||
metadata.append(amd_readelf(compiler.compile(data.src)))
|
||||
return {"src":disasm_str, "lang":"amdgpu" if data.device.startswith("AMD") else None, "metadata":metadata}
|
||||
with soft_err(lambda err: ret.update(err)):
|
||||
metadata = amd_readelf(lib:=compiler.compile(data.src))
|
||||
ret = {"data":amdgpu_cfg(lib, getattr(compiler, "arch")), "metadata":[metadata]}
|
||||
return ret
|
||||
if fmt == "all-pmc":
|
||||
durations, pmc = data
|
||||
ret:dict = {"cols":{}, "rows":[]}
|
||||
for (prg,_),events in pmc.items():
|
||||
ret = {"cols":{}, "rows":[]}
|
||||
for (name, n, k),events in data[1].items():
|
||||
pmc_table = unpack_pmc(events)
|
||||
ret["cols"].update([(r[0], None) for r in pmc_table["rows"]])
|
||||
ret["rows"].append((prg, durations[prg].pop(0), *[r[1] for r in pmc_table["rows"]]))
|
||||
ret["rows"].append((name, durations[k][n-1], *[r[1] for r in pmc_table["rows"]]))
|
||||
ret["cols"] = ["Kernel", "Duration", *ret["cols"]]
|
||||
return ret
|
||||
if fmt == "prg-pmc": return unpack_pmc(data[0])
|
||||
|
||||
Reference in New Issue
Block a user