no junk ops

This commit is contained in:
2026-07-30 15:02:12 +00:00
parent 5331889b06
commit 6a8bb39f3d
9 changed files with 178 additions and 124 deletions
+2 -2
View File
@@ -557,9 +557,9 @@ class TestFunctionTuple(unittest.TestCase):
def f(a:Tensor): return Tensor.custom_kernel(Tensor.empty(*a.shape, dtype=a.dtype, device=a.device), a, fxn=inplace_add)[0]
with self.assertRaisesRegex(RuntimeError, "implicit buffer"): f(Tensor([1., 2., 3., 4.]).contiguous().realize())
def test_vector_load_is_program_input(self):
def test_shrink_load_is_program_input(self):
out, inp = UOp.param(0, dtypes.float, (1,)), UOp.param(1, dtypes.float, (8,))
values = UOp(Ops.VLOAD, dtypes.float, (inp[0],), arg=8)
values = UOp(Ops.SHRINK, src=(inp, UOp.const(dtypes.weakint, 0), UOp.const(dtypes.weakint, 8))).load()
sink = out[0].store(values.index(0)).sink(arg=KernelInfo(name="vector_load"))
info = ProgramInfo.from_sink(sink)
self.assertEqual(info.outs, (0,))
+21 -6
View File
@@ -120,8 +120,17 @@ pm_expand_broadcast = pm_wmma_add+PatternMatcher([
(UPat(Ops.WMMA, name="b"), broadcast_and_devec_wmma),
])
def do_devectorize(b:UOp):
if b.shape == () or b.tag == "vectorized": return None
@functools.cache
def _uses_shrink_memory(x:UOp) -> bool:
if x.op in (Ops.LOAD, Ops.STORE): return x.src[0].op is Ops.SHRINK
if x.op is Ops.AFTER: return _uses_shrink_memory(x.src[0])
if x.op in GroupOp.Elementwise or x.op in (Ops.STACK, Ops.RESHAPE, Ops.PERMUTE):
return any(_uses_shrink_memory(y) for y in x.src)
return False
def do_devectorize(ctx:Renderer|tuple[set[UOp], Renderer], b:UOp):
preserved = ctx[0] if isinstance(ctx, tuple) else set()
if b.shape == () or b in preserved or _uses_shrink_memory(b): return None
# broadcasting needs to be already unpacked, Invalid matches any dtype and shape
if not all(x.shape == b.shape or x.base.arg is Invalid for x in b.src): return None
src = []
@@ -187,6 +196,7 @@ def fix_group_for_reduce(x:UOp):
@dataclass
class ReduceContext:
acc_num: int = 0
renderer: Renderer|None = None
def merge_reduce_ends(sink:UOp):
# merge ENDs that share the same range and nesting context (only those created by reduce_to_acc)
@@ -220,7 +230,8 @@ def reduce_ranges_to_acc(ctx:ReduceContext, r:UOp):
acc_out = acc_initted.store(acc_initted.alu(r.arg[0], inp)).end(*r.src[1:]).rtag("mergeable")
return acc.after(acc_out)
def expand_horizontal_reduce(r:UOp):
def expand_horizontal_reduce(ctx:ReduceContext, r:UOp):
if ctx.renderer is not None and ctx.renderer.has_native_reduce(r): return None
inp = r.src[0]
vals = [inp.index(*idx) for idx in itertools.product(*[range(inp.max_shape[a]) for a in range(r.arg[1])])]
return functools.reduce(lambda x,y: x.alu(r.arg[0], y), vals)
@@ -317,7 +328,7 @@ def full_rewrite_to_sink(ast:UOp, ren:Renderer, optimize:bool=True) -> UOp:
sink = graph_rewrite(sink, expander2, ctx=build_range_map(sink), name="expander")
# remove reduce
sink = graph_rewrite(sink, mop_cleanup+pm_reduce_local, ctx=ReduceContext(), name="remove reduces")
sink = graph_rewrite(sink, mop_cleanup+pm_reduce_local, ctx=ReduceContext(renderer=ren), name="remove reduces")
# add locals
sink = graph_rewrite(sink, pm_add_local_buffers, ctx=itertools.count(0), name="add local buffers")
@@ -330,7 +341,9 @@ def full_rewrite_to_sink(ast:UOp, ren:Renderer, optimize:bool=True) -> UOp:
sink = graph_rewrite(sink, symbolic_simple+pm_expand_broadcast+pm_add_loads, name="*** expand broadcast / add loads")
# devectorize
sink = graph_rewrite(sink, symbolic_simple+devectorizer2, ctx=ren, name="devectorize2")
sink = graph_rewrite(sink, symbolic_simple, name="pre-devectorize symbolic")
native_reduce_uops = {u for r in sink.toposort() if ren.has_native_reduce(r) for u in r.src[0].backward_slice_with_self}
sink = graph_rewrite(sink, devectorizer2, ctx=(native_reduce_uops, ren), name="devectorize2")
# simplify indexing
sink = graph_rewrite(sink, indexing_simplify, name="simplify load/store indexing")
@@ -340,7 +353,9 @@ def full_rewrite_to_sink(ast:UOp, ren:Renderer, optimize:bool=True) -> UOp:
# do memory coalescing (late)
sink = memory_coalescing(sink, ren)
sink = graph_rewrite(sink, symbolic_simple+ew_devectorizer+pm_simplify_add_image, name="add images", ctx=({}, ren), bottom_up=True)
sink = graph_rewrite(sink, symbolic_simple, name="pre-image symbolic", bottom_up=True)
native_reduce_uops = {u for r in sink.toposort() if ren.has_native_reduce(r) for u in r.src[0].backward_slice_with_self}
sink = graph_rewrite(sink, ew_devectorizer+pm_simplify_add_image, name="add images", ctx=(native_reduce_uops, ren), bottom_up=True)
# extra symbolic before decomp. crashes without this?
sink = graph_rewrite(sink, sym, name="extra symbolic")
+1 -1
View File
@@ -106,7 +106,7 @@ def memory_coalescing(sink:UOp, ctx:Renderer) -> UOp:
# TODO: this should handle images too, it's just memory coalescing
if u.op in {Ops.LOAD, Ops.STORE}:
assert len(u.src) == (2 if u.op is Ops.STORE else 1), "memory coalescing does not support gated loads/stores"
if u.tag == "vectorized": continue
if u.src[0].op is Ops.SHRINK: continue
assert u.src[0].op is Ops.INDEX, f"memory coalescing should be on INDEX, not {u.src[0].op}"
buf, idx_u = u.src[0].src
if buf.addrspace == AddrSpace.REG: continue
+62 -53
View File
@@ -5,7 +5,7 @@ from tinygrad import Tensor, nn, UOp, getenv, dtypes
from tinygrad.dtype import DType, AddrSpace
from tinygrad.llm.gguf import _GGML_QUANT
from tinygrad.renderer import Estimates
from tinygrad.uop.ops import Ops, GroupOp, KernelInfo, AxisType
from tinygrad.uop.ops import Ops, KernelInfo, AxisType
if TYPE_CHECKING:
from tinygrad.llm.model import ExpertWeights, FFNBlock, Linear
@@ -17,14 +17,33 @@ def _concrete_int(value:int|UOp) -> int:
def _dot_byte_parts(a:tuple[UOp, ...], b:tuple[UOp, ...]) -> UOp:
av, bv = UOp.stack(*a), UOp.stack(*b)
return UOp(Ops.DOT, dtypes.int32, (av, bv), arg=4)
return _dot_byte_vectors(av, bv)
def _dot_byte_vectors(a:UOp, b:UOp, scale:UOp|None=None) -> UOp:
assert a.shape == b.shape and len(a.shape) == 1 and a.shape[0] % 4 == 0
product = a.cast(dtypes.int32) * b.cast(dtypes.int32)
if scale is not None: product = product * scale
return product.reshape(a.shape[0]//4, 4)._rop(Ops.ADD, (1,))
def _unpack_nibbles(packed:UOp, values:tuple[int, ...]) -> UOp:
assert packed.shape == (16,) and len(values) == 16
def lookup(index:UOp) -> UOp:
value = UOp.stack(*(UOp.const(dtypes.int8, values[-1]) for _ in range(packed.shape[0])))
for i in range(14, -1, -1): value = index.eq(i).where(UOp.const(dtypes.int8, values[i]), value)
return value
lowv, highv = lookup(packed & 15), lookup((packed >> 4) & 15)
return UOp.stack(lowv, highv).reshape(32)
def _dot_bytes(a:tuple[UOp, ...], b:tuple[UOp, ...]) -> UOp:
parts = _dot_byte_parts(a, b)
return sum((parts.index(i) for i in range(len(a)//4)), UOp.const(dtypes.int32, 0))
def _contiguous_vector_load(ptr:UOp, lanes:int, dtype:DType|None=None) -> UOp:
return UOp(Ops.VLOAD, dtype or ptr.dtype, (ptr,), arg=lanes)
assert ptr.op is Ops.INDEX
buf, coords = ptr.src[0], ptr.src[1:]
index = sum((coord * math.prod(buf.shape[i+1:]) for i,coord in enumerate(coords)), UOp.const(dtypes.weakint, 0))
address = UOp(Ops.SHRINK, src=(buf.flatten(), index, UOp.const(dtypes.weakint, lanes)))
return address.load(dtype=dtype or ptr.dtype)
def _contiguous_vector_ptr(buf:UOp, index:UOp, lanes:int) -> UOp:
return UOp(Ops.SHRINK, src=(buf, index, UOp.const(dtypes.weakint, lanes)))
@@ -32,14 +51,14 @@ def _contiguous_vector_ptr(buf:UOp, index:UOp, lanes:int) -> UOp:
def _dot_bytes_ptr(a:UOp, b:UOp) -> UOp:
av = _contiguous_vector_load(a, 32, dtypes.int8)
bv = _contiguous_vector_load(b, 32, dtypes.int8)
return UOp(Ops.DOT, dtypes.int32, (av, bv), arg=4)
return _dot_byte_vectors(av, bv)
def _dot_nibbles_ptr(packed:UOp, x:UOp, values:tuple[int, ...]) -> UOp:
assert len(values) == 16
pv = _contiguous_vector_load(packed, 16)
qvalues = UOp(Ops.UNPACK_LUT, dtypes.int8, (pv,), arg=values)
qvalues = _unpack_nibbles(pv, values)
xv = _contiguous_vector_load(x, 32, dtypes.int8)
return UOp(Ops.DOT, dtypes.int32, (qvalues, xv), arg=4)
return _dot_byte_vectors(qvalues, xv)
def _dot_nibbles_pair_ptr(packed:UOp, x:UOp, values:tuple[int, ...]) -> tuple[UOp, UOp]:
assert len(values) == 16
@@ -56,7 +75,7 @@ def _dot_q6_ptr(block:UOp, x:UOp, subgroup:int) -> UOp:
hi = ((_contiguous_vector_load(hi_ptr, 32) >> high_shift) & 3) << 4
qvalues = ((lo | hi) - 32).bitcast(dtypes.int8)
xv = _contiguous_vector_load(x, 32, dtypes.int8)
return UOp(Ops.DOT, dtypes.int32, (qvalues, xv), arg=4)
return _dot_byte_vectors(qvalues, xv)
def _rms_f16_product_ptr(x:UOp, norm:UOp, weight:UOp, scale:UOp) -> UOp:
return _contiguous_vector_load(x, 8).cast(dtypes.float32) * _contiguous_vector_load(norm, 8).cast(dtypes.float32) * \
@@ -250,23 +269,18 @@ def _load_f16(raw:UOp, offset:UOp) -> UOp:
return bits.bitcast(dtypes.float16).cast(dtypes.float32)
def _load_f16x8_ptr(raw:UOp) -> UOp:
return UOp(Ops.VLOAD, dtypes.float16, (raw,), arg=8).cast(dtypes.float32).rtag("vectorized")
return _contiguous_vector_load(raw, 8, dtypes.float16).cast(dtypes.float32)
def _vector_reg(reg:UOp, *deps:UOp) -> UOp:
reg = reg.after(*deps)
return UOp(Ops.SHRINK, src=(reg, UOp.const(dtypes.weakint, 0), UOp.const(dtypes.weakint, reg.max_numel())))
def _vector_acc_init(reg:UOp, *deps:UOp) -> UOp:
return reg.after(_vector_reg(reg, *deps).store(reg.const_like(0), tag="vectorized"))
def _vectorized_tree(x:UOp) -> UOp:
if x.max_numel() == 1: return x
src = tuple(_vectorized_tree(y) if y.max_numel() > 1 else y for y in x.src)
return x.replace(src=src, tag="vectorized") if x.op in GroupOp.Elementwise else x.replace(src=src)
return reg.after(_vector_reg(reg, *deps).store(reg.const_like(0)))
def _vector_acc_update(reg:UOp, value:UOp, *deps:UOp) -> UOp:
previous = _vector_reg(reg, *deps).load(tag="vectorized")
return _vector_reg(reg, *deps).store((previous + _vectorized_tree(value)).rtag("vectorized"), tag="vectorized")
previous = _vector_reg(reg, *deps).load()
return _vector_reg(reg, *deps).store(previous + value)
def _finite_exp2(x:UOp) -> UOp:
# The causal-convolution activation is finite. Bounding the exponent preserves sigmoid saturation while avoiding
@@ -512,7 +526,7 @@ def _cpu_expert_weighted_grouped_uop(out:UOp, raw:UOp, probs:UOp, head:UOp, next
qvalues, scales = [], []
for subgroup in range(8):
packed = _contiguous_vector_load(raw[base + 8 + subgroup * 16], 16)
qvalues.append(UOp(Ops.UNPACK_LUT, dtypes.int8, (packed,), arg=values))
qvalues.append(_unpack_nibbles(packed, values))
low_byte = raw[base + 4 + subgroup // 2].load()
low = (low_byte >> (4 * (subgroup % 2))) & 15
high = (high_word >> (2 * subgroup)) & 3
@@ -526,10 +540,10 @@ def _cpu_expert_weighted_grouped_uop(out:UOp, raw:UOp, probs:UOp, head:UOp, next
route = matched_routes[route_idx].load().cast(dtypes.weakint)
input_idx = route // routes_per_input
block_acc = UOp.placeholder((8,), dtypes.int32, slot=4, addrspace=AddrSpace.REG)
stage = _vector_reg(block_acc, route_loop).store(block_acc.const_like(0), tag="vectorized")
stage = _vector_reg(block_acc, route_loop).store(block_acc.const_like(0))
for subgroup in range(8):
xv = _contiguous_vector_load(xq[route, block, subgroup * 32], 32, dtypes.int8)
stage = _vector_acc_update(block_acc, UOp(Ops.DOT, dtypes.int32, (qvalues[subgroup], xv), arg=4) * scales[subgroup], stage)
stage = _vector_acc_update(block_acc, _dot_byte_vectors(qvalues[subgroup], xv, scales[subgroup]), stage)
dot = sum((block_acc.after(stage).index(i) for i in range(8)), UOp.const(dtypes.int32, 0)).cast(dtypes.float32)
contribution = dot * xd[route, block] * block_scale * matched_probs[route_idx].load()
updated = totals[output_lane, input_idx].store(totals.after(route_loop)[output_lane, input_idx].load() + contribution)
@@ -573,7 +587,7 @@ def _cpu_expert_silu_uop(out:UOp, raw0:UOp, raw1:UOp, sel:UOp, xq:UOp, xd:UOp, l
vectorized = ggml_type in (14, 23) or (ggml_type == 21 and repacked)
acc0 = UOp.placeholder((8 if vectorized else 1,), dtypes.float32, slot=0, addrspace=AddrSpace.REG)
acc1 = UOp.placeholder((8 if vectorized else 1,), dtypes.float32, slot=1, addrspace=AddrSpace.REG)
initialized = UOp.group(*((_vector_reg(acc, job).store(acc.const_like(0), tag="vectorized") if vectorized else
initialized = UOp.group(*((_vector_reg(acc, job).store(acc.const_like(0)) if vectorized else
acc.after(job).store(acc.const_like(0))) for acc in (acc0, acc1)))
if ggml_type == 23:
block = UOp.range(in_features // 256, 110)
@@ -684,9 +698,9 @@ def _cpu_expert_silu_grouped_uop(out:UOp, raw0:UOp, raw1:UOp, head:UOp, next_rou
qvalues:list[UOp] = []
scales:list[UOp] = []
for subgroup in range(0, 8, 2):
packed = _contiguous_vector_load(raw[row_base + meta_size + block * 128 + subgroup * 16], 32)
unpacked = UOp(Ops.UNPACK_LUT, dtypes.int8, (packed,), arg=values)
qvalues.extend(UOp.stack(*(unpacked.index(i) for i in range(off, off + 32))) for off in (0, 32))
for offset in (0, 16):
packed = _contiguous_vector_load(raw[row_base + meta_size + block * 128 + subgroup * 16 + offset], 16)
qvalues.append(_unpack_nibbles(packed, values))
for subgroup in range(8):
scale_byte = raw[meta + 2 + subgroup // 2].load()
scales.append(1 + 2 * ((scale_byte >> (4 * (subgroup % 2))) & 15).cast(dtypes.int32))
@@ -698,11 +712,11 @@ def _cpu_expert_silu_grouped_uop(out:UOp, raw0:UOp, raw1:UOp, head:UOp, next_rou
route = matched[route_idx].load().cast(dtypes.weakint)
xidx = (route.cast(dtypes.uint32) // routes_per_input).cast(dtypes.weakint)
block_acc = UOp.placeholder((8,), dtypes.int32, slot=6 + projection, addrspace=AddrSpace.REG)
stage = _vector_reg(block_acc, route_loop).store(block_acc.const_like(0), tag="vectorized")
stage = _vector_reg(block_acc, route_loop).store(block_acc.const_like(0))
for subgroup in range(8):
xv = _contiguous_vector_load(xq[xidx, block, subgroup * 32], 32, dtypes.int8)
parts = UOp(Ops.DOT, dtypes.int32, (qvalues[subgroup], xv), arg=4)
stage = _vector_acc_update(block_acc, parts * scales[subgroup], stage)
parts = _dot_byte_vectors(qvalues[subgroup], xv, scales[subgroup])
stage = _vector_acc_update(block_acc, parts, stage)
block_sum = block_acc.after(stage)
value = sum((block_sum.index(i) for i in range(8)), UOp.const(dtypes.int32, 0)).cast(dtypes.float32) * \
xd[xidx, block] * _load_f16(raw, meta)
@@ -771,7 +785,7 @@ def _moe_stage1_uop(rhidden:UOp, shidden:UOp, rgate:UOp, rup:UOp, sgate:UOp, sup
expert = sel[route].load().cast(dtypes.weakint)
racc0 = UOp.placeholder((8 if expert_repacked else 1,), dtypes.float32, slot=0, addrspace=AddrSpace.REG)
racc1 = UOp.placeholder((8 if expert_repacked else 1,), dtypes.float32, slot=1, addrspace=AddrSpace.REG)
rinit = UOp.group(*((_vector_reg(acc, routed_job).store(acc.const_like(0), tag="vectorized") if expert_repacked else
rinit = UOp.group(*((_vector_reg(acc, routed_job).store(acc.const_like(0)) if expert_repacked else
acc.after(routed_job).store(acc.const_like(0))) for acc in (racc0, racc1)))
if expert_repacked:
rblock = UOp.range(dim // 256, 100)
@@ -802,7 +816,7 @@ def _moe_stage1_uop(rhidden:UOp, shidden:UOp, rgate:UOp, rup:UOp, sgate:UOp, sup
shared_output, groups = shared_begin + shared_job, dim // 32
sacc0 = UOp.placeholder((8,), dtypes.float32, slot=2, addrspace=AddrSpace.REG)
sacc1 = UOp.placeholder((8,), dtypes.float32, slot=3, addrspace=AddrSpace.REG)
sinit = UOp.group(*(_vector_reg(acc, shared_job).store(acc.const_like(0), tag="vectorized") for acc in (sacc0, sacc1)))
sinit = UOp.group(*(_vector_reg(acc, shared_job).store(acc.const_like(0)) for acc in (sacc0, sacc1)))
if shared_repacked and groups % 8 == 0:
sblock = UOp.range(groups // 8, 101)
svalues = []
@@ -1264,7 +1278,7 @@ def _attention_prefill_online_uop(out:UOp, q:UOp, cache:UOp, start_pos:UOp) -> U
row_sums = tuple(UOp.placeholder((1,), dtypes.float32, slot=2*token_tile+i, addrspace=AddrSpace.REG) for i in range(token_tile))
clear = UOp.range(chunks, 90)
cleared = UOp.group(*(_contiguous_vector_ptr(numerator.after(job), clear * 8, 8).store(
UOp.stack(*(UOp.const(dtypes.float32, 0) for _ in range(8))), tag="vectorized") for numerator in numerators)).end(clear)
UOp.stack(*(UOp.const(dtypes.float32, 0) for _ in range(8)))) for numerator in numerators)).end(clear)
initialized = UOp.group(cleared,
*(row_max.after(job).store(-math.inf) for row_max in row_maxes),
*(row_sum.after(job).store(0.0) for row_sum in row_sums))
@@ -1299,8 +1313,7 @@ def _attention_prefill_online_uop(out:UOp, q:UOp, cache:UOp, start_pos:UOp) -> U
nbase = vchunk * 8
previous = _contiguous_vector_load(numerator.after(initialized, position)[nbase], 8)
updated = previous * old_scale + values * weight
numerator_updates.append(_contiguous_vector_ptr(numerator.after(qk_done), nbase, 8).store(
_vectorized_tree(updated), tag="vectorized"))
numerator_updates.append(_contiguous_vector_ptr(numerator.after(qk_done), nbase, 8).store(updated))
values_done = UOp.group(*numerator_updates).end(vchunk)
state_updates = [row_max[0].store(next_max) for row_max,next_max in zip(row_maxes, next_maxes)]
state_updates += [row_sum[0].store(row_sum.after(initialized, position)[0].load() * old_scale + weight)
@@ -1312,7 +1325,7 @@ def _attention_prefill_online_uop(out:UOp, q:UOp, cache:UOp, start_pos:UOp) -> U
for token_offset,(numerator,row_sum) in enumerate(zip(numerators, row_sums)):
query = bh * tokens + token_base + token_offset
value = _contiguous_vector_load(numerator.after(positions_done)[output * 8], 8) / row_sum.after(positions_done)[0].load()
stores.append(_contiguous_vector_ptr(outf, query * dim + output * 8, 8).store(_vectorized_tree(value), tag="vectorized"))
stores.append(_contiguous_vector_ptr(outf, query * dim + output * 8, 8).store(value))
return UOp.group(*stores).end(output, job, core).sink(
arg=KernelInfo(name=f"attention_prefill_online_uop_{batch}_{heads}_{tokens}_{kv_heads}_{dim}_{cache_len}",
opts_to_apply=())).rtag("cpu_parallel")
@@ -1364,8 +1377,7 @@ def _gated_delta_prefill_uop(core:UOp, next_state:UOp, q:UOp, k:UOp, v:UOp, beta
current = UOp.placeholder((dim * dim,), dtypes.float32, slot=0, addrspace=AddrSpace.REG)
init_chunk = UOp.range(dim * dim // 8, 90)
initial_values = _contiguous_vector_load(statef[bh * dim * dim + init_chunk * 8], 8).cast(dtypes.float32)
initialized_state = _contiguous_vector_ptr(current, init_chunk * 8, 8).store(
_vectorized_tree(initial_values), tag="vectorized").end(init_chunk)
initialized_state = _contiguous_vector_ptr(current, init_chunk * 8, 8).store(initial_values).end(init_chunk)
token = UOp.range(tokens, 91)
token_base = (bh * tokens + token) * dim
@@ -1381,8 +1393,8 @@ def _gated_delta_prefill_uop(core:UOp, next_state:UOp, q:UOp, k:UOp, v:UOp, beta
row = UOp.range(dim, 93)
state_k = UOp.placeholder((8,), dtypes.float32, slot=3, addrspace=AddrSpace.REG)
state_q = UOp.placeholder((8,), dtypes.float32, slot=4, addrspace=AddrSpace.REG)
dot_init = UOp.group(_vector_reg(state_k, row).store(state_k.const_like(0), tag="vectorized"),
_vector_reg(state_q, row).store(state_q.const_like(0), tag="vectorized"))
dot_init = UOp.group(_vector_reg(state_k, row).store(state_k.const_like(0)),
_vector_reg(state_q, row).store(state_q.const_like(0)))
col = UOp.range(chunks, 94)
state_vec = _contiguous_vector_load(current.after(initialized_state, token)[row * dim + col * 8], 8)
q_vec = _contiguous_vector_load(qf[token_base + col * 8], 8)
@@ -1399,8 +1411,7 @@ def _gated_delta_prefill_uop(core:UOp, next_state:UOp, q:UOp, k:UOp, v:UOp, beta
current_values = _contiguous_vector_load(current.after(initialized_state, token)[update_base], 8)
key_values = _contiguous_vector_load(kf[token_base + update * 8], 8)
next_values = current_values * av + delta * key_values
updated_state = _contiguous_vector_ptr(current.after(dots), update_base, 8).store(
_vectorized_tree(next_values), tag="vectorized").end(update)
updated_state = _contiguous_vector_ptr(current.after(dots), update_base, 8).store(next_values).end(update)
rows_done = UOp.group(saved_core, updated_state).end(row)
norm_acc = UOp.placeholder((8,), dtypes.float32, slot=5, addrspace=AddrSpace.REG)
@@ -1416,8 +1427,7 @@ def _gated_delta_prefill_uop(core:UOp, next_state:UOp, q:UOp, k:UOp, v:UOp, beta
save_chunk = UOp.range(dim * dim // 8, 98)
saved_values = _contiguous_vector_load(current.after(token_done)[save_chunk * 8], 8).cast(next_state.dtype)
saved_state = _contiguous_vector_ptr(nextf, bh * dim * dim + save_chunk * 8, 8).store(
_vectorized_tree(saved_values), tag="vectorized").end(save_chunk)
saved_state = _contiguous_vector_ptr(nextf, bh * dim * dim + save_chunk * 8, 8).store(saved_values).end(save_chunk)
return saved_state.end(bh).sink(
arg=KernelInfo(name=f"gated_delta_prefill_uop_{batch}_{heads}_{tokens}_{dim}_{state.dtype.name}",
opts_to_apply=())).rtag("cpu_parallel")
@@ -1446,8 +1456,8 @@ def _gated_delta_uop(core:UOp|None, next_state:UOp, q:UOp, k:UOp, v:UOp, beta:UO
row = UOp.range(dim, 91)
state_k = UOp.placeholder((8,), dtypes.float32, slot=2, addrspace=AddrSpace.REG)
state_q = UOp.placeholder((8,), dtypes.float32, slot=3, addrspace=AddrSpace.REG)
initialized = UOp.group(_vector_reg(state_k, row).store(state_k.const_like(0), tag="vectorized"),
_vector_reg(state_q, row).store(state_q.const_like(0), tag="vectorized"))
initialized = UOp.group(_vector_reg(state_k, row).store(state_k.const_like(0)),
_vector_reg(state_q, row).store(state_q.const_like(0)))
col = UOp.range(chunks, 92)
state_vec = _contiguous_vector_load(statef[bh * dim * dim + row * dim + col * 8], 8).cast(dtypes.float32)
q_vec = _contiguous_vector_load(qf[bh * dim + col * 8], 8)
@@ -1464,8 +1474,8 @@ def _gated_delta_uop(core:UOp|None, next_state:UOp, q:UOp, k:UOp, v:UOp, beta:UO
state_base = bh * dim * dim + row * dim + update * 8
state_values = _contiguous_vector_load(statef[state_base], 8).cast(dtypes.float32)
key_values = _contiguous_vector_load(kf[bh * dim + update * 8], 8)
next_values = _vectorized_tree(state_values * av + delta * key_values).cast(next_state.dtype)
update_state = _contiguous_vector_ptr(nextf.after(dots), state_base, 8).store(next_values, tag="vectorized").end(update)
next_values = (state_values * av + delta * key_values).cast(next_state.dtype)
update_state = _contiguous_vector_ptr(nextf.after(dots), state_base, 8).store(next_values).end(update)
rows_done = UOp.group(saved_core, update_state).end(row)
if normalize:
@@ -1498,14 +1508,14 @@ def _gated_delta_uop(core:UOp|None, next_state:UOp, q:UOp, k:UOp, v:UOp, beta:UO
norm_values = _contiguous_vector_load(norm_weight[quant_group * 32 + offset], 8).cast(dtypes.float32)
gate_values = _contiguous_vector_load(gatef[base + offset], 8).cast(dtypes.float32)
gate_sigmoid = UOp.stack(*(gate_values.index(i).sigmoid() for i in range(8)))
value_vecs.append(_vectorized_tree(source_values * scale * norm_values * gate_values * gate_sigmoid))
value_vecs.append(source_values * scale * norm_values * gate_values * gate_sigmoid)
values = tuple(value_vecs[i // 8].index(i % 8) for i in range(32))
amax = functools.reduce(lambda a,b:a.maximum(b), (value.abs() for value in values))
d = (amax / 127).maximum(1e-8)
quant_stores = []
for chunk_idx,value_vec in enumerate(value_vecs):
quant_values = UOp.stack(*((value_vec.index(i) / d).round().maximum(-127).minimum(127).cast(dtypes.int8) for i in range(8)))
quant_stores.append(_contiguous_vector_ptr(quantf, base + chunk_idx * 8, 8).store(quant_values, tag="vectorized"))
quant_stores.append(_contiguous_vector_ptr(quantf, base + chunk_idx * 8, 8).store(quant_values))
stores = UOp.group(scalef[bh * (dim // 32) + quant_group].store(d), *quant_stores).end(quant_group)
name = f"gated_delta_q8_uop_{batch}_{heads}_{dim}_{state.dtype.name}"
return stores.end(bh).sink(arg=KernelInfo(name=name, opts_to_apply=())).rtag("cpu_parallel")
@@ -1611,8 +1621,8 @@ def _gdn_qkv_uop(q:UOp, k:UOp, v:UOp, conv:UOp, k_heads:int, v_heads:int, dim:in
qsum = UOp.placeholder((8,), dtypes.float32, slot=0, addrspace=AddrSpace.REG)
ksum = UOp.placeholder((8,), dtypes.float32, slot=1, addrspace=AddrSpace.REG)
initialized = UOp.group(_vector_reg(qsum, job).store(qsum.const_like(0), tag="vectorized"),
_vector_reg(ksum, job).store(ksum.const_like(0), tag="vectorized"))
initialized = UOp.group(_vector_reg(qsum, job).store(qsum.const_like(0)),
_vector_reg(ksum, job).store(ksum.const_like(0)))
chunk = UOp.range(dim // 8, 100)
qvalue = _contiguous_vector_load(conv[qbase + chunk * 8], 8).cast(dtypes.float32)
kvalue = _contiguous_vector_load(conv[kbase + chunk * 8], 8).cast(dtypes.float32)
@@ -1627,7 +1637,7 @@ def _gdn_qkv_uop(q:UOp, k:UOp, v:UOp, conv:UOp, k_heads:int, v_heads:int, dim:in
kvalues = _contiguous_vector_load(conv[kbase + out_chunk * 8], 8).cast(dtypes.float32) * kscale
vvalues = _contiguous_vector_load(conv[vbase + out_chunk * 8], 8).cast(dtypes.float32)
stores = UOp.group(*(
target[batch_idx, head, token, out_chunk * 8 + lane].store(_vectorized_tree(values).index(lane))
target[batch_idx, head, token, out_chunk * 8 + lane].store(values.index(lane))
for target,values in ((q, qvalues), (k, kvalues), (v, vvalues)) for lane in range(8))).end(out_chunk)
return stores.end(job, core).sink(
arg=KernelInfo(name=f"gdn_qkv_uop_{batch}_{tokens}_{k_heads}_{v_heads}_{dim}", opts_to_apply=())).rtag("cpu_parallel")
@@ -1744,7 +1754,7 @@ def _f16_matvec_uop(out:UOp, x:UOp, weight:UOp) -> UOp:
token_base = token_block * token_tile
xf, wf, outf = x.flatten(), weight.flatten(), out.flatten()
accs = tuple(UOp.placeholder((8,), dtypes.float32, slot=i, addrspace=AddrSpace.REG) for i in range(token_tile))
accs = tuple(acc.after(_vector_reg(acc, output_job, token_block).store(acc.const_like(0), tag="vectorized")) for acc in accs)
accs = tuple(acc.after(_vector_reg(acc, output_job, token_block).store(acc.const_like(0))) for acc in accs)
chunk = UOp.range(in_features // 8, 92)
weights = _load_f16x8_ptr(wf[output * in_features + chunk * 8])
updates = []
@@ -1752,9 +1762,8 @@ def _f16_matvec_uop(out:UOp, x:UOp, weight:UOp) -> UOp:
xbase = (token_base + token) * in_features + chunk * 8
values = _load_f16x8_ptr(xf[xbase]) if x.dtype == dtypes.float16 else \
UOp.stack(*(xf[xbase + lane].load() for lane in range(8)))
previous = _vector_reg(acc, chunk).load(tag="vectorized")
updates.append(_vector_reg(acc, chunk).store(
(previous + (values * weights).rtag("vectorized")).rtag("vectorized"), tag="vectorized"))
previous = _vector_reg(acc, chunk).load()
updates.append(_vector_reg(acc, chunk).store(previous + values * weights))
done = UOp.group(*updates).end(chunk)
stores = [outf[(token_base + token) * out_features + output].store(
sum((acc.after(done).index(lane) for lane in range(8)), UOp.const(dtypes.float32, 0)).cast(out.dtype))
+4 -3
View File
@@ -30,12 +30,12 @@ class Estimates:
if u.op in {Ops.INDEX, Ops.SHRINK}:
excluded = excluded.union(set(UOp.sink(*u.src[1:]).toposort(lambda x: x.op is not Ops.END)))
for u in uops:
if u.op in {Ops.LOAD, Ops.VLOAD, Ops.STORE}:
if u.op in {Ops.LOAD, Ops.STORE}:
buf = u
while len(buf.src) and buf.op is not Ops.PARAM: buf = buf.src[0]
if buf.op is Ops.PARAM:
# u.src[0] is INDEX, cap at buffer size for re-reads (e.g. matmul)
elements = u.max_numel() if u.op is Ops.VLOAD else u.src[0].max_numel()
elements = u.src[0].max_numel()
accessed = mem.get((buf, u.op), 0) + elements * u.src[0].dtype.scalar().itemsize * mults
mem[(buf, u.op)] = smin(accessed, buf.max_numel() * buf.dtype.scalar().itemsize)
if u.op is Ops.RANGE:
@@ -47,7 +47,7 @@ class Estimates:
elif u.op is Ops.END: mults = mult_stack.pop(-1)
elif u.op is Ops.SPECIAL: mults *= cast(sint, u.src[0].ssimplify()) # NOTE: we don't push to the mult_stack here, you can't end these
elif u.op is Ops.PARAM and u.arg.addrspace == AddrSpace.ALU and u.expr == 'core_id': mults *= int(u.vmax) + 1
elif u.op in {Ops.LOAD, Ops.VLOAD} and u.src[0].addrspace != AddrSpace.REG:
elif u.op is Ops.LOAD and u.src[0].addrspace != AddrSpace.REG:
lds += u.max_numel() * u.dtype.scalar().itemsize * mults
elif u.op is Ops.STORE and u.src[0].addrspace != AddrSpace.REG:
lds += u.max_numel() * u.src[1].dtype.scalar().itemsize * mults
@@ -62,6 +62,7 @@ class Renderer:
suffix: str = ""
# TODO: make this generic with a list of supported types
supports_float4: bool = True
def has_native_reduce(self, x:UOp) -> bool: return False
has_local: bool = True
has_threads: bool = False
has_shared: bool = True
+79 -27
View File
@@ -22,7 +22,7 @@ base_rewrite = PatternMatcher([
# casting
(UPat(Ops.CAST, name="x"), lambda ctx,x: f"__builtin_convertvector({ctx[x.src[0]]}, {ctx.render_type(x)})" \
if x.max_numel() > 1 and (x.addrspace is AddrSpace.REG or x.tag == "vectorized") else None),
if x.max_numel() > 1 and x.addrspace is AddrSpace.REG else None),
(UPat(Ops.CAST, name="x"), lambda ctx,x: f"({ctx.render_cast(x, ctx[x.src[0]])})"),
(UPat(Ops.BITCAST, name="x"), lambda ctx,x: ctx[x.src[0]] if x.addrspace in (AddrSpace.GLOBAL, AddrSpace.LOCAL) else None),
(UPat(Ops.BITCAST, name="x"), lambda ctx,x: f"__builtin_bit_cast({ctx.render_type(x)}, ({ctx.render_type(x.src[0])})({ctx[x.src[0]]}))"),
@@ -131,6 +131,7 @@ class CStyleLanguage(Renderer):
float4: str|None = None
float4_style: tuple[str, str] = ('(', ')')
gep_arr_threshold: int = 4
def should_inline_where(self, x:UOp) -> bool: return False
type_map: dict[DType, str] = {}
infinity: str = "INFINITY"
nan: str = "NAN"
@@ -239,7 +240,7 @@ class CStyleLanguage(Renderer):
if (u.op is not Ops.CAST or u.max_numel() == 1) and (u.op in {Ops.CONST, Ops.INDEX, Ops.SHRINK, Ops.CUSTOMI} or \
(u.op is Ops.LOAD and u.src[0].addrspace == AddrSpace.REG and child_count[u] == 1) or \
(u.op is Ops.CAST and u.addrspace in (AddrSpace.GLOBAL, AddrSpace.LOCAL)) or \
(u.op in {Ops.STACK, Ops.DOT, *(GroupOp.ALU-{Ops.WHERE}), Ops.CAST, Ops.BITCAST} and
(u.op in {Ops.STACK, Ops.REDUCE, *GroupOp.ALU, Ops.CAST, Ops.BITCAST} and (u.op is not Ops.WHERE or self.should_inline_where(u)) and
child_count[u] == 1 and not getenv("EXPAND_SSA"))):
r[u] = l
else:
@@ -260,6 +261,23 @@ class ClangRenderer(CStyleLanguage):
gep_arr_threshold = 0
has_local = False
has_threads = bool(getenv("THREADS", 1))
def should_inline_where(self, x:UOp) -> bool: return self._render_lut_lookup(self, x) is not None
@staticmethod
def _byte_dot_reduce(x:UOp) -> tuple[UOp, UOp, UOp|None]|None:
if x.op is not Ops.REDUCE or x.arg != (Ops.ADD, 1) or len(x.src) != 1: return None
permute = x.src[0]
if permute.op is not Ops.PERMUTE or permute.arg != (1, 0) or permute.src[0].op is not Ops.RESHAPE: return None
product, scale = permute.src[0].src[0], None
if product.op is Ops.MUL:
for base, candidate_scale in (product.src, product.src[::-1]):
if base.op is Ops.MUL and all(s.op is Ops.CAST and s.dtype is dtypes.int32 and s.src[0].dtype is dtypes.int8 for s in base.src):
product, scale = base, candidate_scale
break
if product.op is not Ops.MUL or not all(s.op is Ops.CAST and s.dtype is dtypes.int32 and s.src[0].dtype is dtypes.int8 for s in product.src):
return None
a, b = (s.src[0] for s in product.src)
return (a, b, scale) if a.shape == b.shape and a.shape in ((16,), (32,)) else None
def has_native_reduce(self, x:UOp) -> bool: return self._byte_dot_reduce(x) is not None
global_max = (NUM_CPU_THREADS.value, 0, 0)
infinity = "__builtin_inff()"
nan = '__builtin_nanf("")'
@@ -283,6 +301,32 @@ class ClangRenderer(CStyleLanguage):
f"__builtin_ia32_pmaddubsw{suffix}(__builtin_elementwise_abs({ctx[a]}), __builtin_ia32_psignb{suffix}({ctx[b]}, {ctx[a]}))), " \
f"(short __attribute__((ext_vector_type({lanes})))){{{','.join([multiplier] * lanes)}}})"
@staticmethod
def _uniform_vector_const(x:UOp):
return x.src[0].arg if x.op is Ops.STACK and x.src and all(v.op is Ops.CONST and v.arg == x.src[0].arg for v in x.src) else None
@staticmethod
def _render_lut_lookup(ctx, x:UOp):
values:dict[int, int] = {}
index:UOp|None = None
node = x
while node.op is Ops.WHERE and node.src[0].op in (Ops.CMPEQ, Ops.CMPNE):
cond, true_value, false_value = node.src
left, right = cond.src
key = ClangRenderer._uniform_vector_const(right)
if key is None: left, right, key = right, left, ClangRenderer._uniform_vector_const(left)
if not isinstance(key, int) or not 0 <= key < 16 or (index is not None and left is not index): return None
index = left
selected, node = (true_value, false_value) if cond.op is Ops.CMPEQ else (false_value, true_value)
value = ClangRenderer._uniform_vector_const(selected)
if not isinstance(value, int): return None
values[key] = value
default = ClangRenderer._uniform_vector_const(node)
if index is None or not isinstance(default, int) or index.shape not in ((16,), (32,)): return None
lut_values = tuple(values.get(i, default) for i in range(16))
lanes, suffix = index.shape[0], "128" if index.shape == (16,) else "256"
charv = f"signed char __attribute__((ext_vector_type({lanes})))"
lut = f"({charv}){{{','.join(map(str, lut_values * (lanes // 16)))}}}"
return f"__builtin_ia32_pshufb{suffix}({lut}, __builtin_bit_cast({charv}, {ctx[index]}))"
@staticmethod
def _render_contiguous_stack(ctx, x:UOp):
if not x.src or not all(v.op is Ops.INDEX and len(v.src) == 2 and v.src[0] is x.src[0] and
v.src[1].op is Ops.CONST for v in x.src): return None
@@ -290,19 +334,25 @@ class ClangRenderer(CStyleLanguage):
if indices != tuple(range(indices[0], indices[0] + len(indices))): return None
return f"__builtin_shufflevector({ctx[x.src[0]]}, {ctx[x.src[0]]}, {','.join(map(str, indices))})"
@staticmethod
def _render_unpack_lut(ctx, x:UOp, packed:UOp):
if packed.shape not in ((16,), (32,)): return None
lanes, suffix = packed.shape[0], "128" if packed.shape[0] == 16 else "256"
charv = f"signed char __attribute__((ext_vector_type({lanes})))"
pv = f"__builtin_bit_cast({charv}, {ctx[packed]})"
lut_values = x.arg if lanes == 16 else x.arg * 2
lut = f"({charv}){{{','.join(map(str, lut_values))}}}"
mask = f"({charv}){{{','.join(['15'] * lanes)}}}"
lo = f"__builtin_ia32_pshufb{suffix}({lut}, {pv} & {mask})"
hi = f"__builtin_ia32_pshufb{suffix}({lut}, ({pv} >> 4) & {mask})"
if lanes == 16: indices = tuple(range(32))
else: indices = (*range(16), *range(32, 48), *range(16, 32), *range(48, 64))
return f"__builtin_shufflevector({lo}, {hi}, {','.join(map(str, indices))})"
def _render_concat_stack(ctx, x:UOp):
if len(x.src) != 2 or x.src[0].shape != x.src[1].shape or len(x.src[0].shape) != 1: return None
lanes = x.src[0].shape[0]
return f"__builtin_shufflevector({ctx[x.src[0]]}, {ctx[x.src[1]]}, {','.join(map(str, range(lanes*2)))})"
@staticmethod
def _render_vector_permute(ctx, x:UOp):
old_shape, order = x.src[0].shape, x.arg
new_shape = tuple(old_shape[i] for i in order)
indices = []
for flat in range(x.max_numel()):
coord, rem = [], flat
for size in reversed(new_shape):
coord.append(rem % size)
rem //= size
new_coord = tuple(reversed(coord))
old_coord = tuple(new_coord[order.index(i)] for i in range(len(order)))
old_flat = sum(c * math.prod(old_shape[i+1:]) for i,c in enumerate(old_coord))
indices.append(old_flat)
return f"__builtin_shufflevector({ctx[x.src[0]]}, {ctx[x.src[0]]}, {','.join(map(str, indices))})"
@staticmethod
def _render_vector_load(ctx, x:UOp, address:UOp):
vec_type = ctx._render_dtype(x.dtype, x.max_numel(), AddrSpace.REG)
@@ -312,23 +362,25 @@ class ClangRenderer(CStyleLanguage):
@staticmethod
def _render_vector_store(ctx, address:UOp, value:UOp):
vec_type = ctx._render_dtype(value.dtype, value.max_numel(), AddrSpace.REG)
# Like VLOAD, explicit vector stores can target unaligned packed data or register-backed arrays.
# Explicit vector stores can target unaligned packed data or register-backed arrays.
return f"do {{ {vec_type} _v = {ctx[value]}; __builtin_memcpy({ctx[address]}, &_v, sizeof(_v)); }} while (0);"
string_rewrite = PatternMatcher([
(UPat(Ops.WHERE, name="x"), lambda ctx,x: ClangRenderer._render_lut_lookup(ctx, x)),
(UPat(Ops.MUL, dtypes.int32, src=(UPat(Ops.REDUCE, name="dot"), UPat.var("scale"))),
lambda ctx,dot,scale: ClangRenderer._render_byte_dot(ctx, pair[0], pair[1], scale)
if (pair:=ClangRenderer._byte_dot_reduce(dot)) is not None and pair[2] is None and scale.vmin >= -32768 and scale.vmax <= 32767 else None),
(UPat(Ops.REDUCE, name="x"), lambda ctx,x: ClangRenderer._render_byte_dot(ctx, *pair)
if (pair:=ClangRenderer._byte_dot_reduce(x)) is not None else None),
(UPat(Ops.CAST, name="x"), lambda ctx,x: f"__builtin_convertvector({ctx[x.src[0]]}, {ctx.render_type(x)})"
if x.max_numel() > 1 else None),
(UPat(Ops.RESHAPE, name="x"), lambda ctx,x: ctx[x.src[0]]),
(UPat(Ops.PERMUTE, name="x"), lambda ctx,x: ClangRenderer._render_vector_permute(ctx, x)),
(UPat(Ops.STACK, name="x"), lambda ctx,x: ClangRenderer._render_concat_stack(ctx, x)),
(UPat(Ops.STACK, name="x"), lambda ctx,x: ClangRenderer._render_contiguous_stack(ctx, x)),
(UPat(Ops.MUL, dtypes.int32,
src=(UPat(Ops.DOT, dtypes.int32, src=(UPat.var("a", dtypes.int8), UPat.var("b", dtypes.int8)), arg=4),
UPat.var("scale", dtypes.int32))),
lambda ctx,a,b,scale: ClangRenderer._render_byte_dot(ctx, a, b, scale)
if scale.vmin >= -32768 and scale.vmax <= 32767 else None),
(UPat(Ops.DOT, dtypes.int32, src=(UPat.var("a", dtypes.int8), UPat.var("b", dtypes.int8)), arg=4),
lambda ctx,a,b: ClangRenderer._render_byte_dot(ctx, a, b)),
(UPat(Ops.UNPACK_LUT, dtypes.int8, src=(UPat.var("packed", dtypes.uint8),), name="x"),
lambda ctx,x,packed: ClangRenderer._render_unpack_lut(ctx, x, packed)),
(UPat(Ops.STORE, src=(UPat(Ops.SHRINK, name="address"), UPat.var("value")), name="x"),
lambda ctx,x,address,value: ClangRenderer._render_vector_store(ctx, address, value)
if x.tag == "vectorized" and value.max_numel() > 1 else None),
(UPat(Ops.VLOAD, src=(UPat.var("address"),), name="x"),
if value.max_numel() > 1 else None),
(UPat(Ops.LOAD, src=(UPat(Ops.SHRINK, name="address"),), name="x"),
lambda ctx,x,address: ClangRenderer._render_vector_load(ctx, x, address)),
]) + base_rewrite
+1 -5
View File
@@ -52,16 +52,12 @@ class Ops(FastEnum):
INDEX = auto(); SHRINK = auto()
# load/store before math
LOAD = auto(); VLOAD = auto(); STORE = auto()
LOAD = auto(); STORE = auto()
# ** 4 -- math **
# tensor core math op, not elementwise
WMMA = auto()
# grouped dot product. arg is the number of input lanes reduced into each output lane
DOT = auto()
# unpack low/high nibbles through a byte lookup table. arg is the tuple of table values
UNPACK_LUT = auto()
# UnaryOps
CAST = auto(); BITCAST = auto(); EXP2 = auto(); LOG2 = auto(); SIN = auto()
+3 -16
View File
@@ -150,8 +150,6 @@ def dtype_from_uop(op:Ops, src:tuple[UOp,...], arg:Any) -> DType|None:
case Ops.WMMA:
# WMMA output dtype is the accumulator dtype (src[2])
return src[2].dtype
case Ops.DOT | Ops.UNPACK_LUT | Ops.VLOAD:
return None
case Ops.GETTUPLE:
# GETTUPLE extracts from a TUPLE (possibly through a FUNCTION)
in_tuple = src[0].src[0] if src[0].op is Ops.FUNCTION else src[0]
@@ -396,17 +394,6 @@ class UOp(RandMixin, metaclass=UOpMetaClass):
case Ops.WMMA:
wmma_b = _broadcast_shape(self.src[0].shape[:-1], self.src[1].shape[:-1], self.src[2].shape[:-1])
return wmma_b + (self.src[2].shape[-1],)
case Ops.DOT:
assert len(self.src) == 2 and self.src[0].shape == self.src[1].shape and len(self.src[0].shape) and \
isinstance(self.arg, int) and self.arg > 0 and self.src[0].shape[-1] % self.arg == 0
return self.src[0].shape[:-1] + (self.src[0].shape[-1] // self.arg,)
case Ops.UNPACK_LUT:
assert len(self.src) == 1 and isinstance(self.arg, tuple)
return self.src[0].shape[:-1] + (self.src[0].shape[-1] * 2,)
case Ops.VLOAD:
assert len(self.src) == 1 and self.src[0].shape == () and isinstance(self.arg, int) and self.arg > 0
return (self.arg,)
# passthrough ops
case Ops.MSTACK | Ops.MSELECT | Ops.DETACH | Ops.CONTIGUOUS | Ops.CONTIGUOUS_BACKWARD | Ops.AFTER | Ops.LOAD | \
Ops.COPY | Ops.ALLREDUCE | Ops.STORE | Ops.END:
@@ -872,12 +859,12 @@ class UOp(RandMixin, metaclass=UOpMetaClass):
if self.op is Ops.PARAM: return self.arg.addrspace
if self.op is Ops.BUFFER: return self.arg.addrspace
if self.op in {Ops.SPECIAL, Ops.RANGE}: return AddrSpace.ALU
if self.op in {Ops.LOAD, Ops.VLOAD}: return AddrSpace.ALU # LOAD brings things into the ALU
if self.op is Ops.LOAD: return AddrSpace.ALU # LOAD brings things into the ALU
if self.op in {Ops.CUSTOM, Ops.CUSTOMI}: return AddrSpace.ALU
if self.op in {Ops.INDEX, Ops.CAST, Ops.AFTER, Ops.REDUCE, Ops.STORE, Ops.MSTACK, Ops.MSELECT, Ops.END, Ops.UNSHARD}:
return self.src[0].addrspace
if self.op in GroupOp.Movement: return self.src[0].addrspace
if self.op in {Ops.STACK, Ops.WMMA, Ops.DOT, Ops.UNPACK_LUT, Ops.GROUP} or self.op in GroupOp.Elementwise:
if self.op in {Ops.STACK, Ops.WMMA, Ops.GROUP} or self.op in GroupOp.Elementwise:
ad = [x.addrspace for x in self.src if x.addrspace is not None]
if not len(ad) or not all_same(ad): return None
return ad[0]
@@ -1245,7 +1232,7 @@ class ProgramInfo:
for u in sink.toposort():
if u.op is Ops.PARAM and u.addrspace == AddrSpace.ALU: _vars.append(u)
if u.op is Ops.PARAM and u.addrspace != AddrSpace.ALU: _globals.append(u.arg.slot)
if u.op in (Ops.STORE, Ops.LOAD, Ops.VLOAD):
if u.op in (Ops.STORE, Ops.LOAD):
if (idx:=u.src[0]).op in (Ops.INDEX, Ops.SHRINK) or (u.src[0].op is Ops.CAST and (idx:=u.src[0].src[0]).op is Ops.INDEX):
if (buf:=idx.src[0].buf_uop).op is Ops.PARAM: (outs if u.op is Ops.STORE else ins).append(buf.arg.slot)
if u.op is Ops.SPECIAL:
+5 -11
View File
@@ -120,22 +120,11 @@ spec_shared = PatternMatcher([
lambda uidx,gate,alt,load: validate_index(uidx, gate) if matches_dtype(alt, load.dtype) else False),
(UPat((Ops.INDEX, Ops.SHRINK), name="uidx").or_casted().store(UPat()), validate_index),
(UPat((Ops.INDEX, Ops.SHRINK), name="uidx").or_casted().store(UPat(), UPat.var("gate", dtype=dtypes.bool)), validate_index),
# VLOAD reinterprets a scalar address as a contiguous vector of its output dtype
(UPat(Ops.VLOAD, src=(UPat((Ops.INDEX, Ops.SHRINK), name="uidx").or_casted(),), name="x"),
lambda x,uidx: isinstance(x.arg, int) and x.arg > 0 and validate_index(uidx)),
# STORE in tensor graph: store a value into a target
(UPat(Ops.STORE, dtypes.void, (UPat(name="x"), UPat())), lambda x: True),
# WMMA has a <a, b, acc>
(UPat(Ops.WMMA, src=(UPat(), UPat(), UPat()), name="x"), lambda x: isinstance(x.arg, tuple) and len(x.arg) == 5),
# DOT reduces equal vector inputs in fixed-size groups
(UPat(Ops.DOT, src=(UPat(), UPat()), name="x"), lambda x:
isinstance(x.arg, int) and x.arg > 0 and x.src[0].shape == x.src[1].shape and len(x.src[0].shape) > 0 and
isinstance(x.src[0].shape[-1], int) and x.src[0].shape[-1] % x.arg == 0),
# UNPACK_LUT maps the low nibbles followed by the high nibbles through an immutable byte table
(UPat(Ops.UNPACK_LUT, dtypes.int8, src=(UPat(dtype=dtypes.uint8),), name="x"), lambda x:
len(x.src[0].shape) > 0 and isinstance(x.src[0].shape[-1], int) and isinstance(x.arg, tuple) and len(x.arg) == 16 and
all(isinstance(v, int) and -128 <= v <= 127 for v in x.arg)),
])
def is_device(d): return isinstance(d, str) or (isinstance(d, tuple) and all(isinstance(s, str) for s in d))
@@ -217,6 +206,11 @@ spec_program = PatternMatcher([
# allow special SHRINK
(UPat(Ops.SHRINK, src=(UPat((Ops.PARAM, Ops.BUFFER, Ops.AFTER)), UPat(), UPat(Ops.CONST))), lambda: True),
# register-vector views can remain when the renderer consumes their shape (for example a native horizontal reduction)
(UPat((Ops.RESHAPE, Ops.PERMUTE), name="x"), lambda x: x.addrspace is AddrSpace.ALU),
(UPat(Ops.REDUCE, src=(UPat(),), name="x"),
lambda x: isinstance(x.arg, tuple) and len(x.arg) == 2 and x.arg[0] in GroupOp.Reduce and isinstance(x.arg[1], int)),
# movement ops are not allowed in programs
(UPat(GroupOp.Movement), lambda: False),