from typing import Optional, Any import unittest, math import numpy as np from tinygrad.tensor import Tensor, _to_np_dtype from tinygrad.helpers import Context from tinygrad.dtype import dtypes, DType, AddrSpace, ConstFloat # noqa: F401 from tinygrad.device import Buffer, Device from tinygrad.uop.ops import Ops, UOp, KernelInfo, AxisType from tinygrad.renderer.cstyle import CStyleLanguage from tinygrad.engine.realize import run_linear from tinygrad.renderer.ptx import PTXRenderer from tinygrad.runtime.ops_python import PythonRenderer from test.helpers import buffer_uops def run_uops(uops_list:list[UOp], bufs:list[Buffer]): buf_uops = buffer_uops(UOp.sink(*uops_list), bufs) run_linear(UOp(Ops.LINEAR, src=(UOp.sink(*uops_list, arg=KernelInfo()).call(*buf_uops),))) def uop(uops:list[UOp], op:Ops, dtype:Optional[DType], src:tuple[UOp, ...], arg:Any=None) -> UOp: if op is Ops.CONST: uops.append(UOp.const(arg).cast(dtype)) elif op is Ops.PARAM: uops.append(UOp.param(arg, dtype, 1)) else: uops.append(UOp(op, tuple(src), arg)) return uops[-1] def _test_single_value(vals, op, dts): uops = [] output_dtype = dtypes.bool if op in (Ops.CMPLT, Ops.CMPNE) else dts[-1] buf_store = uop(uops, Ops.PARAM, output_dtype, (), 0) buf_loads = [uop(uops, Ops.PARAM, dtype, (), i+1) for i,dtype in enumerate(dts)] loads = (buf_loads[i].index(uop(uops, Ops.CONST, dtypes.int32, (), 0)) for i, dtype in enumerate(dts)) alu = uop(uops, op, output_dtype, loads) out = uop(uops, Ops.STORE, dtypes.void, (buf_store.index(uop(uops, Ops.CONST, dtypes.int32, (), 0)), alu)) buf = Buffer(Device.DEFAULT, output_dtype.itemsize).allocate() buf2 = [Buffer(Device.DEFAULT, dtype.itemsize, initial_value=np.array([a], dtype=_to_np_dtype(dtype)).tobytes()) for a,dtype in zip(vals, dts)] run_uops([out], [buf]+buf2) return np.frombuffer(buf.as_memoryview(), _to_np_dtype(output_dtype))[0] def _test_single_value_const(vals, op, dts): uops = [] output_dtype = dtypes.bool if op in (Ops.CMPLT, Ops.CMPNE) else dts[-1] buf_store = uop(uops, Ops.PARAM, output_dtype, (), 0) loads = (uop(uops, Ops.CONST, dtype, [], a) for a,dtype in zip(vals, dts)) alu = uop(uops, op, output_dtype, loads) out = buf_store[UOp.const(0).cast(dtypes.int32)].store(alu) buf = Buffer(Device.DEFAULT, output_dtype.itemsize).allocate() run_uops([out], [buf]) return np.frombuffer(buf.as_memoryview(), _to_np_dtype(output_dtype))[0] def _test_uops_result(output_dtype, uops, res): # uops = [] buf_store = uop(uops, Ops.PARAM, output_dtype, (), 0) # res = output_fn(uops) out = uop(uops, Ops.STORE, dtypes.void, (buf_store.index(uop(uops, Ops.CONST, dtypes.int32, (), 0)), res)) buf = Buffer(Device.DEFAULT, output_dtype.itemsize).allocate() run_uops([out], [buf]) return np.frombuffer(buf.as_memoryview(), _to_np_dtype(output_dtype))[0] @unittest.skipUnless(isinstance(Device[Device.DEFAULT].renderer, (CStyleLanguage, PythonRenderer)) and dtypes.uint64 in Device[Device.DEFAULT].renderer.supported_dtypes(), "requires buffer bitcast and 64-bit ints") class TestBitcastBufferView(unittest.TestCase): @Context(SPEC=2) def test_load(self): val = 0x1122334455667788 src, out = UOp.param(0, dtypes.uint32, 4), UOp.param(1, dtypes.uint64, 1) ibuf = Buffer(Device.DEFAULT, 16, initial_value=np.array([0, 0x55667788, 0x11223344, 0], dtype=np.uint32).tobytes()) obuf = Buffer(Device.DEFAULT, 8).allocate() run_uops([out.index(0).store(src.shrink(((1, 3),)).bitcast(dtypes.uint64).index(0))], [ibuf, obuf]) self.assertEqual(np.frombuffer(obuf.as_memoryview(), dtype=np.uint64)[0], val) @Context(SPEC=2) def test_store(self): val = 0x1122334455667788 dst = UOp.param(0, dtypes.uint32, 6) buf = Buffer(Device.DEFAULT, 24, initial_value=bytes(24)) view = dst.shrink(((1, 5),)).bitcast(dtypes.uint64) # two stores through one view: it must inline, not get a declared vector-pointer run_uops([view.index(0).store(val ^ 0xff), view.index(1).store(val)], [buf]) self.assertEqual(np.frombuffer(buf.as_memoryview(), dtype=np.uint64, count=2, offset=4).tolist(), [val ^ 0xff, val]) def test_vector_load_store(self): for src_dt, dst_dt in [(dtypes.uint8, dtypes.uint32), (dtypes.uint32, dtypes.uint8)]: with self.subTest(src=src_dt, dst=dst_dt): src, dst = [UOp.param(i, dt, 16 // dt.itemsize) for i, dt in enumerate((src_dt, dst_dt))] src, dst = [b.bitcast(dtypes.uint32).index(UOp.stack(*[UOp.const(i) for i in range(4)])) for b in (src, dst)] bufs = [Buffer(Device.DEFAULT, 16, initial_value=bytes(range(16)) if i == 0 else bytes(16)) for i, dt in enumerate((src_dt, dst_dt))] run_uops([dst.store(src.load())], bufs) self.assertEqual(bytes(bufs[1].as_memoryview()), bytes(range(16))) class TestUOps(unittest.TestCase): def _equal(self, v1, v2): assert isinstance(v2, (float, int, bool)) if isinstance(v2, float): np.testing.assert_allclose(v1, v2, rtol=2e-7) else: np.testing.assert_equal(v1, v2) def _test_uop_fxn(self, op, fxn, dts=(dtypes.float32, )): for f in [_test_single_value, _test_single_value_const]: for a in [-2.0, 0.0, 1.0]: a = dts[0].const(a) self._equal(f([a], op, dts), fxn(a)) def _test_bop_fxn(self, op, fxn, dts=(dtypes.float32, )*2, no_b_zero=False, no_b_neg=False): for f in [_test_single_value, _test_single_value_const]: for a in [-2.0, 0.0, 1.0]: for b in [-3.0, 1.0] + ([] if no_b_zero else [0.0]): a = dts[0].const(a) b = dts[1].const(abs(b) if no_b_neg else b) self._equal(f([a,b], op, dts), fxn(a,b)) def _test_top_fxn(self, op, fxn, dts=(dtypes.float32, )*3): for f in [_test_single_value, _test_single_value_const]: for a in [-2.0, 0, 1]: for b in [-3.0, 3.0]: for c in [-4.0, 4.0]: a = dts[0].const(a) b = dts[1].const(b) c = dts[2].const(c) self._equal(f([a,b,c], op, dts), fxn(a,b,c)) class TestFloatUOps(TestUOps): @unittest.skipIf(Device.DEFAULT == "CPU", 'not supported as uop') def test_exp2(self): self._test_uop_fxn(Ops.EXP2, lambda a: np.exp2(a)) @unittest.skipIf(Device.DEFAULT == "CPU", 'not supported as uop') 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.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) def test_mul(self): self._test_bop_fxn(Ops.MUL, lambda a,b: a*b) def test_max(self): self._test_bop_fxn(Ops.MAX, lambda a,b: max(a,b)) def test_cmplt(self): self._test_bop_fxn(Ops.CMPLT, lambda a,b: a>int(b), (dtypes.int32, dtypes.int32), no_b_neg=True) @unittest.skipUnless(isinstance(Device[Device.DEFAULT].renderer, (PTXRenderer, CStyleLanguage)), "only ptx and cstyle use bitshifts") def test_shl_int32(self): self._test_bop_fxn(Ops.SHL, lambda a,b: int(a)<