forked from tinygrad/tinygrad
Compare commits
32
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcc7623025 | ||
|
|
8c987b3293 | ||
|
|
bf467c623d | ||
|
|
02353588cb | ||
|
|
712a5c651a | ||
|
|
9c9e337c78 | ||
|
|
57ad69160a | ||
|
|
c5b52e9321 | ||
|
|
31619774a9 | ||
|
|
2ea54d7337 | ||
|
|
b67345caa3 | ||
|
|
50e789e290 | ||
|
|
4b3fcb4064 | ||
|
|
67d0ba5bd8 | ||
|
|
4afa0b86bb | ||
|
|
ca28db5a97 | ||
|
|
c10e4c4e20 | ||
|
|
b518a7378a | ||
|
|
61884f2057 | ||
|
|
18db8fa311 | ||
|
|
799a637b03 | ||
|
|
fef97547f9 | ||
|
|
c30a113b2a | ||
|
|
1c62a3833b | ||
|
|
eb3c918c5b | ||
|
|
d762edd694 | ||
|
|
eeeea29171 | ||
|
|
9366a23eb0 | ||
|
|
4666df71c1 | ||
|
|
3d7c35d615 | ||
|
|
d1224a7c4a | ||
|
|
58c8991fa4 |
@@ -1,7 +1,7 @@
|
||||
name: Unit Tests
|
||||
env:
|
||||
# increment this when downloads substantially change to avoid the internet
|
||||
DOWNLOAD_CACHE_VERSION: '10'
|
||||
DOWNLOAD_CACHE_VERSION: '11'
|
||||
PYTHON_CACHE_VERSION: '2'
|
||||
CAPTURE_PROCESS_REPLAY: 1
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -46,6 +46,11 @@ jobs:
|
||||
with:
|
||||
deps: docs
|
||||
pydeps: "capstone"
|
||||
- name: Build wheel and show size
|
||||
run: |
|
||||
pip install build
|
||||
python -m build --wheel --outdir dist
|
||||
ls -lh dist/*.whl
|
||||
- name: Use as an external package
|
||||
run: |
|
||||
mkdir $HOME/test_external_dir
|
||||
|
||||
@@ -198,11 +198,7 @@ generate_amd() {
|
||||
clang2py -k cdefstum \
|
||||
extra/hip_gpu_driver/sdma_registers.h \
|
||||
extra/hip_gpu_driver/nvd.h \
|
||||
extra/hip_gpu_driver/kfd_pm4_headers_ai.h \
|
||||
extra/hip_gpu_driver/soc21_enum.h \
|
||||
extra/hip_gpu_driver/sdma_v6_0_0_pkt_open.h \
|
||||
extra/hip_gpu_driver/gc_11_0_0_offset.h \
|
||||
extra/hip_gpu_driver/gc_10_3_0_offset.h \
|
||||
extra/hip_gpu_driver/sienna_cichlid_ip_offset.h \
|
||||
--clang-args="-I/opt/rocm/include -x c++" \
|
||||
-o $BASE/amd_gpu.py
|
||||
@@ -376,26 +372,6 @@ generate_am() {
|
||||
-o $BASE/am/pm4_nv.py
|
||||
fixup $BASE/am/pm4_nv.py
|
||||
|
||||
clang2py -k cdefstum \
|
||||
$AMKERN_INC/vega10_enum.h \
|
||||
-o $BASE/am/vega10.py
|
||||
fixup $BASE/am/vega10.py
|
||||
|
||||
clang2py -k cdefstum \
|
||||
$AMKERN_INC/navi10_enum.h \
|
||||
-o $BASE/am/navi10.py
|
||||
fixup $BASE/am/navi10.py
|
||||
|
||||
clang2py -k cdefstum \
|
||||
$AMKERN_INC/soc21_enum.h \
|
||||
-o $BASE/am/soc21.py
|
||||
fixup $BASE/am/soc21.py
|
||||
|
||||
clang2py -k cdefstum \
|
||||
$AMKERN_INC/soc24_enum.h \
|
||||
-o $BASE/am/soc24.py
|
||||
fixup $BASE/am/soc24.py
|
||||
|
||||
clang2py -k cdefstum \
|
||||
extra/hip_gpu_driver/sdma_registers.h \
|
||||
$AMKERN_AMD/amdgpu/vega10_sdma_pkt_open.h \
|
||||
|
||||
@@ -29,8 +29,7 @@ if __name__ == "__main__":
|
||||
opt.zero_grad()
|
||||
samples = Tensor.randint(getenv("BS", 512), high=X_train.shape[0])
|
||||
loss = model(X_train[samples]).sparse_categorical_crossentropy(Y_train[samples]).backward()
|
||||
opt.step()
|
||||
return loss
|
||||
return loss.realize(*opt.schedule_step())
|
||||
|
||||
@TinyJit
|
||||
def get_test_acc() -> Tensor: return (model(X_test).argmax(axis=1) == Y_test).mean()*100
|
||||
|
||||
@@ -18,7 +18,7 @@ testing_minimal = [
|
||||
]
|
||||
|
||||
setup(name='tinygrad',
|
||||
version='0.10.3',
|
||||
version='0.11.0',
|
||||
description='You like pytorch? You like micrograd? You love tinygrad! <3',
|
||||
author='George Hotz',
|
||||
license='MIT',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import ctypes, time
|
||||
from test.mockgpu.gpu import VirtGPU
|
||||
from tinygrad.helpers import getbits, to_mv, init_c_struct_t
|
||||
import tinygrad.runtime.autogen.amd_gpu as amd_gpu, tinygrad.runtime.autogen.am.pm4_nv as pm4, tinygrad.runtime.autogen.am.soc21 as soc21
|
||||
import tinygrad.runtime.autogen.amd_gpu as amd_gpu, tinygrad.runtime.autogen.am.pm4_nv as pm4
|
||||
|
||||
SDMA_MAX_COPY_SIZE = 0x400000
|
||||
|
||||
@@ -14,6 +14,9 @@ regSQ_THREAD_TRACE_BUF0_SIZE = 0x39e9 + amd_gpu.GC_BASE__INST0_SEG1
|
||||
regSQ_THREAD_TRACE_WPTR = 0x39ef + amd_gpu.GC_BASE__INST0_SEG1
|
||||
regSQ_THREAD_TRACE_STATUS = 0x39f4 + amd_gpu.GC_BASE__INST0_SEG1
|
||||
|
||||
class SQTT_EVENTS:
|
||||
THREAD_TRACE_FINISH = 0x00000037
|
||||
|
||||
CACHE_FLUSH_AND_INV_TS_EVENT = 0x14
|
||||
|
||||
WAIT_REG_MEM_FUNCTION_ALWAYS = 0
|
||||
@@ -208,7 +211,7 @@ class PM4Executor(AMDQueue):
|
||||
assert n == 0
|
||||
event_dw = self._next_dword()
|
||||
match (event_dw & 0xFF): # event type
|
||||
case soc21.THREAD_TRACE_FINISH:
|
||||
case SQTT_EVENTS.THREAD_TRACE_FINISH:
|
||||
old_idx = self.gpu.regs.grbm_index
|
||||
for se in range(self.gpu.regs.n_se):
|
||||
self.gpu.regs.grbm_index = 0b011 << 29 | se << 16 # select se, broadcast sa and instance
|
||||
|
||||
@@ -117,6 +117,14 @@ class TestFuse(unittest.TestCase):
|
||||
c = (a.sum(axis=1) + b.sum(axis=1)).fuse()
|
||||
self.assertListEqual(c.tolist(), [30]*16)
|
||||
|
||||
@unittest.skipUnless(Device.DEFAULT == "METAL", "METAL TC")
|
||||
def test_fuse_and_tc_opt(self):
|
||||
A = Tensor.randn(8, 8).realize()
|
||||
B = Tensor.randn(8, 8).realize()
|
||||
C = Tensor.ones(1, 8, 8).pad(((1,1), None, None),).sum(0)
|
||||
out = (C + (A @ B)).fuse()
|
||||
out.realize()
|
||||
|
||||
class TestSoftmaxFusion(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
||||
@@ -106,6 +106,24 @@ class TestTiny(unittest.TestCase):
|
||||
probs = Tensor.rand(1, 1, 28, 28).sequential(layers).tolist()
|
||||
self.assertEqual(len(probs[0]), 10)
|
||||
|
||||
# TODO: this is failing because of how swizzling rewrites the ShapeTracker of the final STORE
|
||||
@unittest.skipIf(IMAGE>0 or (CI and Device.DEFAULT == "DSP"), "failing because of make things that can't be images not images")
|
||||
def test_mnist_backward(self):
|
||||
# NOTE: we don't have the whole model here for speed
|
||||
layers = [
|
||||
nn.Conv2d(1, 32, 5), Tensor.relu,
|
||||
nn.Conv2d(32, 32, 5), Tensor.relu]
|
||||
|
||||
# replace random weights with ones
|
||||
# TODO: there's a bug here where it's tying two of the biases together. we need UNIQUE const
|
||||
#Tensor.realize(*[p.replace(Tensor.ones_like(p).contiguous()) for p in nn.state.get_parameters(layers)])
|
||||
for p in nn.state.get_parameters(layers): p.replace(Tensor.empty(p.shape))
|
||||
|
||||
# realize gradients
|
||||
for x in nn.state.get_parameters(layers): x.requires_grad_()
|
||||
Tensor.empty(4, 1, 28, 28).sequential(layers).sum().backward()
|
||||
Tensor.realize(*[x.grad for x in nn.state.get_parameters(layers) if x.grad is not None])
|
||||
|
||||
# *** image ***
|
||||
|
||||
@unittest.skipIf(Device.DEFAULT != "GPU", "image only supported on GPU")
|
||||
|
||||
@@ -53,5 +53,13 @@ class TestCastConvenienceMethod(unittest.TestCase):
|
||||
self.assertEqual(t.float().dtype, dtypes.float)
|
||||
self.assertEqual(t.double().dtype, dtypes.double)
|
||||
|
||||
class TestDtypeTolist(unittest.TestCase):
|
||||
def test_bfloat16(self):
|
||||
self.assertEqual(Tensor([-60000, 1.5, 3.1, 60000], device="PYTHON", dtype=dtypes.bfloat16).tolist(), [-59904.0, 1.5, 3.09375, 59904.0])
|
||||
# 448
|
||||
self.assertEqual(Tensor([-30000, 1.5, 3.1, 30000], device="PYTHON", dtype=dtypes.fp8e4m3).tolist(), [-448.0, 1.5, 3.0, 448.0])
|
||||
# 57344
|
||||
self.assertEqual(Tensor([-30000, 1.5, 3.1, 30000], device="PYTHON", dtype=dtypes.fp8e5m2).tolist(), [-28672.0, 1.5, 3.0, 28672.0])
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
+12
-12
@@ -250,7 +250,7 @@ class TestVizProfiler(unittest.TestCase):
|
||||
|
||||
j = json.loads(get_profile(prof))
|
||||
|
||||
dev_events = j['layout']['NV']['timeline']['shapes']
|
||||
dev_events = j['layout']['NV']['shapes']
|
||||
self.assertEqual(len(dev_events), 1)
|
||||
event = dev_events[0]
|
||||
self.assertEqual(event['name'], 'E_2')
|
||||
@@ -263,7 +263,7 @@ class TestVizProfiler(unittest.TestCase):
|
||||
|
||||
j = json.loads(get_profile(prof))
|
||||
|
||||
event = j['layout']['NV']['timeline']['shapes'][0]
|
||||
event = j['layout']['NV']['shapes'][0]
|
||||
self.assertEqual(event['name'], 'COPYxx')
|
||||
self.assertEqual(event['st'], 900) # diff clock
|
||||
self.assertEqual(event['dur'], 10)
|
||||
@@ -278,23 +278,23 @@ class TestVizProfiler(unittest.TestCase):
|
||||
|
||||
j = json.loads(get_profile(prof))
|
||||
|
||||
devices = list(j['layout'])
|
||||
self.assertEqual(devices[0], 'NV Graph')
|
||||
self.assertEqual(devices[1], 'NV')
|
||||
self.assertEqual(devices[2], 'NV:1')
|
||||
tracks = list(j['layout'])
|
||||
self.assertEqual(tracks[0], 'NV Graph')
|
||||
self.assertEqual(tracks[2], 'NV')
|
||||
self.assertEqual(tracks[4], 'NV:1')
|
||||
|
||||
nv_events = j['layout']['NV']['timeline']['shapes']
|
||||
nv_events = j['layout']['NV']['shapes']
|
||||
self.assertEqual(nv_events[0]['name'], 'E_25_4n2')
|
||||
self.assertEqual(nv_events[0]['st'], 0)
|
||||
self.assertEqual(nv_events[0]['dur'], 2)
|
||||
#self.assertEqual(j['devEvents'][6]['pid'], j['devEvents'][0]['pid'])
|
||||
|
||||
nv1_events = j['layout']['NV:1']['timeline']['shapes']
|
||||
nv1_events = j['layout']['NV:1']['shapes']
|
||||
self.assertEqual(nv1_events[0]['name'], 'NV -> NV:1')
|
||||
self.assertEqual(nv1_events[0]['st'], 954)
|
||||
#self.assertEqual(j['devEvents'][7]['pid'], j['devEvents'][3]['pid'])
|
||||
|
||||
graph_events = j['layout']['NV Graph']['timeline']['shapes']
|
||||
graph_events = j['layout']['NV Graph']['shapes']
|
||||
self.assertEqual(graph_events[0]['st'], nv_events[0]['st'])
|
||||
self.assertEqual(graph_events[0]['st']+graph_events[0]['dur'], nv1_events[0]['st']+nv1_events[0]['dur'])
|
||||
|
||||
@@ -308,7 +308,7 @@ class TestVizMemoryLayout(BaseTestViz):
|
||||
a = _alloc(1)
|
||||
_b = _alloc(1)
|
||||
profile_ret = json.loads(get_profile(Buffer.profile_events))
|
||||
ret = profile_ret["layout"][a.device]["mem"]
|
||||
ret = profile_ret["layout"][f"{a.device} Memory"]
|
||||
self.assertEqual(ret["peak"], 2)
|
||||
self.assertEqual(ret["shapes"][0]["x"], [0, 2])
|
||||
self.assertEqual(ret["shapes"][1]["x"], [1, 2])
|
||||
@@ -318,7 +318,7 @@ class TestVizMemoryLayout(BaseTestViz):
|
||||
del a
|
||||
b = _alloc(1)
|
||||
profile_ret = json.loads(get_profile(Buffer.profile_events))
|
||||
ret = profile_ret["layout"][b.device]["mem"]
|
||||
ret = profile_ret["layout"][f"{b.device} Memory"]
|
||||
self.assertEqual(ret["peak"], 1)
|
||||
self.assertEqual(ret["shapes"][0]["x"], [0, 2])
|
||||
self.assertEqual(ret["shapes"][1]["x"], [2, 3])
|
||||
@@ -331,7 +331,7 @@ class TestVizMemoryLayout(BaseTestViz):
|
||||
del a
|
||||
c = _alloc(1)
|
||||
profile_ret = json.loads(get_profile(Buffer.profile_events))
|
||||
ret = profile_ret["layout"][c.device]["mem"]
|
||||
ret = profile_ret["layout"][f"{c.device} Memory"]
|
||||
self.assertEqual(ret["peak"], 2)
|
||||
self.assertEqual(ret["shapes"][0]["x"], [0, 3])
|
||||
self.assertEqual(ret["shapes"][1]["x"], [1, 3, 3, 4])
|
||||
|
||||
@@ -87,7 +87,7 @@ def _get_rewrites_for_renderer(opts:Renderer, linearizer:bool, _QUANTIZE, _DEVEC
|
||||
|
||||
# decompositions
|
||||
pm_decomp = symbolic_simple+get_late_rewrite_patterns(supported_ops, _TRANSCENDENTAL>=2)
|
||||
ret.append(RewriteStep(pm_decomp, name="decompositions"))
|
||||
ret.append(RewriteStep(pm_decomp, lambda _: opts.device, name="decompositions"))
|
||||
|
||||
# final rules for the renderer (without sym)
|
||||
pm_final_rewrite = pm_decomp+pm_render+extra_matcher
|
||||
|
||||
@@ -378,9 +378,9 @@ class Kernel:
|
||||
tensor_cores = self.opts.tensor_cores if tc_select == -1 else [self.opts.tensor_cores[tc_select]]
|
||||
for tc in tensor_cores:
|
||||
tensor_core_opts = [self._create_tc_opts(reduceop, tc, axis, opt_level) for reduceop in self.reduceops]
|
||||
if tensor_core_opts[0] is None: continue
|
||||
# can only fuse reduces with the same tc options
|
||||
assert all_same(tensor_core_opts)
|
||||
if tensor_core_opts[0] is None: continue
|
||||
self.tensor_core_opts = tc_opts = tensor_core_opts[0]
|
||||
|
||||
# attempt to pad the tensor axes that require it
|
||||
|
||||
@@ -5,7 +5,7 @@ from io import BufferedReader
|
||||
from tinygrad.nn.state import TensorIO
|
||||
from tinygrad.tensor import Tensor, _broadcast_shape, ReductionStr
|
||||
from tinygrad.helpers import getenv, DEBUG, all_same, prod, flatten, make_tuple, argsort, is_numpy_ndarray, get_single_element, polyN
|
||||
from tinygrad.dtype import DType, ConstType, dtypes, _from_np_dtype
|
||||
from tinygrad.dtype import DType, ConstType, dtypes, _from_np_dtype, truncate
|
||||
from tinygrad.device import is_dtype_supported, Device
|
||||
|
||||
# ***** protobuf definitions ******
|
||||
@@ -105,9 +105,7 @@ class PBBufferedReader(BufferedReader):
|
||||
def read_bytes(self) -> Tensor: return self.read_delimited(use_tensor=True)
|
||||
def read_float(self) -> float: return struct.unpack("<f", self.read(4))[0]
|
||||
def read_packed_floats(self) -> Tensor: return self.read_delimited(use_tensor=True)
|
||||
def read_int64(self) -> int:
|
||||
val = self.decode_varint()
|
||||
return val - 2**64 if val & (1 << 63) else val
|
||||
def read_int64(self) -> int: return truncate[dtypes.int64](self.decode_varint())
|
||||
def read_packed_int64s(self) -> list[int]:
|
||||
total_bytes_len = self.decode_varint()
|
||||
old_pos = self.tell()
|
||||
@@ -1074,7 +1072,7 @@ def get_onnx_ops() -> dict[str, types.FunctionType|dict[OpSetId, types.FunctionT
|
||||
if X.ndim == 4: X = X.permute(0, 2, 1, 3)
|
||||
elif X.ndim == 3:
|
||||
assert num_heads is not None, "num_heads must be provided for 3D input"
|
||||
X = X.reshape(*X.shape[:-1], num_heads, X.shape[-1] // num_heads)
|
||||
X = X.unflatten(-1, (num_heads, X.shape[-1] // num_heads))
|
||||
|
||||
head_size = cast(int, X.shape[-1])
|
||||
rot_dim = rotary_embedding_dim or head_size
|
||||
@@ -1085,16 +1083,10 @@ def get_onnx_ops() -> dict[str, types.FunctionType|dict[OpSetId, types.FunctionT
|
||||
cos = cos[..., :rot_dim//2].unsqueeze(2)
|
||||
sin = sin[..., :rot_dim//2].unsqueeze(2)
|
||||
|
||||
if interleaved:
|
||||
x1, x2 = x_rotate[..., ::2], x_rotate[..., 1::2]
|
||||
real = x1 * cos - x2 * sin
|
||||
imag = x1 * sin + x2 * cos
|
||||
x_rotated = Tensor.stack(real, imag, dim=-1).flatten(start_dim=-2)
|
||||
else:
|
||||
x1, x2 = x_rotate.chunk(2, dim=-1)
|
||||
real = x1 * cos - x2 * sin
|
||||
imag = x1 * sin + x2 * cos
|
||||
x_rotated = real.cat(imag, dim=-1)
|
||||
x1, x2 = (x_rotate[..., ::2], x_rotate[..., 1::2]) if interleaved else x_rotate.chunk(2, dim=-1)
|
||||
real = x1 * cos - x2 * sin
|
||||
imag = x1 * sin + x2 * cos
|
||||
x_rotated = real.stack(imag, dim=-1).flatten(start_dim=-2) if interleaved else real.cat(imag, dim=-1)
|
||||
|
||||
output = x_rotated.cat(x_pass, dim=-1)
|
||||
return output.flatten(start_dim=2) if len(original_input_shape) == 3 else output.permute(0, 2, 1, 3)
|
||||
|
||||
+2
-2
@@ -137,7 +137,7 @@ PICKLE_BUFFERS, PROFILE, LRU = ContextVar("PICKLE_BUFFERS", 1), ContextVar("PROF
|
||||
CACHELEVEL, IGNORE_BEAM_CACHE, DEVECTORIZE = ContextVar("CACHELEVEL", 2), ContextVar("IGNORE_BEAM_CACHE", 0), ContextVar("DEVECTORIZE", 1)
|
||||
DISABLE_COMPILER_CACHE = ContextVar("DISABLE_COMPILER_CACHE", 0)
|
||||
DONT_REALIZE_EXPAND, DONT_GROUP_REDUCES = ContextVar("DONT_REALIZE_EXPAND", 0), ContextVar("DONT_GROUP_REDUCES", 0)
|
||||
QUANTIZE, VALIDATE_WITH_CPU = ContextVar("QUANTIZE", 0), ContextVar("VALIDATE_WITH_CPU", 0)
|
||||
QUANTIZE, VALIDATE_WITH_CPU, DISABLE_FAST_IDIV = ContextVar("QUANTIZE", 0), ContextVar("VALIDATE_WITH_CPU", 0), ContextVar("DISABLE_FAST_IDIV", 0)
|
||||
CORRECT_DIVMOD_FOLDING, FUSE_OPTIM = ContextVar("CORRECT_DIVMOD_FOLDING", 0), ContextVar("FUSE_OPTIM", 0)
|
||||
ALLOW_DEVICE_USAGE, MAX_BUFFER_SIZE, AMD_LLVM = ContextVar("ALLOW_DEVICE_USAGE", 1), ContextVar("MAX_BUFFER_SIZE", 0), ContextVar("AMD_LLVM", 1)
|
||||
|
||||
@@ -293,7 +293,7 @@ def fetch(url:str, name:pathlib.Path|str|None=None, subdir:str|None=None, gunzip
|
||||
else: fp = _ensure_downloads_dir() / (subdir or "") / ((name or hashlib.md5(url.encode('utf-8')).hexdigest()) + (".gunzip" if gunzip else ""))
|
||||
if not fp.is_file() or not allow_caching:
|
||||
(_dir := fp.parent).mkdir(parents=True, exist_ok=True)
|
||||
with urllib.request.urlopen(urllib.request.Request(url, headers={"User-Agent": "tinygrad 0.10.3"}), timeout=10) as r:
|
||||
with urllib.request.urlopen(urllib.request.Request(url, headers={"User-Agent": "tinygrad 0.11.0"}), timeout=10) as r:
|
||||
assert r.status == 200, r.status
|
||||
length = int(r.headers.get('content-length', 0)) if not gunzip else None
|
||||
readfile = gzip.GzipFile(fileobj=r) if gunzip else r
|
||||
|
||||
@@ -63,8 +63,6 @@ extra_pm = PatternMatcher([
|
||||
# insert a PRECAST before BITCAST to force it to be rendered. not needed on all backends?
|
||||
(UPat(Ops.BITCAST, name="x"), lambda x: UOp(Ops.BITCAST, x.dtype, (UOp(Ops.PRECAST, x.src[0].dtype, x.src),))
|
||||
if x.src[0].op not in {Ops.PRECAST, Ops.LOAD, Ops.CUSTOM} else None),
|
||||
# rewrite MAX to CMPLT + WHERE (max function is annoying on many cstyle backends)
|
||||
(UPat(Ops.MAX, name="m"), lambda m: (m.src[0] < m.src[1]).where(m.src[1], m.src[0])),
|
||||
# devectorize any bools
|
||||
(UPat((*GroupOp.ALU, Ops.CAST, Ops.BITCAST, Ops.INDEX), dtype=dtypes.bool, name="alu"), no_vectorized_alu),
|
||||
# CAST (from bool) can't be vectorized
|
||||
|
||||
@@ -206,12 +206,13 @@ class AMDLLVMRenderer(LLVMRenderer):
|
||||
string_rewrite = PatternMatcher([
|
||||
(UPat(Ops.SPECIAL, name="x"), lambda ctx, x: f" {ctx[x]} = " + f"{ code_for_workitem[x.arg[0][0]](x.arg[0][-1])}; "),
|
||||
(UPat(Ops.BARRIER), lambda ctx: barrier),
|
||||
(UPat(Ops.CAST, name="x", dtype=dtypes.half.vec(16), src=UPat.var("y", dtypes.half.vec(8))), lambda ctx, x, y: f" {ctx[x]} = shufflevector "\
|
||||
f"<8 x half> {ctx[y]}, <8 x half> zeroinitializer, <16 x i32> <{', '.join([f'i32 {i}, i32 {j}' for i, j in zip(range(0, 8), range(8, 16))])}>"),
|
||||
(UPat(Ops.CAST, name="x", dtype=dtypes.half.vec(8), src=UPat.var("y", dtypes.half.vec(16))), lambda ctx, x, y:
|
||||
f" {ctx[x]}= shufflevector <16 x half> {ctx[y]}, <16 x half> undef, <8 x i32> <{', '.join([f'i32 {x}' for x in range(0, 16, 2)])}>"),
|
||||
]) + base_rewrite
|
||||
extra_matcher = LLVMRenderer.extra_matcher
|
||||
extra_matcher = LLVMRenderer.extra_matcher + PatternMatcher([
|
||||
(UPat(Ops.CAST, name="x", dtype=dtypes.half.vec(16), src=UPat.var("y", dtypes.half.vec(8))),
|
||||
lambda x, y: UOp(Ops.VECTORIZE, dtypes.half.vec(16), tuple(y.gep(i // 2) if i % 2 == 0 else UOp.const(dtypes.half, 0.0) for i in range(16)))),
|
||||
(UPat(Ops.CAST, name="x", dtype=dtypes.half.vec(8), src=UPat.var("y", dtypes.half.vec(16))),
|
||||
lambda x, y: UOp(Ops.VECTORIZE, dtypes.half.vec(8), tuple(y.gep(i * 2) for i in range(8)))),
|
||||
])
|
||||
def _render_footer(self, uops: list[UOp]) -> str:
|
||||
# TODO: this is copied from cstyle
|
||||
requiredMaxThreadsPerBlock = prod(u.arg[1] for u in uops if u.op is Ops.SPECIAL and u.arg[0][0] == "l")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+404
-67420
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@ from tinygrad.runtime.autogen.am import am
|
||||
from tinygrad.runtime.support.compiler_amd import HIPCompiler, AMDLLVMCompiler
|
||||
from tinygrad.runtime.support.elf import elf_loader
|
||||
from tinygrad.runtime.support.am.amdev import AMDev, AMMemoryManager
|
||||
from tinygrad.runtime.support.amd import AMDReg, AMDIP, import_module, setup_pci_bars
|
||||
from tinygrad.runtime.support.amd import AMDReg, AMDIP, import_module, import_soc, setup_pci_bars
|
||||
from tinygrad.runtime.support.system import System, PCIIfaceBase, PCIAllocationMeta, MAP_FIXED, MAP_NORESERVE
|
||||
from tinygrad.runtime.support.usb import ASM24Controller, USBMMIOInterface
|
||||
if getenv("IOCTL"): import extra.hip_gpu_driver.hip_ioctl # noqa: F401 # pylint: disable=unused-import
|
||||
@@ -751,7 +751,7 @@ class AMDDevice(HCQCompiled):
|
||||
debug_memory_size = round_up((self.max_cu_id + 1 if self.target >= (10,1,0) else 1) * (self.max_wave_id + 1) * 32, 64)
|
||||
if self.target[0] == 10: ctl_stack_size = min(ctl_stack_size, 0x7000)
|
||||
|
||||
self.soc = importlib.import_module(f"tinygrad.runtime.autogen.am.{({9: 'vega10', 10: 'navi10', 11: 'soc21', 12: 'soc24'}[self.target[0]])}")
|
||||
self.soc = import_soc(self.target)
|
||||
self.pm4 = importlib.import_module(f"tinygrad.runtime.autogen.am.pm4_{'nv' if self.target[0] >= 10 else 'soc15'}")
|
||||
self.sdma = import_module('sdma', min(self.iface.ip_versions[am.SDMA0_HWIP], (6, 0, 0)))
|
||||
self.gc = AMDIP('gc', self.iface.ip_versions[am.GC_HWIP], self.iface.ip_offsets[am.GC_HWIP])
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
from tinygrad.device import Compiled, Compiler, Renderer, Allocator
|
||||
from tinygrad.uop.ops import Ops
|
||||
from tinygrad.device import Compiled, Compiler, Allocator
|
||||
from tinygrad.engine.jit import MultiGraphRunner
|
||||
from tinygrad.renderer.cstyle import CStyleLanguage
|
||||
from tinygrad.uop.ops import Ops
|
||||
|
||||
class NullRenderer(Renderer):
|
||||
class NullRenderer(CStyleLanguage):
|
||||
device = "NULL"
|
||||
code_for_op = {k:lambda:None for k in [Ops.EXP2, Ops.LOG2, Ops.SIN, Ops.SQRT]}
|
||||
has_local = False
|
||||
def render(self, uops:list) -> str: return ""
|
||||
float4 = "float4"
|
||||
code_for_op = {**CStyleLanguage.code_for_op, Ops.THREEFRY: lambda a,b,dtype: f"threefry({a},{b})", Ops.MAX: lambda a,b,dtype: f"max({a},{b})"}
|
||||
|
||||
class NullProgram:
|
||||
def __init__(self, name:str, lib:bytes): pass
|
||||
|
||||
@@ -29,7 +29,7 @@ class AMFirmware:
|
||||
# Load SOS firmware
|
||||
self.sos_fw = {}
|
||||
|
||||
blob, sos_hdr = self.load_fw(f"psp_{fmt_ver(am.MP0_HWIP)}_sos.bin", am.struct_psp_firmware_header_v2_0)
|
||||
blob, sos_hdr = self.load_fw(f"psp_{fmt_ver(am.MP0_HWIP)}_sos.bin", versioned_header='struct_psp_firmware_header')
|
||||
fw_bin = sos_hdr.psp_fw_bin
|
||||
|
||||
for fw_i in range(sos_hdr.psp_fw_bin_count):
|
||||
@@ -45,11 +45,11 @@ class AMFirmware:
|
||||
self.smu_psp_desc = self.desc(blob, hdr.header.ucode_array_offset_bytes, hdr.header.ucode_size_bytes, am.GFX_FW_TYPE_SMU)
|
||||
|
||||
# SDMA firmware
|
||||
blob, hdr, hdr_v3 = self.load_fw(f"sdma_{fmt_ver(am.SDMA0_HWIP)}.bin", am.struct_sdma_firmware_header_v2_0, am.struct_sdma_firmware_header_v3_0)
|
||||
blob, hdr = self.load_fw(f"sdma_{fmt_ver(am.SDMA0_HWIP)}.bin", versioned_header='struct_sdma_firmware_header')
|
||||
if hdr.header.header_version_major < 3:
|
||||
self.descs += [self.desc(blob, hdr.ctl_ucode_offset, hdr.ctl_ucode_size_bytes, am.GFX_FW_TYPE_SDMA_UCODE_TH1)]
|
||||
self.descs += [self.desc(blob, hdr.header.ucode_array_offset_bytes, hdr.ctx_ucode_size_bytes, am.GFX_FW_TYPE_SDMA_UCODE_TH0)]
|
||||
else: self.descs += [self.desc(blob, hdr_v3.header.ucode_array_offset_bytes, hdr_v3.ucode_size_bytes, am.GFX_FW_TYPE_SDMA_UCODE_TH0)]
|
||||
else: self.descs += [self.desc(blob, hdr.header.ucode_array_offset_bytes, hdr.ucode_size_bytes, am.GFX_FW_TYPE_SDMA_UCODE_TH0)]
|
||||
|
||||
# PFP, ME, MEC firmware
|
||||
for (fw_name, fw_cnt) in ([('PFP', 1), ('ME', 1)] if self.adev.ip_ver[am.GC_HWIP] >= (12,0,0) else []) + [('MEC', 1)]:
|
||||
@@ -83,10 +83,13 @@ class AMFirmware:
|
||||
|
||||
self.descs += [self.desc(blob, hdr0.header.ucode_array_offset_bytes, hdr0.header.ucode_size_bytes, am.GFX_FW_TYPE_RLC_G)]
|
||||
|
||||
def load_fw(self, fname:str, *headers):
|
||||
def load_fw(self, fname:str, *headers, versioned_header:str|None=None):
|
||||
fpath = fetch(f"https://gitlab.com/kernel-firmware/linux-firmware/-/raw/45f59212aebd226c7630aff4b58598967c0c8c91/amdgpu/{fname}", subdir="fw")
|
||||
blob = memoryview(bytearray(fpath.read_bytes()))
|
||||
if AM_DEBUG >= 1: print(f"am {self.adev.devfmt}: loading firmware {fname}: {hashlib.sha256(blob).hexdigest()}")
|
||||
if versioned_header:
|
||||
chdr = am.struct_common_firmware_header.from_address(mv_address(blob))
|
||||
headers += (getattr(am, versioned_header + f"_v{chdr.header_version_major}_{chdr.header_version_minor}"),)
|
||||
return tuple([blob] + [hdr.from_address(mv_address(blob)) for hdr in headers])
|
||||
|
||||
def desc(self, blob:memoryview, offset:int, size:int, *types:int) -> tuple[list[int], memoryview]: return (list(types), blob[offset:offset+size])
|
||||
@@ -223,7 +226,7 @@ class AMDev(PCIDevImplBase):
|
||||
|
||||
self.bhdr = am.struct_binary_header.from_buffer(bytearray(self.vram.view(tmr_offset, tmr_size)[:]))
|
||||
ihdr = am.struct_ip_discovery_header.from_address(ctypes.addressof(self.bhdr) + self.bhdr.table_list[am.IP_DISCOVERY].offset)
|
||||
assert ihdr.signature == am.DISCOVERY_TABLE_SIGNATURE and not ihdr.base_addr_64_bit, f"0x{ihdr.signature:X} != 0x{am.DISCOVERY_TABLE_SIGNATURE:X}"
|
||||
assert self.bhdr.binary_signature == am.BINARY_SIGNATURE and ihdr.signature == am.DISCOVERY_TABLE_SIGNATURE, "discovery signatures mismatch"
|
||||
|
||||
# Mapping of HW IP to Discovery HW IP
|
||||
hw_id_map = {am.__dict__[x]: int(y) for x,y in am.hw_id_map}
|
||||
@@ -256,4 +259,3 @@ class AMDev(PCIDevImplBase):
|
||||
for prefix, hwip in mods:
|
||||
self.__dict__.update(import_asic_regs(prefix, self.ip_ver[hwip], cls=functools.partial(AMRegister, adev=self, bases=self.regs_offset[hwip])))
|
||||
self.__dict__.update(import_asic_regs('mp', (11, 0), cls=functools.partial(AMRegister, adev=self, bases=self.regs_offset[am.MP1_HWIP])))
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import ctypes, time, contextlib, importlib, functools
|
||||
import ctypes, time, contextlib, functools
|
||||
from typing import Literal
|
||||
from tinygrad.runtime.autogen.am import am
|
||||
from tinygrad.helpers import to_mv, data64, lo32, hi32, DEBUG, wait_cond
|
||||
from tinygrad.runtime.autogen.am import am
|
||||
from tinygrad.runtime.support.amd import import_soc
|
||||
|
||||
class AM_IP:
|
||||
def __init__(self, adev): self.adev = adev
|
||||
@@ -11,9 +12,7 @@ class AM_IP:
|
||||
def set_clockgating_state(self): pass # Set clockgating state for this IP
|
||||
|
||||
class AM_SOC(AM_IP):
|
||||
def init_sw(self):
|
||||
self.soc_ver = 24 if self.adev.ip_ver[am.GC_HWIP] >= (12,0,0) else 21
|
||||
self.module = importlib.import_module(f"tinygrad.runtime.autogen.am.soc{self.soc_ver}")
|
||||
def init_sw(self): self.module = import_soc(self.adev.ip_ver[am.GC_HWIP])
|
||||
|
||||
def init_hw(self):
|
||||
self.adev.regRCC_DEV0_EPF2_STRAP2.update(strap_no_soft_reset_dev0_f2=0x0)
|
||||
|
||||
@@ -43,12 +43,22 @@ def fixup_ip_version(ip:str, version:tuple[int, ...]) -> list[tuple[int, ...]]:
|
||||
|
||||
return [version, version[:2], version[:2]+(0,), version[:1]+(0, 0)]
|
||||
|
||||
def header_download(file, name=None, subdir="defines") -> str:
|
||||
url = "https://gitlab.com/linux-kernel/linux-next/-/raw/cf6d949a409e09539477d32dbe7c954e4852e744/drivers/gpu/drm/amd"
|
||||
return fetch(f"{url}/{file}", name=name, subdir=subdir).read_text()
|
||||
|
||||
def import_header(path:str):
|
||||
t = re.sub(r'//.*|/\*.*?\*/','', header_download(path, subdir="defines"), flags=re.S)
|
||||
return {k:int(v,0) for k,v in re.findall(r'\b([A-Za-z_]\w*)\s*=\s*(0x[0-9A-Fa-f]+|\d+)', t)}
|
||||
|
||||
def import_module(name:str, version:tuple[int, ...], version_prefix:str=""):
|
||||
for ver in fixup_ip_version(name, version):
|
||||
try: return importlib.import_module(f"tinygrad.runtime.autogen.am.{name}_{version_prefix}{'_'.join(map(str, ver))}")
|
||||
except ImportError: pass
|
||||
raise ImportError(f"Failed to load autogen module for {name.upper()} {'.'.join(map(str, version))}")
|
||||
|
||||
def import_soc(ip): return type("SOC", (object,), import_header(f"include/{({9: 'vega10', 10: 'navi10', 11: 'soc21', 12: 'soc24'}[ip[0]])}_enum.h"))
|
||||
|
||||
def import_asic_regs(prefix:str, version:tuple[int, ...], cls=AMDReg) -> dict[str, AMDReg]:
|
||||
def _split_name(name): return name[:(pos:=next((i for i,c in enumerate(name) if c.isupper()), len(name)))], name[pos:]
|
||||
def _extract_regs(txt):
|
||||
@@ -56,8 +66,7 @@ def import_asic_regs(prefix:str, version:tuple[int, ...], cls=AMDReg) -> dict[st
|
||||
def _download_file(ver, suff) -> str:
|
||||
dir_prefix = {"osssys": "oss"}.get(prefix, prefix)
|
||||
fetch_name, file_name = f"{prefix}_{'_'.join(map(str, ver))}_{suff}.h", f"{prefix}_{'_'.join(map(str, version))}_{suff}.h"
|
||||
url = "https://gitlab.com/linux-kernel/linux-next/-/raw/cf6d949a409e09539477d32dbe7c954e4852e744/drivers/gpu/drm/amd/include/asic_reg"
|
||||
return fetch(f"{url}/{dir_prefix}/{fetch_name}", name=file_name, subdir="asic_regs").read_text()
|
||||
return header_download(f"include/asic_reg/{dir_prefix}/{fetch_name}", name=file_name, subdir="asic_regs")
|
||||
|
||||
for ver in fixup_ip_version(prefix, version):
|
||||
try: offs, sh_masks = _extract_regs(_download_file(ver, "offset")), _extract_regs(_download_file(ver, "sh_mask"))
|
||||
|
||||
@@ -13,6 +13,10 @@ def handle_allreduce_multirank(buf:UOp, red:UOp) -> UOp|None:
|
||||
for i,dev in enumerate(buf.device):
|
||||
groups.setdefault(Device[dev].group_id, []).append(buf.mselect(i))
|
||||
|
||||
# Put reduce leader of each group first
|
||||
reduce_leaders = set(getenv("REDUCE_LEADERS", "").split(","))
|
||||
groups = {gid: sorted(bufs, key=lambda x: (x.device not in reduce_leaders, x.device)) for gid,bufs in groups.items()}
|
||||
|
||||
# Skip if only one group or if every group has only one buffer
|
||||
if len(groups) <= 1 or not any(len(g) > 1 for g in groups.values()): return None
|
||||
|
||||
|
||||
+13
-10
@@ -252,7 +252,7 @@ class Tensor(MathTrait):
|
||||
# create the schedule
|
||||
schedule, var_vals = create_schedule_with_vars(sink)
|
||||
schedule = memory_planner(schedule)
|
||||
if DEBUG >= 1 and len(schedule) >= 10: print(f"scheduled {len(schedule)} kernels in {(time.perf_counter()-st)*1000:.2f} ms")
|
||||
if DEBUG >= 1 and len(schedule) > 1: print(f"scheduled {len(schedule)} kernels in {(time.perf_counter()-st)*1000:.2f} ms")
|
||||
return schedule, var_vals
|
||||
|
||||
def schedule(self, *lst:Tensor) -> list[ScheduleItem]:
|
||||
@@ -345,6 +345,7 @@ class Tensor(MathTrait):
|
||||
print(t.tolist())
|
||||
```
|
||||
"""
|
||||
if self.dtype in (dtypes.bfloat16, *dtypes.fp8s): return self.cast(dtypes.float32).tolist()
|
||||
return self.data().tolist()
|
||||
|
||||
def numpy(self) -> 'np.ndarray': # type: ignore [name-defined] # noqa: F821
|
||||
@@ -1127,11 +1128,12 @@ class Tensor(MathTrait):
|
||||
if (isinstance(indices, list) and all_int(indices)) or not isinstance(indices, (tuple, list)): indices = [indices]
|
||||
x, indices = self, list(indices)
|
||||
|
||||
# filter ellipsis and fill with slice(None) or fill rest of indices with slice(None)
|
||||
# fill ellipsis or rest of indices with slice(None)
|
||||
if len(ellipsis_idx := [dim for dim, i in enumerate(indices) if i is Ellipsis]) > 1: raise IndexError("indices can only have a single ellipsis")
|
||||
fill_idx = ellipsis_idx[0] if ellipsis_idx else len(indices)
|
||||
# NOTE: None adds a dim later
|
||||
num_indices = len(indices) - len(ellipsis_idx) - sum(1 for i in indices if i is None)
|
||||
if num_indices > self.ndim: raise IndexError(f"too many {num_indices=} for {self.ndim=}")
|
||||
fill_idx = ellipsis_idx[0] if ellipsis_idx else len(indices)
|
||||
indices[fill_idx:fill_idx+1] = [slice(None)] * (self.ndim - num_indices)
|
||||
|
||||
indices_parsed, dim = [], 0
|
||||
@@ -1147,6 +1149,7 @@ class Tensor(MathTrait):
|
||||
index = Tensor([i+size if i<0 else i for i in fully_flatten(index)], self.device, requires_grad=False).reshape(ti.shape)
|
||||
case int() | UOp(): # sint
|
||||
if index >= size or index < -size: raise IndexError(f"{index=} is out of bounds with {size=}")
|
||||
# TODO: is this right for (negative) symbolic?
|
||||
boundary = [index, index+1] if index >= 0 else [index+size, index+size+1]
|
||||
case slice():
|
||||
if index.step == 0: raise ValueError(f"{index=} cannot have 0 as step")
|
||||
@@ -1160,9 +1163,9 @@ class Tensor(MathTrait):
|
||||
elif stride < 0: boundary = [boundary[1] + 1, boundary[0] + 1]
|
||||
# update size for slice
|
||||
size = ceildiv((boundary[1] - boundary[0]), abs(stride))
|
||||
elif (step == 1) and isinstance(step, int) and all(isinstance(s,(int,UOp)) for s in (start, stop)) and resolve((stop-start) > 0, False):
|
||||
elif resolve(step == 1, False) and all(isinstance(s,sint) for s in (start, stop)) and resolve((stop-start) > 0, False):
|
||||
# simple symbolic slice
|
||||
size = cast(UOp|int, cast(UOp, (stop - start)).ssimplify())
|
||||
size = cast(sint, cast(UOp, (stop - start)).ssimplify())
|
||||
else: raise TypeError(f"slice {index=} is not supported")
|
||||
case None: pass # do nothing
|
||||
case _: raise IndexError(f"{type(index).__name__} indexing is not supported")
|
||||
@@ -1174,9 +1177,9 @@ class Tensor(MathTrait):
|
||||
# flip negative strides
|
||||
shrinks, strides = zip(*((i['boundary'], i['stride']) for i in mops))
|
||||
x = x.shrink(shrinks).flip(tuple(i for i,st in enumerate(strides) if st < 0))
|
||||
# handle stride != 1 or -1
|
||||
if any(abs(st) != 1 for st in strides):
|
||||
strides = tuple(abs(s) for s in strides)
|
||||
strides = tuple(map(abs, strides))
|
||||
# apply stride
|
||||
if any(st != 1 for st in strides):
|
||||
# pad shape to multiple of stride
|
||||
if not all_int(x.shape): raise RuntimeError("symbolic shape not supported")
|
||||
x = x.pad(tuple((0, round_up(s, st) - s) for s, st in zip(x.shape, strides)))
|
||||
@@ -2680,7 +2683,7 @@ class Tensor(MathTrait):
|
||||
print(t.triu(diagonal=-1).numpy())
|
||||
```
|
||||
"""
|
||||
return Tensor._tri(self.shape[-2], self.shape[-1], diagonal=diagonal, device=self.device, dtype=dtypes.bool).where(self, 0).cast(self.dtype)
|
||||
return Tensor._tri(self.shape[-2], self.shape[-1], diagonal=diagonal, device=self.device, dtype=dtypes.bool).where(self, self.zeros_like())
|
||||
|
||||
def tril(self, diagonal:int=0) -> Tensor:
|
||||
"""
|
||||
@@ -2703,7 +2706,7 @@ class Tensor(MathTrait):
|
||||
print(t.tril(diagonal=-1).numpy())
|
||||
```
|
||||
"""
|
||||
return Tensor._tri(self.shape[-2], self.shape[-1], diagonal=diagonal+1, device=self.device, dtype=dtypes.bool).where(0, self).cast(self.dtype)
|
||||
return Tensor._tri(self.shape[-2], self.shape[-1], diagonal=diagonal+1, device=self.device, dtype=dtypes.bool).where(self.zeros_like(), self)
|
||||
|
||||
def interpolate(self, size:tuple[int, ...], mode:str="linear", align_corners:bool=False) -> Tensor:
|
||||
"""
|
||||
|
||||
@@ -9,14 +9,17 @@ class FastEnum(IntEnum):
|
||||
# the order of these Ops controls the order of the toposort
|
||||
class Ops(FastEnum):
|
||||
# uops that aren't rendered
|
||||
NOOP = auto(); SINK = auto(); UNIQUE = auto(); DEVICE = auto(); KERNEL = auto(); PRECAST = auto() # noqa: E702
|
||||
NOOP = auto(); SINK = auto(); UNIQUE = auto(); DEVICE = auto(); KERNEL = auto(); PRECAST = auto(); REWRITE_ERROR = auto() # noqa: E702
|
||||
|
||||
# track children
|
||||
CHILD = auto()
|
||||
CHILD = auto(); CHILDREN = auto() # noqa: E702
|
||||
|
||||
# buffer ops
|
||||
COPY = auto(); BUFFER = auto(); BUFFER_VIEW = auto(); MSELECT = auto(); MSTACK = auto() # noqa: E702
|
||||
|
||||
# create buffer
|
||||
BUFFERIZE = auto()
|
||||
|
||||
# ops that adjust the behavior of the scheduler
|
||||
CONTIGUOUS = auto(); CONTIGUOUS_BACKWARD = auto(); DETACH = auto(); FUSE = auto() # noqa: E702
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ from typing import Callable
|
||||
import math, functools
|
||||
from tinygrad.dtype import dtypes, DType, promo_lattice
|
||||
from tinygrad.device import is_dtype_supported
|
||||
from tinygrad.helpers import polyN, getenv
|
||||
from tinygrad.helpers import polyN, DISABLE_FAST_IDIV
|
||||
from tinygrad.uop.ops import UOp, UPat, Ops, PatternMatcher
|
||||
|
||||
TRANSCENDENTAL_SUPPORTED_DTYPES = (dtypes.float16, dtypes.float32, dtypes.float64)
|
||||
@@ -317,8 +317,10 @@ powers_of_two = {2**i:i for i in range(64)}
|
||||
def get_late_rewrite_patterns(ops:tuple[Ops, ...], force_transcendental=False):
|
||||
pat: list[tuple[UPat, Callable]] = [(UPat(op, dtype=TRANSCENDENTAL_SUPPORTED_DTYPES, src=(UPat.var("d"),)), f) for op,f in \
|
||||
((Ops.EXP2, xexp2), (Ops.LOG2, xlog2), (Ops.SIN, xsin)) if op not in ops or force_transcendental]
|
||||
# no real hardware supports THREEFRY
|
||||
pat.append((UPat(Ops.THREEFRY, dtype=dtypes.uint64, src=(UPat.var("x"), UPat.var("key"))), threefry2x32))
|
||||
# no real hardware supports THREEFRY, but NullRenderer does
|
||||
if Ops.THREEFRY not in ops: pat.append((UPat(Ops.THREEFRY, dtype=dtypes.uint64, src=(UPat.var("x"), UPat.var("key"))), threefry2x32))
|
||||
# MAX can be rewritten as CMPLT + WHERE (max function is annoying on many cstyle backends)
|
||||
if Ops.MAX not in ops and Ops.CMPLT in ops: pat.append((UPat(Ops.MAX, name="m"), lambda m: (m.src[0] < m.src[1]).where(m.src[1], m.src[0])))
|
||||
# rewrite SQRT to xpow 0.5
|
||||
if Ops.SQRT not in ops: pat.append((UPat(Ops.SQRT, src=UPat.var("d")), lambda d: xpow(d, d.const_like(0.5))))
|
||||
# rewrite MOD to AND (which should always be supported, but not for generic in tests): x % (2**y) -> x & (2**y-1)
|
||||
@@ -330,7 +332,7 @@ def get_late_rewrite_patterns(ops:tuple[Ops, ...], force_transcendental=False):
|
||||
pat += [(UPat.var("x", dtypes.uints)//UPat.cvar("c"), lambda x,c: x >> v if (v:=powers_of_two.get(c.arg, 0)) else None)]
|
||||
pat += [(UPat.var("x", dtypes.ints)//UPat.cvar("c"), lambda x,c: (x+(l.const_like(l.vmin) if (l:=(x<0)).vmin==l.vmax else l).where(
|
||||
c-1, 0)) >> v if (v:=powers_of_two.get(c.arg, 0)) else None)] # (x+(x<0).where(c-1, 0)) >> v
|
||||
if not getenv("DISABLE_FAST_IDIV"):
|
||||
if not DISABLE_FAST_IDIV:
|
||||
pat += [(UPat.var("x", dtypes.ints)//UPat.cvar("d"), lambda ctx, x, d: fast_idiv(ctx, x, d.arg))]
|
||||
pat += [(UPat.var("x", dtypes.ints)%UPat.var("d"), lambda x, d: x-d*(x//d))]
|
||||
if Ops.NEG in ops:
|
||||
|
||||
+15
-2
@@ -140,6 +140,8 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
|
||||
from tinygrad.shape.shapetracker import ShapeTracker
|
||||
# VIEW and MovementOps define a new ShapeTracker from the arg
|
||||
if self.op is Ops.VIEW: return self.arg
|
||||
# allow reshape from nothing
|
||||
if self.op is Ops.RESHAPE and self.src[0].st is None: return ShapeTracker.from_shape(self.arg)
|
||||
if self.op in GroupOp.Movement: return unwrap(self.src[0].st).mop(self.op, self.arg)
|
||||
# CONST with a DEVICE has a shape of ()
|
||||
if self.op is Ops.CONST and len(self.src) and self.src[0].op is Ops.DEVICE: return ShapeTracker.from_shape(())
|
||||
@@ -186,10 +188,15 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
|
||||
@functools.cached_property
|
||||
def ranges(self) -> dict[UOp, None]:
|
||||
if self.op is Ops.RANGE: return {self:None}
|
||||
if self.op in {Ops.CONTIGUOUS, Ops.REDUCE, Ops.STORE}:
|
||||
if self.op in {Ops.BUFFERIZE, Ops.REDUCE}:
|
||||
ret = self.src[0].ranges.copy()
|
||||
for s in self.src[1:]:
|
||||
if s in ret: del ret[s]
|
||||
elif self.op in {Ops.STORE}:
|
||||
ret = self.src[0].ranges.copy()
|
||||
ret.update(self.src[1].ranges)
|
||||
for s in self.src[2:]:
|
||||
if s in ret: del ret[s]
|
||||
else:
|
||||
ret = {}
|
||||
for s in self.src: ret.update(s.ranges)
|
||||
@@ -293,6 +300,7 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
|
||||
def reduce(self, *src:UOp, **kwargs): return UOp(Ops.REDUCE, kwargs.pop('dtype', self.dtype), src=(self,)+src, **kwargs)
|
||||
def contiguous(self, *args, **kwargs): return UOp(Ops.CONTIGUOUS, dtype=self.dtype, src=(self,)+args, **kwargs)
|
||||
def contiguous_backward(self): return self.alu(Ops.CONTIGUOUS_BACKWARD)
|
||||
def bufferize(self, *args, **kwargs): return UOp(Ops.BUFFERIZE, dtype=self.dtype, src=(self,)+args, **kwargs)
|
||||
def fuse(self): return self.alu(Ops.FUSE)
|
||||
def allreduce(self, op, device:str|tuple[str, ...]|UOp):
|
||||
assert isinstance(self.device, tuple), f"allreduce must be on tuple {self.device} isn't"
|
||||
@@ -364,6 +372,7 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
|
||||
if self.st == ret.st: return self # ignore NOOPs, also check ret.st
|
||||
return ret
|
||||
|
||||
def forced_reshape(self, arg:tuple[sint, ...]): return UOp(Ops.RESHAPE, self.dtype, src=(self,), arg=arg)
|
||||
def reshape(self, arg:tuple[sint, ...]): return self._mop(Ops.RESHAPE, arg)
|
||||
def pad(self, arg:tuple[tuple[sint, sint], ...]): return self._mop(Ops.PAD, arg)
|
||||
def expand(self, arg:tuple[sint, ...]): return self._mop(Ops.EXPAND, arg)
|
||||
@@ -658,6 +667,9 @@ class UPat(MathTrait):
|
||||
@staticmethod
|
||||
def const(dtype:DType|tuple[DType, ...]|None, b:ConstType): return UPat(Ops.CONST, dtype=dtype, arg=b)
|
||||
|
||||
# lil helper
|
||||
def f(self, op, **kwargs): return UPat(op, src=(self,), **kwargs)
|
||||
|
||||
# copied from UOp
|
||||
def sink(self, *srcs:UPat|None, **kwargs): return UPat(Ops.SINK, dtypes.void, (self,)+tuple([x for x in srcs if x is not None]), **kwargs)
|
||||
def index(self, idx:UPat, valid:UPat|None=None): return UPat(Ops.INDEX, self.dtype, (self,idx,valid) if valid is not None else (self,idx))
|
||||
@@ -671,6 +683,7 @@ class UPat(MathTrait):
|
||||
def reduce(self, *src:UPat, **kwargs): return UPat(Ops.REDUCE, self.dtype, src=(self,)+src, **kwargs)
|
||||
def fuse(self): return self.alu(Ops.FUSE)
|
||||
def or_broadcasted(self, **kwargs): return UPat.any(self, UPat(Ops.VECTORIZE, self.dtype, src=self, **kwargs))
|
||||
def contiguous(self, *args, **kwargs): return UPat(Ops.CONTIGUOUS, dtype=self.dtype, src=(self,)+args, **kwargs)
|
||||
|
||||
def const_like(self, b:ConstLike): return UPat.const(self.dtype, cast(ConstType, b))
|
||||
def alu(self, op:Ops, *src:UPat):
|
||||
@@ -850,7 +863,7 @@ class TrackedPatternMatcher(PatternMatcher):
|
||||
try: ret = match(uop, ctx)
|
||||
except Exception as e:
|
||||
if TRACK_MATCH_STATS >= 2 and active_rewrites and not isinstance(e, RewriteNotReady):
|
||||
active_rewrites[-1].matches.append((track_uop(uop), track_uop(UOp(Ops.NOOP, arg=str(sys.exc_info()[1]))), p.location))
|
||||
active_rewrites[-1].matches.append((track_uop(uop), track_uop(UOp(Ops.REWRITE_ERROR, src=uop.src, arg=str(sys.exc_info()[1]))), p.location))
|
||||
raise
|
||||
if ret is not None and ret is not uop:
|
||||
match_stats[p][0] += 1
|
||||
|
||||
@@ -242,18 +242,18 @@ def gep_through_wmma(gep:UOp, wmma:UOp):
|
||||
|
||||
gep_pushing = PatternMatcher([
|
||||
# GEP/VECTORIZE, GEP/GEP, GEP/CONST, GEP/VCONST
|
||||
(UPat(Ops.GEP, src=(UPat(Ops.GEP, name='g2'),), name='g1'),
|
||||
(UPat(Ops.GEP, name='g2').f(Ops.GEP, name='g1'),
|
||||
lambda g1, g2: g2.src[0].gep(tuple(g2.arg[g1.arg[i]] for i in range(len(g1.arg))))),
|
||||
(UPat(Ops.GEP, src=(UPat(Ops.VECTORIZE, name="vec"),), name="gep"),
|
||||
(UPat(Ops.VECTORIZE, name='vec').f(Ops.GEP, name='gep'),
|
||||
lambda gep, vec: UOp(Ops.VECTORIZE, gep.dtype, tuple(vec.src[i] for i in gep.arg)) if len(gep.arg) > 1 else vec.src[gep.arg[0]]),
|
||||
(UPat(Ops.GEP, src=(UPat.cvar("c", vec=False),), name="gep"), lambda gep, c: gep.const_like(c.arg)),
|
||||
(UPat(Ops.GEP, src=(UPat(Ops.VCONST, name="c"),), name="gep"), lambda gep, c: gep.const_like(tuple(c.arg[x] for x in gep.arg))),
|
||||
(UPat.cvar("c", vec=False).f(Ops.GEP, name="gep"), lambda gep, c: gep.const_like(c.arg)),
|
||||
(UPat(Ops.VCONST, name="c").f(Ops.GEP, name="gep"), lambda gep, c: gep.const_like(tuple(c.arg[x] for x in gep.arg))),
|
||||
# GEP on void is skipped
|
||||
(UPat(Ops.GEP, src=(UPat(dtype=dtypes.void, name="x"),)), lambda x: x),
|
||||
# GEP in order is removed
|
||||
(UPat(Ops.GEP, name="g"), lambda g: g.src[0] if not isinstance(g.dtype, PtrDType) and g.arg == tuple(range(g.src[0].dtype.count)) else None),
|
||||
# push all GEPs through ALUs (fix arange stuff)
|
||||
(UPat(Ops.GEP, src=(UPat((*GroupOp.ALU, Ops.CAST, Ops.BITCAST), name='alu'),), name='gep'),
|
||||
(UPat((*GroupOp.ALU, Ops.CAST, Ops.BITCAST), name='alu').f(Ops.GEP, name='gep'),
|
||||
lambda gep,alu: UOp(alu.op, alu.dtype.scalar().vec(gep.dtype.count), tuple(x.gep(gep.arg) for x in alu.src), alu.arg) \
|
||||
if not isinstance(gep.dtype, PtrDType) else None),
|
||||
# CAT can't be rendered. it's a VECTORIZE on vectors, we expand to a single VECTORIZEs with GEPs (TODO: move this later)
|
||||
@@ -262,7 +262,7 @@ gep_pushing = PatternMatcher([
|
||||
# VECTORIZE on same GEP
|
||||
(UPat(Ops.VECTORIZE, name="v", src=UPat(Ops.GEP, src=(UPat.var("x"),))), lambda v,x: x.gep(tuple(get_single_element(i.arg) for i in v.src))),
|
||||
# push some GEPs through WMMAs
|
||||
(UPat(Ops.GEP, src=(UPat(Ops.WMMA, name="wmma"),), name="gep"), gep_through_wmma),
|
||||
(UPat(Ops.WMMA, name="wmma").f(Ops.GEP, name="gep"), gep_through_wmma),
|
||||
])
|
||||
|
||||
commutative = PatternMatcher([
|
||||
|
||||
@@ -228,7 +228,7 @@
|
||||
}
|
||||
#device-list > div {
|
||||
min-height: 32px;
|
||||
max-width: 100px;
|
||||
max-width: 132px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
+62
-70
@@ -122,17 +122,18 @@ const colorScheme = {TINY:["#1b5745", "#354f52", "#354f52", "#1d2e62", "#63b0cd"
|
||||
CATEGORICAL:["#ff8080", "#F4A261", "#C8F9D4", "#8D99AE", "#F4A261", "#ffffa2", "#ffffc0", "#87CEEB"],}
|
||||
const cycleColors = (lst, i) => lst[i%lst.length];
|
||||
|
||||
const createPolygons = (source, area) => {
|
||||
const shapes = [];
|
||||
const yscale = d3.scaleLinear().domain([0, source.peak]).range([area, 0]);
|
||||
for (const [i,e] of source.shapes.entries()) {
|
||||
const x = e.x.map((i,_) => (source.timestamps[i] ?? data.et)-data.st);
|
||||
const y0 = e.y.map(yscale);
|
||||
const y1 = e.y.map(y => yscale(y+e.arg.nbytes));
|
||||
const arg = { tooltipText:`${e.arg.dtype} len:${formatUnit(e.arg.sz)}\n${formatUnit(e.arg.nbytes, "B")}` };
|
||||
shapes.push({ x, y0, y1, arg, fillColor:cycleColors(colorScheme.BUFFER, i) });
|
||||
const rescaleTrack = (source, tid, k) => {
|
||||
for (const e of source.shapes) {
|
||||
for (let i=0; i<e.y0.length; i++) {
|
||||
e.y0[i] = e.y0[i]*k;
|
||||
e.y1[i] = e.y1[i]*k;
|
||||
}
|
||||
}
|
||||
return shapes;
|
||||
const change = (source.height*k)-source.height;
|
||||
const div = document.getElementById(tid);
|
||||
div.style.height = rect(div).height+change+"px";
|
||||
source.height = source.height*k;
|
||||
return change;
|
||||
}
|
||||
|
||||
const drawLine = (ctx, x, y) => {
|
||||
@@ -150,77 +151,68 @@ async function renderProfiler() {
|
||||
// layout once!
|
||||
if (data != null) return;
|
||||
const profiler = d3.select(".profiler").html("");
|
||||
const deviceList = profiler.append("div").attr("id", "device-list").node();
|
||||
const { layout, st, et } = await (await fetch("/get_profile")).json();
|
||||
// place devices on the y axis and set vertical positions
|
||||
const [tickSize, padding] = [10, 8];
|
||||
const deviceList = profiler.append("div").attr("id", "device-list").style("padding-top", tickSize+padding+"px");
|
||||
const canvas = profiler.append("canvas").attr("id", "timeline").node();
|
||||
// NOTE: scrolling via mouse can only zoom the graph
|
||||
canvas.addEventListener("wheel", e => (e.stopPropagation(), e.preventDefault()), { passive:false });
|
||||
const profileRet = await (await fetch("/get_profile")).json()
|
||||
const { layout, st, et } = profileRet;
|
||||
// place devices on the y axis and set vertical positions
|
||||
const [tickSize, padding] = [10, 8];
|
||||
deviceList.style.paddingTop = `${tickSize+padding}px`;
|
||||
const ctx = canvas.getContext("2d");
|
||||
const canvasTop = rect(canvas).top;
|
||||
// color by key (name/category/device)
|
||||
const colorMap = new Map();
|
||||
data = {tracks:new Map(), axes:{}, st, et};
|
||||
const areaScale = d3.scaleLinear().domain([0, Object.entries(layout).reduce((peak, [_,d]) => Math.max(peak, d.mem.peak), 0)]).range([4,maxArea=100]);
|
||||
for (const [k, { timeline, mem }] of Object.entries(layout)) {
|
||||
if (timeline.shapes.length === 0 && mem.shapes.length == 0) continue;
|
||||
const div = deviceList.appendChild(document.createElement("div"));
|
||||
div.innerText = k;
|
||||
div.style.padding = `${padding}px`;
|
||||
div.onclick = () => { // TODO: make this feature more visible
|
||||
const prevScroll = profiler.node().scrollTop;
|
||||
let newOffset = null;
|
||||
for (const [track, v] of data.tracks) {
|
||||
if (track === `${k} memory`) {
|
||||
// expand the y axis or reset to default size
|
||||
const pick = [areaScale(mem.peak), maxArea*4];
|
||||
const expand = k !== focusedDevice;
|
||||
const [newArea, prevArea] = expand ? pick.reverse() : pick;
|
||||
focusedDevice = expand ? k : null;
|
||||
data.axes.y = expand ? { domain:[0, mem.peak], range:[v.offsetY+newArea, v.offsetY], fmt:"B" } : null;
|
||||
// either way update all offsets
|
||||
v.shapes = createPolygons(mem, newArea);
|
||||
newOffset = newArea-prevArea;
|
||||
v.div.style.height = rect(v.div).height+newOffset+"px";
|
||||
} else if (newOffset != null) v.offsetY += newOffset;
|
||||
}
|
||||
d3.select(canvas).call(canvasZoom.transform, zoomLevel);
|
||||
if (prevScroll) profiler.node().scrollTop = prevScroll;
|
||||
}
|
||||
const { y:baseY, height:baseHeight } = rect(div);
|
||||
const levelHeight = baseHeight-padding;
|
||||
const heightScale = d3.scaleLinear().domain([0, Object.entries(layout).reduce((peak, [_,d]) => Math.max(peak, d.peak||0), 0)]).range([4,maxheight=100]);
|
||||
for (const [k, v] of Object.entries(layout)) {
|
||||
if (v.shapes.length === 0) continue;
|
||||
const div = deviceList.append("div").attr("id", k).text(k).style("padding", padding+"px");
|
||||
const { y:baseY, height:baseHeight } = rect(div.node());
|
||||
const offsetY = baseY-canvasTop+padding/2;
|
||||
const shapes = [];
|
||||
data.tracks.set(k, { shapes, offsetY });
|
||||
let colorKey, ref;
|
||||
for (const e of timeline.shapes) {
|
||||
if (e.depth === 0) colorKey = e.cat ?? e.name;
|
||||
if (!colorMap.has(colorKey)) colorMap.set(colorKey, cycleColors(colorScheme[k] ?? colorScheme.DEFAULT, colorMap.size));
|
||||
const fillColor = d3.color(colorMap.get(colorKey)).brighter(e.depth).toString();
|
||||
const label = parseColors(e.name).map(({ color, st }) => ({ color, st, width:ctx.measureText(st).width }));
|
||||
if (e.ref != null) ref = {ctx:e.ref, step:0};
|
||||
else if (ref != null) {
|
||||
const start = ref.step>0 ? ref.step+1 : 0;
|
||||
const stepIdx = ctxs[ref.ctx+1].steps.findIndex((s, i) => i >= start && s.name == e.name);
|
||||
ref = stepIdx === -1 ? null : {ctx:ref.ctx, step:stepIdx};
|
||||
if (v.shapes[0].dur != null) {
|
||||
const levelHeight = baseHeight-padding;
|
||||
const shapes = [];
|
||||
data.tracks.set(k, { shapes, offsetY });
|
||||
let colorKey, ref;
|
||||
for (const e of v.shapes) {
|
||||
if (e.depth === 0) colorKey = e.cat ?? e.name;
|
||||
if (!colorMap.has(colorKey)) colorMap.set(colorKey, cycleColors(colorScheme[k] ?? colorScheme.DEFAULT, colorMap.size));
|
||||
const fillColor = d3.color(colorMap.get(colorKey)).brighter(e.depth).toString();
|
||||
const label = parseColors(e.name).map(({ color, st }) => ({ color, st, width:ctx.measureText(st).width }));
|
||||
if (e.ref != null) ref = {ctx:e.ref, step:0};
|
||||
else if (ref != null) {
|
||||
const start = ref.step>0 ? ref.step+1 : 0;
|
||||
const stepIdx = ctxs[ref.ctx+1].steps.findIndex((s, i) => i >= start && s.name == e.name);
|
||||
ref = stepIdx === -1 ? null : {ctx:ref.ctx, step:stepIdx};
|
||||
}
|
||||
const arg = { tooltipText:formatTime(e.dur)+(e.info != null ? "\n"+e.info : ""), ...ref };
|
||||
// offset y by depth
|
||||
shapes.push({x:e.st-st, y:levelHeight*e.depth, width:e.dur, height:levelHeight, arg, label, fillColor });
|
||||
}
|
||||
const arg = { tooltipText:formatTime(e.dur)+(e.info != null ? "\n"+e.info : ""), ...ref };
|
||||
// offset y by depth
|
||||
shapes.push({x:e.st-st, y:levelHeight*e.depth, width:e.dur, height:levelHeight, arg, label, fillColor });
|
||||
div.style("height", levelHeight*v.maxDepth+padding+"px").style("pointerEvents", "none");
|
||||
} else {
|
||||
const height = heightScale(v.peak);
|
||||
const yscale = d3.scaleLinear().domain([0, v.peak]).range([height, 0]);
|
||||
const shapes = [];
|
||||
for (const [i,e] of v.shapes.entries()) {
|
||||
const x = e.x.map(tsIdx => v.timestamps[tsIdx]-st);
|
||||
const arg = {tooltipText:`${e.arg.dtype} len:${formatUnit(e.arg.sz)}\n${formatUnit(e.arg.nbytes, "B")}`};
|
||||
shapes.push({ x, y0:e.y.map(yscale), y1:e.y.map(y => yscale(y+e.arg.nbytes)), arg, fillColor:cycleColors(colorScheme.BUFFER, i) });
|
||||
}
|
||||
data.tracks.set(k, { shapes, offsetY, height, peak:v.peak, scaleFactor:maxheight*4/height });
|
||||
div.style("height", height+padding+"px").style("cursor", "pointer").on("click", (e) => {
|
||||
const newFocus = e.currentTarget.id === focusedDevice ? null : e.currentTarget.id;
|
||||
let offset = 0;
|
||||
for (const [tid, track] of data.tracks) {
|
||||
track.offsetY += offset;
|
||||
if (tid === newFocus) offset += rescaleTrack(track, tid, track.scaleFactor);
|
||||
else if (tid === focusedDevice) offset += rescaleTrack(track, tid, 1/track.scaleFactor);
|
||||
}
|
||||
data.axes.y = newFocus != null ? { domain:[0, (t=data.tracks.get(newFocus)).peak], range:[t.offsetY+t.height, t.offsetY], fmt:"B" } : null;
|
||||
focusedDevice = newFocus;
|
||||
return resize();
|
||||
});
|
||||
}
|
||||
// position shapes on the canvas and scale to fit fixed area
|
||||
let area = mem.shapes.length === 0 ? 0 : areaScale(mem.peak);
|
||||
if (area === 0) div.style.pointerEvents = "none";
|
||||
else {
|
||||
const startY = offsetY+(levelHeight*timeline.maxDepth)+padding/2;
|
||||
data.tracks.set(`${k} memory`, { shapes:createPolygons(mem, area), offsetY:startY, div });
|
||||
div.style.cursor = "pointer";
|
||||
}
|
||||
// lastly, adjust device rect by number of levels
|
||||
div.style.height = `${Math.max(levelHeight*timeline.maxDepth, baseHeight)+area+padding}px`;
|
||||
}
|
||||
updateProgress({ "show":false });
|
||||
// draw events on a timeline
|
||||
|
||||
+13
-8
@@ -19,7 +19,7 @@ uops_colors = {Ops.LOAD: "#ffc0c0", Ops.STORE: "#87CEEB", Ops.CONST: "#e0e0e0",
|
||||
**{x:"#D8F9E4" for x in GroupOp.Movement}, **{x:"#ffffc0" for x in GroupOp.ALU}, Ops.THREEFRY:"#ffff80", Ops.BUFFER_VIEW: "#E5EAFF",
|
||||
Ops.BLOCK: "#C4A484", Ops.BLOCKEND: "#C4A4A4", Ops.BUFFER: "#B0BDFF", Ops.COPY: "#a040a0", Ops.FUSE: "#FFa500",
|
||||
Ops.ALLREDUCE: "#ff40a0", Ops.MSELECT: "#d040a0", Ops.MSTACK: "#d040a0", Ops.CONTIGUOUS: "#FFC14D",
|
||||
Ops.CHILD: "#80fff0"}
|
||||
Ops.CHILDREN: "#80ffc0", Ops.CHILD: "#80fff0", Ops.BUFFERIZE: "#FF991C", Ops.REWRITE_ERROR: "#ff2e2e"}
|
||||
|
||||
# VIZ API
|
||||
|
||||
@@ -73,8 +73,8 @@ def uop_to_json(x:UOp) -> dict[int, dict]:
|
||||
if u.dtype != dtypes.void: label += f"\n{u.dtype}"
|
||||
for idx,x in enumerate(u.src):
|
||||
if x in excluded:
|
||||
if x.op is Ops.CONST and dtypes.is_float(u.dtype): label += f"\nCONST{idx} {x.arg:g}"
|
||||
else: label += f"\n{x.op.name}{idx} {x.arg}"
|
||||
arg = f"{x.arg:g}" if x.op is Ops.CONST and dtypes.is_float(u.dtype) else f"{x.arg}"
|
||||
label += f"\n{x.op.name}{idx} {arg}" + (f" {x.src[0].op}" if len(x.src) else "")
|
||||
try:
|
||||
if u.op not in {Ops.VIEW, Ops.BUFFER, Ops.KERNEL, Ops.ASSIGN, Ops.COPY, Ops.SINK, *GroupOp.Buffer} and u.st is not None:
|
||||
label += f"\n{shape_to_str(u.shape)}"
|
||||
@@ -144,7 +144,7 @@ def timeline_layout(events:list[tuple[int, int, float, DevEvent]]) -> dict:
|
||||
shapes.append({"name":name, "ref":ref, "st":st, "dur":dur, "depth":depth, "cat":cat, "info":info})
|
||||
return {"shapes":shapes, "maxDepth":len(levels)}
|
||||
|
||||
def mem_layout(events:list[tuple[int, int, float, DevEvent]]) -> dict:
|
||||
def mem_layout(events:list[tuple[int, int, float, DevEvent]], max_ts:int) -> dict:
|
||||
step, peak, mem = 0, 0, 0
|
||||
shps:dict[int, dict] = {}
|
||||
temp:dict[int, dict] = {}
|
||||
@@ -170,9 +170,10 @@ def mem_layout(events:list[tuple[int, int, float, DevEvent]]) -> dict:
|
||||
for v in temp.values():
|
||||
v["x"].append(step)
|
||||
v["y"].append(v["y"][-1])
|
||||
timestamps.append(max_ts)
|
||||
return {"shapes":list(shps.values()), "peak":peak, "timestamps":timestamps}
|
||||
|
||||
def get_profile(profile:list[ProfileEvent]):
|
||||
def get_profile(profile:list[ProfileEvent]) -> bytes|None:
|
||||
# start by getting the time diffs
|
||||
for ev in profile:
|
||||
if isinstance(ev,ProfileDeviceEvent): device_ts_diffs[ev.device] = (ev.comp_tdiff, ev.copy_tdiff if ev.copy_tdiff is not None else ev.comp_tdiff)
|
||||
@@ -184,10 +185,14 @@ def get_profile(profile:list[ProfileEvent]):
|
||||
dev_events.setdefault(e.device,[]).append((st:=int(ts), et:=int(en), float(en-ts), e))
|
||||
if min_ts is None or st < min_ts: min_ts = st
|
||||
if max_ts is None or et > max_ts: max_ts = et
|
||||
if min_ts is None: return None
|
||||
# return layout of per device events
|
||||
for events in dev_events.values(): events.sort(key=lambda v:v[0])
|
||||
dev_layout = {k:{"timeline":timeline_layout(v), "mem":mem_layout(v)} for k,v in dev_events.items()}
|
||||
return json.dumps({"layout":dev_layout, "st":min_ts, "et":max_ts}).encode("utf-8")
|
||||
layout:dict[str, dict] = {}
|
||||
for k,v in dev_events.items():
|
||||
v.sort(key=lambda e:e[0])
|
||||
layout[k] = timeline_layout(v)
|
||||
layout[f"{k} Memory"] = mem_layout(v, unwrap(max_ts))
|
||||
return json.dumps({"layout":layout, "st":min_ts, "et":max_ts}).encode("utf-8")
|
||||
|
||||
def get_runtime_stats(key) -> list[dict]:
|
||||
ret:list[dict] = []
|
||||
|
||||
Reference in New Issue
Block a user