mirror of
https://github.com/tinygrad/tinygrad.git
synced 2026-08-14 13:58:27 +00:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26f2049d43 | ||
|
|
ff0cb28c21 | ||
|
|
ed8297a102 | ||
|
|
de04781b36 | ||
|
|
4f106ebe87 | ||
|
|
04c271ac41 | ||
|
|
2e5a9a4121 | ||
|
|
e1013a6356 | ||
|
|
f891f5ffd0 | ||
|
|
3686a1758f | ||
|
|
a8d13380f9 | ||
|
|
afb0463f10 | ||
|
|
947d6a3c69 |
@@ -1,6 +1,6 @@
|
||||
# model based off https://medium.com/data-science/going-beyond-99-mnist-handwritten-digits-recognition-cfff96337392
|
||||
from typing import Callable
|
||||
from tinygrad import Tensor, TinyJit, nn, GlobalCounters, Context
|
||||
from tinygrad import Tensor, TinyJit, nn, GlobalCounters, function, Context
|
||||
from tinygrad.helpers import getenv, colored, trange
|
||||
from tinygrad.nn.datasets import mnist
|
||||
|
||||
@@ -15,6 +15,7 @@ class Model:
|
||||
nn.BatchNorm(64), Tensor.max_pool2d,
|
||||
lambda x: x.flatten(1), nn.Linear(576, 10)]
|
||||
|
||||
@function
|
||||
def __call__(self, x:Tensor) -> Tensor: return x.sequential(self.layers)
|
||||
|
||||
@TinyJit
|
||||
|
||||
@@ -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]:
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -214,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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -81,8 +81,7 @@ def create_schedule(sched_sink:UOp) -> UOp:
|
||||
|
||||
from tinygrad.schedule.memory import memory_plan_rewrite
|
||||
from tinygrad.engine.realize import capturing, pm_flatten_linear
|
||||
#from tinygrad.schedule.rangeify import get_kernel_graph
|
||||
from tinygrad.schedule.rangeify2 import get_kernel_graph
|
||||
from tinygrad.schedule.rangeify import get_kernel_graph
|
||||
from tinygrad.helpers import CAPTURING
|
||||
from tinygrad.uop.ops import PatternMatcher, UPat, ParamArg
|
||||
from tinygrad.dtype import AddrSpace
|
||||
|
||||
@@ -1,238 +0,0 @@
|
||||
from dataclasses import dataclass, field, replace
|
||||
from typing import cast
|
||||
import itertools
|
||||
from tinygrad.dtype import dtypes, AddrSpace, Invalid, to_dtype, strong_dtype
|
||||
from tinygrad.uop.ops import PatternMatcher, UPat, Ops, UOp, resolve, GroupOp, KernelInfo, ParamArg, shape_to_shape_arg
|
||||
from tinygrad.uop.ops import graph_rewrite, sint, AxisType, BottomUpGate, rewrite_group, identity_element, remove_all_tags
|
||||
from tinygrad.uop.symbolic import symbolic, pm_fold_cast_const
|
||||
from tinygrad.uop.movement import mop_cleanup
|
||||
from tinygrad.helpers import prod, getenv, dedup, all_int, DEBUG, SPLIT_REDUCEOP, DEBUG_RANGEIFY, VIZ, MAX_KERNEL_BUFFERS, SPEC
|
||||
from tinygrad.helpers import PCONTIG, FLOAT16, OPENPILOT_HACKS, argsort, partition, get_single_element, Context
|
||||
from tinygrad.codegen.simplify import pm_flatten_range, pm_reduce_simplify
|
||||
from tinygrad.codegen.opt import Opt
|
||||
from tinygrad.schedule.indexing import run_rangeify, BufferizeOpts, IndexingContext, apply_movement_op
|
||||
from tinygrad.schedule.multi import multi_pm
|
||||
from tinygrad.schedule.allreduce import create_allreduce_function
|
||||
|
||||
# *** preparation ***
|
||||
|
||||
from tinygrad.helpers import all_same
|
||||
from tinygrad.uop.ops import _broadcast_shape
|
||||
|
||||
def expand_broadcast(x:UOp):
|
||||
shapes = [u._shape for u in x.src]
|
||||
if any(s is None for s in shapes) or all_same(shapes): return None
|
||||
shape = _broadcast_shape(*shapes)
|
||||
return x.replace(src=tuple([u.expand(shape) for u in x.src]))
|
||||
|
||||
pm_expand_broadcast = PatternMatcher([
|
||||
# expand broadcasts first
|
||||
(UPat(GroupOp.Binary|GroupOp.Ternary|{Ops.STORE}, name="x"), expand_broadcast),
|
||||
])
|
||||
|
||||
def convert_copy_to_store(ctx, copy:UOp, existing_buf:UOp|None=None):
|
||||
input_src = copy.src[0]
|
||||
if not input_src.has_buffer_identity(after_ok=True): input_src = input_src.contiguous()
|
||||
input_src = input_src.flatten()
|
||||
if existing_buf is not None:
|
||||
# if the existing buffer is not a full buffer, we can't use it
|
||||
if not existing_buf.has_buffer_identity(after_ok=True): return None
|
||||
# if there's already a buffer, we just use it
|
||||
return existing_buf.flatten().store(input_src)
|
||||
# create the output buffer
|
||||
buf = UOp(Ops.BUFFER, src=(shape_to_shape_arg(input_src.max_shape),), arg=ParamArg(next(ctx), copy.dtype, device=copy.device))
|
||||
# reshape back to input
|
||||
return buf.after(buf.store(input_src)).reshape(copy.shape)
|
||||
|
||||
def convert_contig_to_store(ctx, copy:UOp):
|
||||
input_src = copy.src[0]
|
||||
# create the output buffer
|
||||
buf = UOp(Ops.BUFFER, src=(shape_to_shape_arg(input_src.max_shape),), arg=ParamArg(next(ctx), copy.dtype, device=copy.device))
|
||||
# reshape back to input
|
||||
view = buf.shrink_to(input_src.shape)
|
||||
return view.after(view.store(input_src))
|
||||
|
||||
pm_copy_to_store = PatternMatcher([
|
||||
(UPat(name="existing_buf").store(UPat(Ops.COPY, name="copy")), convert_copy_to_store),
|
||||
(UPat(Ops.COPY, name="copy"), convert_copy_to_store),
|
||||
(UPat(Ops.CONTIGUOUS, name="copy"), convert_contig_to_store),
|
||||
])
|
||||
|
||||
# *** RANGE creation ***
|
||||
|
||||
def rangeify_on_reduce(ctx, inp:UOp, red:UOp, idx:UOp|None=None):
|
||||
if red.arg[1] == 0: return None
|
||||
if idx is None and len(red.shape) > 0: return None
|
||||
# TODO: is AxisType.REDUCE a real thing?
|
||||
rngs = [UOp.range(s, next(ctx), AxisType.REDUCE) for s in inp.shape[:red.arg[1]]]
|
||||
return inp.index(*rngs, *(idx.src[1:] if idx is not None else ())).reduce(*rngs, arg=(red.arg[0], 0))
|
||||
|
||||
def rangeify_on_store(ctx, x:UOp):
|
||||
if x.shape == (): return None
|
||||
rngs = [UOp.range(s, next(ctx)) for s in x.shape]
|
||||
return x.src[0].index(*rngs).store(x.src[1].index(*rngs)).end(*rngs)
|
||||
|
||||
def rangeify_on_stage(ctx, x:UOp):
|
||||
if x.src[0].shape == (): return None
|
||||
# size 1 dims don't get ranges, they are reshaped out and back in
|
||||
if all_int(x.shape) and 0 < len(sq := tuple(s for s in x.shape if s != 1)) < len(x.shape):
|
||||
return rangeify_on_stage(ctx, x.src[0].reshape(sq).bufferize(arg=x.arg)).reshape(x.shape)
|
||||
rngs = [UOp.range(s, next(ctx)) for s in x.shape]
|
||||
return x.replace(src=(x.src[0].index(*rngs), *rngs))
|
||||
|
||||
pm_range_creation = PatternMatcher([
|
||||
# reduce/store are what creates ranges
|
||||
(UPat(Ops.REDUCE, src=(UPat.var('inp'),), name="red").index(name="idx", allow_any_len=True), rangeify_on_reduce),
|
||||
(UPat(Ops.REDUCE, src=(UPat.var('inp'),), name="red"), rangeify_on_reduce),
|
||||
(UPat(Ops.STORE, name="x"), rangeify_on_store),
|
||||
(UPat(Ops.STAGE, name="x"), rangeify_on_stage),
|
||||
])
|
||||
|
||||
# *** RANGE migration ***
|
||||
|
||||
# movement op on INDEX as a PatternMatcher
|
||||
def _mop_index(r:UOp, idx:UOp):
|
||||
idxs = idx.src[1:]
|
||||
if len(idxs) == len(r.shape):
|
||||
ret = r.src[0].index(*apply_movement_op(r.op, r.src[0].shape, r.marg, idxs), dtype=idx.dtype, arg=idx.arg)
|
||||
if r.op is Ops.PAD:
|
||||
# insert 0 for PAD with where
|
||||
# TODO: does this need simplify to ensure the Invalids are at the base?
|
||||
a = UOp.const(True)
|
||||
for s in ret.src[1:]:
|
||||
if s.op is Ops.WHERE and s.src[2].op is Ops.CONST and s.src[2].arg == Invalid: a = a & s.src[0]
|
||||
ret = a.where(ret, ret.const_like(0))
|
||||
return ret
|
||||
if r.op is Ops.RESHAPE:
|
||||
src_prefix = len(r.src[0].shape) - len(r.shape[len(idxs):])
|
||||
if src_prefix >= 0 and r.src[0].shape[src_prefix:] == r.shape[len(idxs):]:
|
||||
if src_prefix == 0: return r.src[0] if r.src[0].dtype == idx.dtype else None
|
||||
ret = r.src[0].index(*apply_movement_op(r.op, r.src[0].shape[:src_prefix], r.shape[:len(idxs)], idxs), dtype=idx.dtype, arg=idx.arg)
|
||||
return ret if ret.shape == idx.shape else None
|
||||
|
||||
# TODO: this should be in _mop_index
|
||||
def index_on_stack(stack:UOp, idx:UOp):
|
||||
srcs = [s.index(*idx.src[2:]) for s in stack.src]
|
||||
r0 = idx.src[1]
|
||||
ret = srcs[-1]
|
||||
for k in range(len(srcs)-2, -1, -1): ret = r0.eq(k).where(srcs[k], ret)
|
||||
return ret
|
||||
|
||||
def walk_mop(u:UOp):
|
||||
if u.op in GroupOp.Movement or u.op is Ops.INDEX: return u.src[0]
|
||||
assert u.op == Ops.AFTER
|
||||
return u
|
||||
|
||||
pm_range_migration = PatternMatcher([
|
||||
# INDEX without src is nothing
|
||||
(UPat(Ops.INDEX, src=(UPat.var('x'),)), lambda x: x),
|
||||
# STAGE on shape () is nothing
|
||||
(UPat(Ops.STAGE, src=(UPat.var('x'),)), lambda x: x if x.shape == () else None),
|
||||
# if INDEX is on STAGE with the same ranges, remove the pair
|
||||
(UPat(Ops.STAGE, allow_any_len=True, name="s").index(allow_any_len=True, name="i"),
|
||||
lambda s,i: s.src[0] if s.src[1:] == i.src[1:] else None),
|
||||
# reshape of a single element shaped value to scalar is an index
|
||||
(UPat(Ops.RESHAPE, name="x"), lambda x: x.src[0].index(0) if x.marg == () and x.src[0].shape == (1,) else None),
|
||||
# handle movement ops on INDEX
|
||||
(UPat(GroupOp.Movement, name="r").index(name="idx", allow_any_len=True), _mop_index),
|
||||
(UPat(Ops.STACK, name="stack").index(name="idx", allow_any_len=True), index_on_stack),
|
||||
# move movement ops and INDEX after AFTER
|
||||
(UPat(GroupOp.Movement|{Ops.INDEX}, name="r").after(name="a", allow_any_len=True),
|
||||
lambda r,a: UOp(r.op, src=(a.replace(src=(r.src[0],)+a.src[1:]),)+r.src[1:], arg=r.arg)),
|
||||
# pass index through elementwise
|
||||
(UPat(GroupOp.Elementwise, name="b").index(name="idx", allow_any_len=True),
|
||||
lambda b,idx: b.replace(src=tuple(s.index(*idx.src[1:]) for s in b.src))),
|
||||
# remove movement ops from SINK. TODO: should be generic
|
||||
(UPat(Ops.SINK, name="s"), lambda s: s.replace(src=tuple(walk_mop(u) for u in s.src))),
|
||||
])
|
||||
|
||||
# *** split into kernels ***
|
||||
|
||||
@dataclass
|
||||
class SplitCtx:
|
||||
call_args:list = field(default_factory=list)
|
||||
range_number:int = -1
|
||||
|
||||
def _split_graph(ctx:SplitCtx, u:UOp) -> UOp:
|
||||
assert len(u.shape) <= 1, f"rangeify needs to reduce to a single idx, not {u.shape}"
|
||||
ctx.call_args.append(u)
|
||||
return u.param_like(len(ctx.call_args)-1)
|
||||
|
||||
def _renumber_range(ctx:SplitCtx, u:UOp) -> UOp:
|
||||
ctx.range_number += 1
|
||||
return u.replace(arg=(ctx.range_number, u.arg[-1]))
|
||||
|
||||
pm_split_graph = PatternMatcher([
|
||||
(UPat((Ops.PARAM, Ops.AFTER, Ops.BUFFER), name="u"), _split_graph),
|
||||
(UPat(Ops.RANGE, name="u"), _renumber_range),
|
||||
])
|
||||
|
||||
def split_store(x:UOp) -> UOp:
|
||||
ret = graph_rewrite(x, pm_split_graph, ctx:=SplitCtx(), name="split kernel", bottom_up=True, walk=True)
|
||||
return ret.sink(arg=KernelInfo()).call(*ctx.call_args)
|
||||
|
||||
split_kernels = PatternMatcher([
|
||||
(UPat((Ops.STORE, Ops.END), name="x"), split_store),
|
||||
])
|
||||
|
||||
# *** main rangeify ***
|
||||
|
||||
debug_tag_factor = PatternMatcher([
|
||||
(UPat(GroupOp.All, name="x"), lambda ctx,x: x.rtag(ctx[0][x] if x not in ctx[1] else 'REAL') if x.tag is None else None),
|
||||
])
|
||||
|
||||
def remove_stage(ctx, x:UOp) -> UOp:
|
||||
buf = UOp.new_buffer(x.arg.device, x.max_numel(), x.dtype, num=next(ctx))
|
||||
return buf.after(buf.reshape(x.shape).index(*x.src[1:]).store(x.src[0]).end(*x.src[1:])).reshape(x.shape)
|
||||
pm_remove_stage = PatternMatcher([(UPat(Ops.STAGE, name="x"), remove_stage)])
|
||||
|
||||
@rewrite_group(new_ctx=False)
|
||||
def get_kernel_graph(sink:UOp) -> UOp:
|
||||
# TODO: multi should just be part of rangeify
|
||||
tsink = graph_rewrite(sink, multi_pm, name="multi_pm")
|
||||
|
||||
# prepare
|
||||
tsink = graph_rewrite(tsink, pm_expand_broadcast, bottom_up=True, name="expand broadcast")
|
||||
tsink = graph_rewrite(tsink, pm_copy_to_store, ctx=itertools.count(0), bottom_up=True, name="convert copy to store")
|
||||
|
||||
# add safe STAGEs to never duplicate compute
|
||||
# we compute the number of times a buffer is consumed. if > 1, we realize
|
||||
realize = {}
|
||||
consumes = {tsink:0}
|
||||
for u in reversed(tsink.toposort()):
|
||||
assert u in consumes, f"{u.op} not in consumes"
|
||||
if (u.op in GroupOp.ALU or u.op is Ops.REDUCE) and consumes[u] > 1 and u.device is not None:
|
||||
# TODO: rename to stage
|
||||
realize[u] = u.rtag(1).bufferize(arg=BufferizeOpts(device=u.device))
|
||||
consumes[u] = 1
|
||||
if u.op is Ops.STORE: consumes[u] = 1
|
||||
if u.op is Ops.EXPAND: consumes[u] *= u.max_numel() // u.src[0].max_numel()
|
||||
for i,s in enumerate(u.src):
|
||||
if s not in consumes: consumes[s] = 0
|
||||
if u.op is not Ops.STORE or i > 0:
|
||||
consumes[s] += consumes[u]
|
||||
if VIZ:
|
||||
with Context(TRACK_MATCH_STATS=0): ctags = graph_rewrite(tsink, debug_tag_factor, ctx=(consumes, realize), bottom_up=True)
|
||||
graph_rewrite(ctags, PatternMatcher([]), name="View Consumes")
|
||||
|
||||
# add stages
|
||||
tsink = graph_rewrite(tsink.substitute(realize), remove_all_tags, name="untag")
|
||||
|
||||
# simple rangeify
|
||||
tsink = graph_rewrite(tsink, pm_range_creation+pm_range_migration, ctx=itertools.count(0), bottom_up=True, name="simple rangeify")
|
||||
|
||||
# TODO: merging and splitting algorithm
|
||||
|
||||
if VIZ: graph_rewrite(tsink, PatternMatcher([]), name="View Rangeify")
|
||||
|
||||
tsink = graph_rewrite(tsink, pm_remove_stage, ctx=itertools.count(0), bottom_up=True, name="remove stage")
|
||||
|
||||
tsink = graph_rewrite(tsink, split_kernels, bottom_up=True, name="split kernels")
|
||||
|
||||
if VIZ: graph_rewrite(tsink, PatternMatcher([]), name="View Kernel Graph")
|
||||
if SPEC:
|
||||
# validate the kernel graph
|
||||
from tinygrad.uop.spec import type_verify, spec_kernel_graph
|
||||
type_verify(tsink, spec_kernel_graph, enter_calls=False)
|
||||
return tsink
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user