mirror of
https://github.com/tinygrad/tinygrad.git
synced 2026-08-14 18:38:27 +00:00
Compare commits
25
Commits
spec_3
...
real_pyrender
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36918a7d7a | ||
|
|
c5a420ed7e | ||
|
|
f55056af48 | ||
|
|
0b81d9b0d6 | ||
|
|
ab7d8c24be | ||
|
|
533f81bfc8 | ||
|
|
701a632907 | ||
|
|
95748a4518 | ||
|
|
8084aa06b2 | ||
|
|
8fb545c475 | ||
|
|
1a3498e6dc | ||
|
|
8f9b09b4f6 | ||
|
|
8743a21d3f | ||
|
|
7139e036c5 | ||
|
|
0b85d31a68 | ||
|
|
804133cffd | ||
|
|
4c63cf3914 | ||
|
|
f4da94af28 | ||
|
|
97a4296d8b | ||
|
|
b987b8b22a | ||
|
|
af3211f73c | ||
|
|
6b54378eba | ||
|
|
dc6d667941 | ||
|
|
189582db5e | ||
|
|
70ba84eb04 |
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from tinygrad.runtime.support.system import System
|
||||
import argparse, glob, os, re, time, subprocess, sys
|
||||
import argparse, glob, os, time, subprocess, sys
|
||||
|
||||
def scan_devs_based_on_lock(prefix:str, args) -> list[str]:
|
||||
target_dev = args.pci_bus if 'pci_bus' in args.__dir__() else ""
|
||||
@@ -12,7 +11,7 @@ def scan_devs_based_on_lock(prefix:str, args) -> list[str]:
|
||||
if os.path.exists(f"/sys/bus/pci/devices/{dev_id}") and dev_id.startswith(target_dev): devs.append(dev_id)
|
||||
return devs
|
||||
|
||||
def _do_reset_device(pci_bus): System.pci_reset(pci_bus)
|
||||
def _do_reset_device(pci_bus): os.system(f"sudo sh -c 'echo 1 > /sys/bus/pci/devices/{pci_bus}/reset'")
|
||||
def _is_module_loaded(name: str) -> bool: return os.path.isdir(f"/sys/module/{name}")
|
||||
|
||||
def cmd_remove_module(args):
|
||||
|
||||
@@ -65,8 +65,8 @@ template<typename _T, int _axis=-9999, bool _swizzle_flag=true> struct descripto
|
||||
namespace detail {
|
||||
template<typename... Args>
|
||||
struct descriptor_dict {
|
||||
__host__ descriptor_dict() {}
|
||||
template<typename T> __host__ descriptor_dict(T _, int b, int d, int r, int c) {}
|
||||
__host__ __device__ descriptor_dict() {}
|
||||
template<typename T> __host__ __device__ descriptor_dict(T _, int b, int d, int r, int c) {}
|
||||
__host__ __device__ descriptor_dict(const descriptor_dict &other) {}
|
||||
#ifdef KITTENS_HOPPER
|
||||
template<typename T, int U> __device__ const CUtensorMap* get() const {
|
||||
@@ -85,8 +85,8 @@ struct descriptor_dict<_T, Args...> {
|
||||
using DESC = kittens::tma::descriptor<_T>; // copy or initialize with a default value
|
||||
CUtensorMap tma_desc;
|
||||
descriptor_dict<Args...> other_descs;
|
||||
__host__ descriptor_dict() {}
|
||||
__host__ descriptor_dict(typename DESC::T::dtype *data, int b, int d, int r, int c): other_descs(data, b, d, r, c) {
|
||||
__host__ __device__ descriptor_dict() {}
|
||||
__host__ __device__ descriptor_dict(typename DESC::T::dtype *data, int b, int d, int r, int c): other_descs(data, b, d, r, c) {
|
||||
kittens::detail::tma::create_tensor_map<typename DESC::T, DESC::axis, DESC::swizzle_flag>(&tma_desc, data, b, d, r, c);
|
||||
}
|
||||
__host__ __device__ inline descriptor_dict(const descriptor_dict &other) :
|
||||
@@ -135,7 +135,7 @@ struct gl {
|
||||
|
||||
detail::descriptor_dict<TMA_Types...> tma_descs;
|
||||
|
||||
__host__ inline gl(T *_data,
|
||||
__host__ __device__ inline gl(T *_data,
|
||||
ducks::gl::make_arg_t<b> _batch,
|
||||
ducks::gl::make_arg_t<d> _depth,
|
||||
ducks::gl::make_arg_t<r> _rows,
|
||||
|
||||
@@ -425,4 +425,4 @@ __host__ static inline CUtensorMap* allocate_and_create_tensor_map(const typenam
|
||||
|
||||
} // namespace tma
|
||||
} // namespace detail
|
||||
} // namespace kittens
|
||||
} // namespace kittens
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// https://github.com/HazyResearch/ThunderKittens/blob/main/kernels/matmul/educational/level_04.cu
|
||||
#include "kittens.cuh"
|
||||
using namespace kittens;
|
||||
|
||||
constexpr int g_N = 8192;
|
||||
constexpr int BLOCK_SIZE = 32;
|
||||
#define NUM_WORKERS (1)
|
||||
#define NUM_THREADS (NUM_WORKERS*kittens::WARP_THREADS)
|
||||
|
||||
using sub_tile = st_bf<BLOCK_SIZE,BLOCK_SIZE>;
|
||||
using tile_gl = gl<bf16, 1, 1, g_N, g_N, sub_tile>;
|
||||
|
||||
__global__ void kernel(bf16 *c_ptr, bf16 *a_ptr, bf16 *b_ptr) {
|
||||
tile_gl g_C{c_ptr, nullptr, nullptr, nullptr, nullptr};
|
||||
tile_gl g_A{a_ptr, nullptr, nullptr, nullptr, nullptr};
|
||||
tile_gl g_B{b_ptr, nullptr, nullptr, nullptr, nullptr};
|
||||
|
||||
extern __shared__ alignment_dummy __shm[];
|
||||
shared_allocator al((int*)&__shm[0]);
|
||||
st_bf<BLOCK_SIZE,BLOCK_SIZE> &As = al.allocate<st_bf<BLOCK_SIZE,BLOCK_SIZE>>();
|
||||
st_bf<BLOCK_SIZE,BLOCK_SIZE> &Bs = al.allocate<st_bf<BLOCK_SIZE,BLOCK_SIZE>>();
|
||||
|
||||
rt_bf<BLOCK_SIZE,BLOCK_SIZE> A_reg;
|
||||
rt_bf<BLOCK_SIZE,BLOCK_SIZE> B_reg;
|
||||
rt_bf<BLOCK_SIZE,BLOCK_SIZE, ducks::rt_layout::col> B_reg_col;
|
||||
rt_fl<BLOCK_SIZE,BLOCK_SIZE> C_accum;
|
||||
|
||||
int col = blockIdx.x;
|
||||
int row = blockIdx.y;
|
||||
|
||||
warp::zero(C_accum);
|
||||
int num_tiles = (g_N + BLOCK_SIZE - 1) / BLOCK_SIZE;
|
||||
for (int tile = 0; tile < num_tiles; ++tile) {
|
||||
warp::load(As, g_A, {0, 0, row, tile});
|
||||
warp::load(Bs, g_B, {0, 0, tile, col});
|
||||
__syncthreads();
|
||||
warp::load(A_reg, As);
|
||||
warp::load(B_reg, Bs);
|
||||
warp::swap_layout(B_reg_col, B_reg);
|
||||
__syncthreads();
|
||||
warp::mma_AB(C_accum, A_reg, B_reg_col, C_accum);
|
||||
__syncthreads();
|
||||
}
|
||||
warp::store(g_C, C_accum, {0, 0, row, col});
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import pathlib
|
||||
from tinygrad import Device, Tensor
|
||||
from tinygrad.helpers import Context
|
||||
from tinygrad.runtime.support.compiler_cuda import pretty_ptx, NVCCCompiler
|
||||
|
||||
if __name__ == "__main__":
|
||||
code = (pathlib.Path(__file__).parent / "matmul.cu").read_text()
|
||||
device = Device["CUDA"]
|
||||
kitten_args = [f"-I{(pathlib.Path(__file__).parent / 'include').as_posix()}", "-std=c++20", "--expt-relaxed-constexpr", "-DKITTENS_HOPPER"]
|
||||
lib = NVCCCompiler(device.compiler.arch, kitten_args).compile(code)
|
||||
kernel_name = lib.decode().split(".globl\t")[1].split("\n")[0]
|
||||
print("kernel name", kernel_name)
|
||||
print(pretty_ptx(lib.decode()))
|
||||
|
||||
prg = device.runtime(kernel_name, lib)
|
||||
prg.smem = 10000
|
||||
|
||||
N = 8192
|
||||
a = Tensor.randn(N, N, device='CUDA', dtype="bfloat16")
|
||||
b = Tensor.randn(N, N, device='CUDA', dtype="bfloat16")
|
||||
c = Tensor.empty(N, N, device='CUDA', dtype="bfloat16")
|
||||
Tensor.realize(a, b, c)
|
||||
|
||||
BLOCK_SIZE = 32
|
||||
|
||||
gsz = (N // BLOCK_SIZE, N // BLOCK_SIZE, 1)
|
||||
for _ in range(5):
|
||||
et = prg(c.uop.buffer.ensure_allocated()._buf, a.uop.buffer._buf, b.uop.buffer._buf,
|
||||
global_size=gsz, local_size=(32,1,1), wait=True)
|
||||
print(f"{N*N*N*2/(et*1e9):2f} GFLOPS")
|
||||
|
||||
for _ in range(5):
|
||||
with Context(DEBUG=2):
|
||||
ref = (a@b).realize()
|
||||
|
||||
ref, c = ref.float(), c.float()
|
||||
print((ref-c).mean().item(), (ref-c).max().item())
|
||||
@@ -759,7 +759,7 @@ class TestSchedule(unittest.TestCase):
|
||||
|
||||
def test_pow_neg_05_is_rsqrt(self):
|
||||
t = Tensor([1.0, 2.0, 3.0]) ** -0.5
|
||||
self.assertEqual(self._alu_from_tensor(t), [Ops.RECIP, Ops.SQRT])
|
||||
self.assertEqual(self._alu_from_tensor(t), [Ops.RECIPROCAL, Ops.SQRT])
|
||||
|
||||
def test_pow_2_has_1_mul(self):
|
||||
t = Tensor([1.0, 2.0, 3.0]) ** Tensor(2.0)
|
||||
|
||||
+18
-12
@@ -115,7 +115,7 @@ class TestFloatUOps(TestUOps):
|
||||
def test_log2(self): self._test_uop_fxn(Ops.LOG2, lambda a: math.log2(a) if a > 0 else float('-inf' if a==0 else 'nan'))
|
||||
@unittest.skipIf(Device.DEFAULT == "CPU", 'not supported as uop')
|
||||
def test_sin(self): self._test_uop_fxn(Ops.SIN, lambda a: math.sin(a))
|
||||
def test_recip(self): self._test_uop_fxn(Ops.RECIP, lambda a: 1/a if a != 0 else float('inf'))
|
||||
def test_recip(self): self._test_uop_fxn(Ops.RECIPROCAL, lambda a: 1/a if a != 0 else float('inf'))
|
||||
def test_sqrt(self): self._test_uop_fxn(Ops.SQRT, lambda a: math.sqrt(a) if a >= 0 else float('nan'))
|
||||
|
||||
def test_add(self): self._test_bop_fxn(Ops.ADD, lambda a,b: a+b)
|
||||
@@ -218,18 +218,18 @@ class TestExecALU(TestUOps):
|
||||
self.assertEqual(exec_alu(Ops.IDIV, dtypes.int8, (7, -3)), -2)
|
||||
self.assertEqual(exec_alu(Ops.IDIV, dtypes.int8, (-50, 6)), -8)
|
||||
|
||||
np.testing.assert_allclose(exec_alu(Ops.MUL, dtypes.float32, (7.0, exec_alu(Ops.RECIP, dtypes.float32, (3.0,)))), 2+(1.0/3.0))
|
||||
np.testing.assert_allclose(exec_alu(Ops.MUL, dtypes.float32, (7.0, exec_alu(Ops.RECIP, dtypes.float32, (-3.0,)))), -2-(1.0/3.0))
|
||||
np.testing.assert_allclose(exec_alu(Ops.MUL, dtypes.float32, (7.0, exec_alu(Ops.RECIPROCAL, dtypes.float32, (3.0,)))), 2+(1.0/3.0))
|
||||
np.testing.assert_allclose(exec_alu(Ops.MUL, dtypes.float32, (7.0, exec_alu(Ops.RECIPROCAL, dtypes.float32, (-3.0,)))), -2-(1.0/3.0))
|
||||
|
||||
def test_recip(self):
|
||||
np.testing.assert_allclose(exec_alu(Ops.RECIP, dtypes.float32, (8,)), 1/8)
|
||||
np.testing.assert_allclose(exec_alu(Ops.RECIP, dtypes.float32, (7,)), 1/7)
|
||||
np.testing.assert_allclose(exec_alu(Ops.RECIP, dtypes.float32, (-3,)), 1/-3)
|
||||
np.testing.assert_allclose(exec_alu(Ops.RECIP, dtypes.float32, (-50,)), 1/-50)
|
||||
np.testing.assert_allclose(exec_alu(Ops.RECIPROCAL, dtypes.float32, (8,)), 1/8)
|
||||
np.testing.assert_allclose(exec_alu(Ops.RECIPROCAL, dtypes.float32, (7,)), 1/7)
|
||||
np.testing.assert_allclose(exec_alu(Ops.RECIPROCAL, dtypes.float32, (-3,)), 1/-3)
|
||||
np.testing.assert_allclose(exec_alu(Ops.RECIPROCAL, dtypes.float32, (-50,)), 1/-50)
|
||||
|
||||
np.testing.assert_allclose(exec_alu(Ops.RECIP, dtypes.float32, ((32+521+3),)), 1/(32+521+3))
|
||||
np.testing.assert_allclose(exec_alu(Ops.RECIP, dtypes.float32, ((34**2),)), 1/(34**2))
|
||||
np.testing.assert_allclose(exec_alu(Ops.RECIP, dtypes.float32, (10,)), 1/10)
|
||||
np.testing.assert_allclose(exec_alu(Ops.RECIPROCAL, dtypes.float32, ((32+521+3),)), 1/(32+521+3))
|
||||
np.testing.assert_allclose(exec_alu(Ops.RECIPROCAL, dtypes.float32, ((34**2),)), 1/(34**2))
|
||||
np.testing.assert_allclose(exec_alu(Ops.RECIPROCAL, dtypes.float32, (10,)), 1/10)
|
||||
|
||||
def test_bool_cmplt(self):
|
||||
self.assertEqual(exec_alu(Ops.CMPLT, dtypes.bool, (False, False)), False)
|
||||
@@ -548,10 +548,16 @@ class TestUopsObject(unittest.TestCase):
|
||||
class TestUOpRender(unittest.TestCase):
|
||||
def test_render_vectorize_same(self):
|
||||
u = UOp(Ops.VECTORIZE, dtype=dtypes.int.vec(3), src=(UOp.const(dtypes.int, 0), UOp.const(dtypes.int, 0), UOp.const(dtypes.int, 0)))
|
||||
self.assertEqual(u.render(), "{0, ...}")
|
||||
self.assertEqual(u.render(simplify=False), "{0, ...}")
|
||||
def test_render_vectorize_different(self):
|
||||
u = UOp(Ops.VECTORIZE, dtype=dtypes.int.vec(3), src=(UOp.const(dtypes.int, 0), UOp.const(dtypes.int, 1), UOp.const(dtypes.int, 2)))
|
||||
self.assertEqual(u.render(), "{0,1,2}")
|
||||
self.assertEqual(u.render(simplify=False), "{0,1,2}")
|
||||
def test_render_vectorize_same_simplified(self):
|
||||
u = UOp(Ops.VECTORIZE, dtype=dtypes.int.vec(3), src=(UOp.const(dtypes.int, 0), UOp.const(dtypes.int, 0), UOp.const(dtypes.int, 0)))
|
||||
self.assertEqual(u.render(), "0")
|
||||
def test_render_vectorize_different_simplified(self):
|
||||
u = UOp(Ops.VECTORIZE, dtype=dtypes.int.vec(3), src=(UOp.const(dtypes.int, 0), UOp.const(dtypes.int, 1), UOp.const(dtypes.int, 2)))
|
||||
self.assertEqual(u.render(), "(0, 1, 2)")
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from tinygrad.helpers import QUANTIZE, DEVECTORIZE, TRANSCENDENTAL, SPEC
|
||||
from tinygrad.uop.ops import PatternMatcher, graph_rewrite, UOp, pm_lower_index_dtype
|
||||
from tinygrad.uop.ops import PatternMatcher, graph_rewrite, UOp, pm_lower_index_dtype, test_pyrender
|
||||
from tinygrad.uop.spec import type_verify, program_spec, kernel_spec
|
||||
from tinygrad.renderer import Renderer
|
||||
|
||||
@@ -20,6 +20,7 @@ def full_rewrite_to_sink(sink:UOp, ren:Renderer|None=None, optimize:bool=True) -
|
||||
if ren is None: ren = Renderer()
|
||||
|
||||
if SPEC: type_verify(list(sink.toposort()), kernel_spec)
|
||||
if SPEC > 1: test_pyrender(sink)
|
||||
|
||||
# first we optimize
|
||||
if optimize:
|
||||
@@ -87,6 +88,7 @@ def full_rewrite_to_sink(sink:UOp, ren:Renderer|None=None, optimize:bool=True) -
|
||||
sink = graph_rewrite(sink, pm_add_control_flow, ctx=CFGContext(sink), name="add control flow", bottom_up=True)
|
||||
|
||||
# return the rewritten sink
|
||||
if SPEC > 1: test_pyrender(sink)
|
||||
return sink
|
||||
|
||||
def full_rewrite(sink:UOp, ren:Renderer|None=None) -> list[UOp]:
|
||||
|
||||
@@ -137,7 +137,7 @@ def beam_search(lin:Scheduler, rawbufs:list[Buffer], amt:int, allow_test_size=Tr
|
||||
min_progress = getenv("BEAM_MIN_PROGRESS", 0.01)/1e6
|
||||
if BEAM_DEBUG:
|
||||
print("BEAM_SEARCH:")
|
||||
print('\n'.join(pyrender(lin.ast.replace(arg=None))))
|
||||
print(pyrender(lin.ast.replace(arg=None)))
|
||||
if DEBUG >= 2: print(f" 0.00s: from 1 -> 1 actions {lin.colored_shape()}")
|
||||
|
||||
try:
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ class DType(metaclass=DTypeMetaClass):
|
||||
@staticmethod
|
||||
def new(priority:int, itemsize:int, name:str, fmt:FmtStr|None): return DType(priority, itemsize, name, fmt, 1, None)
|
||||
def __reduce__(self): return type(self), tuple(getattr(self, f.name) for f in fields(self))
|
||||
def __repr__(self): return f"dtypes.{INVERSE_DTYPES_DICT[self.scalar().name]}"+(f".vec({self.count})" if self.count > 1 else "")
|
||||
def __repr__(self): return f"dtypes.{INVERSE_DTYPES_DICT[self.scalar().name]}"+(f".vec({self.count})" if self.count != 1 else "")
|
||||
def __lt__(self, o:DType): return (self.priority, self.itemsize, self.name, self.fmt, self.count) < (o.priority, o.itemsize, o.name, o.fmt, o.count)
|
||||
@property
|
||||
def base(self): return self
|
||||
|
||||
@@ -26,7 +26,7 @@ def get_program(ast:UOp, renderer:Renderer|None=None, opts:list[Opt]|None=None)
|
||||
"""
|
||||
|
||||
if getenv("VIZ"): graph_rewrite(ast, PatternMatcher([]), name="View Base AST")
|
||||
if DEBUG >= 5: print('\n'.join(pyrender(ast)))
|
||||
if DEBUG >= 5: print(pyrender(ast))
|
||||
|
||||
# linearize
|
||||
if renderer is None: renderer = Device.default.renderer
|
||||
@@ -38,7 +38,7 @@ def get_program(ast:UOp, renderer:Renderer|None=None, opts:list[Opt]|None=None)
|
||||
except RuntimeError as e:
|
||||
print("***** LINEARIZE FAILURE *****")
|
||||
print(e)
|
||||
print('\n'.join(pyrender(ast)))
|
||||
print(pyrender(ast))
|
||||
raise
|
||||
assert uops[-1].op is Ops.SINK, "last uop must be sink"
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ def reduce_gradient(ctx:UOp, ret:UOp):
|
||||
# ctx is grad_output
|
||||
pm_gradient = PatternMatcher([
|
||||
(UPat(Ops.CAST, name="ret"), lambda ctx, ret: (ctx.cast(ret.src[0].dtype),)),
|
||||
(UPat(Ops.RECIP, name="ret"), lambda ctx, ret: (-ctx * ret * ret,)),
|
||||
(UPat(Ops.RECIPROCAL, name="ret"), lambda ctx, ret: (-ctx * ret * ret,)),
|
||||
(UPat(Ops.SIN, name="ret"), lambda ctx, ret: ((math.pi/2 - ret.src[0]).sin() * ctx,)),
|
||||
(UPat(Ops.LOG2, name="ret"), lambda ctx, ret: (ctx / (ret.src[0] * math.log(2)),)),
|
||||
(UPat(Ops.EXP2, name="ret"), lambda ctx, ret: (ret * ctx * math.log(2),)),
|
||||
|
||||
@@ -184,7 +184,6 @@ class Metadata:
|
||||
caller: str
|
||||
backward: bool = False
|
||||
def __hash__(self): return hash(self.name)
|
||||
def __repr__(self): return str(self) + (f" - {self.caller}" if self.caller else "")
|
||||
def __str__(self): return self.name + (" bw" if self.backward else "")
|
||||
|
||||
# **************** global state Counters ****************
|
||||
|
||||
@@ -95,7 +95,7 @@ class CStyleLanguage(Renderer):
|
||||
infinity: str = "INFINITY"
|
||||
nan: str = "NAN"
|
||||
code_for_op: dict = {
|
||||
Ops.SQRT: lambda x,dtype: f"sqrt({x})", Ops.RECIP: lambda x,dtype: f"(1/{x})", Ops.NEG: lambda x,dtype: f"-{x}",
|
||||
Ops.SQRT: lambda x,dtype: f"sqrt({x})", Ops.RECIPROCAL: lambda x,dtype: f"(1/{x})", Ops.NEG: lambda x,dtype: f"-{x}",
|
||||
Ops.EXP2: lambda x,dtype: f"exp2({x})", Ops.LOG2: lambda x,dtype: f"log2({x})", Ops.SIN: lambda x,dtype: f"sin({x})",
|
||||
Ops.TRUNC: lambda x,dtype: f"trunc({x})",
|
||||
Ops.AND: lambda a,b,dtype: f"({a}&{b})", Ops.XOR: lambda a,b,dtype: f"({a}^{b})", Ops.OR: lambda a,b,dtype: f"({a}|{b})",
|
||||
@@ -208,7 +208,7 @@ class ClangRenderer(CStyleLanguage):
|
||||
# language options
|
||||
buffer_suffix = " restrict"
|
||||
type_map = {dtypes.bool:"_Bool", dtypes.half:"__fp16"}
|
||||
code_for_op = {**({k:v for k,v in CStyleLanguage.code_for_op.items() if k not in [Ops.EXP2, Ops.SIN, Ops.LOG2, Ops.TRUNC, Ops.RECIP]}),
|
||||
code_for_op = {**({k:v for k,v in CStyleLanguage.code_for_op.items() if k not in [Ops.EXP2, Ops.SIN, Ops.LOG2, Ops.TRUNC, Ops.RECIPROCAL]}),
|
||||
Ops.SQRT: lambda x,dtype: f"__builtin_sqrt({x})" if dtype == dtypes.float64 else f"__builtin_sqrtf({x})",
|
||||
Ops.TRUNC: lambda x,dtype: f"__builtin_trunc({x})" if dtype == dtypes.float64 else f"__builtin_truncf({x})",
|
||||
Ops.FDIV: lambda a,b,dtype: f"({a}/{b})"}
|
||||
@@ -365,7 +365,7 @@ class CUDARenderer(CStyleLanguage):
|
||||
Ops.LOG2: lambda x,dtype: f"hlog2({x})" if dtype in (dtypes.half, dtypes.bfloat16) else f"log2({x})",
|
||||
Ops.EXP2: lambda x,dtype: f"hexp2({x})" if dtype in (dtypes.half, dtypes.bfloat16) else f"exp2({x})",
|
||||
Ops.SQRT: lambda x,dtype: f"hsqrt({x})" if dtype in (dtypes.half, dtypes.bfloat16) else f"sqrt({x})",
|
||||
Ops.RECIP: lambda x,dtype: f"hrcp({x})" if dtype in (dtypes.half, dtypes.bfloat16) else f"(1/{x})" }
|
||||
Ops.RECIPROCAL: lambda x,dtype: f"hrcp({x})" if dtype in (dtypes.half, dtypes.bfloat16) else f"(1/{x})" }
|
||||
type_map = {dtypes.bfloat16: "nv_bfloat16", dtypes.fp8e4m3: "__nv_fp8_e4m3", dtypes.fp8e5m2: "__nv_fp8_e5m2"}
|
||||
extra_matcher = PatternMatcher([
|
||||
(UPat(Ops.CAST, dtypes.fp8s, UPat.var("x", dtypes.fp8s), name='y'), lambda x,y: x.cast(dtypes.float).cast(y.dtype) if x.dtype!=y.dtype else None),
|
||||
|
||||
@@ -21,7 +21,7 @@ def glsl_type(t:DType) -> mesa.struct_glsl_type:
|
||||
u_aop = { Ops.ADD: "iadd", Ops.MUL: "imul", Ops.IDIV: "udiv", Ops.MOD: "umod", Ops.CMPLT: "ult", Ops.CMPNE: "ine", Ops.CMPEQ: "ieq", Ops.OR: "ior",
|
||||
Ops.AND: "iand", Ops.XOR: "ixor", Ops.WHERE: "bcsel", Ops.MAX: "umax"}
|
||||
s_aop = {**u_aop, Ops.CMPLT: "ilt", Ops.IDIV: "idiv", Ops.MOD: "irem", Ops.MAX: "imax"}
|
||||
f_aop = { Ops.ADD: "fadd", Ops.MUL: "fmul", Ops.CMPLT: "flt", Ops.CMPNE: "fneu", Ops.CMPEQ: "feq", Ops.FDIV: "fdiv", Ops.RECIP: "frcp",
|
||||
f_aop = { Ops.ADD: "fadd", Ops.MUL: "fmul", Ops.CMPLT: "flt", Ops.CMPNE: "fneu", Ops.CMPEQ: "feq", Ops.FDIV: "fdiv", Ops.RECIPROCAL: "frcp",
|
||||
Ops.MAX: "fmax", Ops.TRUNC: "ftrunc", Ops.SIN: "fsin", Ops.EXP2: "fexp2", Ops.LOG2: "flog2"}
|
||||
aop = {**{x:u_aop for x in (dtypes.bool,)+dtypes.uints}, **{x:s_aop for x in dtypes.sints}, **{x:f_aop for x in dtypes.floats}}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ def render_val(x, dtype):
|
||||
return str(int(x)) + ("U" if dtypes.is_unsigned(dtype) else "")
|
||||
|
||||
asm_for_op: dict[Ops, Callable] = {
|
||||
Ops.RECIP: lambda d,a,dt,name: f"rcp{'.approx' if dtypes.is_float(dt) else ''}.{name} {d}, {a};",
|
||||
Ops.RECIPROCAL: lambda d,a,dt,name: f"rcp{'.approx' if dtypes.is_float(dt) else ''}.{name} {d}, {a};",
|
||||
Ops.EXP2: lambda d,a,dt,name: f"ex2.approx.{name} {d}, {a};", Ops.LOG2: lambda d,a,dt,name: f"lg2.approx.{name} {d}, {a};",
|
||||
Ops.SIN: lambda d,a,dt,name: f"sin.approx.{name} {d}, {a};", Ops.SQRT: lambda d,a,dt,name: f"sqrt.approx.{name} {d}, {a};",
|
||||
Ops.TRUNC: lambda d,a,dt,name: f"cvt.rzi.{name}.{name} {d}, {a};",
|
||||
|
||||
@@ -71,7 +71,7 @@ class NVMemoryManager(MemoryManager):
|
||||
|
||||
class NVDev(PCIDevImplBase):
|
||||
def __init__(self, pci_dev:PCIDevice):
|
||||
self.pci_dev, self.devfmt, self.vram, self.mmio = pci_dev, pci_dev.pcibus, pci_dev.map_bar(1), pci_dev.map_bar(0, fmt='I')
|
||||
self.pci_dev, self.devfmt, self.mmio = pci_dev, pci_dev.pcibus, pci_dev.map_bar(0, fmt='I')
|
||||
|
||||
self.lock_fd = System.flock_acquire(f"nv_{self.devfmt}.lock")
|
||||
|
||||
@@ -120,7 +120,7 @@ class NVDev(PCIDevImplBase):
|
||||
self.include("src/common/inc/swref/published/turing/tu102/dev_fb.h")
|
||||
if self.reg("NV_PFB_PRI_MMU_WPR2_ADDR_HI").read() != 0:
|
||||
if DEBUG >= 2: print(f"nv {self.devfmt}: WPR2 is up. Issuing a full reset.", flush=True)
|
||||
System.pci_reset(self.devfmt)
|
||||
self.pci_dev.reset()
|
||||
time.sleep(0.5)
|
||||
|
||||
self.include("src/common/inc/swref/published/turing/tu102/dev_vm.h")
|
||||
@@ -134,6 +134,8 @@ class NVDev(PCIDevImplBase):
|
||||
self.pte_t, self.pde_t, self.dual_pde_t = tuple([self.__dict__[name] for name in mmu_pd_names])
|
||||
|
||||
self.vram_size = self.reg("NV_PGC6_AON_SECURE_SCRATCH_GROUP_42").read() << 20
|
||||
|
||||
self.vram, self.mmio = self.pci_dev.map_bar(1), self.pci_dev.map_bar(0, fmt='I')
|
||||
self.large_bar = self.vram.nbytes >= self.vram_size
|
||||
|
||||
def _alloc_boot_struct(self, struct:ctypes.Structure) -> tuple[ctypes.Structure, int]:
|
||||
|
||||
@@ -90,10 +90,6 @@ class _System:
|
||||
if data is not None: sysmem_view[:len(data)] = data
|
||||
return sysmem_view, [p + i for p, sz in paddrs for i in range(0, sz, 0x1000)][:ceildiv(size, 0x1000)]
|
||||
|
||||
def pci_reset(self, gpu):
|
||||
if OSX: System.iokit_pci_rpc(__TinyGPURPCReset:=2)
|
||||
else: os.system(f"sudo sh -c 'echo 1 > /sys/bus/pci/devices/{gpu}/reset'")
|
||||
|
||||
def pci_scan_bus(self, target_vendor:int, target_devices:list[int]) -> list[str]:
|
||||
result = []
|
||||
for pcibus in FileIOInterface("/sys/bus/pci/devices").listdir():
|
||||
@@ -216,6 +212,7 @@ class PCIDevice:
|
||||
fd, sz = self.bar_fds[bar], size or (self.bar_info[bar].size - off)
|
||||
libc.madvise(loc:=fd.mmap(addr, sz, mmap.PROT_READ | mmap.PROT_WRITE, mmap.MAP_SHARED | (MAP_FIXED if addr else 0), off), sz, libc.MADV_DONTFORK)
|
||||
return MMIOInterface(loc, sz, fmt=fmt)
|
||||
def reset(self): os.system(f"sudo sh -c 'echo 1 > /sys/bus/pci/devices/{self.pcibus}/reset'")
|
||||
|
||||
class APLPCIDevice(PCIDevice):
|
||||
def __init__(self, pcibus:str, bars:list[int], resize_bars:list[int]|None=None):
|
||||
@@ -224,6 +221,7 @@ class APLPCIDevice(PCIDevice):
|
||||
def map_bar(self, bar:int, off:int=0, addr:int=0, size:int|None=None, fmt='B') -> MMIOInterface: return self.bars[bar].view(off, size, fmt)
|
||||
def read_config(self, offset:int, size:int): return System.iokit_pci_rpc(__TinyGPURPCReadCfg:=0, offset, size)[0]
|
||||
def write_config(self, offset:int, value:int, size:int): System.iokit_pci_rpc(__TinyGPURPCWriteCfg:=1, offset, size, value)
|
||||
def reset(self): System.iokit_pci_rpc(__TinyGPURPCReset:=2)
|
||||
|
||||
class USBPCIDevice(PCIDevice):
|
||||
def __init__(self, pcibus:str, bars:list[int], resize_bars:list[int]|None=None):
|
||||
|
||||
@@ -11,6 +11,7 @@ from tinygrad.helpers import suppress_finalizing
|
||||
from tinygrad.gradient import compute_gradient
|
||||
from tinygrad.uop.mathtraits import MathTrait
|
||||
from tinygrad.uop.ops import smax, smin, resolve, UOp, Ops, sint, identity_element, all_metadata, _index_to_concrete_int, sint_to_uop, srender
|
||||
from tinygrad.uop.ops import test_pyrender
|
||||
from tinygrad.uop.spec import type_verify, tensor_spec
|
||||
from tinygrad.device import Device, Buffer
|
||||
from tinygrad.engine.realize import run_schedule
|
||||
@@ -230,6 +231,7 @@ class Tensor(MathTrait):
|
||||
|
||||
# verify Tensors match the spec
|
||||
if SPEC: type_verify(list(big_sink.toposort()), tensor_spec)
|
||||
if SPEC > 1: test_pyrender(big_sink)
|
||||
|
||||
if any(isinstance(x._device, tuple) for x in big_sink.toposort()):
|
||||
_apply_map_to_tensors(get_multi_map(big_sink), "Apply Multi Map")
|
||||
|
||||
@@ -3,6 +3,7 @@ from enum import auto, IntEnum, Enum
|
||||
# wrapper around IntEnum that preserves Enum.__str__ and makes auto() unique across all FastEnum subclasses
|
||||
class FastEnum(IntEnum):
|
||||
def __str__(self): return Enum.__str__(self)
|
||||
def __repr__(x): return str(x)
|
||||
@staticmethod
|
||||
def _generate_next_value_(_, __, ___, last_values): return 1 + max([0, *last_values, *[max(c) for c in FastEnum.__subclasses__()]])
|
||||
|
||||
@@ -47,7 +48,7 @@ class Ops(FastEnum):
|
||||
UNROLL = auto(); CONTRACT = auto(); GEP = auto(); VECTORIZE = auto(); CAT = auto(); PTRCAT = auto() # noqa: E702
|
||||
|
||||
# UnaryOps
|
||||
CAST = auto(); BITCAST = auto(); EXP2 = auto(); LOG2 = auto(); SIN = auto(); SQRT = auto(); RECIP = auto(); NEG = auto(); TRUNC = auto() # noqa: E702
|
||||
CAST = auto(); BITCAST = auto(); EXP2 = auto(); LOG2 = auto(); SIN = auto(); SQRT = auto(); RECIPROCAL = auto(); NEG = auto(); TRUNC = auto() # noqa: E702
|
||||
|
||||
# load/store before math
|
||||
LOAD = auto(); STORE = auto() # noqa: E702
|
||||
@@ -78,7 +79,7 @@ class Ops(FastEnum):
|
||||
CUSTOM = auto(); CUSTOMI = auto() # noqa: E702
|
||||
|
||||
class GroupOp:
|
||||
Unary = {Ops.EXP2, Ops.LOG2, Ops.SIN, Ops.SQRT, Ops.RECIP, Ops.NEG, Ops.TRUNC}
|
||||
Unary = {Ops.EXP2, Ops.LOG2, Ops.SIN, Ops.SQRT, Ops.RECIPROCAL, Ops.NEG, Ops.TRUNC}
|
||||
Binary = {Ops.ADD, Ops.MUL, Ops.IDIV, Ops.MAX, Ops.MOD, Ops.CMPLT, Ops.CMPNE, Ops.CMPEQ,
|
||||
Ops.XOR, Ops.SHL, Ops.SHR, Ops.OR, Ops.AND, Ops.THREEFRY, Ops.SUB, Ops.FDIV, Ops.POW}
|
||||
Ternary = {Ops.WHERE, Ops.MULACC}
|
||||
@@ -107,6 +108,6 @@ class GroupOp:
|
||||
Comparison = {Ops.CMPLT, Ops.CMPNE, Ops.CMPEQ}
|
||||
|
||||
# do not preserve f(0) = 0
|
||||
UnsafePad = {Ops.RECIP, Ops.LOG2, Ops.EXP2, Ops.IDIV, Ops.POW}
|
||||
UnsafePad = {Ops.RECIPROCAL, Ops.LOG2, Ops.EXP2, Ops.IDIV, Ops.POW}
|
||||
|
||||
All = set(Ops)
|
||||
|
||||
@@ -114,7 +114,8 @@ class MathTrait:
|
||||
return self._binop(Ops.IDIV, x, reverse)
|
||||
def mod(self:TMT, x:TMT|ConstType, reverse:bool=False): return self._binop(Ops.MOD, x, reverse)
|
||||
def sub(self:TMT, x:TMT|ConstType, reverse:bool=False): return self.ufix(x).alu(Ops.ADD, -self) if reverse else self.alu(Ops.ADD, self.ufix(-x))
|
||||
def div(self:TMT, x:TMT|ConstType, reverse:bool=False): return (self.ufix(x)*self.alu(Ops.RECIP)) if reverse else (self*self.ufix(x).alu(Ops.RECIP))
|
||||
def div(self:TMT, x:TMT|ConstType, reverse:bool=False):
|
||||
return (self.ufix(x)*self.alu(Ops.RECIPROCAL)) if reverse else (self*self.ufix(x).alu(Ops.RECIPROCAL))
|
||||
|
||||
def __neg__(self): return self.neg()
|
||||
|
||||
@@ -162,7 +163,7 @@ class MathTrait:
|
||||
if isinstance(y, type(self)): return self.alu(Ops.WHERE, y.ufix(x), y)
|
||||
raise RuntimeError("where needs at least one UOp arg")
|
||||
def threefry(self:TMT, seed:TMT): return self.alu(Ops.THREEFRY, seed)
|
||||
def reciprocal(self): return self.alu(Ops.RECIP)
|
||||
def reciprocal(self): return self.alu(Ops.RECIPROCAL)
|
||||
def trunc(self): return self.alu(Ops.TRUNC)
|
||||
def sqrt(self): return self.alu(Ops.SQRT)
|
||||
def sin(self): return self.alu(Ops.SIN)
|
||||
|
||||
+121
-42
@@ -65,6 +65,7 @@ class UOpMetaClass(type):
|
||||
assert op is Ops.BUFFER, f"trying to set Buffer {_buffer} for {op}"
|
||||
buffers[created] = _buffer
|
||||
if SPEC > 1:
|
||||
if SPEC > 2: test_pyrender(created)
|
||||
from tinygrad.uop.spec import full_spec
|
||||
with Context(IGNORE_OOB=1): ret = full_spec.rewrite(created)
|
||||
if cast(bool|None, ret) is not True: raise RuntimeError(f"SPEC ISSUE {ret}: {created}")
|
||||
@@ -296,6 +297,7 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
|
||||
with Context(TRACK_MATCH_STATS=0 if not tracked else TRACK_MATCH_STATS.value):
|
||||
return graph_rewrite(self, symbolic if full_symbolic else commutative, name="simplify")
|
||||
def ssimplify(self) -> UOp|ConstType: return ret.arg if (ret:=self.simplify()).op is Ops.CONST else ret
|
||||
def sintify(self) -> sint: return self.arg if self.op is Ops.CONST else self
|
||||
def _eval(self, dtype, expected_type:Type[T]) -> T:
|
||||
assert self.dtype in dtype, f"eval with wrong dtype {self}"
|
||||
vmin, vmax = (simple_self:=self.simplify())._min_max
|
||||
@@ -358,9 +360,8 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
|
||||
def store(self, *src:UOp, **kwargs): return UOp(Ops.STORE, kwargs.pop("dtype", dtypes.void), (self,)+src, **kwargs)
|
||||
def end(self, *src:UOp):
|
||||
if len(src) == 0: return self
|
||||
assert all(x.op is Ops.RANGE for x in src), "end only ends ranges"
|
||||
return UOp(Ops.END, src=(self,)+src)
|
||||
def after(self, *src:UOp): return UOp(Ops.AFTER, self.dtype, (self,)+src)
|
||||
def after(self, *src:UOp, **kwargs): return UOp(Ops.AFTER, self.dtype, (self,)+src, **kwargs)
|
||||
def assign(self, x:UOp): return UOp(Ops.ASSIGN, self.dtype, (self, x))
|
||||
def barrier(self, *src:UOp): return UOp(Ops.BARRIER, src=(self,)+src)
|
||||
def alu(self, op, *src:UOp, **kwargs):
|
||||
@@ -371,15 +372,17 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
|
||||
def const(dtype:DType, b:ConstLike, device:str|tuple[str, ...]|None=None, shape:tuple[sint, ...]|None=None, src=None):
|
||||
if isinstance(b, UOp): return b.unbind()[0] if b.op is Ops.BIND else b
|
||||
if isinstance(b, tuple) and all_same(b): b = b[0] # doesn't have to be a VCONST if they are all the same
|
||||
# NOTE: float('nan') != float('nan'), so we canonicalize here
|
||||
if isinstance(b, float) and math.isnan(b): b = math.nan
|
||||
ret = UOp(Ops.VCONST if isinstance(b, tuple) else Ops.CONST, dtype, arg=dtypes.as_const(b, dtype), src=() if src is None else (src,))
|
||||
if device is not None: ret = ret.replace(src=(UOp(Ops.DEVICE, arg=device),))
|
||||
if shape is not None: ret = ret.reshape((1,)*len(shape)).expand(shape)
|
||||
return ret
|
||||
@staticmethod
|
||||
def range(end:sint, *arg, dtype=dtypes.index):
|
||||
def range(end:sint, *arg, dtype=dtypes.index, src=(), **kwargs):
|
||||
if len(arg) == 0: raise RuntimeError("range needs an arg")
|
||||
if len(arg) == 1: arg = arg+(AxisType.LOOP,)
|
||||
return UOp(Ops.RANGE, dtype=dtype, src=(sint_to_uop(end, dtype),), arg=arg)
|
||||
return UOp(Ops.RANGE, dtype=dtype, src=(sint_to_uop(end, dtype),)+src, arg=arg, **kwargs)
|
||||
@staticmethod
|
||||
def special(end:sint, name:str, dtype=dtypes.index): return UOp(Ops.SPECIAL, dtype=dtype, src=(sint_to_uop(end, dtype),), arg=name)
|
||||
def r(self, op:Ops, axis:tuple[int, ...]):
|
||||
@@ -484,7 +487,7 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
|
||||
match self.op:
|
||||
case Ops.CONST: return self.arg
|
||||
case Ops.VCONST: return self.arg[i]
|
||||
case Ops.VECTORIZE: return cast(sint, self.src[i].ssimplify())
|
||||
case Ops.VECTORIZE: return self.src[i].sintify()
|
||||
case _: raise RuntimeError(f"no sgep on {self.op}")
|
||||
|
||||
@functools.cached_property
|
||||
@@ -506,7 +509,8 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
|
||||
if len(arg) == 0: usrcs.append(UOp(Ops.VECTORIZE, dtypes.index.vec(0)))
|
||||
elif all(isinstance(x, int) for x in arg): usrcs.append(UOp.const(dtypes.index.vec(len(arg)), arg))
|
||||
else: usrcs.append(UOp(Ops.VECTORIZE, dtypes.index.vec(len(arg)), tuple(UOp.const(dtypes.index, x) if isinstance(x, int) else x for x in arg)))
|
||||
ret = UOp(op, self.dtype, (self,)+tuple(usrcs), arg if len(usrcs) == 0 else None)
|
||||
if len(usrcs) == 0: ret = UOp(op, self.dtype, (self,), arg)
|
||||
else: ret = UOp(op, self.dtype, (self,)+UOp.sink(*usrcs).simplify().src)
|
||||
# for all movement ops, we check shape property
|
||||
if ret.shape == self.shape and same_shape_noop: return self
|
||||
return ret
|
||||
@@ -527,12 +531,13 @@ class UOp(MathTrait, metaclass=UOpMetaClass):
|
||||
# TODO: use this in Buffer
|
||||
unique_num = itertools.count(0)
|
||||
@staticmethod
|
||||
def unique(): return UOp(Ops.UNIQUE, arg=next(UOp.unique_num))
|
||||
def unique(arg:int|None=None): return UOp(Ops.UNIQUE, arg=next(UOp.unique_num) if arg is None else arg)
|
||||
|
||||
# *** uop Buffer stuff ***
|
||||
|
||||
@staticmethod
|
||||
def new_buffer(device:str|tuple[str, ...], size:int, dtype:DType): return UOp(Ops.BUFFER, dtype, (UOp.unique(), UOp(Ops.DEVICE, arg=device)), size)
|
||||
def new_buffer(device:str|tuple[str, ...], size:int, dtype:DType, num=None):
|
||||
return UOp(Ops.BUFFER, dtype, (UOp.unique(num), UOp(Ops.DEVICE, arg=device)), size)
|
||||
@property
|
||||
def device(self) -> str|tuple[str, ...]: return cast(str|tuple[str, ...], unwrap(self._device))
|
||||
@recursive_property
|
||||
@@ -750,7 +755,7 @@ def safe_pow(x, y):
|
||||
|
||||
python_alu: dict[Ops, Callable] = {
|
||||
Ops.LOG2: lambda x: math.log2(x) if x > 0 else -math.inf if x == 0 else math.nan, Ops.EXP2: safe_exp2,
|
||||
Ops.SQRT: lambda x: math.sqrt(x) if x >= 0 else math.nan, Ops.RECIP: lambda x: 1/x if x != 0 else math.copysign(math.inf, x),
|
||||
Ops.SQRT: lambda x: math.sqrt(x) if x >= 0 else math.nan, Ops.RECIPROCAL: lambda x: 1/x if x != 0 else math.copysign(math.inf, x),
|
||||
Ops.SIN: lambda x: math.sin(x) if not math.isinf(x) else math.nan, Ops.POW: safe_pow, Ops.TRUNC: math.trunc,
|
||||
Ops.NEG: operator.neg, Ops.ADD: operator.add, Ops.SUB: operator.sub, Ops.MUL: operator.mul, Ops.CMPNE: operator.ne, Ops.CMPLT: operator.lt,
|
||||
Ops.XOR: operator.xor, Ops.OR: operator.or_, Ops.AND: operator.and_, Ops.SHR: operator.rshift, Ops.SHL: operator.lshift, Ops.MAX: max,
|
||||
@@ -1214,7 +1219,7 @@ renderer = PatternMatcher([
|
||||
(UPat(Ops.BIND, src=UPat(Ops.NOOP), name="x"), lambda x: x.src[0]),
|
||||
#(UPat(Ops.BIND, src=UPat(Ops.NOOP), name="x"), lambda x: UOp(Ops.NOOP, arg=f"{x.src[0].arg}[={x.src[1].arg}]")),
|
||||
(UPat(Ops.NEG, src=UPat(Ops.NOOP), name="x"), lambda x: UOp(Ops.NOOP, arg=f"(-{x.src[0].arg})")),
|
||||
(UPat(Ops.RECIP, src=UPat(Ops.NOOP), name="x"), lambda x: UOp(Ops.NOOP, arg=f"(1/{x.src[0].arg})")),
|
||||
(UPat(Ops.RECIPROCAL, src=UPat(Ops.NOOP), name="x"), lambda x: UOp(Ops.NOOP, arg=f"(1/{x.src[0].arg})")),
|
||||
(UPat(Ops.MAX, src=UPat(Ops.NOOP), name="x"), lambda x: UOp(Ops.NOOP, arg=f"max({x.src[0].arg}, {x.src[1].arg})")),
|
||||
(UPat(Ops.MULACC, src=UPat(Ops.NOOP), name="x"), lambda x: UOp(Ops.NOOP, arg=f"({x.src[0].arg}*{x.src[1].arg}+{x.src[2].arg})")),
|
||||
(UPat(Ops.WHERE, src=UPat(Ops.NOOP), name="x"), lambda x: UOp(Ops.NOOP, arg=f"({x.src[1].arg} if {x.src[0].arg} else {x.src[2].arg})")),
|
||||
@@ -1230,44 +1235,118 @@ renderer_infer = PatternMatcher([
|
||||
*renderer.patterns
|
||||
])
|
||||
|
||||
sugar = { Ops.SINK: "sink", Ops.STORE: "store", Ops.LOAD: "load", Ops.SQRT: "sqrt", Ops.INDEX: "index", Ops.REDUCE: "reduce",
|
||||
Ops.WHERE: "where", Ops.RECIP: "reciprocal", Ops.EXP2: "exp2", Ops.LOG2: "log2", Ops.SIN: "sin"}
|
||||
pm_pyrender = PatternMatcher([
|
||||
(UPat(Ops.CONST, src=(UPat(Ops.NOOP),), name="x"), lambda x: UOp(Ops.NOOP, arg=f"UOp.const({x.dtype}, {x.arg}, src={x.src[0].arg})")),
|
||||
(UPat(Ops.CONST, name="x"), lambda x: UOp(Ops.NOOP, arg=f"UOp.const({x.dtype}, {x.arg})")),
|
||||
(UPat(Ops.END, src=UPat(Ops.NOOP), name="x"), lambda x: UOp(Ops.NOOP, arg=f"{x.src[0].arg}.end({', '.join([y.arg for y in x.src[1:]])})")),
|
||||
(UPat(Ops.CAST, src=(UPat(Ops.NOOP),), name="x"), lambda x: UOp(Ops.NOOP, arg=f"{x.src[0].arg}.cast({x.dtype})")),
|
||||
(UPat(Ops.BITCAST, src=(UPat(Ops.NOOP),), name="x"), lambda x: UOp(Ops.NOOP, arg=f"{x.src[0].arg}.bitcast({x.dtype})")),
|
||||
(UPat({Ops.MAX, Ops.THREEFRY, Ops.CMPLT, Ops.CMPNE, Ops.POW}, src=UPat(Ops.NOOP), name="x"),
|
||||
lambda x: UOp(Ops.NOOP, arg=f"{x.src[0].arg}.alu({x.op}, {x.src[1].arg})")),
|
||||
(UPat(Ops.RANGE, src=(UPat(Ops.NOOP),), name="x"), lambda x: UOp(Ops.NOOP, arg=
|
||||
f"UOp.range({x.src[0].arg}, {str(x.arg[0])}, {str(x.arg[1])}{', dtype='+str(x.dtype) if x.dtype is not dtypes.index else ''})")),
|
||||
(UPat(Ops.SPECIAL, src=(UPat(Ops.NOOP),), name="x"), lambda x: UOp(Ops.NOOP, arg= f"UOp.special({x.src[0].arg}, \"{x.arg}\", dtype={x.dtype})")),
|
||||
(UPat(Ops.DEFINE_VAR, name="x"), lambda x: UOp(Ops.NOOP, arg=
|
||||
f"UOp.variable(\"{x.arg[0]}\", {x.arg[1]}, {x.arg[2]}{', dtype='+str(x.dtype) if x.dtype is not dtypes.index else ''})")),
|
||||
(UPat(set(sugar.keys()), src=UPat(Ops.NOOP), name="x"), lambda x: UOp(Ops.NOOP,
|
||||
arg=f"{x.src[0].arg}.{sugar[x.op]}({', '.join([y.arg for y in x.src[1:]] + ([f'arg={str(x.arg)}'] if x.arg is not None else []))})")),
|
||||
(UPat(Ops.REDUCE_AXIS, src=(UPat(Ops.NOOP),), name="x"),
|
||||
lambda x: UOp(Ops.NOOP, arg=f"{x.src[0].arg}.f({x.op}, arg=({', '.join([str(y) for y in x.arg])}))")),
|
||||
# *** pyrender ***
|
||||
|
||||
def render_marg(ctx,x:UOp):
|
||||
if x.op in {Ops.PERMUTE, Ops.FLIP}: return str(x.marg)
|
||||
pieces = []
|
||||
if x.op in {Ops.RESHAPE, Ops.EXPAND}:
|
||||
pieces = [f"{ctx[a] if isinstance(a, UOp) else str(a)}" for a in x.marg]
|
||||
if x.op in {Ops.PAD, Ops.SHRINK}:
|
||||
pieces = [f"({ctx[a[0]] if isinstance(a[0], UOp) else str(a[0])}, {ctx[a[1]] if isinstance(a[1], UOp) else str(a[1])})" for a in x.marg]
|
||||
return f"({','.join(pieces)})" if len(pieces) != 1 else f"({pieces[0]},)"
|
||||
|
||||
# TODO: use this more in pyrender
|
||||
def srcs(ctx, src): return f"({ctx[src[0]]},)" if len(src) == 1 else f"({', '.join([ctx[x] for x in src])})"
|
||||
|
||||
sugar = {Ops.SINK, Ops.END, Ops.STORE, Ops.LOAD, Ops.UNIQUE, Ops.SQRT, Ops.INDEX, Ops.REDUCE, Ops.AFTER,
|
||||
Ops.WHERE, Ops.RECIPROCAL, Ops.EXP2, Ops.LOG2, Ops.SIN, Ops.CONTIGUOUS, Ops.BARRIER}
|
||||
pm_pyrender_extra = PatternMatcher([
|
||||
(UPat(Ops.CONST, src=(UPat(Ops.DEVICE, name="d"),), name="x"), lambda x,d: f"UOp.const({x.dtype}, {x.arg}, device={repr(d.arg)})"),
|
||||
(UPat(Ops.CONST, name="x"), lambda x: f"UOp.const({x.dtype}, {x.arg})"),
|
||||
(UPat(Ops.DEFINE_VAR, src=(), name="x"), lambda x:
|
||||
f"UOp.variable(\"{x.arg[0]}\", {x.arg[1]}, {x.arg[2]}{', dtype='+str(x.dtype) if x.dtype is not dtypes.index else ''})"),
|
||||
(UPat((Ops.CAST, Ops.BITCAST), name="x"), lambda ctx,x: f"{ctx[x.src[0]]}.{x.op.name.lower()}({x.dtype})"),
|
||||
(UPat(Ops.SPECIAL, src=(UPat(Ops.CONST),), name="x"), lambda x: f"UOp.special({x.src[0].arg}, {repr(x.arg)}, dtype={x.dtype})"),
|
||||
(UPat(Ops.BUFFER, src=(UPat(Ops.UNIQUE, name="u"), UPat(Ops.DEVICE, name="d")), name="x"), lambda x,u,d:
|
||||
f"UOp.new_buffer({repr(d.arg)}, {x.size}, {x.dtype}, {u.arg})"),
|
||||
(UPat(Ops.COPY, src=(UPat(name="x"), UPat(Ops.DEVICE, name="d"))), lambda ctx,x,d: f"{ctx[x]}.copy_to_device({repr(d.arg)})"),
|
||||
(UPat(Ops.REDUCE_AXIS, name="r"), lambda ctx,r: f"{ctx[r.src[0]]}.r({r.arg[0]}, {r.arg[1]})"),
|
||||
# NOTE: range has srcs sometimes after control flow
|
||||
(UPat(Ops.RANGE, src=(UPat(Ops.CONST, name="c"),), allow_any_len=True, name="x"), lambda ctx,x,c:
|
||||
"UOp.range("+', '.join([str(c.arg)] + [str(y) for y in x.arg])+
|
||||
(f', src={srcs(ctx, x.src[1:])}' if len(x.src) > 1 else '')+\
|
||||
(', dtype='+str(x.dtype) if x.dtype is not dtypes.index else '')+\
|
||||
(', tag='+str(x.tag) if x.tag is not None else '')+")"),
|
||||
# TODO: index shouldn't mismatch dtype
|
||||
(UPat(Ops.INDEX, src=(UPat(), UPat()), name="x"), lambda ctx,x:
|
||||
f"{ctx[x.src[0]]}.index({ctx[x.src[1]]}, dtype={x.dtype})" if x.src[0].dtype != x.dtype else None),
|
||||
# TODO: fix forced_reshape
|
||||
(UPat(Ops.RESHAPE, name="x"), lambda ctx,x: f"{ctx[x.src[0]]}.forced_reshape({render_marg(ctx,x)})" if x.src[0].shape == x.shape else None),
|
||||
(UPat(GroupOp.Movement, name="x"), lambda ctx,x: f"{ctx[x.src[0]]}.{x.op.name.lower()}({render_marg(ctx,x)})"),
|
||||
# NOTE: CMPNE doesn't work cause there's no __rne__
|
||||
(UPat(set(syms.keys())-{Ops.SUB, Ops.CMPNE}, src=(UPat(Ops.CONST, name="y"), UPat(name="z")), name="x"),
|
||||
lambda ctx,x,y,z: f"({y.arg}{syms[x.op]}{ctx[z]})"),
|
||||
# NOTE: sub doesn't work cause it's written as add/mul
|
||||
(UPat(set(syms.keys())-{Ops.SUB}, src=(UPat(name="y"), UPat(Ops.CONST, name="z")), name="x"), lambda ctx,x,y,z: f"({ctx[y]}{syms[x.op]}{z.arg})"),
|
||||
(UPat(set(syms.keys())-{Ops.SUB}, name="x"), lambda ctx,x: f"({ctx[x.src[0]]}{syms[x.op]}{ctx[x.src[1]]})"),
|
||||
(UPat(sugar, src=(), name="x"), lambda x: f"UOp.{x.op.name.lower()}("+', '.join( \
|
||||
([f'arg={repr(x.arg)}'] if x.arg is not None else []) + ([f'tag={repr(x.tag)}'] if x.tag is not None else []))+")"),
|
||||
(UPat(sugar, name="x"), lambda ctx,x: f"{ctx[x.src[0]]}.{x.op.name.lower()}("+', '.join([ctx[y] for y in x.src[1:]] + \
|
||||
([f'arg={repr(x.arg)}'] if x.arg is not None else []) + ([f'tag={repr(x.tag)}'] if x.tag is not None else []))+")"),
|
||||
])
|
||||
|
||||
@Context(SPEC=0)
|
||||
def pyrender(ast:UOp) -> list[str]:
|
||||
# NOTE: you can remove pm_pyrender_extra and it'll still be correct
|
||||
pm_pyrender = pm_pyrender_extra+PatternMatcher([
|
||||
(UPat(Ops.KERNEL, name="u"), lambda ctx,u: "UOp(Ops.KERNEL, src="+', '.join( \
|
||||
([f"({ctx[u.src[0]]},)"] if len(u.src) == 1 else ([f"({', '.join([ctx[x] for x in u.src])})"] if len(u.src) > 1 else []))) + \
|
||||
f", arg=Kernel({ctx[u.arg.ast]}(), {u.arg.metadata})"+(f", tag={repr(u.tag)}" if u.tag is not None else "")+")"),
|
||||
(UPat(GroupOp.All, name="u"), lambda ctx,u: "UOp("+', '.join([str(u.op), str(u.dtype)] + \
|
||||
([f"({ctx[u.src[0]]},)"] if len(u.src) == 1 else ([f"({', '.join([ctx[x] for x in u.src])})"] if len(u.src) > 1 else [])) + \
|
||||
([f"arg={repr(u.arg)}"] if u.arg is not None else []) + ([f"tag={repr(u.tag)}"] if u.tag is not None else []))+")"),
|
||||
])
|
||||
|
||||
def pyrender(ast:UOp) -> str:
|
||||
cmap = ast.get_consumer_map()
|
||||
to_render = set()
|
||||
for u in ast.toposort():
|
||||
if u.op is Ops.STORE: to_render.add(u.src[1])
|
||||
if len(cmap[u]) == 1 and u.op not in {Ops.DEFINE_GLOBAL, Ops.LOAD} or u.op in {Ops.CONST}: continue
|
||||
uops = list(ast.toposort())
|
||||
ret: dict[str, str] = {}
|
||||
r: dict[UOp, str] = {}
|
||||
|
||||
not_rendered = {Ops.CONST, Ops.VCONST, Ops.DEVICE}
|
||||
always_rendered = {Ops.DEFINE_GLOBAL, Ops.LOAD, Ops.SPECIAL, Ops.RANGE, Ops.CONTIGUOUS, Ops.BUFFER, Ops.COPY, Ops.KERNEL, Ops.WHERE}
|
||||
to_render: set[UOp] = {ast}
|
||||
for u in uops:
|
||||
if u.op in {Ops.SINK}:
|
||||
for s in u.src: to_render.add(s)
|
||||
if u.op is Ops.STORE: to_render.add(u.src[1])
|
||||
if u.op in {Ops.REDUCE, Ops.REDUCE_AXIS}: to_render.add(u.src[0])
|
||||
if u.op in not_rendered: continue
|
||||
# checking the consumers is not enough, you have to make sure it's not used twice by the one consumer
|
||||
if len(cmap[u]) == 1 and len([x for x in list(cmap[u].keys())[0].src if x is u]) == 1 and u.op not in always_rendered: continue
|
||||
to_render.add(u)
|
||||
ret: list[str] = []
|
||||
rep: dict[UOp, UOp] = {}
|
||||
for u in ast.toposort():
|
||||
if u not in to_render: continue
|
||||
ret.append(f"c{len(ret)} = {u.substitute(rep).render(simplify=False, pm=pm_pyrender+renderer)}")
|
||||
rep[u] = UOp(Ops.NOOP, arg=f"c{len(ret)-1}")
|
||||
return ret[0:-1] + ["ast ="+ret[-1].split("=", 1)[1]]
|
||||
|
||||
kernels: dict[UOp, tuple[str, str]] = {}
|
||||
for i,u in enumerate(uops):
|
||||
if u.op is Ops.KERNEL:
|
||||
if u.arg.ast not in kernels:
|
||||
kernels[u.arg.ast] = (f"k{len(kernels)}", f"def k{len(kernels)}():\n " + pyrender(u.arg.ast).replace('\n', '\n ') + "\n return ast\n\n")
|
||||
r[u.arg.ast] = kernels[u.arg.ast][0]
|
||||
ren = cast(str, pm_pyrender.rewrite(u, ctx=r))
|
||||
assert isinstance(ren, str)
|
||||
#if u.tag is not None: ren += f".rtag({u.tag})"
|
||||
if u not in to_render: r[u] = ren
|
||||
else:
|
||||
r[u] = f"c{i}" if u is not uops[-1] else "ast"
|
||||
ret[r[u]] = ren
|
||||
return ''.join([v[1] for v in kernels.values()]) + '\n'.join([f"{k} = {v}" for k,v in ret.items()])
|
||||
|
||||
def eval_pyrender(code:str) -> UOp:
|
||||
from tinygrad.dtype import AddrSpace
|
||||
from tinygrad.codegen.opt import Opt, OptOps
|
||||
from tinygrad.schedule.rangeify import BufferizeOpts, Kernel
|
||||
lcls:dict[str, Any] = {"inf": math.inf, "nan": math.nan, "KernelInfo": KernelInfo, "Kernel": Kernel,
|
||||
"Opt": Opt, "OptOps": OptOps, "BufferizeOpts": BufferizeOpts, "AddrSpace": AddrSpace}
|
||||
exec(code, None, lcls)
|
||||
return lcls['ast']
|
||||
|
||||
def test_pyrender(test_ast:UOp, check_parents=True):
|
||||
code = pyrender(test_ast)
|
||||
ast:UOp = eval_pyrender(code)
|
||||
if ast is not test_ast:
|
||||
if check_parents:
|
||||
for u in test_ast.toposort(): test_pyrender(u, check_parents=False)
|
||||
raise RuntimeError(f"PYRENDER ISSUE:\nSTR MATCH: {str(test_ast) == str(ast)}\nUOP:\n{test_ast}\nPRODUCED:\n{ast}\nCODE:\n{code}")
|
||||
return code
|
||||
|
||||
# *** what was symbolic.py ***
|
||||
|
||||
|
||||
@@ -382,6 +382,8 @@ symbolic = symbolic_simple+commutative+PatternMatcher([
|
||||
tuple(flatten([(y,) if y.op in {Ops.RANGE, Ops.IF, Ops.STORE, Ops.KERNEL, Ops.BARRIER, Ops.END, Ops.UNROLL} else y.src for y in x.src[1:]])))),
|
||||
# after with 1 src is just src[0]
|
||||
(UPat(Ops.AFTER, src=(UPat.var("s"),)), lambda s: s),
|
||||
# VECTORIZE/CONST
|
||||
(UPat(Ops.VECTORIZE, src=UPat(Ops.CONST), name="vec"), lambda vec: UOp.const(vec.dtype, tuple(x.arg for x in vec.src))),
|
||||
])+gep_pushing
|
||||
|
||||
symbolic_flat = symbolic+PatternMatcher([
|
||||
@@ -511,8 +513,7 @@ sym = symbolic_flat+pm_simplify_valid+PatternMatcher([
|
||||
# LOAD/STORE -> NOOP
|
||||
(UPat.var('x').store(UPat.var('x').load(), allow_any_len=True), lambda x: None if x.dtype.addrspace != AddrSpace.REG else x.src[0].src[0]),
|
||||
(UPat(Ops.LOAD, src=(UPat.cvar('c'))), lambda c: c),
|
||||
# VECTORIZE/CONST, VECTORIZE/GEP
|
||||
(UPat(Ops.VECTORIZE, src=UPat(Ops.CONST), name="vec"), lambda vec: UOp.const(vec.dtype, tuple(x.arg for x in vec.src))),
|
||||
# VECTORIZE/GEP
|
||||
(UPat(Ops.VECTORIZE, src=UPat(Ops.GEP, src=(UPat.var("x"),)), name="vec"), lambda vec,x: x.gep(tuple(y.arg[0] for y in vec.src))),
|
||||
# reorder ALU/VECTORIZE
|
||||
(UPat(GroupOp.ALU, src=(UPat(Ops.VECTORIZE, src=UPat(name='x')), UPat(Ops.VECTORIZE, src=UPat(name='y'))), name='alu'),
|
||||
|
||||
@@ -70,11 +70,10 @@ const drawGraph = (data) => {
|
||||
nodes.selectAll("rect").data(d => [d]).join("rect").attr("width", d => d.width).attr("height", d => d.height).attr("fill", d => d.color)
|
||||
.attr("x", d => -d.width/2).attr("y", d => -d.height/2);
|
||||
const STROKE_WIDTH = 1.4;
|
||||
nodes.selectAll("g.label").data(d => [d]).join("g").attr("class", "label").attr("transform", d => {
|
||||
const x = (d.width-d.padding*2)/2;
|
||||
const y = (d.height-d.padding*2)/2+STROKE_WIDTH;
|
||||
return `translate(-${x}, -${y})`;
|
||||
}).selectAll("text").data(d => {
|
||||
const labels = nodes.selectAll("g.label").data(d => [d]).join("g").attr("class", "label").attr("transform", d => {
|
||||
return d.labelWidth != null ? `translate(-${d.labelWidth/2}, -${d.labelHeight/2+STROKE_WIDTH*2})` : null;
|
||||
});
|
||||
labels.selectAll("text").data(d => {
|
||||
const ret = [[]];
|
||||
for (const { st, color } of parseColors(d.label, defaultColor="initial")) {
|
||||
const lines = st.split("\n");
|
||||
|
||||
@@ -8,7 +8,7 @@ onmessage = (e) => {
|
||||
const { graph, additions } = 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:"", className:"overlay", padding:0});
|
||||
if (additions.length !== 0) g.setNode("addition", {label:"", className:"overlay"});
|
||||
for (let [k, {label, src, ref, ...rest }] of Object.entries(graph)) {
|
||||
// adjust node dims by label size (excluding escape codes) + add padding
|
||||
let [width, height] = [0, 0];
|
||||
@@ -16,7 +16,7 @@ onmessage = (e) => {
|
||||
width = Math.max(width, ctx.measureText(line).width);
|
||||
height += LINE_HEIGHT;
|
||||
}
|
||||
g.setNode(k, {width:width+NODE_PADDING*2, height:height+NODE_PADDING*2, padding:NODE_PADDING, label, ref, id:k, ...rest});
|
||||
g.setNode(k, {width:width+NODE_PADDING*2, height:height+NODE_PADDING*2, label, labelHeight:height, labelWidth:width, ref, id:k, ...rest});
|
||||
// add edges
|
||||
const edgeCounts = {}
|
||||
for (const [_, s] of src) edgeCounts[s] = (edgeCounts[s] || 0)+1;
|
||||
|
||||
@@ -53,10 +53,8 @@ class GraphRewriteDetails(TypedDict):
|
||||
def shape_to_str(s:tuple[sint, ...]): return "(" + ','.join(srender(x) for x in s) + ")"
|
||||
def mask_to_str(s:tuple[tuple[sint, sint], ...]): return "(" + ','.join(shape_to_str(x) for x in s) + ")"
|
||||
def pystr(u:UOp, i:int) -> str:
|
||||
if isinstance(trace.keys[i].ret, ProgramSpec):
|
||||
try: return "\n".join(pyrender(u))
|
||||
except Exception: pass
|
||||
return str(u)
|
||||
try: return pyrender(u)
|
||||
except Exception: return str(u)
|
||||
|
||||
def uop_to_json(x:UOp, ignore_indexing=False) -> dict[int, dict]:
|
||||
assert isinstance(x, UOp)
|
||||
|
||||
Reference in New Issue
Block a user