forked from tinygrad/tinygrad
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6234b42cf7 | ||
|
|
ae9da86538 | ||
|
|
df6df141ec |
Binary file not shown.
+7
-8
@@ -51,7 +51,7 @@ All nodes in the tinygrad graph are \textbf{UOps}. A UOp is a tuple $(\mathrm{op
|
||||
\textbf{Op} & \textbf{src} & \textbf{arg} & \textbf{Semantics} \\
|
||||
\midrule
|
||||
\op{Param} & $(\mathbf{s})$ & slot, dtype, device?, addrspace? &
|
||||
Placeholder with shape $\mathbf{s}$. Substituted in \op{Function}. \\[4pt]
|
||||
Placeholder with shape $\mathbf{s}$. Substituted in \op{Call}. \\[4pt]
|
||||
\op{Buffer} & $(\mathbf{s})$ & slot, dtype, device, addrspace &
|
||||
Concrete buffer slot with shape $\mathbf{s}$. If device is a tuple, it creates the fully sized buffer across multiple devices. \\
|
||||
\op{Const} & () & value, dtype &
|
||||
@@ -102,9 +102,8 @@ All nodes in the tinygrad graph are \textbf{UOps}. A UOp is a tuple $(\mathrm{op
|
||||
\toprule
|
||||
\textbf{Op} & \textbf{src} & \textbf{arg} & \textbf{Semantics} \\
|
||||
\midrule
|
||||
\op{Function} & (body, $a_0$, $a_1$, \ldots) & --- & Substitute each \op{Param} $k$ in \op{Tuple} body with $a_k$. Gradient-able. \\
|
||||
\op{Call} & (body, $a_0$, $a_1$, \ldots) & --- & Opaque invocation of a compiled kernel or custom function. \\
|
||||
\op{Tuple} & $(v_0, v_1, \ldots)$ & --- & Pack values; required as \op{Function} body to return a value. \\
|
||||
\op{Call} & (body, $a_0$, $a_1$, \ldots) & --- & Substitute each \op{Param} $k$ in body with $a_k$. \\
|
||||
\op{Tuple} & $(v_0, v_1, \ldots)$ & --- & Pack values; required as \op{Call} body to return a value. \\
|
||||
\op{GetTuple} & $(T,)$ & idx & Extract element at idx from a \op{Tuple}. \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
@@ -272,7 +271,7 @@ ALU unary & $\mathrm{src}[0].\mathrm{dtype}$ & $\mathrm{src}[0].\mathrm{shape}$
|
||||
Other binary & $\mathrm{src}[0].\mathrm{dtype}$ & broadcast & $\mathrm{src}[0].\mathrm{device}$ & dtype range \\
|
||||
\op{CmpLt}, \op{CmpNe} & bool & broadcast & $\mathrm{src}[0].\mathrm{device}$ & from intervals \\
|
||||
\op{Where} & $\mathrm{src}[1].\mathrm{dtype}$ & broadcast & $\mathrm{src}[0].\mathrm{device}$ & $[\min(b,c),\, \max(B,C)]$ \\[3pt]
|
||||
\op{Function}, \op{Call} & $\mathrm{src}[0].\mathrm{dtype}$ & substitute \op{Param} shapes & $\mathrm{src}[1].\mathrm{device}$ & dtype range \\
|
||||
\op{Call} & $\mathrm{src}[0].\mathrm{dtype}$ & substitute \op{Param} shapes & $\mathrm{src}[1].\mathrm{device}$ & dtype range \\
|
||||
\op{Range} & index & $()$ & \textsc{null} & $[0,\, n{-}1]$ \\
|
||||
\op{Index} & $\mathrm{src}[0].\mathrm{dtype}$ & remaining dims & $\mathrm{src}[0].\mathrm{device}$ & $\mathrm{src}[0]$ \\
|
||||
\op{Store} & void & $()$ & $\mathrm{src}[0].\mathrm{device}$ & --- \\
|
||||
@@ -421,7 +420,7 @@ def allreduce(T):
|
||||
%% ============================================================
|
||||
\subsection*{{\color{callblue}The \texttt{@function} Decorator} \normalfont\small--- graph capture via tracing}
|
||||
|
||||
The \texttt{@function} decorator transforms a Python function on Tensors into a single \op{Function} node.
|
||||
The \texttt{@function} decorator transforms a Python function on Tensors into a single \op{Call} node.
|
||||
|
||||
\begin{lstlisting}
|
||||
@function
|
||||
@@ -436,11 +435,11 @@ When \texttt{f(x, y)} is called, the decorator:
|
||||
\item \textbf{Runs the function} lazily (no device execution), building a UOp graph from the result.
|
||||
\item \textbf{Parameterizes}: replaces each input UOp with a \op{Param}$(k)$ placeholder.
|
||||
\item \textbf{Wraps the body} in a \op{Tuple} (even for single returns) and creates\\
|
||||
\op{Function}(\op{Tuple}(body), $x$, $y$).
|
||||
\op{Call}(\op{Tuple}(body), $x$, $y$).
|
||||
\item \textbf{Returns} the result via \op{GetTuple}$(0)$, or one \op{GetTuple} per element for tuple returns.
|
||||
\end{enumerate}
|
||||
|
||||
The result is a reusable graph fragment: the body contains only \op{Param} references, not concrete buffers. At schedule time, the \op{Function} is resolved by substituting each \op{Param}$(k)$ back with its corresponding argument $a_k$, or lowered into an opaque \op{Call} if it is to be compiled as a reusable kernel.
|
||||
The result is a reusable graph fragment: the body contains only \op{Param} references, not concrete buffers. At schedule time, the \op{Call} is resolved by substituting each \op{Param}$(k)$ back with its corresponding argument $a_k$, or compiled as a reusable kernel.
|
||||
|
||||
%% ============================================================
|
||||
\subsection*{Lowering Pipeline \normalfont\small--- from Tensor graph to machine code}
|
||||
|
||||
@@ -358,7 +358,7 @@ def split_store(x:UOp) -> UOp|None:
|
||||
ret = graph_rewrite(x, to_define_global+pm_flatten_range+rangeify_codegen, ctx=lctx, name="kernel split", bottom_up=True)
|
||||
|
||||
# create the Kernel. NOTE: buffers can be on different devices here now, they are compiled to SDMA copies later by schedule
|
||||
return ret.sink(arg=KernelInfo(opts_to_apply=lctx.opts)).call(*lctx.map.values())
|
||||
return ret.sink(arg=KernelInfo(opts_to_apply=lctx.opts)).call(*[x.flatten() for x in lctx.map.values()])
|
||||
|
||||
split_kernels = PatternMatcher([
|
||||
(UPat((Ops.STORE, Ops.END), name="x"), split_store),
|
||||
|
||||
+2
-2
@@ -1198,8 +1198,8 @@ class UOp(RandMixin, metaclass=UOpMetaClass):
|
||||
body = self if self.op is Ops.TUPLE else UOp.maketuple(self)
|
||||
return UOp(Ops.FUNCTION, src=(body,)+srcs, arg=CallInfo(grad_fxn, name, precompile, precompile_backward, aux))
|
||||
def custom_kernel(*srcs:UOp, fxn:Callable, grad_fxn:Callable|None=None) -> list[UOp]:
|
||||
placeholders = [UOp.placeholder_like(s, slot=i) for i,s in enumerate(srcs)]
|
||||
kernel = fxn(*placeholders).call(*srcs, grad_fxn=grad_fxn)
|
||||
placeholders = [UOp.placeholder_like(s.flatten(), slot=i).reshape(s.shape) for i,s in enumerate(srcs)]
|
||||
kernel = fxn(*placeholders).call(*[s.flatten() for s in srcs], grad_fxn=grad_fxn)
|
||||
return [s.after(kernel) for s in srcs]
|
||||
|
||||
def to_elf(self) -> TinyELF:
|
||||
|
||||
@@ -268,8 +268,9 @@ spec_kernel_graph = PatternMatcher([
|
||||
# mstack/mselect
|
||||
(UPat(Ops.MSTACK, name="x"), lambda x: all(isinstance(s.device, str) for s in x.src) or (all_same(x.src) and x.src[0].device is None)),
|
||||
(UPat(Ops.MSELECT, name="x"), lambda x: isinstance(x.src[0].device, tuple) and x.arg < len(x.src[0].device)),
|
||||
# all calls are on various sinks
|
||||
(UPat(Ops.CALL, src=(UPat((Ops.SINK, Ops.LINEAR, Ops.PROGRAM, Ops.CUSTOM_FUNCTION)),), allow_any_len=True), lambda: True),
|
||||
# CALL (for kernels, all input args must be 0 or 1-d)
|
||||
(UPat(Ops.CALL, src=(UPat((Ops.SINK, Ops.LINEAR, Ops.PROGRAM, Ops.CUSTOM_FUNCTION)),), allow_any_len=True, name="c"),
|
||||
lambda c: all(u.ndim <= 1 for u in c.src[1:])),
|
||||
# after on PARAM or AFTER
|
||||
(UPat(Ops.AFTER, src=(UPat(GroupOp.Movement.union({Ops.PARAM, Ops.AFTER, Ops.BUFFER, Ops.MSTACK, Ops.MSELECT, Ops.BITCAST, Ops.RESHAPE})),),
|
||||
allow_any_len=True, name="x"), lambda x: matches_dtype(x.src[0], x.dtype)),
|
||||
|
||||
Reference in New Issue
Block a user