Compare commits

...
Author SHA1 Message Date
George HotzandGitHub 26f2049d43 Merge branch 'master' into bitcast_spec 2026-08-12 19:24:33 -07:00
geohot ff0cb28c21 skip slow whisper tests 2026-08-12 13:12:37 -07:00
qazalandGitHub ed8297a102 kerenl opts test from nan in llama 8b (#17510)
* all2all

* nan

* remove that

* less

* has_local

* only the nan change here

* use nice getitem syntax for INDEX

* work

* remove

* even simpler
2026-08-13 04:07:30 +09:00
RaineandGitHub de04781b36 simplify equivalent const max (#17505)
* add const max folds

* add regression test

* move
2026-08-12 08:39:29 -07:00
nimlgenandGitHub 4f106ebe87 hcq2: enqueue speed (#17504) 2026-08-12 13:08:53 +03:00
qazalandGitHub 04c271ac41 simplify digitalocean_mi350x (#17502)
* simplify digitalocean_mi350x

* no hardcoded rocm path
2026-08-12 16:00:04 +09:00
qazalandGitHub 2e5a9a4121 no hardcoded device names in test_sliced_buffer_function (#17501) 2026-08-12 15:19:25 +09:00
qazalandGitHub e1013a6356 llama: create dataset cache by default in dev_beam (#17500) 2026-08-12 15:03:39 +09:00
wozeparrotandGitHub f891f5ffd0 gptoss: route lm_head thru asm_gemm (#17497) 2026-08-11 18:32:50 -07:00
George HotzandGitHub 3686a1758f mac/rdma imports lazy (#17496)
* mac/rdma imports lazy

* ish

* fixes
2026-08-11 17:23:11 -07:00
George HotzandGitHub 4a253db9b4 minor cleanups to improve import speed (#17495)
* minor cleanups to improve import speed

* dumb
2026-08-11 16:06:25 -07:00
sirhcmandGitHub 479ffb0cda remove Ops.SLICE (#17492) 2026-08-11 18:50:04 -04:00
geohot a8d13380f9 simpler, but not inverse 2026-06-25 10:24:01 -07:00
geohot afb0463f10 fix bitcast 2026-06-25 10:11:13 -07:00
geohot 947d6a3c69 enforce a cast for bitcast 2026-06-25 10:00:14 -07:00
29 changed files with 158 additions and 146 deletions
+6 -2
View File
@@ -12,7 +12,7 @@ from tinygrad.helpers import Timing, colored, GlobalCounters, profile_marker
from tinygrad.uop.ops import Ops, UOp
from extra.models.llama import apply_rotary_emb
from extra.llama_kernels.rmsnorm import rmsnorm
from extra.gemm.cdna_asm_gemm import _mx_block_scale, _mx_block_scale_3d, quantize_mxfp8
from extra.gemm.cdna_asm_gemm import _mx_block_scale, _mx_block_scale_3d, quantize_mxfp8, asm_gemm, can_use_asm_gemm
from extra.gemm.moe_gemm import grouped_mx_gemm
from extra.gemm.moe_routing import route, dispatch, combine
@@ -263,7 +263,11 @@ class GPTOSS:
w_down=self.w_down[i], w_down_scale=self.w_down_scale[i], w_down_bias=self.w_down_bias[i])
h, *_ = self.run_layer(h, freqs_cis, mask_full, i % 2 == 0, attn_kwargs, ffn_kwargs, save=save)
logits = self.norm(h) @ self.output.T
h_normed = self.norm(h)
pad = (-self.dim) % 256
h_padded, w_padded = h_normed.pad((None, None, (0, pad))), self.output.pad(((0, 0), (0, pad)))
if ASM_GEMM and can_use_asm_gemm(h_padded, w_padded.T): logits = asm_gemm(h_padded, w_padded.T)
else: logits = h_normed @ self.output.T
return logits
def _get_pads(uop:UOp) -> list[UOp]:
@@ -46,7 +46,7 @@ export DATA_SEED=${DATA_SEED:-5760}
export JITBEAM=${JITBEAM:-3}
export BEAM_UOPS_MAX=6000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5 BEAM_PADTO=1
export FAKEDATA=${FAKEDATA:-1} BENCHMARK=${BENCHMARK:-10}
export FAKEDATA=${FAKEDATA:-$([[ "$DEV" == NULL:* ]] && echo 1 || echo 0)} BENCHMARK=${BENCHMARK:-10}
if [ -z "$FULL_LAYERS" ]; then
export LLAMA_LAYERS=${LLAMA_LAYERS:-2}
fi
@@ -1,8 +1,8 @@
#!/usr/bin/env bash
export PYTHONPATH="."
export PATH="/opt/rocm-7.1.1/bin:$PATH"
export ROCM_PATH="/opt/rocm-7.1.1"
export ROCM_PATH=${ROCM_PATH:-/opt/rocm-7.1.1}
export PATH="$ROCM_PATH/bin:$PATH"
export DEV=${DEV:-AMD}
export CHECK_OOB=0
export REWRITE_STACK_LIMIT=5000000 HCQDEV_WAIT_TIMEOUT_MS=240000
+5 -19
View File
@@ -74,8 +74,8 @@ rclone config create mlc-training s3 provider=Cloudflare \
secret_access_key=a53625c4d45e3ca8ac0df8a353ea3a41ffc3292aa25259addd8b7dc5a6ce2936 \
endpoint=c2686074cb2caf5cbaf6d134bdba8b47.r2.cloudflarestorage.com
mkdir -p /root/datasets/c4-8b
rclone copy mlc-training:mlcommons-training-wg-public/llama3_1/datasets/c4/llama3_1_8b/ /root/datasets/c4-8b/ -P
mkdir -p /raid/datasets/c4-8b
rclone copy mlc-training:mlcommons-training-wg-public/llama3_1/datasets/c4/llama3_1_8b/ /raid/datasets/c4-8b/ -P
```
Files downloaded (~85GB total, ~6 minutes):
@@ -85,13 +85,6 @@ Files downloaded (~85GB total, ~6 minutes):
- `c4-validation-91205-samples.en_text_document.idx` (1.8 MB)
- `LICENSE.txt`, `NOTICE.txt`
### Symlink for the submission script
The `dev_run.sh` script hardcodes `BASEDIR="/raid/datasets/c4-8b/"`. Symlink:
```bash
mkdir -p /raid/datasets
ln -s /root/datasets/c4-8b /raid/datasets/c4-8b
```
## Phase 4: wandb Login
```bash
wandb login
@@ -100,7 +93,7 @@ Enter API key from https://wandb.ai/authorize
## Phase 5: Run Training
### 5.1 Smoke test (beam search, 2 layers, fake data)
### 5.1 Smoke test (beam search, 2 layers, real data)
Always run beam first to validate the pipeline:
```bash
cd /root/tinygrad
@@ -108,7 +101,7 @@ COMGR_PATH=/opt/rocm/lib/libamd_comgr.so \
COMGR_3_PATH=/opt/rocm/lib/libamd_comgr.so \
CC=/opt/rocm/core-7.14/lib/llvm/bin/clang \
DEV=AMD:HIP \
ROCM_PATH=/opt/rocm BASEDIR=/root/datasets/c4-8b/ \
ROCM_PATH=/opt/rocm \
bash examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama31_8b/implementations/tinybox_8xMI350X/dev_beam.sh
```
@@ -119,7 +112,7 @@ COMGR_PATH=/opt/rocm/lib/libamd_comgr.so \
COMGR_3_PATH=/opt/rocm/lib/libamd_comgr.so \
CC=/opt/rocm/core-7.14/lib/llvm/bin/clang \
DEV=AMD:HIP \
ROCM_PATH=/opt/rocm BASEDIR=/root/datasets/c4-8b/ \
ROCM_PATH=/opt/rocm \
WANDB=1 \
bash examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama31_8b/implementations/tinybox_8xMI350X/dev_run.sh
```
@@ -133,7 +126,6 @@ WANDB=1 \
| `CC` | `/opt/rocm/core-7.14/lib/llvm/bin/clang` | System clang doesn't know gfx950; must use ROCm's bundled clang |
| `DEV` | `AMD:HIP` | Force HIPRenderer (comgr-based) over HIPCCRenderer (hipcc subprocess) |
| `ROCM_PATH` | `/opt/rocm` | Script defaults to `/opt/rocm-7.1.1` which doesn't exist |
| `BASEDIR` | `/root/datasets/c4-8b/` | Where C4 dataset was downloaded (script hardcodes `/raid/datasets/c4-8b/`) |
| `WANDB` | `1` | Enable wandb logging (off by default) |
## Architecture
@@ -173,12 +165,6 @@ ldconfig
### `comgr not available: try setting COMGR_3_PATH?`
comgr 3.x uses a separate module. Set `COMGR_3_PATH=/opt/rocm/lib/libamd_comgr.so` too.
### `FileNotFoundError: '/raid/datasets/c4-8b/...'`
Script hardcodes `BASEDIR`. Either symlink or edit the script:
```bash
mkdir -p /raid/datasets && ln -s /root/datasets/c4-8b /raid/datasets/c4-8b
```
### `No such file or directory: 'clang'`
Install clang: `apt-get install -y clang` (for CPU compilation).
For gfx950 HIP compilation, comgr (not clang) is used — ensure the ROCm 7.14 comgr 3.3 is properly loaded via `COMGR_PATH` and `COMGR_3_PATH`.
+2 -3
View File
@@ -422,9 +422,8 @@ class TestCustomKernel(unittest.TestCase):
return Tensor.custom_kernel(y, x, fxn=custom_add_one_kernel)[0]
GlobalCounters.reset()
y = run(x[0]).realize()
# it's copying the input and the output
# TODO: subbuffer usage has runtime specific behavior, this will be fixed after the removal of SLICE.
assert_kernel_count(2 if y.device in ("CL", "WEBGPU") else 1)
# backends that support contiguous views don't launch extra kernels
assert_kernel_count(2 if x[0].uop.contiguous_view() is None else 1)
self.assertEqual(y.tolist(), [1, 2, 3, 4])
@Context(DEV="CPU")
+2
View File
@@ -74,6 +74,7 @@ class TestWhisper(unittest.TestCase):
err
)
@slow
def test_transcribe_file1(self):
self.assertEqual(transcribe_file(self.model, self.enc, TEST_FILE_1), TRANSCRIPTION_1)
@@ -89,6 +90,7 @@ class TestWhisper(unittest.TestCase):
self.assertEqual(TRANSCRIPTION_1, transcriptions[0])
self.assertEqual(TRANSCRIPTION_2, transcriptions[1])
@slow
def test_transcribe_batch21(self):
waveforms = [load_file_waveform(TEST_FILE_2), load_file_waveform(TEST_FILE_1)]
transcriptions = transcribe_waveform(self.model, self.enc, waveforms)
+45
View File
@@ -0,0 +1,45 @@
import unittest
from tinygrad import UOp, dtypes
from tinygrad.uop.ops import shape_to_shape_arg, ParamArg, Ops, AddrSpace
def placeholder(shape, dtype, slot):
return UOp(Ops.PARAM, dtype, (shape_to_shape_arg(shape),), arg=ParamArg(slot, AddrSpace.GLOBAL))
class TestBitcastSpec(unittest.TestCase):
def test_bitcast_no_shape_change(self):
pl = placeholder((10,10), dtypes.int, 0)
out = pl.bitcast(dtypes.float)
self.assertEqual(out.shape, (10,10))
def test_bitcast_increase_shape(self):
pl = placeholder((10,10), dtypes.int, 0)
out = pl.bitcast(dtypes.short)
self.assertEqual(out.shape, (10,20))
def test_bitcast_decrease_shape(self):
pl = placeholder((10,10), dtypes.int, 0)
out = pl.bitcast(dtypes.long)
self.assertEqual(out.shape, (10,5))
def test_bitcast_remove_ones(self):
pl = placeholder((10,2), dtypes.int, 0)
out = pl.bitcast(dtypes.long)
self.assertEqual(out.shape, (10,1))
def test_bitcast_remove_ones_full(self):
pl = placeholder((2,), dtypes.int, 0)
out = pl.bitcast(dtypes.long)
self.assertEqual(out.shape, (1,))
def test_bitcast_add_ones_full(self):
pl = placeholder((), dtypes.long, 0)
out = pl.bitcast(dtypes.int)
self.assertEqual(out.shape, (2,))
def test_bitcast_add_ones_full_uchar(self):
pl = placeholder((), dtypes.long, 0)
out = pl.bitcast(dtypes.uchar)
self.assertEqual(out.shape, (8,))
if __name__ == '__main__':
unittest.main()
+8 -3
View File
@@ -27,10 +27,15 @@ def _make_linear(buffer_lists, copies=None):
calls.append(UOp(Ops.CALL, src=(src0, *bufs)))
return UOp(Ops.LINEAR, src=tuple(calls))
def _get_planned_view(buf:UOp) -> tuple[UOp, int, int]|None:
view = buf.src[0] if buf.op is Ops.BITCAST else buf
if view.op is not Ops.SHRINK or view.src[0].op is not Ops.BUFFER: return None
return (arena:=view.src[0]), view.src[1].val * arena.dtype.itemsize, view.src[2].val * arena.dtype.itemsize
def _get_arena(buf, linear, result):
for orig_si, new_si in zip(linear.src, result.src):
for orig, new in zip(orig_si.src[1:], new_si.src[1:]):
if orig is buf and new.op is Ops.SLICE: return new.src[0]
if orig is buf and (planned:=_get_planned_view(new)) is not None: return planned[0]
return None
def check_assign(buffer_lists, copies=None):
@@ -41,8 +46,8 @@ def check_assign(buffer_lists, copies=None):
replace_map: dict[int, tuple[UOp, int, int]] = {}
for orig_si, new_si in zip(linear.src, result.src):
for orig, new in zip(orig_si.src[1:], new_si.src[1:]):
if new.op is Ops.SLICE and id(orig) not in replace_map:
replace_map[id(orig)] = (new.src[0], new.src[1].val * new.src[0].dtype.itemsize, new.arg * new.dtype.itemsize)
if (planned:=_get_planned_view(new)) is not None and id(orig) not in replace_map:
replace_map[id(orig)] = planned
# verify pinned buffers are not planned
for buf in held_bufs:
+5
View File
@@ -948,6 +948,11 @@ class TestSymbolic(unittest.TestCase):
self.helper_test_variable(cond.where(u0, u1), 0, 1, "((a<2)!=True)")
self.helper_test_variable(cond.where(u0, u1).where(u0, u1), 0, 1, "(a<2)")
def test_equivalent_const_max(self):
x = Variable("x", -10, 10)
self.helper_test_variable((x < 0).where(0, x), 0, 10, "x.maximum(0)")
self.helper_test_variable((0 < x).where(x, 0), 0, 10, "x.maximum(0)")
def test_where_combine(self):
cond = Variable("x", 0, 3) < 2
a = Variable("a", 0, 3)
+9
View File
@@ -239,6 +239,15 @@ class TestKernelOpts(unittest.TestCase):
helper_linearizer_opt(a.sum().exp(), [[Opt(OptOps.PADTO, 0, 32)],])
helper_linearizer_opt(a.sum(0).exp(), [[Opt(OptOps.PADTO, 1, 32)],])
@unittest.skipUnless(Device[Device.DEFAULT].renderer.has_local, "test requires locals")
@unittest.skipUnless(Device[Device.DEFAULT].renderer.has_shared, "test requires shared")
@unittest.expectedFailure
def test_padto_group_full_unroll_sum(self):
a = Tensor.ones(2, 28, 4096, dtype=dtypes.bfloat16).realize()
out = ((a * 0.5).float().square()).sum(axis=(0, 2))
opts_to_apply = [Opt(OptOps.GROUPTOP, 1, 256), Opt(OptOps.PADTO, 3, 32), Opt(OptOps.UNROLL, 2, 0), Opt(OptOps.UPCAST, 0, 7)]
helper_linearizer_opt(out, [opts_to_apply], check_default_opt=False)
def test_padto_sum(self):
N = 18
# NOTE: this setup prevents 17 * 17 contiguous merged into one dimension
+15 -25
View File
@@ -1,5 +1,4 @@
import unittest
from unittest.mock import MagicMock
from tinygrad import Device
from tinygrad.uop.ops import Ops, UOp
from tinygrad.dtype import dtypes
@@ -11,36 +10,27 @@ class TestMetalGraph(unittest.TestCase):
self.MetalGraph = MetalGraph
self.dev = Device[Device.DEFAULT]
def metal_buf(self, offset):
buf = MagicMock()
if offset > 0:
buf.op = Ops.SLICE
src = MagicMock()
src.dtype = dtypes.uint8
buf.src = (src, UOp.const(offset))
buf.dtype = dtypes.uint8
else:
buf.op = Ops.BUFFER
buf.device = Device.DEFAULT
return buf
def metal_buf(self, offset, bitcast=False):
size = 4 if bitcast else 1
buf = UOp.new_buffer(Device.DEFAULT, offset+size, dtypes.uint8)
if offset: buf = buf[offset:offset+size]
return buf.bitcast(dtypes.float32) if bitcast else buf
def call(self, *bufs):
c = MagicMock()
c.src = (MagicMock(op=Ops.PROGRAM),) + tuple(bufs)
return c
def supports_uop(self, *bufs):
return self.MetalGraph.supports_uop([self.dev], UOp(Ops.PROGRAM, src=(UOp.sink(),)).call(*bufs))
def test_supports_uop_normal_offset(self):
assert self.MetalGraph.supports_uop([self.dev], self.call(self.metal_buf(0), self.metal_buf(100), self.metal_buf(0xFFFFFFFF))) is True
assert self.supports_uop(self.metal_buf(0), self.metal_buf(100), self.metal_buf(0xFFFFFFFF)) is True
def test_supports_uop_overflow_offset(self):
assert self.MetalGraph.supports_uop([self.dev], self.call(self.metal_buf(0), self.metal_buf(0x100000000))) is False
assert self.supports_uop(self.metal_buf(0), self.metal_buf(0x100000000)) is False
def test_supports_uop_nonmetal_buf(self):
# non-SLICE ops should not be checked for offset
buf = MagicMock()
buf.op = Ops.BUFFER
buf.device = Device.DEFAULT
self.MetalGraph.supports_uop([self.dev], self.call(buf))
def test_supports_uop_non_view_buf(self):
assert self.supports_uop(self.metal_buf(0)) is True
def test_supports_uop_bitcast(self):
assert self.supports_uop(self.metal_buf(0xFFFFFFFF, bitcast=True)) is True
assert self.supports_uop(self.metal_buf(0x100000000, bitcast=True)) is False
if __name__ == "__main__":
unittest.main()
+1 -3
View File
@@ -44,8 +44,6 @@ def graph_split_rewrite(linear:UOp, max_batch_size:int=0) -> UOp:
current_batch, current_batch_devs = [], []
for si in linear.src:
if si.src[0].op is Ops.SLICE: continue
devs = dedup([Device[x] for b in si.src[1:] if b.op is not Ops.BIND for x in (b.device if isinstance(b.device, tuple) else (b.device,))])
graph_t = graph_class(devs[0]) if devs[0].graph is not None else None
@@ -180,7 +178,7 @@ class CapturedJit(Generic[ReturnType]):
if call.op is not Ops.CALL: continue
arg_uops = get_call_arg_uops(call)
outs, ins = get_call_outs_ins(call)
out |= {arg_uops[k] for k in set(outs) - set(ins) if arg_uops[k].op in (Ops.BUFFER, Ops.SLICE)}
out |= {b for k in set(outs) - set(ins) if (b:=u if (cv:=(u:=arg_uops[k]).contiguous_view()) is None else cv[0]).op is Ops.BUFFER}
return out
def __call__(self, input_uops:list[UOp], var_vals:dict[str, int]) -> ReturnType:
+7 -18
View File
@@ -4,7 +4,7 @@ import time, random, itertools, math, contextlib, weakref, array
from dataclasses import dataclass, replace, field
from tinygrad.helpers import colored, DEBUG, GlobalCounters, ansilen, all_int, prod, flatten, Context, getenv, to_tuple
from tinygrad.helpers import BEAM, size_to_str, time_to_str, VALIDATE_WITH_CPU, PROFILE, ProfilePointEvent, cpu_events, wait_cond
from tinygrad.uop.ops import Ops, PatternMatcher, UOp, UPat, AxisType, sym_infer, buffers, graph_rewrite
from tinygrad.uop.ops import Ops, PatternMatcher, UOp, UPat, AxisType, sym_infer, graph_rewrite
from tinygrad.device import Device, Buffer, MultiBuffer, ProfileGraphEntry
from tinygrad.renderer import Estimates
from tinygrad.codegen import to_program
@@ -17,7 +17,7 @@ def get_call_arg_uops(call:UOp) -> tuple[UOp, ...]: return tuple(s for s in call
def get_call_outs_ins(call:UOp) -> tuple[tuple[int, ...], tuple[int, ...]]:
ast = call.src[0]
if ast.op is Ops.PROGRAM: return tuple(ast.arg.outs), tuple(ast.arg.ins)
if ast.op in (Ops.COPY, Ops.SLICE): return (0,), (1,)
if ast.op is Ops.COPY: return (0,), (1,)
if ast.op is Ops.CUSTOM_FUNCTION and ast.arg == "encdec": return (0,), tuple(range(1, len(get_call_arg_uops(call))))
return (), ()
@@ -27,9 +27,6 @@ def get_call_name(call:UOp, bufs:Sequence[Buffer|UOp], var_vals:dict[str, int]|N
ast, arg_uops = call.src[0], get_call_arg_uops(call)
if ast.op is Ops.PROGRAM: return ast.arg.name
if ast.op is Ops.SLICE:
offset = ast.src[1].val * arg_uops[1].dtype.itemsize
return colored(f"view {_uop_sz_to_str(arg_uops[0]):>10} @ {offset:<10d}", "yellow")
if ast.op is Ops.COPY: return colored(f"copy {_uop_sz_to_str(arg_uops[0]):>10}, {_dev_str(bufs[0]):>7s} <- {_dev_str(bufs[1]):7s}", "yellow")
if ast.op is Ops.CUSTOM_FUNCTION and ast.arg == "encdec": return colored(f"enc/dec {_uop_sz_to_str(arg_uops[0])}", "yellow")
if ast.op is Ops.CUSTOM_FUNCTION and ast.arg == "graph": return colored(f"batched {len(ast.src[0].src)}", "cyan")
@@ -140,7 +137,7 @@ class ExecContext:
cache: bool = True
def _resolve(b:UOp, inputs:tuple[UOp, ...]) -> UOp:
if b.op in (Ops.SLICE, Ops.MSELECT, Ops.SHRINK) and b.src[0].op is Ops.PARAM: return b.replace(src=(inputs[b.src[0].arg.slot], *b.src[1:]))
if b.op in (Ops.MSELECT, Ops.SHRINK) and b.src[0].op is Ops.PARAM: return b.replace(src=(inputs[b.src[0].arg.slot], *b.src[1:]))
if b.op is Ops.MSTACK: return b.replace(src=tuple(_resolve(x, inputs) for x in b.src))
return inputs[b.arg.slot] if b.op is Ops.PARAM else b
def resolve_params(call:UOp, inputs:tuple[UOp, ...]) -> list[UOp]: return [_resolve(b, inputs) for b in get_call_arg_uops(call)]
@@ -154,13 +151,6 @@ def unwrap_multi(call:UOp, resolved:list[UOp]) -> Iterator[tuple[list[Buffer], d
for x in call.src[0].toposort())
for j, per_dev in enumerate(zip(*[cast(MultiBuffer, b).bufs for b in bufs])): yield list(per_dev), {"_device_num": j} if has_dnum else {}
def exec_view(ctx:ExecContext, call:UOp, ast:UOp) -> float|None:
resolved = resolve_params(call, ctx.input_uops)
bufs = [cast(Buffer, b.buffer) for b in resolved]
bv = bufs[1].view(resolved[0].max_numel(), ast.dtype, ast.src[1].val*bufs[1].dtype.itemsize)
with track_stats(ctx, call, bv.device, [bv, bufs[1]], ctx.var_vals): buffers[resolved[0]] = bv
return None
def exec_copy(ctx:ExecContext, call:UOp, ast:UOp) -> float|None:
for bufs, device_vars in unwrap_multi(call, resolve_params(call, ctx.input_uops)):
dest, src = bufs[0].ensure_allocated(), bufs[1].ensure_allocated()
@@ -224,15 +214,15 @@ def exec_hcq(ctx:ExecContext, call:UOp, ast:UOp) -> float|None:
tms = []
for devices,name,estimates,prof in info.kernels:
for device in devices:
d, tm = cast(Any, Device[device]), None
tm = None
if prof:
d.prof_ents[prof[0]] = ProfileGraphEntry(device, name, *prof)
(d:=cast(Any, Device[device])).prof_ents[prof[0]] = ProfileGraphEntry(device, name, *prof)
if ctx.wait:
d.synchronize(timeout=ctx.timeout)
st, en = (d.signal(x)._buf.cpu_view().view(fmt='Q')[0] for x in prof)
tms.append(tm:=float(en-st)/d.timestamp_divider/1e6)
with track_stats(ctx, call.replace(arg=replace(call.arg, name=name, aux=replace(info, estimates=estimates))), d.device, [], ctx.var_vals) as et:
et[0] = tm
stat_call = call.replace(arg=replace(call.arg, name=name, aux=replace(info, estimates=estimates, kernels=())))
with track_stats(ctx, stat_call, device, [], ctx.var_vals) as et: et[0] = tm
return max(tms) if tms else None
# flatten LINEAR-in-LINEAR: any nested LINEAR child gets inlined into its parent's src
@@ -264,7 +254,6 @@ pm_optimize_local_size = PatternMatcher([
])
pm_exec = PatternMatcher([
(UPat(Ops.CALL, src=(UPat(Ops.SLICE, name="ast"),), name="call", allow_any_len=True), exec_view),
(UPat(Ops.CALL, src=(UPat(Ops.COPY, name="ast"),), name="call", allow_any_len=True), exec_copy),
(UPat(Ops.CALL, src=(UPat(Ops.PROGRAM, name="ast"),), name="call", allow_any_len=True), exec_kernel),
(UPat(Ops.CALL, src=(UPat(Ops.CUSTOM_FUNCTION, arg="encdec", name="ast"),), name="call", allow_any_len=True), exec_encdec),
+8 -8
View File
@@ -1,9 +1,9 @@
from __future__ import annotations
import time
START_TIME = time.perf_counter()
import os, functools, platform, re, contextlib, operator, hashlib, pickle, sqlite3, tempfile, pathlib, string, ctypes, sys, gzip, getpass, gc
import os, functools, re, contextlib, operator, hashlib, pickle, sqlite3, tempfile, pathlib, string, ctypes, sys, gzip, getpass, gc
from collections import defaultdict
import subprocess, shutil, math, types, copyreg, inspect, importlib, decimal, itertools, difflib
import shutil, math, types, copyreg, inspect, importlib, decimal, itertools, difflib
from dataclasses import dataclass, field, replace
from typing import ClassVar, Iterable, Any, TypeVar, Callable, Sequence, TypeGuard, Iterator, Generic, Generator, cast, overload
@@ -13,8 +13,7 @@ U = TypeVar("U")
def prod(x:Iterable[T]) -> T|int: return functools.reduce(operator.mul, x, 1)
# NOTE: helpers is not allowed to import from anything else in tinygrad
OSX, WIN = platform.system() == "Darwin", sys.platform == "win32"
ARCH_X86 = any(x in platform.processor() for x in ("Intel", "i386", "x86_64"))
OSX, WIN = sys.platform == "darwin", sys.platform == "win32"
BASEDIR = pathlib.Path(__file__).parent
# fix colors on Windows, https://stackoverflow.com/questions/12492810/python-how-can-i-make-the-ansi-escape-codes-to-work-also-in-windows
@@ -231,7 +230,7 @@ class _DEV(ContextVar):
DEV, DEBUG, BEAM, NOOPT = _DEV("DEV", ""), ContextVar("DEBUG", 0), ContextVar("BEAM", 0), ContextVar("NOOPT", 0)
IMAGE, FLOAT16, OPENPILOT_HACKS = ContextVar("IMAGE", 0), ContextVar("FLOAT16", 0), ContextVar("OPENPILOT_HACKS", 0)
JIT, JIT_BATCH_SIZE = ContextVar("JIT", 2 if OSX and ARCH_X86 else 1), ContextVar("JIT_BATCH_SIZE", 32)
JIT, JIT_BATCH_SIZE = ContextVar("JIT", 1), ContextVar("JIT_BATCH_SIZE", 32)
CHUNK_SIZE = 2**20 # TinyFS content-addressed store: blob chunk + hash-tree node granularity
WINO, CAPTURING, TRACEMETA, NO_COLOR = ContextVar("WINO", 0), ContextVar("CAPTURING", 1), ContextVar("TRACEMETA", 1), ContextVar("NO_COLOR", 0)
TRAINING = ContextVar("TRAINING", 0)
@@ -454,9 +453,9 @@ def _ensure_downloads_dir() -> pathlib.Path:
if pathlib.Path("/etc/tinybox-release").is_file():
# try creating dir with sudo
if not (downloads_dir := pathlib.Path("/raid/downloads")).exists():
subprocess.run(["sudo", "mkdir", "-p", downloads_dir], check=True)
subprocess.run(["sudo", "chown", "tiny:root", downloads_dir], check=True)
subprocess.run(["sudo", "chmod", "775", downloads_dir], check=True)
system(f"sudo mkdir -p {downloads_dir}")
system(f"sudo chown tiny:root {downloads_dir}")
system(f"sudo chmod 775 {downloads_dir}")
return downloads_dir
return pathlib.Path(cache_dir) / "downloads"
@@ -497,6 +496,7 @@ def fetch_fw(path:str, name:str, sha256:str) -> bytes:
# *** Exec helpers
def system(cmd:str, **kwargs) -> str:
import subprocess
st = time.perf_counter()
try: ret = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT, **kwargs).decode().strip()
except subprocess.CalledProcessError as e:
+4 -1
View File
@@ -1,4 +1,4 @@
import json, math, pathlib, zipfile, pickle, tarfile, struct, functools, io, zlib
import json, math, pathlib, struct, functools, io, zlib
from collections import OrderedDict
from typing import Any, Callable, BinaryIO, Iterable, cast
from tinygrad.tensor import Tensor
@@ -219,6 +219,7 @@ def load_state_dict(model, state_dict:dict[str, Tensor], strict=True, verbose=Tr
@accept_filename
def zip_extract(t: Tensor) -> dict[str, Tensor]:
import zipfile
files: dict[str, Tensor] = {}
with zipfile.ZipFile(TensorIO(t), "r") as myzip:
# sadly, the extra length needs to be read from the local header of each file.
@@ -249,6 +250,7 @@ def tar_extract(t: Tensor) -> dict[str, Tensor]:
tensors = nn.state.tar_extract(Tensor(pathlib.Path("archive.tar")))
```
"""
import tarfile
with tarfile.open(fileobj=TensorIO(t), mode="r") as tar:
return {member.name:t[member.offset_data:member.offset_data+member.size] for member in tar if member.type == tarfile.REGTYPE}
@@ -303,6 +305,7 @@ def torch_load(t:Tensor) -> dict[str, Tensor]:
"FloatTensor": None, "Parameter": Parameter}
whitelist = {"torch", "collections", "numpy", "_codecs"} # NOTE: this is not for security, only speed
class Dummy: pass
import pickle, zipfile, tarfile
class TorchPickle(pickle.Unpickler):
def find_class(self, module, name):
module_root = module.split(".")[0]
+3 -2
View File
@@ -1,4 +1,4 @@
import glob, importlib, os, pathlib, shutil, subprocess, tarfile, tempfile
import glob, importlib, os, pathlib, subprocess
from tinygrad.helpers import fetch, flatten, system, getenv
root = (here:=pathlib.Path(__file__).parent).parents[2]
@@ -31,6 +31,7 @@ def load(name, files, **kwargs):
if not (f:=(root/(path:=kwargs.pop("path", __name__)).replace('.','/')/f"{name}.py")).exists() or getenv('REGEN'):
files, kwargs['args'] = files() if callable(files) else files, args() if callable(args:=kwargs.get('args', [])) else args
if (srcs:=kwargs.pop('srcs', None)):
import tempfile, tarfile
srcpath = (td:=tempfile.TemporaryDirectory(f"autogen-src-{name.replace('/','-')}")).name + "/"
for src in (srcs if isinstance(srcs, list) else [srcs]):
if 'tar' in src:
@@ -157,7 +158,7 @@ def __getattr__(nm):
*[f"python3 src/compiler/nir/nir_{s}_h.py --outdir gen" for s in ["intrinsics", "intrinsics_indices"]]]), cwd=path, shell=True, check=True),
srcs="https://gitlab.freedesktop.org/mesa/mesa/-/archive/mesa-25.2.7/mesa-25.2.7.tar.gz",
dll=f"'tinymesa_cpu' if DEV.renderer == 'LVP' else 'tinymesa', {tinymesa_path}, emsg='pip install tinymesa==25.2.7.2'",
prolog=["from tinygrad.helpers import DEV", "import gzip, base64, platform, sysconfig, os"],
prolog=["from tinygrad.helpers import DEV", "import gzip, base64, sysconfig, os"],
epilog=lambda path: [system(f"{root}/extra/mesa/lvp_nir_options.sh {path}")])
case "libclang":
return load("libclang",
+1 -1
View File
@@ -5,7 +5,7 @@ from typing import Literal, TypeAlias
from tinygrad.runtime.support.c import _IO, _IOW, _IOR, _IOWR
from tinygrad.runtime.support import c
from tinygrad.helpers import DEV
import gzip, base64, platform, sysconfig, os
import gzip, base64, sysconfig, os
dll = c.DLL('mesa', 'tinymesa_cpu' if DEV.renderer == 'LVP' else 'tinymesa', os.path.join(sysconfig.get_paths()['platlib'], 'tinymesa'), emsg='pip install tinymesa==25.2.7.2')
class struct_u_printf_info(c.Struct): pass
u_printf_info: TypeAlias = struct_u_printf_info
+2 -2
View File
@@ -6,7 +6,6 @@ from tinygrad.device import Buffer, BufferSpec, Compiled, Device, MultiBuffer, P
from tinygrad.dtype import dtypes
from tinygrad.uop.ops import UOp, Ops, Variable
from tinygrad.engine.jit import GraphRunner, MultiGraphRunner
from tinygrad.runtime.ops_rdma import RDMACopyQueue
class HCQGraph(MultiGraphRunner):
def __init__(self, *args, **kwargs):
@@ -50,7 +49,7 @@ class HCQGraph(MultiGraphRunner):
self.comp_queues: dict[HCQCompiled, HWQueue] = {dev: unwrap(dev.hw_compute_queue_t)() for dev in self.devices}
self.copy_queues: dict[tuple[HCQCompiled, int], HWQueue] = {} # lazy allocation, keyed by (device, queue_idx)
self.rdma_queues: dict[tuple[HCQCompiled, HCQCompiled], RDMACopyQueue] = {} # lazy allocation, keyed by device pair
self.rdma_queues: dict[tuple[HCQCompiled, HCQCompiled], "RDMACopyQueue"] = {} # lazy allocation, keyed by device pair
self.num_copy_queues: int = getenv("HCQ_NUM_SDMA", min(len(self.devices), 8) if ALL2ALL >= 1 else 1)
self.num_rdma_ops: dict[tuple[HCQCompiled, HCQCompiled], int] = collections.defaultdict(int)
@@ -104,6 +103,7 @@ class HCQGraph(MultiGraphRunner):
elif is_rdma:
enqueue_queue = self.comp_queues[enqueue_dev]
rdma_key = (cast(HCQCompiled, Device[bufs[0].device]).rdma_dev(), enqueue_dev.rdma_dev())
from tinygrad.runtime.ops_rdma import RDMACopyQueue
self.rdma_queues.setdefault(rdma_key, RDMACopyQueue(enqueue_dev.rdma_dev()))
else:
assert (enqueue_dev.hw_copy_queue_t is not None), "device must implement a copy queue"
+2 -1
View File
@@ -113,5 +113,6 @@ class MetalGraph(GraphRunner):
@staticmethod
def supports_uop(batch_devs, new_call:UOp) -> bool:
# Metal ICB replay encodes offsets as uint32; reject if any Metal buffer offset exceeds 32-bit range.
if any(b.op in {Ops.SLICE, Ops.SHRINK} and b.src[1].val * b.src[0].dtype.itemsize > 0xFFFFFFFF for b in new_call.src[1:]): return False
for shrink in [s for src in new_call.src[1:] if (s:=src.src[0] if src.op is Ops.BITCAST else src).op is Ops.SHRINK]:
if shrink.src[1].val * shrink.src[0].dtype.itemsize > 0xFFFFFFFF: return False
return GraphRunner.supports_uop(batch_devs, new_call)
+1 -1
View File
@@ -1,5 +1,5 @@
from __future__ import annotations
import platform, sys, os, ctypes, functools, mmap, threading, array, itertools
import platform, sys, os, ctypes, ctypes.util, functools, mmap, threading, array, itertools
from dataclasses import replace
from typing import cast
from tinygrad.helpers import to_mv, OSX, WIN, Context, mv_address, suppress_finalizing, unwrap, data64_le, partition
+8 -7
View File
@@ -39,7 +39,7 @@ def all_devices_in(d:Any, c:frozenset[str]) -> bool: return {x.split(":")[0] for
def unwrap_mstack(u):
if u.op is Ops.MSTACK: return tuple(x for s in u.src for x in unwrap_mstack(s))
return unwrap_mstack(u.src[0]) if u.op in {Ops.MSELECT, Ops.SLICE} else (u,)
return unwrap_mstack(u.src[0]) if u.op is Ops.MSELECT else (u,)
def is_value_known_at_link(val:UOp) -> bool:
runtime_reads = [u for u in val.toposort() if u.op in (Ops.LOAD, Ops.INDEX)]
@@ -377,14 +377,15 @@ pm_replace_params = PatternMatcher([
# *****************
def resolve_getaddr_slice(bv:UOp, g:UOp) -> UOp:
def resolve_getaddr_view(bv:UOp, g:UOp) -> UOp:
base = bv.src[0].after(*g.src[0].src[1:] if g.src[0].op is Ops.AFTER else ())
itemsize = bv.src[0].dtype.itemsize if bv.src[0].without_after.op in (Ops.BUFFER, Ops.SLICE, Ops.MSTACK, Ops.MSELECT) else bv.dtype.itemsize
if bv.op is Ops.BITCAST: return UOp(Ops.GETADDR, src=(base,), arg=g.arg)
itemsize = bv.src[0].dtype.itemsize if bv.src[0].without_after.op in (Ops.BUFFER, Ops.MSTACK, Ops.MSELECT) else bv.dtype.itemsize
return UOp(Ops.GETADDR, src=(base,), arg=g.arg) + UOp.const(bv.src[1].val * itemsize, dtypes.uint64)
pm_early_simplify = PatternMatcher([
(UPat(Ops.GETADDR, src=(UPat.any(sl:=UPat((Ops.SLICE, Ops.SHRINK), name="bv"), sl.after(allow_any_len=True)),), name="g"), resolve_getaddr_slice),
(UPat(Ops.INDEX, src=(UPat(Ops.SLICE, name="bv"),), allow_any_len=True, name="x"),
(UPat(Ops.GETADDR, src=(UPat((Ops.SHRINK, Ops.BITCAST), name="bv").or_after(),), name="g"), resolve_getaddr_view),
(UPat(Ops.INDEX, src=(UPat(Ops.SHRINK, name="bv"),), allow_any_len=True, name="x"),
lambda bv,x: x.replace(src=(bv.src[0], x.src[1] + bv.src[1].cast(x.src[1].dtype), *x.src[2:]))),
])
@@ -402,7 +403,7 @@ def pack_hcq_placeholders(call:UOp) -> UOp|None:
sizes[b.tag] = offs[b] + b.max_numel()
counts = collections.Counter(b.tag for b in bufs)
bases = {b.tag:UOp.placeholder((sizes[b.tag],), b.dtype, next(UOp.unique_num), device=b.device).rtag(b.tag) for b in bufs if counts[b.tag] > 1}
subs = {b:UOp(Ops.SLICE, b.dtype, (bases[b.tag], UOp.const(offs.get(b, 0))), b.max_numel()) for b in bufs if b.tag in bases}
subs = {b:bases[b.tag][(off:=offs.get(b, 0)):off+b.max_numel()] for b in bufs if b.tag in bases}
return call.replace(src=(call.src[0].substitute(subs, walk=True), *call.src[1:])) if subs else None
pm_pack_placeholders = PatternMatcher([
(UPat(Ops.CALL, src=(UPat(Ops.CUSTOM_FUNCTION, arg="hcq"),), name="call", allow_any_len=True), pack_hcq_placeholders)])
@@ -493,7 +494,7 @@ pm_resolve_patches = PatternMatcher([
(UPat(name="buf").index(UPat(Ops.RANGE), allow_any_len=True)
.store(UPat.any(UPat(Ops.BINARY, name="blob"), UPat(Ops.BINARY, name="blob").bitcast()).index(UPat(Ops.RANGE), allow_any_len=True).load())
.end(UPat(Ops.RANGE)), fold_binary),
(UPat({Ops.BUFFER, Ops.SLICE, Ops.MSTACK}, name="buf").index(UPat(Ops.STACK, name="off")).store(UPat(Ops.STACK, name="val")), fold_const_store),
(UPat({Ops.BUFFER, Ops.MSTACK}, name="buf").index(UPat(Ops.STACK, name="off")).store(UPat(Ops.STACK, name="val")), fold_const_store),
])
pm_assert_no_afters = PatternMatcher([(UPat(Ops.AFTER, name="a"), lambda a: panic(RuntimeError, f"AFTER left at hcq_link: {a.src[0].op}"))])
+4 -2
View File
@@ -1,7 +1,8 @@
from __future__ import annotations
import os, mmap, array, functools, ctypes, select, contextlib, dataclasses, sys, itertools, struct, socket, subprocess, time, enum, atexit
import os, mmap, array, functools, ctypes, ctypes.util, select, contextlib, dataclasses, sys, itertools, struct, socket
import subprocess, time, enum, atexit
from tinygrad.helpers import round_up, getenv, OSX, temp, ceildiv, unwrap, fetch, system, _ensure_downloads_dir, DEBUG, flatten, pluralize
from tinygrad.runtime.autogen import libc, pci, vfio, iokit, corefoundation
from tinygrad.runtime.autogen import libc, pci, vfio
from tinygrad.runtime.support.hcq import FileIOInterface, MMIOInterface, HCQBuffer, hcq_filter_visible_devices
from tinygrad.runtime.support.memory import VirtMapping, AddrSpace, BumpAllocator
from tinygrad.runtime.support.usb import USB3, CustomASM24Controller, USBMMIOInterface
@@ -55,6 +56,7 @@ class _System:
def pci_scan_bus(self, vendor:int, devices:tuple[tuple[int, tuple[int, ...]], ...], base_class:int|None=None) -> list[str]:
all_devs = []
if OSX:
from tinygrad.runtime.autogen import iokit, corefoundation
def read_prop(svc, key) -> int:
cfkey = corefoundation.CFStringCreateWithCString(None, key.encode(), corefoundation.kCFStringEncodingUTF8)
cfdata = ctypes.cast(iokit.IORegistryEntryCreateCFProperty(svc, ctypes.cast(cfkey, iokit.CFStringRef), None, 0), corefoundation.CFDataRef)
+3 -7
View File
@@ -23,7 +23,7 @@ class IndexingContext:
return UOp.range(s, next(self.range_idx), axistype) if resolve(s!=1) else UOp.const(0)
ALWAYS_CONTIGUOUS: set[Ops] = {Ops.CONTIGUOUS, Ops.AFTER, Ops.BUFFER, Ops.SLICE,
ALWAYS_CONTIGUOUS: set[Ops] = {Ops.CONTIGUOUS, Ops.AFTER, Ops.BUFFER,
Ops.CONST, Ops.BIND, Ops.MSELECT, Ops.MSTACK, Ops.PARAM,
Ops.LOAD, Ops.CALL, Ops.FUNCTION}
@@ -34,10 +34,6 @@ def realize_srcs(ctx:IndexingContext, rb:UOp) -> None:
if s.base.op not in ALWAYS_CONTIGUOUS: ctx.realize_map[s] = None
def realize_store_after_src(ctx:IndexingContext, dest:UOp, src:UOp):
# don't realize SLICE when it's the direct source of STORE+AFTER — the target buffer is the output
if src.op is Ops.SLICE and src in ctx.realize_map \
and not dest.op_in_backward_slice_with_self(Ops.SHRINK, Ops.PERMUTE, Ops.FLIP, Ops.PAD):
del ctx.realize_map[src]
# you don't usually have to do this for assign unless there's a WAR hazard like TestAssign.test_assign_double_diamond_reduce
if dest.base in src.backward_slice_with_self: ctx.realize_map[src] = None
@@ -74,7 +70,7 @@ def broadcast_rngs(x:UOp, src:UOp, rngs:tuple[UOp, ...]) -> tuple[UOp, ...]:
# TODO: srcs contain (real data srcs, something else, ranges) and the boundary is confusing. see range_start
def data_srcs(op:Ops, src:tuple[UOp, ...]) -> tuple[UOp, ...]:
if op in {Ops.PARAM, Ops.BUFFER, Ops.RANGE, Ops.SPECIAL, Ops.BIND}: return ()
if op in GroupOp.Movement|{Ops.INDEX, Ops.SLICE, Ops.STAGE, Ops.REDUCE, Ops.AFTER, Ops.END}: return src[:1]
if op in GroupOp.Movement|{Ops.INDEX, Ops.STAGE, Ops.REDUCE, Ops.AFTER, Ops.END}: return src[:1]
return src
def create_bufferize_and_index_srcs(ctx:IndexingContext, x:UOp) -> list[UOp]:
@@ -84,7 +80,7 @@ def create_bufferize_and_index_srcs(ctx:IndexingContext, x:UOp) -> list[UOp]:
for i, s in enumerate(x.src):
new_src = s
src_rngs = broadcast_rngs(x, s, ctx.range_map[x][0]) if x in ctx.range_map else ()
if s.op in {Ops.PARAM, Ops.BUFFER, Ops.SLICE, Ops.MSTACK, Ops.MSELECT, Ops.AFTER}:
if s.op in {Ops.PARAM, Ops.BUFFER, Ops.MSTACK, Ops.MSELECT, Ops.AFTER}:
if x in ctx.range_map and i < data_src_count: new_src = new_src.index(*src_rngs)
elif s in ctx.realize_map:
realized_ranges = ctx.realize_map[s]
+2 -4
View File
@@ -52,11 +52,9 @@ def memory_plan_rewrite(linear:UOp, held_bufs:set[UOp]|None=None) -> UOp:
peaks[_key(buf)] = (max(peaks[_key(buf)][0], offsets[buf] + buf.max_numel() * buf.dtype.itemsize), peaks[_key(buf)][1])
arena_sizes = {key: round_up(peak, block_size) for key, (peak, _) in peaks.items()}
# build replace_map: each buffer becomes a SLICE into a shared per-device-lane arena
# build replace_map: each buffer becomes a SHRINK/BITCAST into a shared per-device-lane arena
arenas = {key: UOp.new_buffer(key[0], sz, dtypes.int8) for key, sz in arena_sizes.items()}
replace_map:dict[UOp, UOp] = {}
for buf_uop, offset in offsets.items():
replace_map[buf_uop] = UOp(Ops.SLICE, buf_uop.dtype, (arenas[_key(buf_uop)], UOp.const(offset)), buf_uop.max_numel())
replace_map = {buf_uop:arenas[_key(buf_uop)][offset:offset+buf_uop.nbytes()].bitcast(buf_uop.dtype) for buf_uop, offset in offsets.items()}
if DEBUG >= 1 and (omem:=sum(nbytes.values()) / 1e6) != (nmem:=sum(arena_sizes.values()) / 1e6):
print(f"memory reduced from {omem:.2f} MB -> {nmem:.2f} MB, {len(first_appearance)} -> {len(arenas)} bufs")
+1 -1
View File
@@ -93,7 +93,7 @@ class Ops(FastEnum):
CONTIGUOUS = auto(); CONTIGUOUS_BACKWARD = auto(); DETACH = auto()
# buffer ops
STAGE = auto(); COPY = auto(); SLICE = auto(); MSELECT = auto(); MSTACK = auto(); CUSTOM_FUNCTION = auto()
STAGE = auto(); COPY = auto(); MSELECT = auto(); MSTACK = auto(); CUSTOM_FUNCTION = auto()
# the core 6 movement ops! these only exist in the tensor graph
RESHAPE = auto(); PERMUTE = auto(); EXPAND = auto(); PAD = auto(); FLIP = auto()
+8 -25
View File
@@ -45,8 +45,7 @@ axis_colors = {AxisType.DEVICE: "green", AxisType.GLOBAL: "blue", AxisType.THREA
axis_to_pos = {AxisType.DEVICE: -2, AxisType.WEAK: -1, AxisType.LOOP: -1, AxisType.THREAD: 0, AxisType.GLOBAL: 0, AxisType.WARP: 1,
AxisType.LOCAL: 2, AxisType.UPCAST: 3, AxisType.GROUP_REDUCE: 2, AxisType.REDUCE: 4, AxisType.UNROLL: 5}
range_start = {Ops.STAGE: 1, Ops.REDUCE: 1, Ops.WMMA: 3, Ops.END: 1, Ops.CALL: 1, Ops.FUNCTION: 1,
Ops.SLICE: 2, Ops.LINEAR: 0}
range_start = {Ops.STAGE: 1, Ops.REDUCE: 1, Ops.WMMA: 3, Ops.END: 1, Ops.CALL: 1, Ops.FUNCTION: 1, Ops.LINEAR: 0}
# https://en.wikipedia.org/wiki/Identity_element
def identity_element(op:Ops, dt:DType) -> PyConst: return dt.const({Ops.ADD:0, Ops.MUL:1, Ops.MAX:dt.min}[op])
@@ -171,9 +170,6 @@ def dtype_from_uop(op:Ops, src:tuple[UOp,...], arg:Any) -> DType|None:
return arg.dtype
case Ops.BINARY:
return dtypes.uint8
case Ops.SLICE:
# TODO: slice just shouldn't exist
return None
case Ops.CAST | Ops.BITCAST:
assert isinstance(arg, DType), f"CAST/BITCAST arg must be DType, got {arg}"
return arg
@@ -221,7 +217,7 @@ class UOpMetaClass(type):
return created
# some uops map to other stuff
buffers:weakref.WeakKeyDictionary[UOp, Buffer|MultiBuffer] = weakref.WeakKeyDictionary() # this maps BUFFER/SLICE uops to their device Buffers
buffers:weakref.WeakKeyDictionary[UOp, Buffer|MultiBuffer] = weakref.WeakKeyDictionary() # this maps BUFFER/view uops to their device Buffers
all_metadata:weakref.WeakKeyDictionary[UOp, tuple[Metadata, ...]] = weakref.WeakKeyDictionary() # TODO: should this be here?
# recursive_property replaces functools.cached_property in recursive UOp functions to prevent RecursionError
@@ -386,10 +382,6 @@ class UOp(RandMixin, metaclass=UOpMetaClass):
case Ops.BUFFER:
if len(self.src): return self.src[0].as_shape
return ()
case Ops.SLICE:
# HACK: SLICE is used inside kernels, so we set the shape to () if it's on an INDEX
if self.src[0].op is Ops.INDEX: return ()
return (self.arg,)
case Ops.CUSTOM | Ops.CUSTOMI:
if self.dtype is dtypes.void: return None
input_shapes = [x._shape for x in self.src if x._shape is not None]
@@ -822,7 +814,7 @@ class UOp(RandMixin, metaclass=UOpMetaClass):
unique_num = itertools.count(0)
def getaddr(self, device=None) -> UOp:
if self.without_after.op not in {Ops.BUFFER, Ops.SLICE, Ops.SHRINK, Ops.BINARY, Ops.MSTACK, Ops.MSELECT, Ops.PARAM}: return self
if self.without_after.op not in {Ops.BUFFER, Ops.SHRINK, Ops.BITCAST, Ops.BINARY, Ops.MSTACK, Ops.MSELECT, Ops.PARAM}: return self
return UOp(Ops.GETADDR, src=(self,), arg=device or to_tuple(self.device)[0])
@staticmethod
def new_buffer(device:str|tuple[str, ...], size:int, dtype:DType, num=None):
@@ -924,7 +916,7 @@ class UOp(RandMixin, metaclass=UOpMetaClass):
# TODO: this is confusing because UOp.variable('v', 0, 1, dtypes.weakfloat) is True for jit to work, but it doesn't have a buffer
if self.op in {Ops.RESHAPE, Ops.UNSHARD, Ops.MSELECT}: return self.src[0].has_buffer_identity(after_ok)
if after_ok and self.op == Ops.AFTER: return self.src[0].has_buffer_identity(after_ok)
return self.op in {Ops.BUFFER, Ops.SLICE, Ops.PARAM}
return self.op in {Ops.BUFFER, Ops.PARAM}
def _base_buffer_is_realized(self) -> bool:
"""Walk through AFTER chain to find if the underlying buffer is realized (has allocated memory)."""
@@ -937,25 +929,16 @@ class UOp(RandMixin, metaclass=UOpMetaClass):
if self.op in {Ops.CONTIGUOUS, Ops.CONTIGUOUS_BACKWARD, Ops.RESHAPE, Ops.UNSHARD, Ops.DETACH, Ops.AFTER}: return self.src[0].buffer
# this buffer can process disk tensors and simple movement ops
if self is not self.base or self.op is Ops.BITCAST:
if (cret:=buffers.get(self)) is not None: return cret
if (cv := self.contiguous_view()) is None: raise RuntimeError(f"non-contiguous view is not supported for {self.device} buffer")
buf, offset = (b:=cv[0]).base.buffer, cv[1]
if isinstance(buf, MultiBuffer):
mbuf = MultiBuffer.__new__(MultiBuffer)
mbuf.bufs = [x.view(prod(self.max_shape), self.dtype, offset*b.dtype.itemsize) for x in buf.bufs]
return mbuf
return buf.view(prod(self.max_shape), self.dtype, offset*b.dtype.itemsize)
if self.op is Ops.SLICE:
if (cret:=buffers.get(self)) is not None: return cret
buf = self.src[0].buffer
offset = self.src[1].val
if isinstance(buf, MultiBuffer):
mbuf = MultiBuffer.__new__(MultiBuffer)
mbuf.bufs = [b.view(self.arg, self.dtype, offset * self.src[0].dtype.itemsize) for b in buf.bufs]
buffers[self] = mbuf
return mbuf
assert isinstance(buf, Buffer), "must be a Buffer for SLICE"
buffers[self] = bv = buf.view(self.arg, self.dtype, offset * self.src[0].dtype.itemsize)
return bv
buffers[self] = buf.view(prod(self.max_shape), self.dtype, offset*b.dtype.itemsize)
return buffers[self]
if self.op is Ops.MSELECT:
ret = self.src[0].buffer
assert isinstance(ret, MultiBuffer)
@@ -1181,7 +1164,7 @@ class UOp(RandMixin, metaclass=UOpMetaClass):
def custom_function(name:str, *src:UOp) -> UOp: return UOp(Ops.CUSTOM_FUNCTION, src=src, arg=name)
# opaque bodies stay as Ops.CALL; value-producing bodies become Ops.FUNCTION (wrapped in TUPLE)
_OPAQUE_CALL_BODIES = {Ops.SINK, Ops.PROGRAM, Ops.LINEAR, Ops.COPY, Ops.SLICE, Ops.CUSTOM_FUNCTION}
_OPAQUE_CALL_BODIES = {Ops.SINK, Ops.PROGRAM, Ops.LINEAR, Ops.COPY, Ops.CUSTOM_FUNCTION}
def call(self, *srcs:UOp, ret_dtype:DType|None=None, grad_fxn:Callable|None=None,
name:str|None=None, precompile:bool=False, precompile_backward:bool=False, aux:Any=None) -> UOp:
if ret_dtype is not None: return UOp(Ops.CALL, ret_dtype, src=(self,)+srcs)
-7
View File
@@ -233,13 +233,6 @@ spec_hcq = PatternMatcher([
spec_full = PatternMatcher([
(UPat(Ops.REWRITE_ERROR, dtypes.void, name="x"), lambda x: isinstance(x.arg, str)),
# SLICE on BUFFER is allowed if BUFFER is
(UPat(Ops.SLICE, src=(UPat(GroupOp.Movement.union({Ops.BUFFER, Ops.PARAM, Ops.STAGE, Ops.AFTER})),
UPat(Ops.CONST, dtype=dtypes.weakint)), allow_any_len=True, name="bv"),
lambda bv: isinstance(bv.arg, int)),
(UPat(Ops.CALL, dtypes.void, src=(UPat((Ops.SLICE,)),), allow_any_len=True), lambda: True),
# codegen may end ranges after gpudims has replaced RANGE with SPECIAL.
(UPat(Ops.END, src=(UPat(), UPat()), allow_any_len=True), lambda: True),
+2
View File
@@ -249,6 +249,8 @@ symbolic = symbolic_simple+commutative+PatternMatcher([
lambda x: x.const_like(x.vmin) if x.vmin == x.vmax else None),
(UPat(Ops.RANGE, src=(UPat(Ops.CONST,)), name="x"), lambda x: x.const_like(x.vmin) if x.vmin == x.vmax else None),
# max folding
((UPat.cvar("a") < UPat.var("b")).where(UPat.var("b"), UPat.cvar("c")), lambda a,b,c: UOp.maximum(a,b) if a.val == c.val else None),
((UPat.var("a") < UPat.cvar("b")).where(UPat.cvar("c"), UPat.var("a")), lambda a,b,c: UOp.maximum(a,b) if b.val == c.val else None),
(UPat.maximum(UPat.var("x"), UPat.var("y")), lambda x,y: x if x.vmin >= y.vmax else y if x.vmax <= y.vmin else None),
# TODO: why does this rule break beautiful_mnist?
#((UPat.var("x")+UPat.var("z")).maximum(UPat.var("y")+UPat.var("z")), lambda x,y,z: x.maximum(y) + z),
+1 -1
View File
@@ -50,7 +50,7 @@ uops_colors = {Ops.LOAD: "#ffc0c0", Ops.STORE: "#87CEEB", Ops.CONST: "#e0e0e0",
Ops.INDEX: "#CEF9B7", Ops.STACK: "#D8F9E4",
Ops.WMMA: "#efefc0", Ops.UNSHARD: "#f6ccff", Ops.INS: "#eec4ff",
**{x:"#D8F9E4" for x in GroupOp.Movement}, **{x:"#ffffc0" for x in GroupOp.ALU}, Ops.THREEFRY:"#ffff80",
Ops.SLICE: "#E5EAFF", Ops.BUFFER: "#B0BDFF", Ops.GETADDR: "#9DB1F0", Ops.COPY: "#a040a0", Ops.CUSTOM_FUNCTION: "#bf71b6",
Ops.BUFFER: "#B0BDFF", Ops.GETADDR: "#9DB1F0", Ops.COPY: "#a040a0", Ops.CUSTOM_FUNCTION: "#bf71b6",
Ops.CALL: "#00B7C8", Ops.FUNCTION: "#C07788", Ops.PARAM: "#14686F", Ops.SOURCE: "#c0c0c0", Ops.BINARY: "#404040",
Ops.LINEAR: "#7DF4FF",
Ops.ALLREDUCE: "#ff40a0", Ops.MSELECT: "#d040a0", Ops.MSTACK: "#d040a0", Ops.CONTIGUOUS: "#FFC14D",