mirror of
https://github.com/tinygrad/tinygrad.git
synced 2026-08-29 10:56:08 +00:00
pretty nt loads
This commit is contained in:
@@ -103,7 +103,7 @@ def memory_coalescing(sink:UOp, ctx:Renderer) -> UOp:
|
||||
if getenv("DMC"): return sink
|
||||
|
||||
# collect
|
||||
memory: defaultdict[tuple[Ops, UOp, UOp|str, UOp], dict[int, list[UOp]]] = defaultdict(dict)
|
||||
memory: defaultdict[tuple[Ops, UOp, UOp|str, UOp, object], dict[int, list[UOp]]] = defaultdict(dict)
|
||||
for u in sink.toposort():
|
||||
# TODO: this should handle images too, it's just memory coalescing
|
||||
if u.op in {Ops.LOAD, Ops.STORE}:
|
||||
@@ -118,11 +118,12 @@ def memory_coalescing(sink:UOp, ctx:Renderer) -> UOp:
|
||||
elif idx.op is Ops.CONST and idx.val is Invalid: root_src, arg = "INVALID", 0
|
||||
elif idx.op is Ops.CONST: root_src, arg = "CONST", idx.val
|
||||
else: root_src, arg = idx, 0
|
||||
memory[(u.op, buf, root_src, valid)].setdefault(arg, []).append(u)
|
||||
# loads/stores only coalesce with others carrying the same arg (e.g. the nontemporal flag)
|
||||
memory[(u.op, buf, root_src, valid, u.arg)].setdefault(arg, []).append(u)
|
||||
|
||||
# build replacements
|
||||
replacements = {}
|
||||
for (op,buf,base,valid),offsets in memory.items():
|
||||
for (op,buf,base,valid,ld_arg),offsets in memory.items():
|
||||
# allowed lengths (copied in)
|
||||
lengths = []
|
||||
must_divide = True
|
||||
@@ -157,7 +158,7 @@ def memory_coalescing(sink:UOp, ctx:Renderer) -> UOp:
|
||||
store = idx.store(UOp.stack(*datas) if len(datas) > 1 else datas[0])
|
||||
for i,g in enumerate(grp): replacements[offsets[g][0]] = store
|
||||
else:
|
||||
ld = idx.load()
|
||||
ld = idx.load(arg=ld_arg)
|
||||
for i,g in enumerate(grp):
|
||||
for oo in offsets[g]:
|
||||
replacements[oo] = ld.index(i) if len(grp) > 1 else ld
|
||||
|
||||
@@ -113,7 +113,7 @@ def _amd_byte_perm(a:UOp, b:UOp, selectors:UOp) -> UOp:
|
||||
def _amd_load(ptr:UOp, lanes:int|None=None) -> UOp:
|
||||
assert ptr.op is Ops.INDEX
|
||||
# nontemporal scalar load: streamed weights must not evict the activations/KV cache from L2
|
||||
if lanes is None: return UOp(Ops.CUSTOMI, src=(ptr,), arg=("__builtin_nontemporal_load({0})", ptr.dtype))
|
||||
if lanes is None: return ptr.load(arg="nontemporal")
|
||||
buf, coords = ptr.src[0], ptr.src[1:]
|
||||
idx = sum((coord*math.prod(buf.shape[i+1:]) for i,coord in enumerate(coords)), UOp.const(0))
|
||||
return UOp(Ops.SHRINK, src=(buf.flatten(), idx, UOp.const(lanes))).load(dtype=ptr.dtype)
|
||||
|
||||
@@ -30,7 +30,7 @@ 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.STORE} or (u.op is Ops.CUSTOMI and isinstance(u.arg, str) and "nontemporal_load" in u.arg):
|
||||
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:
|
||||
|
||||
@@ -188,6 +188,11 @@ class CStyleLanguage(Renderer):
|
||||
return prefix + self.type_map.get(dtype, dtype.name) + suffix
|
||||
|
||||
def render_type(self, u:UOp): return self._render_dtype(u.dtype, u.max_numel(), u.addrspace, shape=u._shape)
|
||||
def render_ptr(self, u:UOp):
|
||||
# the address of an access, vector-cast if the access reads/writes more lanes than the pointer's scalar type
|
||||
if u.max_numel() > 1 or u.dtype != u.src[0].dtype:
|
||||
return f"(({self._render_dtype(u.dtype, u.max_numel(), u.addrspace, override_ptr=True, shape=u._shape)})({self[u]}))"
|
||||
else: return f"{self[u]}"
|
||||
def render_access(self, u:UOp):
|
||||
if u.max_numel() > 1 or u.dtype != u.src[0].dtype:
|
||||
return f"*(({self._render_dtype(u.dtype, u.max_numel(), u.addrspace, override_ptr=True, shape=u._shape)})({self[u]}))"
|
||||
@@ -509,6 +514,9 @@ class HIPRenderer(CStyleLanguage):
|
||||
(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
|
||||
# a LOAD flagged nontemporal renders as the cache-bypassing builtin (only used on global loads)
|
||||
self.string_rewrite = PatternMatcher([(UPat(Ops.LOAD, arg="nontemporal", src=(UPat.var("bidx"),)),
|
||||
lambda ctx,bidx: f"__builtin_nontemporal_load({ctx.render_ptr(bidx)})")]) + self.string_rewrite
|
||||
|
||||
# https://clang.llvm.org/docs/AttributeReference.html#amdgpu-flat-work-group-size
|
||||
# NOTE: this makes hlb_cifar10 twice as fast, there may be more gains in tweaking these parameters
|
||||
|
||||
+2
-1
@@ -325,7 +325,8 @@ class UOp(RandMixin, metaclass=UOpMetaClass):
|
||||
|
||||
@functools.cached_property
|
||||
def tuplize(self:UOp) -> tuple:
|
||||
return (self.op.value, self.arg, self.dtype,)+tuple([x.tuplize for x in self.src])
|
||||
# arg goes through repr: args of different types (None, str, tuple) must stay mutually comparable for the sort
|
||||
return (self.op.value, repr(self.arg), self.dtype,)+tuple([x.tuplize for x in self.src])
|
||||
|
||||
# *** uop shape stuff ***
|
||||
|
||||
|
||||
Reference in New Issue
Block a user