From 2363bceb473406e31612a1576bfd4acc9fdc46c0 Mon Sep 17 00:00:00 2001 From: qazal <77887910+Qazalin@users.noreply.github.com> Date: Sat, 21 Mar 2026 22:47:02 +0200 Subject: [PATCH 01/19] viz: no context enters in cli, update llama profile (#15404) --- .../llama8b/implementations/tinybox_8xMI350X/profile.sh | 3 +-- extra/viz/cli.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama8b/implementations/tinybox_8xMI350X/profile.sh b/examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama8b/implementations/tinybox_8xMI350X/profile.sh index 188aea51af..fde461c018 100755 --- a/examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama8b/implementations/tinybox_8xMI350X/profile.sh +++ b/examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama8b/implementations/tinybox_8xMI350X/profile.sh @@ -1,6 +1,5 @@ #!/bin/bash export BENCHMARK=5 export EVAL_BS=0 -export VIZ=${VIZ:--1} -examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama8b/implementations/tinybox_8xMI350X/dev_run.sh +VIZ=${VIZ:--1} examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama8b/implementations/tinybox_8xMI350X/dev_run.sh extra/viz/cli.py --profile --device "AMD" --top 20 diff --git a/extra/viz/cli.py b/extra/viz/cli.py index 91548b0f1c..f8246ba8f5 100755 --- a/extra/viz/cli.py +++ b/extra/viz/cli.py @@ -3,7 +3,7 @@ import argparse, pathlib, sys, struct, json from typing import Iterator from tinygrad.viz import serve as viz from tinygrad.uop.ops import RewriteTrace -from tinygrad.helpers import temp, ansistrip, colored, time_to_str, ansilen, Context +from tinygrad.helpers import temp, ansistrip, colored, time_to_str, ansilen # ** generic helpers @@ -59,7 +59,6 @@ def decode_profile(data:bytes) -> dict: return {"dur":total_dur, "peak":global_peak, "layout":layout, "markers":markers} if __name__ == "__main__": - Context(VIZ=0, TRACK_MATCH_STATS=0).__enter__() parser = argparse.ArgumentParser() g_mode = parser.add_argument_group("mode") g_mode.add_argument("--profile", action="store_true", help="View profile trace") From bcc08307dab6541c83dcdaf6109dbd352fc89d52 Mon Sep 17 00:00:00 2001 From: chenyu Date: Sun, 22 Mar 2026 09:25:46 -0400 Subject: [PATCH 02/19] removed unused named arg in rules [pr] (#15414) --- tinygrad/codegen/late/devectorizer.py | 12 ++++++------ tinygrad/engine/realize.py | 2 +- tinygrad/renderer/cstyle.py | 4 ++-- tinygrad/renderer/llvmir.py | 10 +++++----- tinygrad/renderer/nir.py | 4 ++-- tinygrad/schedule/rangeify.py | 2 +- tinygrad/uop/spec.py | 10 +++++----- tinygrad/uop/validate.py | 10 +++++----- 8 files changed, 27 insertions(+), 27 deletions(-) diff --git a/tinygrad/codegen/late/devectorizer.py b/tinygrad/codegen/late/devectorizer.py index 9dd4022ce8..e8fba00a2f 100644 --- a/tinygrad/codegen/late/devectorizer.py +++ b/tinygrad/codegen/late/devectorizer.py @@ -286,12 +286,12 @@ pm_render = PatternMatcher([ if len(x.src) == 1 or x.src[1].op in (Ops.CUSTOM, Ops.STORE, Ops.BARRIER) else None), # Where after gated load becomes alt value # NOTE: if a is CAST and a.src[0].dtype == l.dtype, use a.src[0] to avoid roundtrip cast (e.g. uint->float->uint) - (UPat.var("c").where(UPat(Ops.LOAD, src=(UPat().index(UPat.var("idx"), UPat.var("c")).or_casted(),), allow_any_len=True, name="l").or_casted(), - UPat.var("a")), lambda c,idx,l,a: l.replace(src=(l.src[0], a.src[0] if a.op is Ops.CAST and a.src[0].dtype == l.dtype else a.cast(l.dtype))+ - l.src[2:]).cast(a.dtype)), - (UPat.var("c").where(UPat.var("a"), UPat(Ops.LOAD, src=(UPat().index(UPat.var("idx"), UPat.var("c").logical_not()).or_casted(),), - allow_any_len=True, name="l").or_casted()), lambda c,idx,l,a: l.replace(src=(l.src[0], a.src[0] if a.op is Ops.CAST and a.src[0].dtype == l.dtype - else a.cast(l.dtype))+l.src[2:]).cast(a.dtype)), + (UPat.var("c").where(UPat(Ops.LOAD, src=(UPat().index(UPat(), UPat.var("c")).or_casted(),), allow_any_len=True, name="l").or_casted(), + UPat.var("a")), lambda c,l,a: l.replace(src=(l.src[0], a.src[0] if a.op is Ops.CAST and a.src[0].dtype == l.dtype else a.cast(l.dtype))+ + l.src[2:]).cast(a.dtype)), + (UPat.var("c").where(UPat.var("a"), UPat(Ops.LOAD, src=(UPat().index(UPat(), UPat.var("c").logical_not()).or_casted(),), + allow_any_len=True, name="l").or_casted()), lambda c,l,a: l.replace(src=(l.src[0], a.src[0] if a.op is Ops.CAST and a.src[0].dtype == l.dtype + else a.cast(l.dtype))+l.src[2:]).cast(a.dtype)), ]) # *** Ops.REDUCE -> Ops.DEFINE_ACC *** diff --git a/tinygrad/engine/realize.py b/tinygrad/engine/realize.py index 31b351864d..c7675d19f7 100644 --- a/tinygrad/engine/realize.py +++ b/tinygrad/engine/realize.py @@ -127,7 +127,7 @@ def get_runner(device:str, ast:UOp) -> CompiledRunner: si_lowerer = PatternMatcher([ (UPat((Ops.SINK, Ops.PROGRAM), name="sink"), lambda ctx,sink: get_runner(ctx[0].device, sink)), (UPat(Ops.BUFFER_VIEW), lambda ctx: ViewOp(ctx[0])), - (UPat(Ops.COPY, name="copy"), lambda ctx,copy: (BufferXfer(ctx[0].nbytes, ctx[0].device, ctx[1].device) \ + (UPat(Ops.COPY), lambda ctx: (BufferXfer(ctx[0].nbytes, ctx[0].device, ctx[1].device) \ if hasattr(alc:=Device[ctx[0].device].allocator, '_transfer') and alc.supports_transfer and all_same([x.device.split(":")[0] for x in ctx]) \ else BufferCopy(ctx[0].nbytes, ctx[0].device, ctx[1].device))), (UPat(Ops.CUSTOM_FUNCTION, arg="encdec", name="cf"), lambda ctx,cf: EncDec(cf, ctx[0].nbytes, ctx[0].device)), diff --git a/tinygrad/renderer/cstyle.py b/tinygrad/renderer/cstyle.py index cc6d98e3be..99f97c6bf5 100644 --- a/tinygrad/renderer/cstyle.py +++ b/tinygrad/renderer/cstyle.py @@ -487,8 +487,8 @@ class AMDHIPRenderer(CStyleLanguage): (UPat(Ops.WMMA, name="x"), lambda ctx,x: f"__{x.arg[0]}({ctx[x.src[0]]}, {ctx[x.src[1]]}, {ctx[x.src[2]]}," f" {fp8_index(x.src[0].dtype)}, {fp8_index(x.src[0].dtype)}, 0, 0, 0, 0)" if x.arg[1][2] == 128 else None), (UPat(Ops.WMMA, name="x"), lambda ctx,x: f"__{x.arg[0]}({ctx[x.src[0]]}, {ctx[x.src[1]]}, {ctx[x.src[2]]}, 0, 0, 0)"), - (UPat(Ops.CAST, dtypes.fp8s, (UPat.var("y", dtypes.float),), name="x",), - lambda ctx,x,y: f"f32_to_fp8({ctx[x.src[0]]}, {fp8_index(x.dtype)})"), + (UPat(Ops.CAST, dtypes.fp8s, (UPat(dtype=dtypes.float),), name="x",), + lambda ctx,x: f"f32_to_fp8({ctx[x.src[0]]}, {fp8_index(x.dtype)})"), (UPat(Ops.CAST, dtypes.float, (UPat.var("y", dtypes.fp8s),), name="x",), lambda ctx,x,y: f"__builtin_amdgcn_cvt_f32_{('fp8', 'bf8')[fp8_index(y.dtype)]}((unsigned int){ctx[x.src[0]]}, 0)"), ]) + base_rewrite diff --git a/tinygrad/renderer/llvmir.py b/tinygrad/renderer/llvmir.py index 0b0cc72140..c871744d2e 100644 --- a/tinygrad/renderer/llvmir.py +++ b/tinygrad/renderer/llvmir.py @@ -224,17 +224,17 @@ class AMDLLVMRenderer(LLVMRenderer): (UPat(tuple(llvm_intrinsics), name="x"), lambda ctx, x: f" {ctx[x]} = call {ldt(x.dtype)} @llvm.{llvm_intrinsics[x.op]}.{ldt(x.dtype.scalar())}({ldt(x.src[0].dtype)} {ctx[x.src[0]]})"), (UPat(Ops.BARRIER), lambda ctx: barrier), - (UPat(Ops.CAST, dtypes.fp8s, (UPat.var("y", dtypes.float),), name="x",), lambda ctx,x,y: + (UPat(Ops.CAST, dtypes.fp8s, (UPat(dtype=dtypes.float),), name="x",), lambda ctx,x: f" {ctx[x]} = call i8 @f32_to_fp8({ldt(x.src[0].dtype)} {ctx[x.src[0]]}, i1 {'1' if x.dtype == dtypes.fp8e5m2 else '0'})"), (UPat(Ops.CAST, dtypes.float, (UPat.var("y", dtypes.fp8s),), name="x",), lambda ctx,x,y: f" {ctx[x.src[0]]}_i32 = zext i8 {ctx[x.src[0]]} to i32\n" f" {ctx[x]} = call float @llvm.amdgcn.cvt.f32.{'bf8' if y.dtype == dtypes.fp8e5m2 else 'fp8'}(i32 {ctx[x.src[0]]}_i32, i32 0)"), ]) + base_rewrite extra_matcher = LLVMRenderer.extra_matcher + create_non_native_float_pats(dtypes.fp8s) + PatternMatcher([ - (UPat(Ops.CAST, name="x", dtype=dtypes.half.vec(16), src=UPat.var("y", dtypes.half.vec(8))), - lambda x, y: UOp(Ops.VECTORIZE, dtypes.half.vec(16), tuple(y.gep(i // 2) if i % 2 == 0 else UOp.const(dtypes.half, 0.0) for i in range(16)))), - (UPat(Ops.CAST, name="x", dtype=dtypes.half.vec(8), src=UPat.var("y", dtypes.half.vec(16))), - lambda x, y: UOp(Ops.VECTORIZE, dtypes.half.vec(8), tuple(y.gep(i * 2) for i in range(8)))), + (UPat(Ops.CAST, dtype=dtypes.half.vec(16), src=UPat.var("y", dtypes.half.vec(8))), + lambda y: UOp(Ops.VECTORIZE, dtypes.half.vec(16), tuple(y.gep(i // 2) if i % 2 == 0 else UOp.const(dtypes.half, 0.0) for i in range(16)))), + (UPat(Ops.CAST, dtype=dtypes.half.vec(8), src=UPat.var("y", dtypes.half.vec(16))), + lambda y: UOp(Ops.VECTORIZE, dtypes.half.vec(8), tuple(y.gep(i * 2) for i in range(8)))), # amd llvm intrinsics llvm.log2/llvm.exp2 don't support double (UPat(Ops.LOG2, dtype=dtypes.double, src=(UPat.var("d"),)), xlog2), (UPat(Ops.EXP2, dtype=dtypes.double, src=(UPat.var("d"),)), xexp2), diff --git a/tinygrad/renderer/nir.py b/tinygrad/renderer/nir.py index 21ef133160..64e36295f6 100644 --- a/tinygrad/renderer/nir.py +++ b/tinygrad/renderer/nir.py @@ -144,8 +144,8 @@ class NIRRenderer(Renderer): (UPat(Ops.PARAM, name="x"), lambda ctx,x: ctx.param(ctx.b, x, 8)), (UPat(Ops.DEFINE_VAR, name="x"), lambda ctx,x: ctx.param(ctx.b, x, 4)), (UPat(Ops.SPECIAL, name="x"), lambda ctx,x: nchannel(ctx.b, {'g':ngid, 'l':nlid, 'i': nid}[x.arg[0]](ctx.b), int(x.arg[-1]))), - (UPat(Ops.STORE, src=(UPat(Ops.INDEX, src=(UPat.var("buf"),UPat.var("off")), allow_any_len=True), UPat.var("val")), allow_any_len=True, name="x"), - lambda ctx,x,buf,off,val: nstore(ctx.b, buf.ptrdtype.addrspace, nidx(ctx.b, ctx.r[buf], ctx.r[off], buf.dtype), ctx.r[val], val.dtype)), + (UPat(Ops.STORE, src=(UPat(Ops.INDEX, src=(UPat.var("buf"),UPat.var("off")), allow_any_len=True), UPat.var("val")), allow_any_len=True), + lambda ctx,buf,off,val: nstore(ctx.b, buf.ptrdtype.addrspace, nidx(ctx.b, ctx.r[buf], ctx.r[off], buf.dtype), ctx.r[val], val.dtype)), (UPat(Ops.LOAD, src=(UPat(Ops.INDEX, src=(UPat.var("buf"), UPat.var("off"), UPat.var("gate"))), UPat.var("alt")), allow_any_len=True, name="x"), lambda ctx,x,buf,off,alt,gate: if_phi(ctx.b, ctx.r[gate], lambda: nload(ctx.b, buf.ptrdtype.addrspace, nidx(ctx.b, ctx.r[buf], ctx.r[off], buf.dtype, ctx.r[gate]), x.dtype), lambda: ctx.r[alt])), diff --git a/tinygrad/schedule/rangeify.py b/tinygrad/schedule/rangeify.py index 6a03638f41..337dabdbec 100644 --- a/tinygrad/schedule/rangeify.py +++ b/tinygrad/schedule/rangeify.py @@ -317,7 +317,7 @@ pm_const_buffer_folding = pm_mops+PatternMatcher([ # copy on CONST is CONST (UPat(Ops.COPY, src=(UPat.cvar("x"), UPat()), name="copy"), lambda copy,x: copy.const_like(x.arg)), # hack if a noop turned to a const - (UPat(Ops.NOOP, src=(UPat.cvar("c"),), name="noop"), lambda c,noop: c), + (UPat(Ops.NOOP, src=(UPat.cvar("c"),)), lambda c: c), # mstack on CONST is CONST (UPat(Ops.MSTACK, src=(UPat.var("s"),), allow_any_len=True).f(Ops.INDEX, allow_any_len=True), lambda s: UOp.const(c.dtype, c.arg) if (c:=s.base).op is Ops.CONST else None), diff --git a/tinygrad/uop/spec.py b/tinygrad/uop/spec.py index 0874e8644c..ffde0dd57a 100644 --- a/tinygrad/uop/spec.py +++ b/tinygrad/uop/spec.py @@ -69,9 +69,9 @@ shared_spec = PatternMatcher([ # ***** UOp spec in the Tensor graph ***** movement_ops = PatternMatcher([ - (UPat((Ops.RESHAPE, Ops.EXPAND), name="mv", src=(UPat.var("x"), UPat(dtype=dtypes.weakint))), lambda mv,x: True), - (UPat((Ops.PAD, Ops.SHRINK), name="mv", src=(UPat.var("x"), UPat(dtype=dtypes.weakint), UPat(dtype=dtypes.weakint))), lambda mv,x: True), - (UPat((Ops.PERMUTE, Ops.FLIP), name="mv", src=(UPat.var("x"),)), lambda mv,x: isinstance(mv.arg, tuple)), + (UPat((Ops.RESHAPE, Ops.EXPAND), src=(UPat(), UPat(dtype=dtypes.weakint))), lambda: True), + (UPat((Ops.PAD, Ops.SHRINK), src=(UPat(), UPat(dtype=dtypes.weakint), UPat(dtype=dtypes.weakint))), lambda: True), + (UPat((Ops.PERMUTE, Ops.FLIP), name="mv", src=(UPat(),)), lambda mv: isinstance(mv.arg, tuple)), # inputs to movement ops (UPat((Ops.VECTORIZE, Ops.VCONST), dtype=dtypes.weakint), lambda: True), @@ -213,7 +213,7 @@ kernel_spec = PatternMatcher([ (UPat(Ops.END, src=(UPat(), UPat()), allow_any_len=True), lambda: True), # bufferize can be on anything - (UPat(Ops.BUFFERIZE, src=(UPat(),), allow_any_len=True, name="x"), lambda x: True), + (UPat(Ops.BUFFERIZE, src=(UPat(),), allow_any_len=True), lambda: True), # reduce must be on ranges (UPat(Ops.REDUCE, src=(UPat(),), allow_any_len=True, name="x"), lambda x: all(y.dtype in (dtypes.weakint, dtypes.int) for y in x.src[1:])), @@ -286,7 +286,7 @@ full_spec = PatternMatcher([ (UPat(Ops.BIND, (dtypes.int, dtypes.weakint), (UPat(), UPat()), arg=None), lambda: True), # in progress MSTACK may lose device - (UPat((Ops.MSELECT, Ops.MSTACK), name="x"), lambda x: True), + (UPat((Ops.MSELECT, Ops.MSTACK)), lambda: True), # temp VECTORIZE/INDEX during rewrite have the wrong dtype (UPat(Ops.VECTORIZE), lambda: True), diff --git a/tinygrad/uop/validate.py b/tinygrad/uop/validate.py index b1f888bddf..5c91e853af 100644 --- a/tinygrad/uop/validate.py +++ b/tinygrad/uop/validate.py @@ -28,19 +28,19 @@ z3_renderer = PatternMatcher([ # loads are variables bounded by the min/max of the dtype. non-pointer INDEX is also a LOAD (UPat((Ops.LOAD, Ops.INDEX), dtypes.ints+(dtypes.weakint,), name="x"), lambda x,ctx: create_bounded(f"load{len(ctx[1])}", x.dtype.min, x.dtype.max, ctx[0])), - (UPat((Ops.LOAD, Ops.INDEX), dtypes.bool, name="x"), lambda x,ctx: (z3.Bool(f"load{len(ctx[1])}", ctx=ctx[0]), None)), + (UPat((Ops.LOAD, Ops.INDEX), dtypes.bool), lambda ctx: (z3.Bool(f"load{len(ctx[1])}", ctx=ctx[0]), None)), # constants - (UPat(Ops.CONST, arg=Invalid, name="x"), lambda x,ctx: (z3.Int("Invalid", ctx=ctx[0]), None)), + (UPat(Ops.CONST, arg=Invalid), lambda ctx: (z3.Int("Invalid", ctx=ctx[0]), None)), (UPat(Ops.CONST, dtypes.ints+(dtypes.weakint,), name="x"), lambda x,ctx: (z3.IntVal(x.arg, ctx=ctx[0]), None)), (UPat(Ops.CONST, dtypes.bool, name="x"), lambda x,ctx: (z3.BoolVal(x.arg, ctx=ctx[0]), None)), # casts from floats create new variables (UPat(Ops.CAST, dtypes.ints+(dtypes.weakint,), src=(UPat(dtype=dtypes.floats),), name="x"), lambda x,ctx: create_bounded(f"cast{len(ctx[1])}", x.dtype.min, x.dtype.max, ctx[0])), # A comparison between floats introduces a new bool variable - (UPat(GroupOp.Comparison, src=UPat(dtype=dtypes.floats), name="x"), lambda x,ctx: (z3.Bool(f"float_cmp{len(ctx[1])}", ctx=ctx[0]), None)), + (UPat(GroupOp.Comparison, src=UPat(dtype=dtypes.floats)), lambda ctx: (z3.Bool(f"float_cmp{len(ctx[1])}", ctx=ctx[0]), None)), # casts from bool/int to int/bool - (UPat(Ops.CAST, dtypes.ints+(dtypes.weakint,),src=(UPat.var("x", dtypes.bool),), name="c"), lambda x,c,ctx: (z3.If(ctx[1][x], 1, 0), None)), - (UPat(Ops.CAST, dtypes.ints+(dtypes.weakint,), src=(UPat.var("x", dtypes.ints+(dtypes.weakint,)),), name="c"), lambda x,c,ctx: (ctx[1][x], None)), + (UPat(Ops.CAST, dtypes.ints+(dtypes.weakint,),src=(UPat.var("x", dtypes.bool),)), lambda x,ctx: (z3.If(ctx[1][x], 1, 0), None)), + (UPat(Ops.CAST, dtypes.ints+(dtypes.weakint,), src=(UPat.var("x", dtypes.ints+(dtypes.weakint,)),)), lambda x,ctx: (ctx[1][x], None)), (UPat(Ops.CAST, dtypes.bool, name="x"), lambda x,ctx: (ctx[1][x.src[0]]!=0, None)), (UPat(GroupOp.ALU, name="x"), lambda x,ctx: (z3_alu[x.op](*(ctx[1][s] for s in x.src)), None)), ]) From c7b18e6108f1f875b140d1009eccad196ddcf0cd Mon Sep 17 00:00:00 2001 From: qazal <77887910+Qazalin@users.noreply.github.com> Date: Sun, 22 Mar 2026 17:17:05 +0200 Subject: [PATCH 03/19] viz: sqtt printer in viz/cli.py (#15411) * work * sqtt timeline in CLI * format all printers nicely * s/Showed/Printed * ansistrip * sys.exit * keep colors in list * work from amd_copy_matmul * has_more always gets returned * linter * don't print colors * more colors * wow this is so deep * work * minor details * selected * improve progress bar * remove it * 22, global_load_vaddr is so long --- .../tinybox_8xMI350X/profile.sh | 2 +- extra/viz/README | 3 +- extra/viz/cli.py | 67 ++++++++++++++++--- test/amd/test_sqttmap.py | 4 +- tinygrad/viz/serve.py | 11 +-- 5 files changed, 71 insertions(+), 16 deletions(-) diff --git a/examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama8b/implementations/tinybox_8xMI350X/profile.sh b/examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama8b/implementations/tinybox_8xMI350X/profile.sh index fde461c018..0e2c61978c 100755 --- a/examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama8b/implementations/tinybox_8xMI350X/profile.sh +++ b/examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama8b/implementations/tinybox_8xMI350X/profile.sh @@ -2,4 +2,4 @@ export BENCHMARK=5 export EVAL_BS=0 VIZ=${VIZ:--1} examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama8b/implementations/tinybox_8xMI350X/dev_run.sh -extra/viz/cli.py --profile --device "AMD" --top 20 +extra/viz/cli.py --profile --device "AMD" --limit 20 diff --git a/extra/viz/README b/extra/viz/README index a7db4f9f66..f0282500ae 100644 --- a/extra/viz/README +++ b/extra/viz/README @@ -1,6 +1,7 @@ A command line tool for exploring the VIZ trace. -After running with VIZ=-1, use `extra/viz/cli.py` to explore the saved trace files. +1. Set VIZ to -1 to save the trace. +2. Use `extra/viz/cli.py` to inspect the trace files. ## Inspect runtime profiling diff --git a/extra/viz/cli.py b/extra/viz/cli.py index f8246ba8f5..1cd2fffb50 100755 --- a/extra/viz/cli.py +++ b/extra/viz/cli.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -import argparse, pathlib, sys, struct, json +import argparse, pathlib, sys, struct, json, itertools from typing import Iterator from tinygrad.viz import serve as viz from tinygrad.uop.ops import RewriteTrace @@ -65,9 +65,12 @@ if __name__ == "__main__": g_mode.add_argument("--rewrites", action="store_true", help="View rewrites trace") g_common = parser.add_argument_group("common options") g_common.add_argument("--kernel", type=str, default=None, metavar="NAME", help="Select a kernel by name (optional name, default: only list names)") + g_common.add_argument("--no-color", action="store_true", default=not (sys.stdin.isatty() and sys.stdout.isatty()), + help="Disable colored output (default: true in non-interactive mode)") g_profile = parser.add_argument_group("profile options") g_profile.add_argument("--device", type=str, default=None, metavar="NAME", help="Select a device (optional name, default: only list names)") - g_profile.add_argument("--top", type=int, default=10, metavar="N", help="Number of top kernels to show (-1 for all, default: 10)") + g_profile.add_argument("--offset", type=int, default=0, metavar="N", help="event offset (default: 0)") + g_profile.add_argument("--limit", type=int, default=10, metavar="N", help="events to display (-1 for all, default: 10)") g_rewrites = parser.add_argument_group("rewrites options") g_rewrites.add_argument("--select", type=str, default=None, metavar="NAME", help="Select an item within the chosen kernel (optional name, default: only list names)") @@ -83,14 +86,61 @@ if __name__ == "__main__": viz.trace = viz.load_pickle(args.rewrites_path, default=RewriteTrace([], [], {})) viz.ctxs = viz.get_rewrites(viz.trace) + def format_colored(s:str) -> str: return ansistrip(s) if args.no_color else s + if args.profile: from tabulate import tabulate - profile = decode_profile(viz.get_profile(viz.load_pickle(args.profile_path, default=[]))) + profile = decode_profile(viz.get_profile(profile_data:=viz.load_pickle(args.profile_path, default=[]))) + viz.load_amd_counters(viz.ctxs, profile_data) + counters = {f'{c["name"]} SQTT {s["name"]}': s["data"] for c in viz.ctxs if c["name"].startswith("Exec") for s in c["steps"] + if s["name"].startswith("PKTS")} + if args.device is None: + print("Select a device:") + for k in (*profile["layout"], *counters): + print(f" {format_colored(k)}") + sys.exit(0) + + # ** SQTT printer + if args.device is not None and (sqtt_data:=next((v for k,v in counters.items() if ansistrip(k) == args.device), None)) is not None: + assert args.limit > 1, f"SQTT limit must be greater than 1, got {args.limit}" + sqtt_events, has_more = viz.sqtt_timeline(*sqtt_data, max_pkts=args.offset+args.limit) + sqtt_pkts = [e for e in sqtt_events if type(e).__name__ == "ProfileRangeEvent"] + pc_map = next(e.arg for e in sqtt_events if type(e).__name__ == "ProfilePointEvent" and e.key == 'pcMap') + # modern terminals support 24-bit color + def hex_colored(st:str, color:str) -> str: return f"\x1b[38;2;{int(color[1:3],16)};{int(color[3:5],16)};{int(color[5:7],16)}m{st}\x1b[0m" + WAVE_COLORS = ((('VALU', 'VINTERP'), '#ffffc0'), (('SALU',), '#cef263'), (('VMEM',), '#b2b7c9'), (('LOAD', 'SMEM'), '#ffc0c0'), + (('STORE',), '#4fa3cc'), (('IMMEDIATE',), '#f3b44a'), (('BARRIER',), '#d00000'), (('LDS',), '#9fb4a6'), (('JUMP',), '#ffb703'), + (('JUMP_NO',), '#fb8500'), (('MESSAGE',), '#90dbf4'), (('WAVERDY',), '#1a2a2a')) + print(f"{'Clk':<12} {'Unit':<20} {'Op':<22} {'Dur':<4} {'Info'}") + print("-" * 90) + # start from the first packet in trace, prepare packet indexes and map dispatches + pkt_idxs:dict[str, itertools.count] = {} + dispatch_to_pc:dict[str, int] = {} + for e in sqtt_pkts[:-args.limit]: + idx = next(pkt_idxs.setdefault(e.device, itertools.count())) + if e.name.ret is not None and e.name.ret.startswith("PC:"): dispatch_to_pc[f"{e.device}-{idx}"] = int(e.name.ret.replace("PC:", "")) + # start printing from the offset point + for e in sqtt_pkts[-args.limit:]: + op_name, info = e.name.display_name, e.name.ret or "" + color = next((c for p, c in WAVE_COLORS if any(x in op_name for x in p)), None) + op_str = hex_colored(op_name, color) if color and not args.no_color else op_name + phase, pc = None, None + idx = next(pkt_idxs.setdefault(e.device, itertools.count())) + if info.startswith("PC:"): + dispatch_to_pc[f"{e.device}-{idx}"] = pc = int(info.replace("PC:", "")) + phase = "DISPATCH" + if info.startswith("LINK:"): phase, pc = "EXEC", dispatch_to_pc[info.replace("LINK:", "")] + if pc and phase: info = f"{phase:<8} 0x{pc:05x} {pc_map[pc]}" + print(f"{int(e.st):<12} {e.device:<20} {op_str}{' '*(22-ansilen(op_str))} {int(e.en-e.st):<4} {info}") + # note: we only print the important packets and skip the rest + if has_more: print(f"Selected packets {args.offset:,}-{args.offset + args.limit:,}. Use --offset and --limit to see others") + sys.exit(0) + + # ** Profiler printer agg, total, n = {}, 0, 0 - if args.device is None: print("Select a device:") for k,v in profile["layout"].items(): if not optional_eq({"name":k}, args.device): continue - print(f" {k}") + print(f" {format_colored(k)}") if args.device is None: continue for e in v.get("events", []): et = e["dur"]*1e-6 @@ -98,7 +148,7 @@ if __name__ == "__main__": if optional_eq(e, args.kernel) and n < 10: ptm = colored(time_to_str(et, w=9), "yellow" if et > 0.01 else None) if et is not None else "" name = e["name"]+(" " * (46 - ansilen(e["name"]))) - print(f"{name} {ptm}/{(et or 0)*1e3:9.2f}ms "+e['fmt'].replace('\n', ' | ')+" ") + print(f"{name} {ptm}/{(et or 0)*1e3:9.2f}ms "+e.get('fmt', '').replace('\n', ' | ')+" ") n += 1 else: a = agg.setdefault(e["name"], [0.0, 0]) @@ -107,14 +157,15 @@ if __name__ == "__main__": total += et if agg and total > 0: items = sorted(agg.items(), key=lambda kv:kv[1][0], reverse=True) - sel = items if args.top == -1 else items[:args.top] + sel = items if args.limit == -1 else items[args.offset:args.offset+args.limit] table = [[name, time_to_str(t, w=9), c, f"{(t/total*100.0):.2f}%"] for name,(t,c) in sel] - if args.top != -1 and (other:=items[len(sel):]): + if args.limit != -1 and (other:=items[len(sel):]): other_t = total-sum(t for _, (t, _) in sel) table.append([f"Other ({len(other)} unique)", time_to_str(other_t, w=9), sum(c for _,(_,c) in other), f"{other_t/total*100.0:.2f}%"]) print(tabulate(table, headers=["name", "total", "count", "pct"], tablefmt="github")) sys.exit(0) + # ** Graph rewrites printer for k in viz.ctxs: if not optional_eq(k, args.kernel): continue print(k["name"]) diff --git a/test/amd/test_sqttmap.py b/test/amd/test_sqttmap.py index 40f6b87059..62cfacbda8 100644 --- a/test/amd/test_sqttmap.py +++ b/test/amd/test_sqttmap.py @@ -79,7 +79,7 @@ class TestSQTTMapBase(unittest.TestCase): if (p:=kern_events.get(event.kern)) is None: continue with self.subTest(example=name, kern=event.kern): # skip if there's no SQTT frequency data - if not (timeline:=sqtt_timeline(event.blob, p.lib, target)): continue + if not (timeline:=sqtt_timeline(event.blob, p.lib, target)[0]): continue if not (frequency:=[e.key for e in timeline if type(e).__name__ == "ProfilePointEvent" and e.name == "freq_hz"]): continue mean = sum(frequency) / len(frequency) variance = sum((v - mean) ** 2 for v in frequency) / len(frequency) @@ -101,7 +101,7 @@ class TestSQTTMapBase(unittest.TestCase): for name, (events, kern_events, target) in self.examples.items(): for event in events: wave_barriers = {} - for e in sqtt_timeline(event.blob, kern_events[event.kern].lib, target): + for e in sqtt_timeline(event.blob, kern_events[event.kern].lib, target)[0]: if type(e).__name__ == "ProfileRangeEvent" and e.name.display_name == "BARRIER": wave_barriers.setdefault(e.device, []).append(e) if not wave_barriers: continue for row, events in wave_barriers.items(): diff --git a/tinygrad/viz/serve.py b/tinygrad/viz/serve.py index 9069993b90..0c3c044479 100755 --- a/tinygrad/viz/serve.py +++ b/tinygrad/viz/serve.py @@ -337,7 +337,7 @@ def load_amd_counters(ctxs:list[dict], profile:list[ProfileEvent]) -> None: steps.append(create_step("SQTT", ("/prg-sqtt", len(ctxs), len(steps)), ((k, tag), sqtt, prg_events[k], arch))) ctxs.append({"name":f"Exec {name}"+(f" n{run_number[k]}" if run_number[k] > 1 else ""), "steps":steps}) -def sqtt_timeline(data:bytes, lib:bytes, target:str) -> list[ProfileEvent]: +def sqtt_timeline(data:bytes, lib:bytes, target:str, max_pkts=getenv("MAX_SQTT_PKTS",50_000)) -> tuple[list[ProfileEvent], bool]: from tinygrad.renderer.amd.sqtt import (map_insts, InstructionInfo, PacketType, INST, InstOp, VALUINST, IMMEDIATE, IMMEDIATE_MASK, VMEMEXEC, ALUEXEC, INST_RDNA4, InstOpRDNA4, TS_DELTA_OR_MARK, TS_DELTA_OR_MARK_RDNA4, CDNA_INST, InstOpCDNA, WAVEEND, CDNA_WAVEEND, WAVERDY) @@ -365,8 +365,11 @@ def sqtt_timeline(data:bytes, lib:bytes, target:str) -> list[ProfileEvent]: if isinstance(p, (VALUINST, INST, INST_RDNA4)) and (exec_type:=dispatch_to_exec.get(name.split("_")[0])) is not None: exec_pending.setdefault(exec_type, []).append(f"{row}-{idx}") if isinstance(p, (ALUEXEC, VMEMEXEC)) and "ALT" not in str(p.src): e.name = TracingKey(op or name, ret=f"LINK:{exec_pending[name].pop(0)}") + has_more = False for p, info in map_insts(data, lib, target): - if len(ret) > getenv("MAX_SQTT_PKTS", 50_000): break + if len(ret) > max_pkts: + has_more = True + break if isinstance(p, (TS_DELTA_OR_MARK, TS_DELTA_OR_MARK_RDNA4)) and p.is_marker: pair = (p._time, p.delta) if prev_pair is None: prev_pair = pair @@ -393,7 +396,7 @@ def sqtt_timeline(data:bytes, lib:bytes, target:str) -> list[ProfileEvent]: else: add(name.replace("_ALT", ""), p, op=name) pc_map = {addr:str(inst) for addr,inst in amd_decode(lib, target).items()} - return [ProfilePointEvent(r, "JSON", "pcMap", pc_map, ts=Decimal(0)) for r in row_ends]+ret + return [ProfilePointEvent(r, "JSON", "pcMap", pc_map, ts=Decimal(0)) for r in row_ends]+ret, has_more # ** SQTT OCC only unpacks wave start, end time and SIMD location @@ -619,7 +622,7 @@ def get_render(query:str) -> dict: if fmt.startswith("prg-pkts"): ret = {} with soft_err(lambda err:ret.update(err)): - if (events:=get_profile(sqtt_timeline(*data), sort_fn=row_tuple)): ret = {"value":events, "content_type":"application/octet-stream"} + if (events:=get_profile(sqtt_timeline(*data)[0], sort_fn=row_tuple)): ret = {"value":events, "content_type":"application/octet-stream"} else: ret = {"src":"No SQTT trace on this SE."} return ret if fmt == "prg-sqtt": From 2087df814f483efade51a6b5dde79526e2d1b465 Mon Sep 17 00:00:00 2001 From: gg Date: Sun, 22 Mar 2026 12:49:26 -0400 Subject: [PATCH 04/19] remove *0 hack in sign, gradient materializes zeros for unconnected nodes (#15416) Amp-Thread-ID: https://ampcode.com/threads/T-019d1612-6322-706b-a94d-a812400a55cb Co-authored-by: Amp --- tinygrad/mixin/elementwise.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tinygrad/mixin/elementwise.py b/tinygrad/mixin/elementwise.py index 4f22fda96b..3ae9762652 100644 --- a/tinygrad/mixin/elementwise.py +++ b/tinygrad/mixin/elementwise.py @@ -677,7 +677,7 @@ class ElementwiseMixin(DTypeMixin): print(Tensor([-3., -2., -1., 0., 1., 2., 3.]).sign().numpy()) ``` """ - return self.ne(0).where((self < 0).where(self.const_like(-1), self.const_like(1)), self.const_like(0)) + self * 0 + return self.ne(0).where((self < 0).where(self.const_like(-1), self.const_like(1)), self.const_like(0)) def abs(self) -> Self: """ From 67dcc79fdd1b9795e59c141c7a8d1932476dd338 Mon Sep 17 00:00:00 2001 From: chenyu Date: Sun, 22 Mar 2026 23:49:35 -0400 Subject: [PATCH 05/19] push Tensor(symbolic) logic to Tensor.from_uop (#15420) --- tinygrad/tensor.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tinygrad/tensor.py b/tinygrad/tensor.py index 57094b030f..68c814de23 100644 --- a/tinygrad/tensor.py +++ b/tinygrad/tensor.py @@ -134,13 +134,7 @@ class Tensor(OpMixin): if isinstance(data, UOp): assert _dtype is None or _dtype==data.dtype or data.dtype==dtypes.weakint, f"dtype mismatch: {_dtype} vs {data.dtype}" # if data is dtype.weakint that means that this is a symbolic int and we need to lower it to something we can make a Tensor out of - # TODO: remove this and stay in weakint - if data.dtype==dtypes.weakint: data = _index_to_concrete_int(data) - if data.op is Ops.BIND: - var, val = data.unbind() - # give the bound constant a device - const = UOp.const(var.dtype, val, _device, ()) - data = data.replace(src=(var.replace(src=const.src), const)) + if data.dtype == dtypes.weakint: data = Tensor.from_uop(data).uop elif data is None: data = UOp.const(_dtype or dtypes.default_float, 0, _device) elif isinstance(data, get_args(ConstType)): @@ -503,7 +497,13 @@ class Tensor(OpMixin): @staticmethod def from_uop(y:UOp, **kwargs) -> Tensor: - if y.op is Ops.BIND: return Tensor(y, **kwargs, requires_grad=False) + # TODO: remove this and stay in weakint + if y.dtype == dtypes.weakint: y = _index_to_concrete_int(y) + if y.op is Ops.BIND: + var, val = y.unbind() + _device = canonicalize_device(kwargs.get("device")) + const = UOp.const(var.dtype, val, _device, ()) + return Tensor(y.replace(src=(var.replace(src=const.src), const)), **kwargs, requires_grad=False) if y.op is Ops.CONST: return Tensor(y.arg, **kwargs, requires_grad=False) if y.op is Ops.MUL: return Tensor.from_uop(y.src[0]) * Tensor.from_uop(y.src[1]) if y.op is Ops.ADD: return Tensor.from_uop(y.src[0]) + Tensor.from_uop(y.src[1]) From 248cd9b39fede3608d0b0000b088b3664a2f72f6 Mon Sep 17 00:00:00 2001 From: chenyu Date: Mon, 23 Mar 2026 00:29:08 -0400 Subject: [PATCH 06/19] make Tensor init the only caller of Tensor.from_uop (#15421) * make Tensor init the only caller of Tensor.from_uop prep broadcast cleanups * type --- tinygrad/tensor.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tinygrad/tensor.py b/tinygrad/tensor.py index 68c814de23..3fa3115d9d 100644 --- a/tinygrad/tensor.py +++ b/tinygrad/tensor.py @@ -134,7 +134,7 @@ class Tensor(OpMixin): if isinstance(data, UOp): assert _dtype is None or _dtype==data.dtype or data.dtype==dtypes.weakint, f"dtype mismatch: {_dtype} vs {data.dtype}" # if data is dtype.weakint that means that this is a symbolic int and we need to lower it to something we can make a Tensor out of - if data.dtype == dtypes.weakint: data = Tensor.from_uop(data).uop + if data.dtype == dtypes.weakint: data = Tensor.from_uop(data, device=_device).uop elif data is None: data = UOp.const(_dtype or dtypes.default_float, 0, _device) elif isinstance(data, get_args(ConstType)): @@ -1822,7 +1822,7 @@ class Tensor(OpMixin): output_dtype = self.dtype if dtypes.is_float(self.dtype) else dtypes.float32 numerator = self.cast(sum_acc_dtype(self.dtype)).sum(axis=axis, keepdim=keepdim) denominator = prod([si for si, so in zip(self.shape, self.sum(axis=axis, keepdim=True).shape) if resolve(si != so)]) - return numerator.div(Tensor.from_uop(denominator, device=numerator.device) if isinstance(denominator, UOp) else denominator).cast(output_dtype) + return numerator.div(denominator).cast(output_dtype) def var(self, axis:int|Sequence[int]|None=None, keepdim=False, correction=1) -> Tensor: """ @@ -1848,7 +1848,8 @@ class Tensor(OpMixin): """ squares = (self - self.mean(axis=axis, keepdim=True)).square() n = prod([si for si, so in zip(self.shape, squares.sum(axis=axis, keepdim=True).shape) if resolve(si != so)]) - denominator = (Tensor.from_uop(n, device=self.device) if isinstance(n, UOp) else Tensor(n, device=self.device)) - correction + denominator = Tensor(n, device=self.device) - correction + # TODO: infer device and remove relu return squares.sum(axis=axis, keepdim=keepdim).div(denominator.relu()) def var_mean(self, axis:int|Sequence[int]|None=None, keepdim=False, correction=1) -> tuple[Tensor, Tensor]: @@ -2954,9 +2955,8 @@ class Tensor(OpMixin): # make y a Tensor assert isinstance(y, (*get_args(ConstType), UOp)), f"{type(y)=}, {y=}" if y is Invalid or dtypes.is_float(x.dtype) or (dtypes.is_int(x.dtype) and isinstance(y, int)): y_dtype = x.dtype - elif not isinstance(y, UOp): y_dtype = dtypes.from_py(y) - if isinstance(y, UOp): y = Tensor.from_uop(y, device=x.device) - else: y = Tensor(y_dtype.const(y), x.device, y_dtype, requires_grad=False) + else: y_dtype = y.dtype if isinstance(y, UOp) else dtypes.from_py(y) + y = Tensor(y, x.device, y_dtype, requires_grad=False) if match_dtype and x.dtype != y.dtype: output_dtype = least_upper_dtype(x.dtype, y.dtype) @@ -2993,7 +2993,7 @@ class Tensor(OpMixin): a, b = self._broadcasted(x, reverse) return a + (-b) - def div(self, x:Tensor|ConstType, reverse=False, rounding_mode:Literal["trunc", "floor"]|None=None) -> Tensor: + def div(self, x:Tensor|ConstType|UOp, reverse=False, rounding_mode:Literal["trunc", "floor"]|None=None) -> Tensor: """ Divides `self` by `x`. Equivalent to `self / x`. From 395aacd77dcf6452e80012b10975ae600a27b640 Mon Sep 17 00:00:00 2001 From: nimlgen <138685161+nimlgen@users.noreply.github.com> Date: Mon, 23 Mar 2026 14:10:34 +0800 Subject: [PATCH 07/19] jit: prune on linear (#15423) * jit: prune on linear * x * this is from the future --- tinygrad/engine/jit.py | 43 +++++++++++++++++++++++---------------- tinygrad/engine/memory.py | 7 ++++++- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/tinygrad/engine/jit.py b/tinygrad/engine/jit.py index b97443c69d..7d10a9d2e1 100644 --- a/tinygrad/engine/jit.py +++ b/tinygrad/engine/jit.py @@ -1,17 +1,27 @@ from typing import TypeVar, Generic, Callable, cast, Any import functools, collections from tinygrad.tensor import Tensor -from tinygrad.helpers import flatten, merge_dicts, DEBUG, Context, BEAM, getenv, colored, JIT, JIT_BATCH_SIZE, dedup, partition, unwrap +from tinygrad.helpers import flatten, merge_dicts, DEBUG, Context, BEAM, getenv, colored, JIT, JIT_BATCH_SIZE, dedup, unwrap from tinygrad.device import Buffer, Compiled, Device, MultiBuffer from tinygrad.dtype import DType -from tinygrad.uop.ops import UOp, Variable, sym_infer, Ops +from tinygrad.uop.ops import UOp, Variable, sym_infer, Ops, buffers from tinygrad.engine.realize import ExecItem, capturing, ViewOp, BufferCopy, BufferXfer, EncDec, CompiledRunner, Runner, Estimates -from tinygrad.engine.memory import _internal_memory_planner +from tinygrad.engine.memory import _internal_memory_planner, _collect_bufs from tinygrad.engine.schedule import linear_to_schedule from tinygrad.nn.state import get_parameters from tinygrad.schedule.rangeify import mop_cleanup from dataclasses import dataclass, replace +def prune_linear(linear:UOp, needed:set[UOp]) -> tuple[UOp, UOp]: + kept, onetime = [], [] + for si in linear.src: + si_bufs = {b for src in si.src[1:] for b in _collect_bufs(src)} + if not si_bufs.isdisjoint(needed): + kept.append(si) + needed |= si_bufs + else: onetime.append(si) + return linear.replace(src=tuple(kept)), linear.replace(src=tuple(onetime)) + class GraphException(Exception): pass class JitError(Exception): pass @@ -323,9 +333,20 @@ class TinyJit(Generic[ReturnType]): if DEBUG >= 1: print(f"JIT captured {len(self._linears)} linears with {len(input_buffers)} inputs") # combine all captured linears into one and convert to ExecItems - jit_cache = [ei.lower() for ei in linear_to_schedule(UOp(Ops.LINEAR, src=tuple(flatten([l.src for l in self._linears]))))] + big_linear = UOp(Ops.LINEAR, src=tuple(flatten([l.src for l in self._linears]))) del self._linears + if self.prune: + big_linear, onetime_linear = prune_linear(big_linear, {k for k,v in buffers.items() if isinstance(v, Buffer) and v in set(input_buffers)}) + if DEBUG >= 1: print(f"pruned from {len(big_linear.src) + len(onetime_linear.src)} -> {len(big_linear.src)} kernels") + for ei in (si.lower() for si in linear_to_schedule(onetime_linear)): + for b in ei.bufs: cast(Buffer, b).ensure_allocated() + ei.run(var_vals, jit=True) + del onetime_linear + + jit_cache = [ei.lower() for ei in linear_to_schedule(big_linear)] + del big_linear + # track inputs that are views of buffers # TODO: eventually expected_buffers should live in ExecItem extra_view_inputs: list[tuple[int, int, str, int, DType]] = [] @@ -335,20 +356,6 @@ class TinyJit(Generic[ReturnType]): input_buffers.append(b) extra_view_inputs.append((input_buffers.index(b.base), b.offset, b.device, b.size, b.dtype)) - # prune independent kernels (optional) - if self.prune: - depends = set(input_buffers) - update_depends(depends, jit_cache) - pruned, onetime = partition(jit_cache, lambda ei: any(b in depends for b in get_out_buffers_for_ei(ei))) - if DEBUG >= 1: print(f"pruned from {len(jit_cache)} -> {len(pruned)} kernels") - # sync before re-executing onetime kernels - for dev in set(Device[b.device] for ei in onetime for b in ei.bufs if b is not None): dev.synchronize() - # run the onetime kernels here - for ei in onetime: - for b in ei.bufs: cast(Buffer, b).ensure_allocated() - ei.run(var_vals, jit=True) - jit_cache = pruned - # memory planning (optional) copies = [(cast(Buffer,ji.bufs[0]),cast(Buffer,ji.bufs[1])) for ji in jit_cache if isinstance(ji.prg, (BufferXfer, BufferCopy, EncDec))] assigned = _internal_memory_planner([cast(list[Buffer], item.bufs) for item in jit_cache], copies, debug_prefix="JIT ") diff --git a/tinygrad/engine/memory.py b/tinygrad/engine/memory.py index d8800b519c..25748a5a0a 100644 --- a/tinygrad/engine/memory.py +++ b/tinygrad/engine/memory.py @@ -3,10 +3,15 @@ from collections import defaultdict from tinygrad.engine.realize import ExecItem from tinygrad.device import Device, Buffer from tinygrad.helpers import NO_MEMORY_PLANNER, dedup, DEBUG, round_up -from tinygrad.uop.ops import Ops +from tinygrad.uop.ops import UOp, Ops from tinygrad.dtype import dtypes from tinygrad.runtime.support.memory import TLSFAllocator +def _collect_bufs(u:UOp) -> list[UOp]: + if u.op is Ops.BUFFER: return [u] + if u.op in {Ops.MSELECT, Ops.MSTACK}: return [b for s in u.src for b in _collect_bufs(s)] + return [] + LaneKey = tuple[str, int] # **************** memory planning **************** From fd3559103b30a45170cd22bea68281c8948696ad Mon Sep 17 00:00:00 2001 From: qazal <77887910+Qazalin@users.noreply.github.com> Date: Mon, 23 Mar 2026 08:50:20 +0200 Subject: [PATCH 08/19] viz/cli: better error message for empty itrace (#15425) --- extra/viz/cli.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/extra/viz/cli.py b/extra/viz/cli.py index 1cd2fffb50..a1f2c65328 100755 --- a/extra/viz/cli.py +++ b/extra/viz/cli.py @@ -105,7 +105,10 @@ if __name__ == "__main__": assert args.limit > 1, f"SQTT limit must be greater than 1, got {args.limit}" sqtt_events, has_more = viz.sqtt_timeline(*sqtt_data, max_pkts=args.offset+args.limit) sqtt_pkts = [e for e in sqtt_events if type(e).__name__ == "ProfileRangeEvent"] - pc_map = next(e.arg for e in sqtt_events if type(e).__name__ == "ProfilePointEvent" and e.key == 'pcMap') + pc_map = next((e.arg for e in sqtt_events if type(e).__name__ == "ProfilePointEvent" and e.key == 'pcMap'), None) + if pc_map is None: + print(f"No SQTT instruction trace data for {args.device}") + sys.exit(0) # modern terminals support 24-bit color def hex_colored(st:str, color:str) -> str: return f"\x1b[38;2;{int(color[1:3],16)};{int(color[3:5],16)};{int(color[5:7],16)}m{st}\x1b[0m" WAVE_COLORS = ((('VALU', 'VINTERP'), '#ffffc0'), (('SALU',), '#cef263'), (('VMEM',), '#b2b7c9'), (('LOAD', 'SMEM'), '#ffc0c0'), From c74fa9bbe1b8e6427034a65dfb4e7d575de645bc Mon Sep 17 00:00:00 2001 From: nimlgen <138685161+nimlgen@users.noreply.github.com> Date: Mon, 23 Mar 2026 15:34:59 +0800 Subject: [PATCH 09/19] fix jitbeam not triggered (#15424) * um * beam * x * f --- test/backend/test_jit.py | 12 ++++++++++++ tinygrad/engine/jit.py | 13 ++++++------- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/test/backend/test_jit.py b/test/backend/test_jit.py index 5f96286c03..d2fc3d16bb 100644 --- a/test/backend/test_jit.py +++ b/test/backend/test_jit.py @@ -39,6 +39,18 @@ class TestJit(unittest.TestCase): def add(a, b): return (a+b).realize() _simple_test(add) + def test_jitbeam_triggers_beam(self): + from unittest.mock import patch + from tinygrad.helpers import getenv as _getenv + @TinyJit + def add(a, b): return (a+b).realize() + a, b = Tensor.ones(10, 10).contiguous().realize(), Tensor.ones(10, 10).contiguous().realize() + with patch("tinygrad.codegen.opt.search.beam_search", wraps=lambda k,*a,**kw: k) as mock_beam: + add(a, b) + assert mock_beam.call_count == 0 + with patch("tinygrad.engine.jit.getenv", side_effect=lambda k, d=0: 1 if k == "JITBEAM" else _getenv(k, d)): add(a, b) + assert mock_beam.call_count == 1 + def test_simple_jit_reset(self): @TinyJit def add(a, b): return (a+b).realize() diff --git a/tinygrad/engine/jit.py b/tinygrad/engine/jit.py index 7d10a9d2e1..6f4d0cdfac 100644 --- a/tinygrad/engine/jit.py +++ b/tinygrad/engine/jit.py @@ -322,12 +322,11 @@ class TinyJit(Generic[ReturnType]): assert self.fxn is not None if capturing: raise RuntimeError(f"having TinyJit inside another TinyJit is not supported {len(capturing)=} {capturing=}") self._linears: list[UOp] = [] - with Context(BEAM=getenv("JITBEAM", BEAM.value)): - capturing.append(self) - try: - ret = self.fxn(*args, **kwargs) - if len(params:=get_parameters(ret)): Tensor.realize(*params) - finally: capturing.clear() + capturing.append(self) + try: + ret = self.fxn(*args, **kwargs) + if len(params:=get_parameters(ret)): Tensor.realize(*params) + finally: capturing.clear() if not len(self._linears): raise JitError("didn't JIT anything!") _check_no_non_tensor_return(ret) if DEBUG >= 1: print(f"JIT captured {len(self._linears)} linears with {len(input_buffers)} inputs") @@ -344,7 +343,7 @@ class TinyJit(Generic[ReturnType]): ei.run(var_vals, jit=True) del onetime_linear - jit_cache = [ei.lower() for ei in linear_to_schedule(big_linear)] + with Context(BEAM=getenv("JITBEAM", BEAM.value)): jit_cache = [ei.lower() for ei in linear_to_schedule(big_linear)] del big_linear # track inputs that are views of buffers From ddaeebb500cac285782bd1f290fb4b367416f0eb Mon Sep 17 00:00:00 2001 From: Christopher Milan Date: Mon, 23 Mar 2026 00:37:44 -0700 Subject: [PATCH 10/19] nir: add shift support (#15426) --- tinygrad/renderer/nir.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tinygrad/renderer/nir.py b/tinygrad/renderer/nir.py index 64e36295f6..89a611afad 100644 --- a/tinygrad/renderer/nir.py +++ b/tinygrad/renderer/nir.py @@ -17,8 +17,8 @@ def glsl_type(t:DType): return mesa.glsl_array_type(glsl_type(t.base), t.size, 0 # alu ops, aop[][] 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"} + Ops.AND: "iand", Ops.XOR: "ixor", Ops.WHERE: "bcsel", Ops.MAX: "umax", Ops.SHL: "ishl", Ops.SHR: "ushr"} +s_aop = {**u_aop, Ops.CMPLT: "ilt", Ops.IDIV: "idiv", Ops.MOD: "irem", Ops.MAX: "imax", Ops.SHR: "ishr"} 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}} @@ -130,6 +130,8 @@ class NIRRenderer(Renderer): lambda x: x.replace(dtype=dtypes.uint8, src=x.src[0:1]+((x.src[1].cast(dtypes.uint8),) if len(x.src)>=2 else ())+x.src[2:]).cast(dtypes.bool)), (UPat(Ops.STORE, src=(UPat(), UPat(dtype=dtypes.bool)), name="x", allow_any_len=True), lambda x: x.replace(src=x.src[0:1] + (x.src[1].cast(dtypes.uint8),) + x.src[2:])), + # NIR requires shift amount to be 32 bit: https://docs.mesa3d.org/nir/alu.html#nir-alu-op-ishl + (UPat((Ops.SHL, Ops.SHR), name="x"), lambda x: x.replace(src=(x.src[0], x.src[1].cast(dtypes.uint))) if x.src[1].dtype.bitsize != 32 else None), # OpConvertFToU is undefined if Result Type is not wide enough, cast through int32 # ref: https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpConvertFToU (UPat(Ops.CAST, (dtypes.uchar, dtypes.ushort), src=(UPat.var("x", dtypes.floats),), name="c"), lambda x,c: x.cast(dtypes.int32).cast(c.dtype)), From 1568a5ed07333b65ed54e0cc6509530d4b554cc8 Mon Sep 17 00:00:00 2001 From: qazal <77887910+Qazalin@users.noreply.github.com> Date: Mon, 23 Mar 2026 09:59:59 +0200 Subject: [PATCH 11/19] viz: show dispatch to exec delay in sidebar (#15428) --- tinygrad/viz/js/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tinygrad/viz/js/index.js b/tinygrad/viz/js/index.js index 14553c64fe..b039ddccfc 100644 --- a/tinygrad/viz/js/index.js +++ b/tinygrad/viz/js/index.js @@ -315,6 +315,7 @@ function setFocus(key) { p.append("span").style("margin-left", "8px").style("color", "#f0f0f566").text(formatTime(e.x)); tableData.push(["Cycle", formatTime(e.x-data.instSt)], ["Time", p.node()]); } else tableData.push(["Start Time", formatTime(e.x)]); + if (data.link != null) tableData.push(["Delay", `${formatTime(Math.abs(selectShape(data.link[0]).e.x - selectShape(data.link[1]).e.x))} Cycles`]); html.append(() => tabulate(tableData)); let group = html.append("div").classed("args", true); for (const r of rest) group.append("p").text(r); From c62dea688143a650cfd2bb71cb9b300791c27b46 Mon Sep 17 00:00:00 2001 From: George Hotz <72895+geohot@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:15:10 +0800 Subject: [PATCH 12/19] ai slop flash attention (it works) (#15401) * ai slop flash attention (it works) * speed up, 2 TFLOPS + 7 GB/s * simpler * simpler * optimize * faster * warp shuffle * sqtt: link dispatch to exec (#15396) * sqtt packet linking infra python * javascript * ~doubly linked list * ui works * work * exec can also highlight the pc, coloring work * more work * rm sqtt/model.py, doesn't need to be upstreamed * viz: no context enters in cli, update llama profile (#15404) * removed unused named arg in rules [pr] (#15414) * viz: sqtt printer in viz/cli.py (#15411) * work * sqtt timeline in CLI * format all printers nicely * s/Showed/Printed * ansistrip * sys.exit * keep colors in list * work from amd_copy_matmul * has_more always gets returned * linter * don't print colors * more colors * wow this is so deep * work * minor details * selected * improve progress bar * remove it * 22, global_load_vaddr is so long * remove *0 hack in sign, gradient materializes zeros for unconnected nodes (#15416) Amp-Thread-ID: https://ampcode.com/threads/T-019d1612-6322-706b-a94d-a812400a55cb Co-authored-by: Amp * works * cnt=20 * revert that * uop slice tests * simpler --------- Co-authored-by: qazal <77887910+Qazalin@users.noreply.github.com> Co-authored-by: chenyu Co-authored-by: gg Co-authored-by: Amp --- extra/gemm/amd_flash_attention.py | 208 ++++++++++++++++++++++++++++++ test/null/test_uop_graph.py | 53 ++++++++ tinygrad/uop/ops.py | 8 +- 3 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 extra/gemm/amd_flash_attention.py diff --git a/extra/gemm/amd_flash_attention.py b/extra/gemm/amd_flash_attention.py new file mode 100644 index 0000000000..5ba6dcb4ad --- /dev/null +++ b/extra/gemm/amd_flash_attention.py @@ -0,0 +1,208 @@ +from tinygrad import Tensor, UOp, getenv +from tinygrad.uop.ops import AxisType, KernelInfo, Ops +from tinygrad.dtype import AddrSpace, dtypes +from tinygrad.helpers import DEBUG, GlobalCounters, Context +import math + +B = getenv("B", 1) +H = getenv("H", 32) +N = getenv("N", 1024) +D = getenv("D", 64) +assert D % 16 == 0 and N % 16 == 0 + +BLOCK_M, BLOCK_N = 64, 64 +WARP_SIZE = 32 +WMMA_M, WMMA_N, WMMA_K = 16, 16, 16 +WAVES_M, WAVES_N = 4, 1 +LANES_PER_WAVE_M, LANES_PER_WAVE_N = 2, 16 +WMMA_ACC = WMMA_M // LANES_PER_WAVE_M +THREADS_PER_BLOCK = WARP_SIZE * WAVES_M * WAVES_N + +TM = BLOCK_M // (WAVES_M * LANES_PER_WAVE_M) +TN = BLOCK_N // (WAVES_N * LANES_PER_WAVE_N) +TD = D // (WAVES_N * LANES_PER_WAVE_N) +LDS_PAD = 4 # pad LDS rows to reduce bank conflicts + +WMMA_ARG = ((WMMA_M, WMMA_N, WMMA_K), 'AMD', 32) +SCALE = 1.0 / math.sqrt(D) +LOG2E = math.log2(math.e) + +def warp_shfl_xor(val, offset, lane): + """Read val from lane ^ offset using ds_bpermute.""" + idx = ((lane ^ offset) * 4).cast(dtypes.int) + return UOp(Ops.CUSTOM, dtypes.float, (idx, val), + arg="__builtin_bit_cast(float, __builtin_amdgcn_ds_bpermute({0}, __builtin_bit_cast(int, {1})))") + +def warp_reduce_max(val, lane): + """Tree reduce MAX across LANES_PER_WAVE_N=16 lanes.""" + for offset in [8, 4, 2, 1]: + val = UOp(Ops.MAX, dtypes.float, (val, warp_shfl_xor(val, offset, lane))) + return val + +def warp_reduce_sum(val, lane): + """Tree reduce SUM across LANES_PER_WAVE_N=16 lanes.""" + for offset in [8, 4, 2, 1]: + val = val + warp_shfl_xor(val, offset, lane) + return val + +def amd_flash_attention(o:UOp, q:UOp, k:UOp, v:UOp) -> UOp: + block_bh = UOp.range(B * H, 0, AxisType.GLOBAL) + block_m = UOp.range(N // BLOCK_M, 1, AxisType.GLOBAL) + + q = q.reshape(B*H, N//BLOCK_M, BLOCK_M, D)[block_bh, block_m] + k = k.reshape(B*H, N//BLOCK_N, BLOCK_N, D)[block_bh] + v = v.reshape(B*H, N//BLOCK_N, BLOCK_N, D)[block_bh] + o = o.reshape(B*H, N//BLOCK_M, BLOCK_M, D)[block_bh, block_m] + + wave_m = UOp.range(WAVES_M, 2, AxisType.LOCAL) + wave_n = UOp.range(WAVES_N, 3, AxisType.LOCAL) + lane = UOp.range(WARP_SIZE, -1, AxisType.WARP) + tid = (wave_m * WAVES_N + wave_n) * WARP_SIZE + lane + lane_m = lane // LANES_PER_WAVE_N + lane_n = lane % LANES_PER_WAVE_N + + # LDS allocation: slot 0 = Q then P (shared), slot 1 = K then V + # TODO: the memory planner should be able to find this reuse + ELEMS_PER_THREAD = BLOCK_M * D // THREADS_PER_BLOCK + QP_lds = UOp.placeholder((BLOCK_M, D + LDS_PAD), dtypes.half, slot=0, addrspace=AddrSpace.LOCAL) + KV_lds = UOp.placeholder((BLOCK_N, D + LDS_PAD), dtypes.half, slot=1, addrspace=AddrSpace.LOCAL)[:, :D] + + # register state + acc = UOp.placeholder((TM, TD), dtypes.float, slot=2, addrspace=AddrSpace.REG) + m_i = UOp.placeholder((TM,), dtypes.float, slot=3, addrspace=AddrSpace.REG) + l_i = UOp.placeholder((TM,), dtypes.float, slot=4, addrspace=AddrSpace.REG) + acc = acc.after(acc.store(acc.const_like(0))) + m_i = m_i.after(m_i.store(m_i.const_like(-math.inf))) + l_i = l_i.after(l_i.store(l_i.const_like(0))) + + # ====== KV tile loop ====== + n_tile = UOp.range(N // BLOCK_N, 100, AxisType.REDUCE) + + # load Q + K into LDS (Q reloaded each iteration since P overwrites slot 0) + Q_lds = QP_lds[:, :D] + Q_store = Q_lds.after(n_tile).reshape(THREADS_PER_BLOCK, ELEMS_PER_THREAD)[tid].store( + q.reshape(THREADS_PER_BLOCK, ELEMS_PER_THREAD)[tid]) + K_store = KV_lds.reshape(THREADS_PER_BLOCK, ELEMS_PER_THREAD)[tid].store( + k[n_tile].reshape(THREADS_PER_BLOCK, ELEMS_PER_THREAD)[tid]) + qk_load_barrier = UOp.barrier(UOp.group(Q_store, K_store)) + Q_lds = Q_lds.after(qk_load_barrier) + KV_lds_k = KV_lds.after(qk_load_barrier) + + # -- S = Q @ K^T via WMMA (re-init each n_tile) -- + S_reg = UOp.placeholder((TM, TN), dtypes.float, slot=6, addrspace=AddrSpace.REG) + S_reg = S_reg.after(S_reg.after(n_tile).store(S_reg.const_like(0))) + k_qk = UOp.range(D // WMMA_K, 101, AxisType.REDUCE) + tm1 = UOp.range(TM // WMMA_ACC, 200, AxisType.LOOP) + tn1 = UOp.range(TN, 201, AxisType.LOOP) + S_frag = S_reg.reshape(TM // WMMA_ACC, WMMA_ACC, TN).permute(0, 2, 1)[tm1, tn1] + q_frag = Q_lds.reshape(WAVES_M, TM // WMMA_ACC, WMMA_M, D // WMMA_K, WMMA_K)[wave_m, tm1, lane_n, k_qk] + k_frag = KV_lds_k.reshape(WAVES_N, TN, WMMA_N, D // WMMA_K, WMMA_K)[wave_n, tn1, lane_n, k_qk] + qk = UOp(Ops.SHAPED_WMMA, dtypes.float, (q_frag, k_frag, S_frag.after(k_qk)), arg=WMMA_ARG) + qk_done = S_frag.store(qk).end(tm1, tn1).end(k_qk) + S_reg = S_reg.after(qk_done) + + # -- softmax in registers with warp shuffles -- + S_reg = S_reg.after(S_reg.store(S_reg * SCALE)) + + # per-thread local row max over TN=4 elements, then warp reduce across 16 lanes + m_ij = UOp.placeholder((TM,), dtypes.float, slot=7, addrspace=AddrSpace.REG) + m_ij = m_ij.after(m_ij.after(n_tile).store(m_ij.const_like(-math.inf))) + rm1 = UOp.range(TM, 260, AxisType.LOOP) + rm2 = UOp.range(TN, 261, AxisType.REDUCE) + m_ij = m_ij.after(m_ij[rm1].store(UOp(Ops.MAX, dtypes.float, (m_ij.after(rm1, rm2)[rm1], S_reg[rm1, rm2]))).end(rm2, rm1)) + # warp reduce max (in-place) + ri_w = UOp.range(TM, 270, AxisType.LOOP) + m_ij = m_ij.after(m_ij[ri_w].store(warp_reduce_max(m_ij[ri_w], lane)).end(ri_w)) + + # compute P = exp(S - m_ij) in S_reg (manual ranges) + rp0a = UOp.range(TM, 275, AxisType.LOOP) + rp0b = UOp.range(TN, 276, AxisType.LOOP) + S_reg = S_reg.after(S_reg[rp0a, rp0b].store(((S_reg[rp0a, rp0b] - m_ij[rp0a]) * LOG2E).exp2()).end(rp0a, rp0b)) + + p_local = UOp.placeholder((TM,), dtypes.float, slot=8, addrspace=AddrSpace.REG) + p_local = p_local.after(p_local.after(n_tile).store(p_local.const_like(0))) + rp1 = UOp.range(TM, 290, AxisType.LOOP) + rp2 = UOp.range(TN, 291, AxisType.REDUCE) + p_local = p_local.after(p_local[rp1].store(p_local.after(rp1, rp2)[rp1] + S_reg[rp1, rp2]).end(rp2, rp1)) + ri_ws = UOp.range(TM, 295, AxisType.LOOP) + p_sum = p_local.after(p_local[ri_ws].store(warp_reduce_sum(p_local[ri_ws], lane)).end(ri_ws)) + + # write P = exp(S - m_ij) to P_lds (reuses slot 0, Q no longer needed) + P_lds = QP_lds[:, :BLOCK_N] + P_write = P_lds.reshape(WAVES_M, TM // WMMA_ACC, WMMA_ACC, LANES_PER_WAVE_M, WAVES_N, TN, LANES_PER_WAVE_N) + P_write = P_write.permute((0, 4, 3, 6, 1, 2, 5)).reshape(THREADS_PER_BLOCK, TM, TN) + rw1 = UOp.range(TM, 296, AxisType.LOOP) + rw2 = UOp.range(TN, 297, AxisType.LOOP) + P_store = P_write[tid, rw1, rw2].store(S_reg[rw1, rw2].cast(dtypes.half)).end(rw1, rw2) + + # -- online softmax correction -- + ri4 = UOp.range(TM, 330, AxisType.LOOP) + m_new_val = UOp(Ops.MAX, dtypes.float, (m_i[ri4], m_ij[ri4])) + alpha_val = ((m_i[ri4] - m_new_val) * LOG2E).exp2() + beta_val = ((m_ij[ri4] - m_new_val) * LOG2E).exp2() + rj4 = UOp.range(TD, 331, AxisType.LOOP) + correction = UOp.group( + acc[ri4, rj4].store(alpha_val * acc[ri4, rj4]).end(rj4), + l_i[ri4].store(alpha_val * l_i[ri4] + beta_val * p_sum[ri4]), + m_i[ri4].store(m_new_val), + ).end(ri4) + acc = acc.after(correction) + l_i = l_i.after(correction) + m_i = m_i.after(correction) + + # load V into KV_lds (must wait for QK WMMA to finish reading K from KV_lds) + V_store = KV_lds.after(qk_done).reshape(THREADS_PER_BLOCK, ELEMS_PER_THREAD)[tid].store( + v[n_tile].reshape(THREADS_PER_BLOCK, ELEMS_PER_THREAD)[tid]) + pv_barrier = UOp.barrier(UOp.group(P_store, V_store)) + P_lds = P_lds.after(pv_barrier) + KV_lds_v = KV_lds.after(pv_barrier) + + # -- acc += P @ V via WMMA -- + k_pv = UOp.range(BLOCK_N // WMMA_K, 400, AxisType.REDUCE) + tm2 = UOp.range(TM // WMMA_ACC, 401, AxisType.LOOP) + tn2 = UOp.range(TD, 402, AxisType.LOOP) + acc_frag = acc.reshape(TM // WMMA_ACC, WMMA_ACC, TD).permute(0, 2, 1)[tm2, tn2] + p_frag = P_lds.reshape(WAVES_M, TM // WMMA_ACC, WMMA_M, BLOCK_N // WMMA_K, WMMA_K)[wave_m, tm2, lane_n, k_pv] + v_frag = KV_lds_v.reshape(WAVES_N, TD, WMMA_N, BLOCK_N // WMMA_K, WMMA_K)[wave_n, tn2, lane_n, k_pv] + pv = UOp(Ops.SHAPED_WMMA, dtypes.float, (p_frag, v_frag, acc_frag.after(k_pv)), arg=WMMA_ARG) + + # end KV tile loop + n_tile_end = acc_frag.store(pv).end(tm2, tn2).end(k_pv).barrier().end(n_tile) + acc = acc.after(n_tile_end) + l_i = l_i.after(n_tile_end) + m_i = m_i.after(n_tile_end) + + # normalize: acc /= l_i + acc = acc.after(acc.store(acc * (1 / l_i).reshape(TM, 1).expand(TM, TD))) + + # store output + o = o.reshape(WAVES_M, TM // WMMA_ACC, WMMA_ACC, LANES_PER_WAVE_M, WAVES_N, TD, LANES_PER_WAVE_N) + o = o.permute((0, 4, 3, 6, 1, 2, 5)).reshape(THREADS_PER_BLOCK, TM, TD) + return o[tid].store(acc).end(wave_m, wave_n, lane).end(block_m, block_bh).sink(arg=KernelInfo(opts_to_apply=())) + +if __name__ == "__main__": + q = Tensor.rand(B, H, N, D).cast(dtypes.half) + k = Tensor.rand(B, H, N, D).cast(dtypes.half) + v = Tensor.rand(B, H, N, D).cast(dtypes.half) + o = Tensor.empty(B, H, N, D, dtype=dtypes.float) + with Context(DEBUG=0): Tensor.realize(q, k, v) + + q_flat, k_flat, v_flat, o_flat = q.reshape(B*H, N, D), k.reshape(B*H, N, D), v.reshape(B*H, N, D), o.reshape(B*H, N, D) + NUM_RUNS = getenv("CNT", 5) + ets = [] + with Context(DEBUG=getenv("KDBG", 2)): + for _ in range(NUM_RUNS): + GlobalCounters.reset() + tst = Tensor.custom_kernel(o_flat, q_flat, k_flat, v_flat, fxn=amd_flash_attention)[0].realize() + ets.append(GlobalCounters.time_sum_s) + print(f"best time: {min(ets)*1e3:.2f}ms") + + if getenv("VERIFY", 1): + with Context(DEBUG=0): + ref = q.float().scaled_dot_product_attention(k.float(), v.float()).reshape(B*H, N, D).realize() + err = (ref - tst).square().mean().item() + print(f"mean squared error {err}") + if err > 1e-2: + raise RuntimeError("flash attention is wrong!") + else: + print("flash attention is correct!") diff --git a/test/null/test_uop_graph.py b/test/null/test_uop_graph.py index 0071fd8617..6d01213bdd 100644 --- a/test/null/test_uop_graph.py +++ b/test/null/test_uop_graph.py @@ -816,5 +816,58 @@ class TestUOpTags(unittest.TestCase): g = graph_rewrite(g, pm_plus_1) assert g.ssimplify() == 6 +class TestUOpGetItem(unittest.TestCase): + def _placeholder(self, shape, dtype=dtypes.half): + return UOp.placeholder(shape, dtype, slot=0, addrspace=AddrSpace.LOCAL) + + # full slices (no shrink) + def test_full_slice(self): + p = self._placeholder((64, 64)) + self.assertEqual(p[:, :].shape, (64, 64)) + def test_full_slice_explicit(self): + p = self._placeholder((64, 64)) + self.assertEqual(p[0:64, 0:64].shape, (64, 64)) + + # partial slices (shrink) + def test_shrink_cols(self): + p = self._placeholder((64, 80)) + self.assertEqual(p[:, :64].shape, (64, 64)) + def test_shrink_rows(self): + p = self._placeholder((80, 64)) + self.assertEqual(p[:64, :].shape, (64, 64)) + def test_shrink_both(self): + p = self._placeholder((80, 80)) + self.assertEqual(p[:64, :64].shape, (64, 64)) + def test_shrink_start(self): + p = self._placeholder((64, 64)) + self.assertEqual(p[8:, :].shape, (56, 64)) + def test_shrink_start_and_end(self): + p = self._placeholder((64, 64)) + self.assertEqual(p[8:56, 4:60].shape, (48, 56)) + + # mixed slice and index + def test_index_and_slice(self): + p = self._placeholder((64, 80)) + r = UOp.range(64, 100) + result = p[r, :64] + self.assertEqual(result.shape, (64,)) + def test_slice_and_index(self): + p = self._placeholder((80, 64)) + r = UOp.range(64, 100) + result = p[:64, r] + self.assertEqual(result.shape, (64,)) + def test_shrink_then_index(self): + p = self._placeholder((64, 80)) + s = p[:, :64] + r = UOp.range(64, 100) + result = s[r] + self.assertEqual(result.shape, (64,)) + + # integer index (no slice) + def test_int_index(self): + p = self._placeholder((64, 64)) + result = p[0] + self.assertEqual(result.shape, (64,)) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/tinygrad/uop/ops.py b/tinygrad/uop/ops.py index ffe9a84303..af8efc5619 100644 --- a/tinygrad/uop/ops.py +++ b/tinygrad/uop/ops.py @@ -431,13 +431,17 @@ class UOp(OpMixin, metaclass=UOpMetaClass): if len(idx) < len(self.shape): idx += tuple([slice(None)]*(len(self.shape)-len(idx))) assert len(idx) == len(self.shape), f"__getitem__ shape mismatch, indexing {self.shape} with {len(idx)} args" if len(slice_idx:=[i for i,x in enumerate(idx) if isinstance(x, slice)]): - perm = self.permute(tuple([i for i in range(self.ndim) if i not in slice_idx] + slice_idx)) + # apply SHRINK for slices that aren't the full range + bounds = tuple((s.start or 0, s.stop if s.stop is not None else self.shape[i]) if isinstance(s, slice) else (0, self.shape[i]) + for i, s in enumerate(idx)) + src = self if all(b == (0, self.shape[i]) for i, b in enumerate(bounds)) else self.shrink(bounds) + perm = src.permute(tuple([i for i in range(src.ndim) if i not in slice_idx] + slice_idx)) return perm.index(*[UOp.const(dtypes.weakint, x) if isinstance(x, int) else x for x in idx if not isinstance(x, slice)], ptr=True) else: return self.index(*[UOp.const(dtypes.weakint, x) if isinstance(x, int) else x for x in idx]) def const_like(self, b:ConstLike): # constants can optionally have a DEVICE source - return UOp.const(self.dtype, b, device=self._device, shape=self._shape) + return UOp.const(self.dtype.base, b, device=self._device, shape=self._shape) def broadcast(self, count:int): assert self.dtype.vcount == 1 if count == 1: return self From c89576921dd3e1e7d508871286d231ff566819ac Mon Sep 17 00:00:00 2001 From: Pham Nguyen Hung <97870091+HangenYuu@users.noreply.github.com> Date: Mon, 23 Mar 2026 09:04:00 +0000 Subject: [PATCH 13/19] Updated the APIs of mnist_gan (#15429) Co-authored-by: pnhung1703@gmail.com --- examples/mnist_gan.py | 55 +++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/examples/mnist_gan.py b/examples/mnist_gan.py index 8e47942adf..3e1ebe0ef0 100644 --- a/examples/mnist_gan.py +++ b/examples/mnist_gan.py @@ -1,12 +1,11 @@ from pathlib import Path -import numpy as np import torch from torchvision.utils import make_grid, save_image from tinygrad.nn.state import get_parameters from tinygrad.tensor import Tensor from tinygrad.helpers import trange from tinygrad.nn import optim -from extra.datasets import fetch_mnist +from tinygrad.nn.datasets import mnist class LinearGen: def __init__(self): @@ -38,14 +37,14 @@ class LinearDisc: return x def make_batch(images): - sample = np.random.randint(0, len(images), size=(batch_size)) - image_b = images[sample].reshape(-1, 28*28).astype(np.float32) / 127.5 - 1.0 - return Tensor(image_b) + sample = Tensor.randint(batch_size, low=0, high=images.shape[0]) + return images[sample].reshape(batch_size, 28*28).cast('float').div(127.5).sub(1.0) def make_labels(bs, col, val=-2.0): - y = np.zeros((bs, 2), np.float32) - y[range(bs), [col] * bs] = val # Can we do label smoothing? i.e -2.0 changed to -1.98789. - return Tensor(y) + y = Tensor.zeros(bs, 2) + if col == 0: y = y + Tensor([val, 0.0]) + else: y = y + Tensor([0.0, val]) + return y def train_discriminator(optimizer, data_real, data_fake): real_labels = make_labels(batch_size, 1) @@ -71,12 +70,12 @@ def train_generator(optimizer, data_fake): if __name__ == "__main__": # data for training and validation - images_real = np.vstack(fetch_mnist()[::2]) + X_train, _, _, _ = mnist() ds_noise = Tensor.randn(64, 128, requires_grad=False) # parameters epochs, batch_size, k = 300, 512, 1 sample_interval = epochs // 10 - n_steps = len(images_real) // batch_size + n_steps = X_train.shape[0] // batch_size # models and optimizer generator = LinearGen() discriminator = LinearDisc() @@ -84,24 +83,24 @@ if __name__ == "__main__": output_dir = Path(".").resolve() / "outputs" output_dir.mkdir(exist_ok=True) # optimizers - optim_g = optim.Adam(get_parameters(generator),lr=0.0002, b1=0.5) # 0.0002 for equilibrium! - optim_d = optim.Adam(get_parameters(discriminator),lr=0.0002, b1=0.5) + optim_g = optim.Adam(get_parameters(generator), lr=0.0002, b1=0.5) # 0.0002 for equilibrium! + optim_d = optim.Adam(get_parameters(discriminator), lr=0.0002, b1=0.5) # training loop - Tensor.training = True - for epoch in (t := trange(epochs)): - loss_g, loss_d = 0.0, 0.0 - for _ in range(n_steps): - data_real = make_batch(images_real) - for step in range(k): # Try with k = 5 or 7. + with Tensor.train(): + for epoch in (t := trange(epochs)): + loss_g, loss_d = 0.0, 0.0 + for _ in range(n_steps): + data_real = make_batch(X_train) + for step in range(k): # Try with k = 5 or 7. + noise = Tensor.randn(batch_size, 128) + data_fake = generator.forward(noise).detach() + loss_d += train_discriminator(optim_d, data_real, data_fake) noise = Tensor.randn(batch_size, 128) - data_fake = generator.forward(noise).detach() - loss_d += train_discriminator(optim_d, data_real, data_fake) - noise = Tensor.randn(batch_size, 128) - data_fake = generator.forward(noise) - loss_g += train_generator(optim_g, data_fake) - if (epoch + 1) % sample_interval == 0: - fake_images = generator.forward(ds_noise).detach().numpy() - fake_images = (fake_images.reshape(-1, 1, 28, 28) + 1) / 2 # 0 - 1 range. - save_image(make_grid(torch.tensor(fake_images)), output_dir / f"image_{epoch+1}.jpg") - t.set_description(f"Generator loss: {loss_g/n_steps}, Discriminator loss: {loss_d/n_steps}") + data_fake = generator.forward(noise) + loss_g += train_generator(optim_g, data_fake) + if (epoch + 1) % sample_interval == 0: + fake_images = generator.forward(ds_noise).detach().numpy() + fake_images = (fake_images.reshape(-1, 1, 28, 28) + 1) / 2 # 0 - 1 range. + save_image(make_grid(torch.tensor(fake_images)), output_dir / f"image_{epoch+1}.jpg") + t.set_description(f"Generator loss: {loss_g/n_steps}, Discriminator loss: {loss_d/n_steps}") print("Training Completed!") From 66a86f88a0eb9dd7477689bab76aad909ecc1d36 Mon Sep 17 00:00:00 2001 From: chenyu Date: Mon, 23 Mar 2026 05:20:11 -0400 Subject: [PATCH 14/19] simpler Tensor._broadcasted inferred dtype (#15430) --- tinygrad/tensor.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tinygrad/tensor.py b/tinygrad/tensor.py index 3fa3115d9d..db3583a13e 100644 --- a/tinygrad/tensor.py +++ b/tinygrad/tensor.py @@ -5,7 +5,7 @@ from contextlib import ContextDecorator from typing import Any, Callable, ClassVar, Sequence, cast, get_args, Literal, SupportsIndex, ParamSpec, TypeVar, Generic, TYPE_CHECKING if TYPE_CHECKING: import numpy from tinygrad.dtype import DType, DTypeLike, dtypes, ConstType, least_upper_float, least_upper_dtype, sum_acc_dtype, to_dtype, truncate -from tinygrad.dtype import _from_np_dtype, _to_np_dtype, PyConst, Invalid +from tinygrad.dtype import _from_np_dtype, _to_np_dtype, PyConst, Invalid, InvalidType from tinygrad.helpers import argfix, make_tuple, flatten, prod, all_int, round_up, merge_dicts, argsort, getenv, all_same, fully_flatten from tinygrad.helpers import IMAGE, FLOAT16, WINO, Metadata, TRACEMETA, ASM_GEMM, ceildiv, fetch, is_numpy_ndarray, TracingKey, cpu_profile from tinygrad.helpers import suppress_finalizing, disable_gc @@ -2954,8 +2954,7 @@ class Tensor(OpMixin): if not isinstance(y, Tensor): # make y a Tensor assert isinstance(y, (*get_args(ConstType), UOp)), f"{type(y)=}, {y=}" - if y is Invalid or dtypes.is_float(x.dtype) or (dtypes.is_int(x.dtype) and isinstance(y, int)): y_dtype = x.dtype - else: y_dtype = y.dtype if isinstance(y, UOp) else dtypes.from_py(y) + y_dtype = x.dtype if dtypes.is_float(x.dtype) or (dtypes.is_int(x.dtype) and isinstance(y, (int, InvalidType))) else None y = Tensor(y, x.device, y_dtype, requires_grad=False) if match_dtype and x.dtype != y.dtype: From c4c53418f8a63330fc69ccff2ef3719300bc645c Mon Sep 17 00:00:00 2001 From: qazal <77887910+Qazalin@users.noreply.github.com> Date: Mon, 23 Mar 2026 12:24:04 +0200 Subject: [PATCH 15/19] sqtt: comment out flaky rocprof timestamp assert (#15432) * comment out rocprof assert, add new assert * better than > 0 assert * string --- test/amd/test_sqtt_examples.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/amd/test_sqtt_examples.py b/test/amd/test_sqtt_examples.py index dcdf2a23d3..b3ed48e48e 100644 --- a/test/amd/test_sqtt_examples.py +++ b/test/amd/test_sqtt_examples.py @@ -183,11 +183,18 @@ class SQTTExamplesTestBase(unittest.TestCase): our_waves: list[tuple[int, int]] = [] for event in events: wave_starts: dict[tuple[int, int, int], int] = {} + first_timestamp:int|None = None for p in decode(event.blob): + if first_timestamp is None: first_timestamp = p._time if isinstance(p, (WAVESTART, CDNA_WAVESTART, WAVESTART_RDNA4)): wave_starts[(p.wave, p.simd, p.cu)] = p._time elif isinstance(p, (WAVEEND, CDNA_WAVEEND)) and (key := (p.wave, p.simd, p.cu)) in wave_starts: our_waves.append((wave_starts[key], p._time)) - self.assertEqual(sorted(our_waves), sorted(roc_waves), f"wave times mismatch in {name}") + for st in wave_starts.values(): + self.assertGreater(st, first_timestamp, "wave start must be after the first packet") + # rocprof fails non deterministically and gives inaccurate timestamps. + #self.assertEqual(sorted(our_waves), sorted(roc_waves), f"wave times mismatch in {name}") + for st, et in our_waves: + self.assertGreater(et, st, "wave end must be after start") def test_rocprof_inst_times_match(self): """Instruction times must match rocprof exactly (excluding s_endpgm).""" From 2da008ae3b06f48485fcae50c4af76290e670958 Mon Sep 17 00:00:00 2001 From: nimlgen <138685161+nimlgen@users.noreply.github.com> Date: Mon, 23 Mar 2026 19:31:51 +0800 Subject: [PATCH 16/19] jit: rm replan (#15433) --- examples/benchmark_onnx.py | 2 +- test/backend/test_jit.py | 19 ------------------- tinygrad/engine/jit.py | 14 ++------------ 3 files changed, 3 insertions(+), 32 deletions(-) diff --git a/examples/benchmark_onnx.py b/examples/benchmark_onnx.py index 27568117f3..71bf7b8ed5 100644 --- a/examples/benchmark_onnx.py +++ b/examples/benchmark_onnx.py @@ -5,7 +5,7 @@ from extra.onnx_helpers import get_example_inputs, validate def load_onnx_model(onnx_file): run_onnx = OnnxRunner(onnx_file) - run_onnx_jit = TinyJit(lambda **kwargs: next(iter(run_onnx({k:v.to(None) for k,v in kwargs.items()}).values())), prune=True, optimize=True) + run_onnx_jit = TinyJit(lambda **kwargs: next(iter(run_onnx({k:v.to(None) for k,v in kwargs.items()}).values())), prune=True) return run_onnx_jit, run_onnx.graph_inputs if __name__ == "__main__": diff --git a/test/backend/test_jit.py b/test/backend/test_jit.py index d2fc3d16bb..765600d370 100644 --- a/test/backend/test_jit.py +++ b/test/backend/test_jit.py @@ -660,25 +660,6 @@ class TestJitFree(unittest.TestCase): fxn(Tensor([2])) self.assertEqual(x.item(), 8) - def test_replan_buffers_memory_layout(self): - if not hasattr(Device[Device.DEFAULT].allocator, '_offset'): raise unittest.SkipTest("replan_buffers_memory_layout useless") - - ext_tensor = Tensor([1,24,23,45,1]).contiguous() - ext_tensor_2 = Tensor([2,2,2,2,2]).contiguous() - @TinyJit - def fxn(x:Tensor): - out = (x*ext_tensor_2+ext_tensor).reshape(5,1).expand(5, 100).contiguous() - return out.sum() - for i in range(5): - out = fxn(Tensor([i,1,2,3,4])) - self.assertEqual(out.item(), 11400+200*i) - self.assertEqual(len(set([b.base for item in fxn.captured.jit_cache for b in item.bufs if b is not None])), 4) - fxn.captured.replan_buffers_memory_layout() - self.assertEqual(len(set([b.base for item in fxn.captured.jit_cache for b in item.bufs if b is not None])), 2) - - out = fxn(Tensor([11,1,2,3,4])) - self.assertEqual(out.item(), 13600) - class TestJitGraphSplit(unittest.TestCase): def compute(self, device, inp): assert inp.device == device, f"Input device {inp.device} does not match expected {device}" diff --git a/tinygrad/engine/jit.py b/tinygrad/engine/jit.py index 6f4d0cdfac..ad1ece845b 100644 --- a/tinygrad/engine/jit.py +++ b/tinygrad/engine/jit.py @@ -190,7 +190,7 @@ class CapturedJit(Generic[ReturnType]): expected_input_info: list[tuple[UOp, tuple[Variable, ...], DType, str]] # (view, variables, dtype, device) per input def __reduce__(self): - # TODO: free_intermediates here? replan_buffers_memory_layout here? + # TODO: free_intermediates here? return self.__class__, (self.ret, self.jit_cache, self.input_replace, self.extra_view_inputs, self.expected_names, self.expected_input_info) def __post_init__(self): @@ -218,14 +218,6 @@ class CapturedJit(Generic[ReturnType]): if b._base is not None and b._base.allocated_views == 0 and b._base.is_allocated(): b._base.deallocate() self.__post_init__() # reset the graph state - def replan_buffers_memory_layout(self): - blacklist = [t.uop.buffer for t in get_parameters(self.ret)] - asgn = _internal_memory_planner([[b for item in self.jit_cache for b in item.bufs if b is not None and b not in blacklist]], ignore_checks=True) - self.jit_cache = [replace(item, bufs=[asgn.get(b,b) if b is not None else None for b in item.bufs]) for item in self.jit_cache] - for old, new in asgn.items(): - if old.is_allocated(): new.ensure_allocated().copyin(old.as_memoryview()) - self.__post_init__() - # jit exec def __call__(self, input_buffers:list[Buffer], var_vals:dict[str, int]) -> ReturnType: # assign inputs @@ -282,13 +274,12 @@ def _prepare_jit_inputs(args, kwargs): return input_buffers, var_vals, names, expected_input_info class TinyJit(Generic[ReturnType]): - def __init__(self, fxn:Callable[..., ReturnType]|None, captured:CapturedJit|None=None, prune=False, optimize=False): + def __init__(self, fxn:Callable[..., ReturnType]|None, captured:CapturedJit|None=None, prune=False): assert fxn or captured, "need either a function or a CapturedJit" self.fxn = fxn self.captured: CapturedJit|None = captured self.cnt: int = 2 if self.fxn is None else 0 self.prune = prune - self.optimize = optimize def add_linear(self, linear:UOp, var_vals:dict[str, int]): self._linears.append(linear) @@ -367,7 +358,6 @@ class TinyJit(Generic[ReturnType]): for ei in jit_cache: ei.run(var_vals) self.captured = CapturedJit(ret, jit_cache, input_replace, extra_view_inputs, names, expected_input_info) - if self.optimize: self.captured.replan_buffers_memory_layout() elif self.cnt >= 2: # jit exec assert self.captured is not None From fa4cdb422e0570a9c4effff745713739582f7cc3 Mon Sep 17 00:00:00 2001 From: nimlgen <138685161+nimlgen@users.noreply.github.com> Date: Mon, 23 Mar 2026 19:50:16 +0800 Subject: [PATCH 17/19] memplan on linears (#15422) * memplan * test * x * arenas * correct * set any size * ugh * make hevc happy * x * x * held * rm old * del * x * fu * f * cl * cl * ok --- .github/workflows/test.yml | 4 +- extra/export_model.py | 8 +++ test/null/test_memory_planner.py | 109 +++++++++++++++++++++---------- test/null/test_real_world.py | 5 +- tinygrad/engine/jit.py | 27 ++++---- tinygrad/engine/memory.py | 109 +++++++++++++------------------ tinygrad/engine/schedule.py | 6 +- 7 files changed, 151 insertions(+), 117 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7756087b7b..1edb3dec95 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -70,7 +70,7 @@ jobs: source venv/bin/activate pip install $GITHUB_WORKSPACE cp $GITHUB_WORKSPACE/examples/beautiful_mnist.py . - BS=2 STEPS=10 python beautiful_mnist.py + BS=2 STEPS=10 MAX_BUFFER_SIZE=0 python beautiful_mnist.py - name: Test Docs Build run: python -m mkdocs build --strict - name: Test Docs @@ -141,7 +141,7 @@ jobs: sudo apt update || true sudo apt install -y --no-install-recommends ninja-build - name: Test beautiful_mnist in torch with TINY_BACKEND - run: STEPS=20 CPU=1 TARGET_EVAL_ACC_PCT=90.0 TINY_BACKEND=1 python3 examples/other_mnist/beautiful_mnist_torch.py + run: STEPS=20 CPU=1 TARGET_EVAL_ACC_PCT=90.0 MAX_BUFFER_SIZE=0 TINY_BACKEND=1 python3 examples/other_mnist/beautiful_mnist_torch.py - name: Test some torch tests (expect failure) run: python3 -m pytest extra/torch_backend/torch_tests.py -v --tb=no || true diff --git a/extra/export_model.py b/extra/export_model.py index 1d19976da1..e3b4a9a7f5 100644 --- a/extra/export_model.py +++ b/extra/export_model.py @@ -13,12 +13,20 @@ from collections import OrderedDict EXPORT_SUPPORTED_DEVICE = ["WEBGPU", "CPU", "CUDA", "CL"] def compile_net(run:TinyJit, special_names:Dict[int,str]) -> Tuple[Dict[str,str],List[Tuple[str,List[str],List[int]]],Dict[str,Tuple[int,DType,int]],Dict[str,Tensor]]: + # memory-planned subbuffers can have multiple Buffer objects for the same memory region + canon, _seen = {}, {} + for ji in run.jit_cache: + for b in ji.bufs: + if b is not None: canon[id(b)] = _seen.setdefault((id(b.base._buf), b.offset, b.size, b.dtype), b) + special_names = {id(canon[k]): v for k, v in special_names.items() if k in canon} + functions, bufs, bufs_to_save, statements, bufnum = {}, {}, {}, [], 0 for ji in run.jit_cache: fxn: ProgramSpec = ji.prg.p functions[fxn.function_name] = fxn.src # NOTE: this assumes all with the same name are the same cargs = [] for i,arg in enumerate(ji.bufs): + arg = canon[id(arg)] key = id(arg) if key not in bufs: if key in special_names: diff --git a/test/null/test_memory_planner.py b/test/null/test_memory_planner.py index b60a9f0f69..d83869c8aa 100644 --- a/test/null/test_memory_planner.py +++ b/test/null/test_memory_planner.py @@ -1,42 +1,73 @@ import unittest from tinygrad import dtypes -from tinygrad.device import Buffer -from tinygrad.engine.memory import _internal_memory_planner +from tinygrad.uop.ops import UOp, Ops +from tinygrad.engine.memory import memory_plan_rewrite global_map = {} +held_bufs: set[UOp] = set() def b(i, base=None, offset=0, pin=False, size=16): global global_map if i in global_map: return global_map[i] - global_map[i] = Buffer("NULL", size, dtypes.int8, base=global_map[base] if base is not None else None, offset=offset) - if pin: global_map[i].ref(1) + if base is not None: + global_map[i] = global_map[base] + return global_map[i] + global_map[i] = UOp.new_buffer("NULL", size, dtypes.int8) + if pin: held_bufs.add(global_map[i]) return global_map[i] -def check_assign(buffers:list[list[Buffer]|tuple[Buffer, ...]], copies:list[tuple[Buffer, Buffer]]|None=None): - assigned = _internal_memory_planner(buffers, copies=copies) +def _make_linear(buffer_lists, copies=None): + copy_pairs = {frozenset((id(dst), id(src))) for dst, src in copies} if copies else set() + calls = [] + for bufs in buffer_lists: + is_copy = len(bufs) == 2 and frozenset((id(bufs[0]), id(bufs[1]))) in copy_pairs + calls.append(UOp(Ops.CALL, dtypes.void, (UOp(Ops.COPY if is_copy else Ops.SINK), *bufs))) + return UOp(Ops.LINEAR, src=tuple(calls)) - taken_parts = set() +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.BUFFER_VIEW: return new.src[0] + return None + +def check_assign(buffer_lists, copies=None): + linear = _make_linear(buffer_lists, copies) + result = memory_plan_rewrite(linear, held_bufs) + + # build mapping: original buf -> (arena, offset_bytes, nbytes) from the result + 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.BUFFER_VIEW and id(orig) not in replace_map: + replace_map[id(orig)] = (new.src[0], new.arg[1] * new.dtype.itemsize, new.arg[0] * new.dtype.itemsize) + + # verify pinned buffers are not planned + for buf in held_bufs: + assert id(buf) not in replace_map, "pinned buffer was planned" + + # compute lifetimes first_appearance, last_appearance = {}, {} - for i,u in enumerate(buffers): - for buf in u: - if buf.is_allocated() or buf.base.is_allocated() or buf.uop_refcount > 0: continue - if buf.base not in first_appearance: first_appearance[buf.base] = i - last_appearance[buf.base] = i + for i, bufs in enumerate(buffer_lists): + for buf in bufs: + if buf in held_bufs: continue + if id(buf) not in first_appearance: first_appearance[id(buf)] = i + last_appearance[id(buf)] = i - for i,u in enumerate(buffers): - for buf in u: - if buf.is_allocated() or buf.base.is_allocated() or buf.uop_refcount > 0: continue - cur, base = assigned.get(buf, buf), assigned.get(buf.base, buf.base) - if buf._base is not None: - assert cur.base == base.base and cur.offset == buf.offset + base.offset, f"failed: {buf} {cur} {base} {buf.offset} {base.offset}" - else: - for part in taken_parts: - assert buf.base == part[3] or part[0] != cur.base or part[1] + part[2] <= cur.offset or part[1] >= cur.offset + buf.nbytes - if first_appearance[buf.base] == i: taken_parts.add((cur.base, cur.offset, buf.nbytes, buf.base)) - if last_appearance[buf.base] == i: taken_parts.remove((cur.base, cur.offset, buf.nbytes, buf.base)) + # verify non-overlapping: no two live buffers share the same arena region + taken_parts: set[tuple[int, int, int, int]] = set() # (id(arena), offset, nbytes, id(buf)) + for i, bufs in enumerate(buffer_lists): + for buf in bufs: + if buf in held_bufs or id(buf) not in replace_map: continue + arena, off, nb = replace_map[id(buf)] + for part in taken_parts: + assert id(buf) == part[3] or part[0] != id(arena) or part[1] + part[2] <= off or part[1] >= off + nb, \ + f"overlap at step {i}: [{off}, {off+nb}) conflicts with [{part[1]}, {part[1]+part[2]})" + if first_appearance.get(id(buf)) == i: taken_parts.add((id(arena), off, nb, id(buf))) + if last_appearance.get(id(buf)) == i: taken_parts.discard((id(arena), off, nb, id(buf))) class TestMemoryPlanner(unittest.TestCase): def setUp(self): global global_map + held_bufs.clear() global_map = {} def test_simple_buffer(self): @@ -140,9 +171,11 @@ class TestMemoryPlanner(unittest.TestCase): [b(1), b(2)], [b(3), b(2)], ] - assigned = _internal_memory_planner(bs, copies=[(b(1), b(0))]) - r1, r2 = assigned.get(b(1), b(1)), assigned.get(b(2), b(2)) - assert r1.base != r2.base + linear = _make_linear(bs, copies=[(b(1), b(0))]) + result = memory_plan_rewrite(linear) + r1_arena, r2_arena = _get_arena(b(1), linear, result), _get_arena(b(2), linear, result) + assert r1_arena is not None and r2_arena is not None + assert r1_arena is not r2_arena def test_copy_bufs_reuse_among_copies(self): bs = [ @@ -150,9 +183,11 @@ class TestMemoryPlanner(unittest.TestCase): [b(2), b(1)], [b(3), b(2)], ] - assigned = _internal_memory_planner(bs, copies=[(b(1), b(0)), (b(2), b(1))]) - r1, r2 = assigned.get(b(1), b(1)), assigned.get(b(2), b(2)) - assert r1.base == r2.base + linear = _make_linear(bs, copies=[(b(1), b(0)), (b(2), b(1))]) + result = memory_plan_rewrite(linear) + r1_arena, r2_arena = _get_arena(b(1), linear, result), _get_arena(b(2), linear, result) + assert r1_arena is not None and r2_arena is not None + assert r1_arena is r2_arena def test_compute_bufs_reuse_among_compute(self): bs = [ @@ -161,9 +196,11 @@ class TestMemoryPlanner(unittest.TestCase): [b(3), b(2)], [b(4), b(3)], ] - assigned = _internal_memory_planner(bs, copies=[(b(1), b(0))]) - r2, r3 = assigned.get(b(2), b(2)), assigned.get(b(3), b(3)) - assert r2.base == r3.base + linear = _make_linear(bs, copies=[(b(1), b(0))]) + result = memory_plan_rewrite(linear) + r2_arena, r3_arena = _get_arena(b(2), linear, result), _get_arena(b(3), linear, result) + assert r2_arena is not None and r3_arena is not None + assert r2_arena is r3_arena def test_copy_and_compute_no_cross_reuse(self): bs = [ @@ -171,9 +208,11 @@ class TestMemoryPlanner(unittest.TestCase): [b(2), b(1)], [b(3), b(2)], ] - assigned = _internal_memory_planner(bs, copies=[(b(2), b(1))]) - r0, r2 = assigned.get(b(0), b(0)), assigned.get(b(2), b(2)) - assert r0.base != r2.base + linear = _make_linear(bs, copies=[(b(2), b(1))]) + result = memory_plan_rewrite(linear) + r0_arena, r2_arena = _get_arena(b(0), linear, result), _get_arena(b(2), linear, result) + assert r0_arena is not None and r2_arena is not None + assert r0_arena is not r2_arena def test_multiple_copy_bufs_with_offsets(self): bs = [ diff --git a/test/null/test_real_world.py b/test/null/test_real_world.py index 9cfbfdb1de..61b971d883 100644 --- a/test/null/test_real_world.py +++ b/test/null/test_real_world.py @@ -74,7 +74,10 @@ class TestRealWorld(unittest.TestCase): def test(t, t2): for l in model: t = l(t, t2) return t.realize() - helper_test("test_unet_resblock", lambda: (Tensor.empty(4, 16, 8, 8), Tensor.empty(1, 24)), test, 0.0002, 37) + + # TODO: support _offset on CL to get mem down to 0.0002 + exp_mem = 0.00037 if Device.DEFAULT == "CL" else 0.0002 + helper_test("test_unet_resblock", lambda: (Tensor.empty(4, 16, 8, 8), Tensor.empty(1, 24)), test, exp_mem, 37) @unittest.skipUnless(is_dtype_supported(dtypes.float16), "need dtypes.float16") def test_llama(self): diff --git a/tinygrad/engine/jit.py b/tinygrad/engine/jit.py index ad1ece845b..0712e82e5f 100644 --- a/tinygrad/engine/jit.py +++ b/tinygrad/engine/jit.py @@ -6,11 +6,11 @@ from tinygrad.device import Buffer, Compiled, Device, MultiBuffer from tinygrad.dtype import DType from tinygrad.uop.ops import UOp, Variable, sym_infer, Ops, buffers from tinygrad.engine.realize import ExecItem, capturing, ViewOp, BufferCopy, BufferXfer, EncDec, CompiledRunner, Runner, Estimates -from tinygrad.engine.memory import _internal_memory_planner, _collect_bufs +from tinygrad.engine.memory import memory_plan_rewrite, _collect_bufs from tinygrad.engine.schedule import linear_to_schedule from tinygrad.nn.state import get_parameters from tinygrad.schedule.rangeify import mop_cleanup -from dataclasses import dataclass, replace +from dataclasses import dataclass def prune_linear(linear:UOp, needed:set[UOp]) -> tuple[UOp, UOp]: kept, onetime = [], [] @@ -212,11 +212,13 @@ class CapturedJit(Generic[ReturnType]): def free_intermediates(self): depends: set[Buffer|None] = set([None]) update_depends(depends, self.jit_cache) - for b in depends: - if b is not None: - if b.is_allocated(): b.deallocate() - if b._base is not None and b._base.allocated_views == 0 and b._base.is_allocated(): b._base.deallocate() - self.__post_init__() # reset the graph state + arenas = {b._base for b in depends if b is not None and b._base is not None} + to_free = {b for b in depends if b is not None} | {b for ei in self.jit_cache for b in ei.bufs if b is not None and b._base in arenas} + for b in to_free: + if hasattr(b, '_buf'): b.deallocate() + for a in arenas: + if a.allocated_views == 0 and a.is_allocated(): a.deallocate() + self.__post_init__() # jit exec def __call__(self, input_buffers:list[Buffer], var_vals:dict[str, int]) -> ReturnType: @@ -322,7 +324,7 @@ class TinyJit(Generic[ReturnType]): _check_no_non_tensor_return(ret) if DEBUG >= 1: print(f"JIT captured {len(self._linears)} linears with {len(input_buffers)} inputs") - # combine all captured linears into one and convert to ExecItems + # combine all captured linears into one, memory plan, and convert to ExecItems big_linear = UOp(Ops.LINEAR, src=tuple(flatten([l.src for l in self._linears]))) del self._linears @@ -334,7 +336,9 @@ class TinyJit(Generic[ReturnType]): ei.run(var_vals, jit=True) del onetime_linear - with Context(BEAM=getenv("JITBEAM", BEAM.value)): jit_cache = [ei.lower() for ei in linear_to_schedule(big_linear)] + held_bufs = set(buffers) | {t.uop.buf_uop for t in get_parameters(ret) if t.uop.buf_uop.op is Ops.BUFFER} + with Context(BEAM=getenv("JITBEAM", BEAM.value)): + jit_cache = [ei.lower() for ei in linear_to_schedule(memory_plan_rewrite(big_linear, held_bufs))] del big_linear # track inputs that are views of buffers @@ -346,11 +350,6 @@ class TinyJit(Generic[ReturnType]): input_buffers.append(b) extra_view_inputs.append((input_buffers.index(b.base), b.offset, b.device, b.size, b.dtype)) - # memory planning (optional) - copies = [(cast(Buffer,ji.bufs[0]),cast(Buffer,ji.bufs[1])) for ji in jit_cache if isinstance(ji.prg, (BufferXfer, BufferCopy, EncDec))] - assigned = _internal_memory_planner([cast(list[Buffer], item.bufs) for item in jit_cache], copies, debug_prefix="JIT ") - jit_cache = [replace(item, bufs=[assigned.get(b,b).ensure_allocated() for b in item.bufs if b is not None]) for item in jit_cache] - input_replace = get_input_replace(jit_cache, input_buffers) if DEBUG >= 1 and len(set(input_replace.values())) != len(input_buffers): print("WARNING: some input tensors not found") diff --git a/tinygrad/engine/memory.py b/tinygrad/engine/memory.py index 25748a5a0a..783770e044 100644 --- a/tinygrad/engine/memory.py +++ b/tinygrad/engine/memory.py @@ -1,8 +1,6 @@ -from typing import cast from collections import defaultdict -from tinygrad.engine.realize import ExecItem -from tinygrad.device import Device, Buffer -from tinygrad.helpers import NO_MEMORY_PLANNER, dedup, DEBUG, round_up +from tinygrad.device import Device +from tinygrad.helpers import NO_MEMORY_PLANNER, DEBUG, round_up from tinygrad.uop.ops import UOp, Ops from tinygrad.dtype import dtypes from tinygrad.runtime.support.memory import TLSFAllocator @@ -12,71 +10,56 @@ def _collect_bufs(u:UOp) -> list[UOp]: if u.op in {Ops.MSELECT, Ops.MSTACK}: return [b for s in u.src for b in _collect_bufs(s)] return [] +def _can_plan(b:UOp, held_bufs:set[UOp]) -> bool: + if b in held_bufs: return False + devs = (b.device,) if isinstance(b.device, str) else b.device + return all(not d.startswith(("DISK", "TINYFS")) and hasattr(Device[d].allocator, "_offset") for d in devs) + LaneKey = tuple[str, int] -# **************** memory planning **************** +def memory_plan_rewrite(linear:UOp, held_bufs:set[UOp]|None=None) -> UOp: + if NO_MEMORY_PLANNER: return linear + if held_bufs is None: held_bufs = set() -def _internal_memory_planner(buffers:list[list[Buffer]], copies:list[tuple[Buffer, Buffer]]|None=None, - ignore_checks=False, debug_prefix="") -> dict[Buffer, Buffer]: - if NO_MEMORY_PLANNER: return {} - first_appearance, last_appearance, buf_to_opt = {}, {}, set() - for i,u in enumerate(buffers): - for buf in u: - if not ignore_checks and (buf.is_allocated() or buf.base.is_allocated() or buf.uop_refcount > 0): continue - if buf.base not in first_appearance: first_appearance[buf.base] = i - last_appearance[buf.base] = i - buf_to_opt.add(buf) + # compute lifetimes for all plannable internal buffers + first_appearance:dict[UOp, int] = {} + last_appearance:dict[UOp, int] = {} + copy_bufs: set[UOp] = set() + for i, si in enumerate(linear.src): + si_bufs = [b for src in si.src[1:] for b in _collect_bufs(src) if _can_plan(b, held_bufs)] + for b in si_bufs: + if b not in first_appearance: first_appearance[b] = i + last_appearance[b] = i + if si.src[0].op is Ops.COPY: copy_bufs.update(si_bufs) + if not first_appearance: return linear - # Separate copy and compute buffers into different lanes and defer cross-queue frees to avoid introducing dependencies (copy->compute->copy) - copy_dsts, copy_srcs = ({dst.base for dst,_ in copies}, {src.base for _,src in copies}) if copies else (set(), set()) - def _key(buf) -> LaneKey: return (buf.device, 1 if buf in copy_dsts or buf in copy_srcs else 0) - buf_hold = {buf: last_appearance[buf] - first_appearance[buf] + 1 for buf in first_appearance if buf in copy_dsts or buf in copy_srcs} + # separate copy and compute buffers into different lanes to avoid introducing dependencies (copy->compute->copy) + def _key(b:UOp): return (b.device, 1 if b in copy_bufs else 0) + buf_hold = {b: last_appearance[b] - first_appearance[b] + 1 for b in first_appearance if b in copy_bufs} - # Sort buffer operations in timeline order. Two events: buffer is allocated or buffer is freed. - buffer_requests = sorted([((first_appearance[buf], True), buf) for buf in first_appearance.keys()] + \ - [((last_appearance[buf] + 1 + buf_hold.get(buf, 0), False), buf) for buf in first_appearance.keys()], key=lambda x: x[0]) - total_memory = sum(round_up(buf.nbytes, BLK:=0x1000) for buf in first_appearance.keys()) * 2 # *2 for fragmentation (which is about 15%) + # suballocation: build sorted open/close events, then alloc/free in order + block_size = 256 + nbytes = {b: round_up(b.arg * b.dtype.itemsize, block_size) for b in first_appearance} + events = sorted([(first_appearance[b], True, b) for b in first_appearance] + + [(last_appearance[b] + 1 + buf_hold.get(b, 0), False, b) for b in first_appearance], key=lambda x: (x[0], x[1])) + total_memory = sum(nbytes.values()) * 2 - # Try to suballocate from a shared buffer managed by global_planner using TLSFAllocator. - # Also track buffer replacements for buffers that do not support suballocation. - buffer_replace:dict[Buffer, tuple[Buffer|None, int|None]] = {} - reuse_buffers:dict[tuple, list[Buffer]] = defaultdict(list) - global_planner:dict[LaneKey, tuple[int, TLSFAllocator]] = defaultdict(lambda: (0, TLSFAllocator(total_memory, block_size=BLK, lv2_cnt=32))) - for (_, is_open_ev), buf in buffer_requests: - # Check if suballocation is possible for the given buffer and device. - if hasattr(Device[buf.device].allocator, "_offset"): - if is_open_ev: buffer_replace[buf] = (None, global_planner[_key(buf)][1].alloc(round_up(buf.nbytes, BLK))) - else: global_planner[_key(buf)][1].free(cast(int, buffer_replace[buf][1])) - global_planner[_key(buf)] = (max(global_planner[_key(buf)][0], buffer_replace[buf][1] + buf.nbytes), global_planner[_key(buf)][1]) - else: - key = (_key(buf), buf.dtype, buf.options, buf.nbytes) - if is_open_ev: buffer_replace[buf] = (reuse_buffers[key].pop(), None) if key in reuse_buffers and len(reuse_buffers[key]) > 0 else (buf, None) - else: reuse_buffers[key].append(cast(Buffer, buffer_replace[buf][0])) + offsets:dict[UOp, int] = {} + peaks:dict[LaneKey, tuple[int, TLSFAllocator]] = defaultdict(lambda: (0, TLSFAllocator(total_memory, block_size=block_size, lv2_cnt=32))) + for _, is_open, buf in events: + if is_open: offsets[buf] = peaks[_key(buf)][1].alloc(nbytes[buf]) + else: peaks[_key(buf)][1].free(offsets[buf]) + peaks[_key(buf)] = (max(peaks[_key(buf)][0], offsets[buf] + buf.arg * buf.dtype.itemsize), peaks[_key(buf)][1]) + arena_sizes = {key: round_up(peak, block_size) for key, (peak, _) in peaks.items()} - # Allocate global buffers based on the memory planner. - global_buffers = {key: Buffer(key[0], round_up(sz, BLK), dtypes.int8) for key, (sz, _) in global_planner.items()} - buffer_resolve:dict[Buffer, tuple[Buffer, int|None]] = {buf: (base or global_buffers[_key(buf)], off) for buf,(base,off) in buffer_replace.items()} + # build replace_map: each buffer becomes a BUFFER_VIEW 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(): + assert offset % buf_uop.dtype.itemsize == 0, f"offset {offset} not aligned to {buf_uop.dtype.itemsize}" + replace_map[buf_uop] = UOp(Ops.BUFFER_VIEW, buf_uop.dtype, (arenas[_key(buf_uop)],), (buf_uop.arg, offset // buf_uop.dtype.itemsize)) - # Assign buffers. First, assign full buffers (not sub-buffers). - assigned:dict[Buffer, Buffer] = {} - for buf, (base, off) in buffer_resolve.items(): - if buf != base: - assigned[buf] = base if off is None else Buffer(buf.device, buf.size, buf.dtype, base=base, offset=off) + 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") - # Now assign sub-buffers. - for buf in buf_to_opt: - if buf._base is not None: - assigned[buf] = Buffer(buf.device, buf.size, buf.dtype, base=(pbuf:=assigned.get(buf.base, buf.base)).base, offset=pbuf.offset+buf.offset) - - if DEBUG >= 1: - ak, av = dedup(x for x in assigned.keys() if x._base is None),dedup(x for x in assigned.values() if x._base is None)+list(global_buffers.values()) - omem, nmem = sum([x.nbytes for x in ak])/1e6, sum([x.nbytes for x in av])/1e6 - if omem != nmem: print(f"{debug_prefix}memory reduced from {omem:.2f} MB -> {nmem:.2f} MB,", f"{len(ak)} -> {len(av)} bufs") - - return assigned - -def memory_planner(schedule:list[ExecItem]) -> list[ExecItem]: - # Exclude buffers involved in load ops (e.g transfers) to preserve parallelism in graphs. - assigned = _internal_memory_planner([[b for b in si.bufs if b is not None] for si in schedule], - copies=[(cast(Buffer,si.bufs[0]),cast(Buffer,si.bufs[1])) for si in schedule if si.ast.op is Ops.COPY]) - return [ExecItem(si.ast, [assigned.get(x, x) if x is not None else None for x in si.bufs], si.metadata, si.fixedvars) for si in schedule] + return linear.substitute(replace_map, name="memory plan", walk=True) diff --git a/tinygrad/engine/schedule.py b/tinygrad/engine/schedule.py index 8f19332b89..54043ed334 100644 --- a/tinygrad/engine/schedule.py +++ b/tinygrad/engine/schedule.py @@ -83,7 +83,7 @@ def linear_to_schedule(linear:UOp) -> list[ExecItem]: schedule.append(ExecItem(ast, cast(list[Buffer|None], ubufs), metadata)) return schedule -from tinygrad.engine.memory import memory_planner +from tinygrad.engine.memory import memory_plan_rewrite from tinygrad.engine.realize import capturing from tinygrad.schedule.rangeify import get_kernel_graph from tinygrad.helpers import CAPTURING @@ -163,7 +163,9 @@ def complete_create_schedule_with_vars(big_sink:UOp) -> tuple[list[ExecItem], di capturing[0].add_linear(linear, var_vals) return [], var_vals + held_bufs = ({b for b in linear_call.src[1:] if b.op is Ops.BUFFER} if linear_call.op is Ops.CALL else set()) + linear = memory_plan_rewrite(linear, held_bufs) + # convert LINEAR to ExecItems schedule: list[ExecItem] = linear_to_schedule(linear) - with cpu_profile(TracingKey("memory planner")): schedule = memory_planner(schedule) return schedule, var_vals From 109472c37e9991a766ab45209c538d66c1ffa971 Mon Sep 17 00:00:00 2001 From: qazal <77887910+Qazalin@users.noreply.github.com> Date: Mon, 23 Mar 2026 20:25:28 +0200 Subject: [PATCH 18/19] sqtt: new s_barrier pickles, handle rdna4 barriers in emulator (#15437) --- extra/sqtt/examples/generate_examples.py | 2 +- .../examples/gfx1100/profile_sync_run_0.pkl | Bin 95102 -> 137026 bytes .../examples/gfx1100/profile_sync_run_1.pkl | Bin 95072 -> 137010 bytes .../examples/gfx1200/profile_sync_run_0.pkl | Bin 74895 -> 97578 bytes .../examples/gfx1200/profile_sync_run_1.pkl | Bin 76498 -> 97696 bytes test/amd/test_custom_kernel.py | 51 ++++++++++++++++-- test/amd/test_sqttmap.py | 2 +- test/mockgpu/amd/emu.py | 7 ++- 8 files changed, 54 insertions(+), 8 deletions(-) diff --git a/extra/sqtt/examples/generate_examples.py b/extra/sqtt/examples/generate_examples.py index 92146dfb20..51d9da9539 100644 --- a/extra/sqtt/examples/generate_examples.py +++ b/extra/sqtt/examples/generate_examples.py @@ -9,7 +9,7 @@ EXAMPLES = { "empty":"test/backend/test_custom_kernel.py TestCustomKernel.test_empty", "plus":"test/test_tiny.py TestTiny.test_plus", "gemm":"-c \"from tinygrad import Tensor; (Tensor.empty(N:=32, N)@Tensor.empty(N, N)).realize()\"", - "sync":"test/amd/test_custom_kernel.py TestCustomKernel.test_wave_sync", + "sync":"test/amd/test_custom_kernel.py TestCustomKernel.test_lds_sync", } if __name__ == "__main__": diff --git a/extra/sqtt/examples/gfx1100/profile_sync_run_0.pkl b/extra/sqtt/examples/gfx1100/profile_sync_run_0.pkl index a6292729b569eb7c21c21f2f9a48895f0530d607..39e391b3ab447560610d41d6354ec75c7c2e6133 100644 GIT binary patch literal 137026 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 literal 95102 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

q-#amEeUJ5! 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 G+Vnp{rVA_p diff --git a/extra/sqtt/examples/gfx1100/profile_sync_run_1.pkl b/extra/sqtt/examples/gfx1100/profile_sync_run_1.pkl index 605e444a6835f4acfdd42324a0709fee394f69d0..18aaeef299433f89560c4ecb7ac10c3c000b83c0 100644 GIT binary patch literal 137010 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 literal 95072 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 diff --git a/extra/sqtt/examples/gfx1200/profile_sync_run_0.pkl b/extra/sqtt/examples/gfx1200/profile_sync_run_0.pkl index 98c36ea5ec134a2b4f079aca57e19eff3de08b80..c9396be4789235f62ee3c16a0659a369fa6560d2 100644 GIT binary patch literal 97578 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 literal 74895 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 L>Gr~*eCz)YmJCT8 diff --git a/test/amd/test_custom_kernel.py b/test/amd/test_custom_kernel.py index 21f1042c20..7b3301c369 100644 --- a/test/amd/test_custom_kernel.py +++ b/test/amd/test_custom_kernel.py @@ -3,8 +3,10 @@ import functools from tinygrad import Tensor, Device, dtypes from tinygrad.uop.ops import UOp, Ops, KernelInfo from tinygrad.renderer import Estimates +from tinygrad.dtype import AddrSpace from tinygrad.runtime.autogen.amd.rdna3.ins import * -from tinygrad.runtime.autogen.amd.rdna4.ins import s_barrier_wait, s_barrier_signal +import tinygrad.runtime.autogen.amd.rdna3.ins as r3 +import tinygrad.runtime.autogen.amd.rdna4.ins as r4 from tinygrad.renderer.amd.dsl import s, v from test.amd.helpers import TARGET_TO_ARCH @@ -53,12 +55,46 @@ def custom_wave_sync(A:UOp, arch:str) -> UOp: insts = [] for _ in range(4): insts.append(s_sleep(4)) - insts += [s_barrier()] if arch == "rdna3" else [s_barrier_signal(), s_barrier_wait()] + insts += [s_barrier()] if arch == "rdna3" else [r4.s_barrier_signal(), r4.s_barrier_wait()] insts += [s_nop(0)]*4 insts.append(s_endpgm()) sink = UOp.sink(A.base, threads, wg, arg=KernelInfo("custom_wave_sync")) return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.DEVICE, arg="AMD"), UOp(Ops.LINEAR, src=tuple([UOp(Ops.INS, arg=x) for x in insts])))) +def custom_lds_sync(A:UOp, arch:str) -> UOp: + A = A.flatten() + num_threads = A.shape[0] + threads = UOp.special(num_threads, "lidx0") + wg = UOp.special(1, "gidx0") + lds = UOp(Ops.DEFINE_LOCAL, dtypes.uint8.ptr(size=512, addrspace=AddrSpace.LOCAL), (), 'lds') # 128 * 4 bytes + isa = r4 if arch == "rdna4" else r3 + wait_kmcnt = [isa.s_wait_kmcnt(simm16=0)] if arch == "rdna4" else [isa.s_waitcnt(lgkmcnt=0)] + wait_dscnt = [isa.s_wait_dscnt(simm16=0)] if arch == "rdna4" else [isa.s_waitcnt(lgkmcnt=0)] + barrier = [isa.s_barrier_signal(ssrc0=-1), isa.s_barrier_wait(simm16=-1)] if arch == "rdna4" else [isa.s_barrier()] + global_store = [isa.global_store_b32(vaddr=v[6:7], saddr=s[0:1], vsrc=v[5])] if arch == "rdna4" \ + else [isa.global_store_b32(addr=v[6], data=v[5], saddr=s[0:1])] + insts = [ + isa.s_load_b64(s[0:1], s[0:1], soffset=NULL), + *wait_kmcnt, + isa.v_lshlrev_b32_e32(v[1], 2, v[0]), + # lds[thread_idx] = thread_idx + isa.ds_store_b32(addr=v[1], data0=v[0]), + *wait_dscnt, + *barrier, + # out[threaed_idx] = thread_idx == num_threads ? -1 : lds[thread_idx + 1] + isa.v_add_nc_u32_e32(v[2], 4, v[1]), + isa.v_cmp_gt_u32_e32(num_threads-1, v[0]), + isa.ds_load_b32(vdst=v[3], addr=v[2]), + *wait_dscnt, + isa.v_mov_b32_e32(v[4], -1), + isa.v_cndmask_b32_e32(v[5], v[4], v[3]), + isa.v_lshlrev_b32_e32(v[6], 2, v[0]), + *global_store, + isa.s_endpgm(), + ] + sink = UOp.sink(A.base, lds, threads, wg, arg=KernelInfo("custom_lds_sync")) + return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.DEVICE, arg="AMD"), UOp(Ops.LINEAR, src=tuple([UOp(Ops.INS, arg=x) for x in insts])))) + @unittest.skipUnless(Device.DEFAULT == "AMD", "requires AMD device") class TestCustomKernel(unittest.TestCase): def setUp(self): self.arch = TARGET_TO_ARCH[Device["AMD"].arch] @@ -83,9 +119,14 @@ class TestCustomKernel(unittest.TestCase): ei.run({"var":i}) self.assertTrue((a.numpy() == 1+i).all()) - def test_wave_sync(self): - if self.arch not in {"rdna3", "rdna4"}: self.skipTest("only rdna3 or rdna4") - Tensor.empty(1).custom_kernel(fxn=functools.partial(custom_wave_sync, arch=self.arch))[0].realize() + def test_lds_sync(self): + if self.arch not in ("rdna3", "rdna4"): self.skipTest("only rdna3/rdna4") + a = Tensor.empty(128, dtype=dtypes.int32).contiguous().realize() + a = Tensor.custom_kernel(a, fxn=functools.partial(custom_lds_sync, arch=self.arch))[0] + a.realize() + ref = Tensor.arange(1, 129, dtype=dtypes.int32) + ref[127] = -1 + self.assertListEqual(a.tolist(), ref.tolist()) if __name__ == "__main__": unittest.main() diff --git a/test/amd/test_sqttmap.py b/test/amd/test_sqttmap.py index 62cfacbda8..a693144ba6 100644 --- a/test/amd/test_sqttmap.py +++ b/test/amd/test_sqttmap.py @@ -93,7 +93,7 @@ class TestSQTTMapBase(unittest.TestCase): elif "WAVE" in e.device: # sopk/immediates don't get ALU/MEM EXEC if e.name.display_name not in {"IMMEDIATE", "IMMEDIATE_MASK", "JUMP", "JUMP_NO", "MESSAGE", "BARRIER", "BARRIER_SIGNAL", - "WAVEEND"}: insts += 1 + "WAVEEND", "WAVERDY"}: insts += 1 else: raise Exception(f"timeline row must be INST or EXEC, got {e.device}") self.assertEqual(execs, insts) diff --git a/test/mockgpu/amd/emu.py b/test/mockgpu/amd/emu.py index ff99313ce9..674a89b6da 100644 --- a/test/mockgpu/amd/emu.py +++ b/test/mockgpu/amd/emu.py @@ -858,6 +858,8 @@ def _compile_sop(inst: ir3.SOP1|ir3.SOP2|ir3.SOPC|ir3.SOPK|ir4.SOP1|ir4.SOP2|ir4 result = (hw_val >> offset) & mask return UOp.sink(ctx.wsgpr_dyn(sdst_off, result), *ctx.inc_pc()) elif isinstance(inst, (ir3.SOP1, ir4.SOP1, irc.SOP1)): + # S_BARRIER_SIGNAL: no-op in emulator, barrier sync handled by execution loop + if isinstance(inst, ir4.SOP1) and inst.op in _BARRIER_SOP1_OPS: return UOp.sink(*ctx.inc_pc()) sdst_off = ctx.inst_field(type(inst).sdst) ssrc0_off = ctx.inst_field(type(inst).ssrc0) srcs = {'S0': ctx.rsrc_dyn(ssrc0_off, None, bits['s0'], literal)} @@ -1960,6 +1962,8 @@ def _get_runner(inst_bytes: bytes, arch: str = "rdna3"): _BARRIER_OPS = {ir3.SOPPOp.S_BARRIER, irc.SOPPOp.S_BARRIER} if hasattr(ir4.SOPPOp, 'S_BARRIER_WAIT'): _BARRIER_OPS.add(ir4.SOPPOp.S_BARRIER_WAIT) +_BARRIER_SOP1_OPS: set = set() +if hasattr(ir4.SOP1Op, 'S_BARRIER_SIGNAL'): _BARRIER_SOP1_OPS.add(ir4.SOP1Op.S_BARRIER_SIGNAL) _BRANCH_OPS: set[int] = {op.value for op in (ir3.SOPPOp.S_BRANCH, ir3.SOPPOp.S_CBRANCH_SCC0, ir3.SOPPOp.S_CBRANCH_SCC1, ir3.SOPPOp.S_CBRANCH_VCCZ, ir3.SOPPOp.S_CBRANCH_VCCNZ, ir3.SOPPOp.S_CBRANCH_EXECZ, ir3.SOPPOp.S_CBRANCH_EXECNZ)} @@ -2084,7 +2088,8 @@ def run_asm(lib: int, lib_sz: int, gx: int, gy: int, gz: int, lx: int, ly: int, if pc not in program: prev_len = len(_canonical_runner_cache) runner, inst = _decode_at(pc, arch) - is_barrier = isinstance(inst, (ir3.SOPP, ir4.SOPP, irc.SOPP)) and inst.op in _BARRIER_OPS + is_barrier = (isinstance(inst, (ir3.SOPP, ir4.SOPP, irc.SOPP)) and inst.op in _BARRIER_OPS) or \ + (isinstance(inst, (ir4.SOP1,)) and inst.op in _BARRIER_SOP1_OPS) program[pc] = (runner._prg.fxn, runner.p.globals, is_barrier, inst) if DEBUG >= 3: msg = f"[emu] PC={pc - lib}: {inst!r}" From a590eded87b9eca7d2a29c7b9cb7f07e0bb7d6fc Mon Sep 17 00:00:00 2001 From: qazal <77887910+Qazalin@users.noreply.github.com> Date: Mon, 23 Mar 2026 20:49:32 +0200 Subject: [PATCH 19/19] sqtt: rdna4 decoder work (#15434) * sqtt: rdna4 decoder work * diff cleanup * more diff * test * work * works * TS_DELTA_SHORT --- test/amd/test_sqtt_examples.py | 13 +++------- tinygrad/renderer/amd/sqtt.py | 46 +++++++++++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/test/amd/test_sqtt_examples.py b/test/amd/test_sqtt_examples.py index b3ed48e48e..5d5e47c4f8 100644 --- a/test/amd/test_sqtt_examples.py +++ b/test/amd/test_sqtt_examples.py @@ -10,18 +10,11 @@ from tinygrad.runtime.autogen.amd.rdna3.ins import SOPP from tinygrad.runtime.autogen.amd.rdna3.enum import SOPPOp from tinygrad.renderer.amd.sqtt import (decode, LAYOUT_HEADER, WAVESTART, WAVESTART_RDNA4, WAVEEND, INST, INST_RDNA4, VALUINST, IMMEDIATE, IMMEDIATE_MASK, PACKET_TYPES_RDNA3, PACKET_TYPES_RDNA4, PACKET_TYPES_CDNA, CDNA_WAVESTART, - InstOp, InstOpRDNA4, print_packets, CDNA_WAVEEND, CDNA_INST) + print_packets, CDNA_WAVEEND, CDNA_INST) from test.amd.helpers import TARGET_TO_ARCH import tinygrad EXAMPLES_DIR = Path(tinygrad.__file__).parent.parent / "extra/sqtt/examples" -# INST ops for non-traced SIMDs (excluded from instruction count) -OTHER_SIMD_OPS = {InstOp.OTHER_LDS_LOAD, InstOp.OTHER_LDS_STORE, InstOp.OTHER_LDS_STORE_64, InstOp.OTHER_LDS_STORE_128, - InstOp.OTHER_FLAT_LOAD, InstOp.OTHER_FLAT_STORE, InstOp.OTHER_FLAT_STORE_64, InstOp.OTHER_FLAT_STORE_96, - InstOp.OTHER_FLAT_STORE_128, InstOp.OTHER_GLOBAL_LOAD, InstOp.OTHER_GLOBAL_LOAD_VADDR, - InstOp.OTHER_GLOBAL_STORE_64, InstOp.OTHER_GLOBAL_STORE_96, InstOp.OTHER_GLOBAL_STORE_128, - InstOp.OTHER_GLOBAL_STORE_VADDR_128} -OTHER_SIMD_OPS_RDNA4 = {InstOpRDNA4.OTHER_VMEM, InstOpRDNA4.OTHER_VMEM_5, InstOpRDNA4.OTHER_LDS_1, InstOpRDNA4.OTHER_LDS_2} # ═══════════════════════════════════════════════════════════════════════════════ # ROCPROF DECODER @@ -207,8 +200,8 @@ class SQTTExamplesTestBase(unittest.TestCase): our_insts: list[int] = [] for event in events: for p in decode(event.blob): - if isinstance(p, INST) and p.op not in OTHER_SIMD_OPS: our_insts.append(p._time) - elif isinstance(p, INST_RDNA4) and p.op not in OTHER_SIMD_OPS_RDNA4: our_insts.append(p._time) + # INST ops for non-traced SIMDs (excluded from instruction count) + if isinstance(p, (INST, INST_RDNA4)) and not p.op.name.startswith("OTHER_"): our_insts.append(p._time) elif isinstance(p, VALUINST): our_insts.append(p._time) elif isinstance(p, IMMEDIATE): our_insts.append(p._time) elif isinstance(p, IMMEDIATE_MASK): diff --git a/tinygrad/renderer/amd/sqtt.py b/tinygrad/renderer/amd/sqtt.py index 6f224fe76c..fef92d8df2 100644 --- a/tinygrad/renderer/amd/sqtt.py +++ b/tinygrad/renderer/amd/sqtt.py @@ -105,14 +105,25 @@ class InstOpRDNA4(Enum): """SQTT instruction operation types for RDNA4 (gfx1200). Different encoding from RDNA3.""" SALU = 0x0 SMEM = 0x1 + SMEM_WR = 0x2 JUMP = 0x3 JUMP_NO = 0x4 CALL = 0x5 + SALU_NO_EXEC = 0x7 MESSAGE = 0x9 + VALU_1 = 0xa VALU_TRANS = 0xb + VALU_B1 = 0xc VALU_B2 = 0xd VALU_B4 = 0xe + VALU_B16 = 0xf VINTERP = 0x12 + BARRIER_WAIT = 0x13 + FLAT_RD_2 = 0x1c + FLAT_WR_3 = 0x1d + FLAT_WR_4 = 0x1e + FLAT_WR_5 = 0x1f + FLAT_WR_6 = 0x20 VMEM_RD_1 = 0x21 VMEM_RD_2 = 0x22 VMEM_WR_1 = 0x23 @@ -127,18 +138,45 @@ class InstOpRDNA4(Enum): LDS_WR_3 = 0x2c LDS_WR_4 = 0x2d LDS_WR_5 = 0x2e + BUF_RD_1 = 0x2f + BUF_RD_2 = 0x30 + BUF_WR_1 = 0x31 + BUF_WR_2 = 0x32 + BUF_WR_3 = 0x33 + BUF_WR_4 = 0x34 + BUF_WR_5 = 0x35 + BUF_WR_6 = 0x36 OTHER_LDS_1 = 0x50 OTHER_LDS_2 = 0x51 + OTHER_LDS_3 = 0x52 + OTHER_LDS_4 = 0x53 + OTHER_LDS_5 = 0x54 + OTHER_FLAT_2 = 0x55 + OTHER_FLAT_3 = 0x56 + OTHER_FLAT_4 = 0x57 + OTHER_FLAT_5 = 0x58 + OTHER_FLAT_6 = 0x59 + LDS_DIR_LOAD = 0x6e + LDS_PARAM_LOAD = 0x6f + SALU_WR_EXEC = 0x72 + VALU1_WR_EXEC = 0x73 + VALU_B2_WR_EXEC = 0x74 + OTHER_LDS_6 = 0x77 + OTHER_LDS_10 = 0x78 BARRIER_SIGNAL = 0x7a + DYN_VGPR = 0x87 + BARRIER_JOIN = 0x8a WMMA_8 = 0x8c WMMA_16 = 0x8d + WMMA_32 = 0x8e + WMMA_64 = 0x8f VALU_DPFP = 0x92 SALU_FLOAT3 = 0x98 VALU_SCL_TRANS = 0x99 SALU_2 = 0x9b SALU_5 = 0x9c - OTHER_VMEM = 0xbd - OTHER_VMEM_5 = 0xc1 + OTHER_VMEM = 0xbc # 0xbc-0xdd: vmem_other_simd +for _i in range(34): InstOpRDNA4._value2member_map_[0xbc + _i] = InstOpRDNA4.OTHER_VMEM class InstOpCDNA(Enum): SMEM_RD = 0 @@ -650,8 +688,8 @@ def map_insts(data:bytes, lib:bytes, target:str) -> Iterator[tuple[PacketType, I yield (p, InstructionInfo(pc, wave, inst)) elif isinstance(p, (VALUINST, INST, INST_RDNA4, IMMEDIATE)): inst = pc_map[pc:=wave_pc[p.wave]] - # s_delay_alu and s_wait_alu instructions are skipped - while (inst_op:=getattr(inst, 'op_name', '')) in {"S_DELAY_ALU", "S_WAIT_ALU"}: + # s_delay_alu, s_wait_alu and s_barrier_wait instructions are skipped + while (inst_op:=getattr(inst, 'op_name', '')) in {"S_DELAY_ALU", "S_WAIT_ALU", "S_BARRIER_WAIT"}: wave_pc[p.wave] += inst.size() inst = pc_map[pc:=wave_pc[p.wave]] # assert branch always has a JUMP packet