kernel-fun 0.2.0.dev1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kernel_fun/__init__.py +67 -0
- kernel_fun/_common/__init__.py +11 -0
- kernel_fun/_common/cache.py +99 -0
- kernel_fun/_common/compat.py +168 -0
- kernel_fun/_common/support.py +267 -0
- kernel_fun/cconv/__init__.py +24 -0
- kernel_fun/cconv/_kernels/__init__.py +6 -0
- kernel_fun/cconv/_kernels/strip.py +450 -0
- kernel_fun/cconv/_provenance.py +11 -0
- kernel_fun/cconv/ops.py +256 -0
- kernel_fun/kda/__init__.py +23 -0
- kernel_fun/kda/_kernels/__init__.py +7 -0
- kernel_fun/kda/_kernels/bwd_dhu.py +994 -0
- kernel_fun/kda/_kernels/bwd_intra.py +1089 -0
- kernel_fun/kda/_kernels/bwd_intra_triton.py +328 -0
- kernel_fun/kda/_kernels/bwd_scan.py +1105 -0
- kernel_fun/kda/_kernels/bwd_wy.py +320 -0
- kernel_fun/kda/_kernels/bwd_wy_t.py +309 -0
- kernel_fun/kda/_kernels/fwd_intra_triton.py +101 -0
- kernel_fun/kda/_kernels/fwd_state.py +1065 -0
- kernel_fun/kda/_provenance.py +18 -0
- kernel_fun/kda/autograd.py +114 -0
- kernel_fun/kda/chain.py +150 -0
- kernel_fun/kda/ops.py +342 -0
- kernel_fun-0.2.0.dev1.dist-info/METADATA +347 -0
- kernel_fun-0.2.0.dev1.dist-info/RECORD +30 -0
- kernel_fun-0.2.0.dev1.dist-info/WHEEL +4 -0
- kernel_fun-0.2.0.dev1.dist-info/licenses/LICENSE +201 -0
- kernel_fun-0.2.0.dev1.dist-info/licenses/NOTICE +60 -0
- kernel_fun-0.2.0.dev1.dist-info/licenses/THIRD_PARTY_NOTICES.md +175 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
"""The strip kernels (ladder idea cconv/001) — channel-strip, time-sequential, one pass
|
|
2
|
+
each direction.
|
|
3
|
+
|
|
4
|
+
Layout of the work (both kernels):
|
|
5
|
+
|
|
6
|
+
program = (channel strip of BD, time segment of TS rows, batch row)
|
|
7
|
+
thread = VEC contiguous channels (VEC = BD / (32*num_warps): 4 -> 8B, 8 -> 16B vectors),
|
|
8
|
+
looping over the segment's rows in groups of G
|
|
9
|
+
|
|
10
|
+
Because each thread walks TIME sequentially for a fixed set of channels, the W-1 previous
|
|
11
|
+
rows of x (and, in the backward, of g = dy * silu'(z)) live in registers as a ring buffer
|
|
12
|
+
that rotates by assignment. That is what makes a width-4 conv cost ONE load of x, ONE
|
|
13
|
+
sigmoid and ONE store per element instead of fla's W shifted tile loads and W sigmoids
|
|
14
|
+
(kernels.py, `for i_w in tl.static_range(0, W)`), and it is what removes the backward's
|
|
15
|
+
forward re-run: z is recomputed in-register from the x ring the dw accumulation already
|
|
16
|
+
needs.
|
|
17
|
+
|
|
18
|
+
What actually bounds a kernel like this on a B300 (dbg_bw.py, dbg_asm.py, 2026-09-01):
|
|
19
|
+
|
|
20
|
+
- the strip access pattern itself streams at 6.2-6.35 TB/s — the same as a flat copy — so
|
|
21
|
+
the tiling is not the problem;
|
|
22
|
+
- bytes in flight per SM are. A stream needs ~50-60 KB in flight per SM; in-flight rows live
|
|
23
|
+
in registers, and so does the per-thread state (taps, rings, dw accumulators), so
|
|
24
|
+
registers/thread cap warps/SM and warps x rows-in-flight x bytes/row is the bandwidth.
|
|
25
|
+
The first cut (VEC=8, G=4) compiled to 102 regs fwd / 218 bwd -> 20 / 9 warps per SM ->
|
|
26
|
+
~40 KB in flight -> 3.9 / 3.4 TB/s. Hence VEC=4 (halves the state) and G=8 (doubles the
|
|
27
|
+
rows in flight) in the autotune space.
|
|
28
|
+
|
|
29
|
+
The ring is written for WP=4 taps (every ladder config). Narrower W is handled by
|
|
30
|
+
zero-weighting the missing taps — checked by the `w2` case — and W>4 is refused.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import torch
|
|
36
|
+
import triton
|
|
37
|
+
import triton.language as tl
|
|
38
|
+
|
|
39
|
+
WP = tl.constexpr(4) # padded tap count the register ring is written for; W <= WP
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _configs():
|
|
43
|
+
# BD = VEC * 32 * num_warps; every thread holds exactly one VEC-wide vector per row.
|
|
44
|
+
# maxnreg=128 on the VEC=4 backward: ptxas trades a handful of spills for double the
|
|
45
|
+
# warps per SM, and bytes in flight is what this kernel is short of (NOTES.md). On VEC=8
|
|
46
|
+
# it spills by the hundreds, so only the VEC=4 configs carry it.
|
|
47
|
+
cfgs = [
|
|
48
|
+
triton.Config({"BD": vec * 32 * nw, "G": g}, num_warps=nw)
|
|
49
|
+
for vec in (4, 8)
|
|
50
|
+
for nw in (1, 2, 4)
|
|
51
|
+
for g in (4, 8)
|
|
52
|
+
]
|
|
53
|
+
cfgs += [
|
|
54
|
+
triton.Config({"BD": 4 * 32 * nw, "G": g}, num_warps=nw, maxnreg=128)
|
|
55
|
+
for nw in (1, 2, 4)
|
|
56
|
+
for g in (4, 8)
|
|
57
|
+
]
|
|
58
|
+
return cfgs
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# --------------------------------------------------------------------------------------------
|
|
62
|
+
# pieces
|
|
63
|
+
# --------------------------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@triton.jit
|
|
67
|
+
def _tap(w, o_d, m_d, W: tl.constexpr, k: tl.constexpr):
|
|
68
|
+
"""w[:, W-1-k] as fp32 [BD] — the weight multiplying x[t-k]. Zero for a padded tap."""
|
|
69
|
+
if k < W:
|
|
70
|
+
return tl.load(w + o_d * W + (W - 1 - k), mask=m_d, other=0.0).to(tl.float32)
|
|
71
|
+
else:
|
|
72
|
+
return tl.zeros_like(o_d).to(tl.float32)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@triton.jit
|
|
76
|
+
def _sigmoid(z):
|
|
77
|
+
# sigmoid(z) = 0.5 * tanh(0.5 z) + 0.5: two FMAs and ONE MUFU op. `tl.sigmoid` lowers to a
|
|
78
|
+
# libdevice expf plus an IEEE divide — 20-30 instructions against a per-element budget of
|
|
79
|
+
# ~30 (fwd) / ~20 (bwd) at B300 bandwidth. tanh.approx.f32 has ~2^-11 relative error, far
|
|
80
|
+
# inside the 5e-3 / 8e-3 budgets on bf16 outputs; the err_ratio column is the check.
|
|
81
|
+
th = tl.inline_asm_elementwise(
|
|
82
|
+
"tanh.approx.f32 $0, $1;", "=r,r", [0.5 * z], dtype=tl.float32, is_pure=True, pack=1
|
|
83
|
+
)
|
|
84
|
+
return 0.5 * th + 0.5
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@triton.jit
|
|
88
|
+
def _silu(z):
|
|
89
|
+
return z * _sigmoid(z)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@triton.jit
|
|
93
|
+
def _dsilu(z, dy):
|
|
94
|
+
s = _sigmoid(z)
|
|
95
|
+
return dy * s * (1.0 + z * (1.0 - s))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@triton.jit
|
|
99
|
+
def _ld(p, tt, st, m_d, T, MASK_D: tl.constexpr, MASK_T: tl.constexpr):
|
|
100
|
+
"""One row, still in its storage dtype: converting at the use site keeps the in-flight
|
|
101
|
+
rows at half the registers. Masks are constexpr-selected so the steady state has none."""
|
|
102
|
+
if MASK_D and MASK_T:
|
|
103
|
+
return tl.load(p + tt.to(tl.int64) * st, mask=m_d & (tt < T), other=0.0)
|
|
104
|
+
elif MASK_D:
|
|
105
|
+
return tl.load(p + tt.to(tl.int64) * st, mask=m_d, other=0.0)
|
|
106
|
+
elif MASK_T:
|
|
107
|
+
return tl.load(p + tt.to(tl.int64) * st, mask=m_d & (tt < T), other=0.0)
|
|
108
|
+
else:
|
|
109
|
+
return tl.load(p + tt.to(tl.int64) * st)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@triton.jit
|
|
113
|
+
def _ld_ring(p, tt, st, m_d, T):
|
|
114
|
+
"""A row left of the segment start: zero left of the sequence."""
|
|
115
|
+
return tl.load(p + tt.to(tl.int64) * st, mask=m_d & (tt >= 0) & (tt < T), other=0.0).to(tl.float32)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@triton.jit
|
|
119
|
+
def _f(v):
|
|
120
|
+
return v.to(tl.float32)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@triton.jit
|
|
124
|
+
def _st(p, tt, st, v, m, EVEN: tl.constexpr):
|
|
125
|
+
o = tl.cast(v, p.dtype.element_ty, fp_downcast_rounding="rtne")
|
|
126
|
+
if EVEN:
|
|
127
|
+
tl.store(p + tt.to(tl.int64) * st, o)
|
|
128
|
+
else:
|
|
129
|
+
tl.store(p + tt.to(tl.int64) * st, o, mask=m)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@triton.jit
|
|
133
|
+
def _fwd4(py, t, syt, xa, xb, xc, xd, xm1, xm2, xm3, w0, w1, w2, w3, m_d, T, EVEN: tl.constexpr):
|
|
134
|
+
"""Four consecutive rows t..t+3 given their (already loaded) x and the ring behind them.
|
|
135
|
+
Returns the ring for row t+4."""
|
|
136
|
+
xa = _f(xa)
|
|
137
|
+
xb = _f(xb)
|
|
138
|
+
xc = _f(xc)
|
|
139
|
+
xd = _f(xd)
|
|
140
|
+
ya = _silu(w0 * xa + w1 * xm1 + w2 * xm2 + w3 * xm3)
|
|
141
|
+
yb = _silu(w0 * xb + w1 * xa + w2 * xm1 + w3 * xm2)
|
|
142
|
+
yc = _silu(w0 * xc + w1 * xb + w2 * xa + w3 * xm1)
|
|
143
|
+
yd = _silu(w0 * xd + w1 * xc + w2 * xb + w3 * xa)
|
|
144
|
+
_st(py, t, syt, ya, m_d & (t < T), EVEN)
|
|
145
|
+
_st(py, t + 1, syt, yb, m_d & (t + 1 < T), EVEN)
|
|
146
|
+
_st(py, t + 2, syt, yc, m_d & (t + 2 < T), EVEN)
|
|
147
|
+
_st(py, t + 3, syt, yd, m_d & (t + 3 < T), EVEN)
|
|
148
|
+
return xd, xc, xb
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@triton.jit
|
|
152
|
+
def _bwd4(
|
|
153
|
+
pdx, t, sdt, t0,
|
|
154
|
+
xa, xb, xc, xd, da, db, dc, dd,
|
|
155
|
+
xm1, xm2, xm3, gm1, gm2, gm3,
|
|
156
|
+
dw0, dw1, dw2, dw3,
|
|
157
|
+
w0, w1, w2, w3, m_d, T,
|
|
158
|
+
HEAD: tl.constexpr, EVEN: tl.constexpr,
|
|
159
|
+
):
|
|
160
|
+
"""Rows t..t+3 of the backward: g for each, dw from this group's g, and dx for rows
|
|
161
|
+
t-3..t (dx[u] = sum_k w'[k] g[u+k], so a row's dx is final three rows later). HEAD marks
|
|
162
|
+
a group that may be the segment's first, whose dx rows t0-3..t0-1 belong to the previous
|
|
163
|
+
segment and are masked off."""
|
|
164
|
+
xa = _f(xa)
|
|
165
|
+
xb = _f(xb)
|
|
166
|
+
xc = _f(xc)
|
|
167
|
+
xd = _f(xd)
|
|
168
|
+
ga = _dsilu(w0 * xa + w1 * xm1 + w2 * xm2 + w3 * xm3, _f(da))
|
|
169
|
+
gb = _dsilu(w0 * xb + w1 * xa + w2 * xm1 + w3 * xm2, _f(db))
|
|
170
|
+
gc = _dsilu(w0 * xc + w1 * xb + w2 * xa + w3 * xm1, _f(dc))
|
|
171
|
+
gd = _dsilu(w0 * xd + w1 * xc + w2 * xb + w3 * xa, _f(dd))
|
|
172
|
+
dw0 += ga * xa + gb * xb + gc * xc + gd * xd
|
|
173
|
+
dw1 += ga * xm1 + gb * xa + gc * xb + gd * xc
|
|
174
|
+
dw2 += ga * xm2 + gb * xm1 + gc * xa + gd * xb
|
|
175
|
+
dw3 += ga * xm3 + gb * xm2 + gc * xm1 + gd * xa
|
|
176
|
+
r = t - 3
|
|
177
|
+
if HEAD:
|
|
178
|
+
_st(pdx, r, sdt, w3 * ga + w2 * gm1 + w1 * gm2 + w0 * gm3, m_d & (r >= t0) & (r < T), False)
|
|
179
|
+
_st(pdx, r + 1, sdt, w3 * gb + w2 * ga + w1 * gm1 + w0 * gm2, m_d & (r + 1 >= t0) & (r + 1 < T), False)
|
|
180
|
+
_st(pdx, r + 2, sdt, w3 * gc + w2 * gb + w1 * ga + w0 * gm1, m_d & (r + 2 >= t0) & (r + 2 < T), False)
|
|
181
|
+
else:
|
|
182
|
+
_st(pdx, r, sdt, w3 * ga + w2 * gm1 + w1 * gm2 + w0 * gm3, m_d & (r < T), EVEN)
|
|
183
|
+
_st(pdx, r + 1, sdt, w3 * gb + w2 * ga + w1 * gm1 + w0 * gm2, m_d & (r + 1 < T), EVEN)
|
|
184
|
+
_st(pdx, r + 2, sdt, w3 * gc + w2 * gb + w1 * ga + w0 * gm1, m_d & (r + 2 < T), EVEN)
|
|
185
|
+
_st(pdx, r + 3, sdt, w3 * gd + w2 * gc + w1 * gb + w0 * ga, m_d & (r + 3 < T), EVEN)
|
|
186
|
+
return xd, xc, xb, gd, gc, gb, dw0, dw1, dw2, dw3
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
# --------------------------------------------------------------------------------------------
|
|
190
|
+
# forward
|
|
191
|
+
# --------------------------------------------------------------------------------------------
|
|
192
|
+
#
|
|
193
|
+
# Row groups: see the comment on the loop. The masked path is taken only by a group that can
|
|
194
|
+
# run past T (the last group of a ragged final segment).
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@triton.autotune(configs=_configs(), key=["D", "W", "B", "T"])
|
|
198
|
+
@triton.jit
|
|
199
|
+
def cconv_fwd_strip(
|
|
200
|
+
x, y, w,
|
|
201
|
+
B, T, TS,
|
|
202
|
+
sxn, sxt, sxd,
|
|
203
|
+
syn, syt,
|
|
204
|
+
D: tl.constexpr,
|
|
205
|
+
W: tl.constexpr,
|
|
206
|
+
EVEN_T: tl.constexpr,
|
|
207
|
+
BD: tl.constexpr,
|
|
208
|
+
G: tl.constexpr,
|
|
209
|
+
):
|
|
210
|
+
i_d, i_s, i_b = tl.program_id(0), tl.program_id(1), tl.program_id(2)
|
|
211
|
+
o_d = i_d * BD + tl.arange(0, BD)
|
|
212
|
+
m_d = o_d < D
|
|
213
|
+
t0 = i_s * TS
|
|
214
|
+
t1 = t0 + TS
|
|
215
|
+
MD: tl.constexpr = D % BD != 0 # channel mask needed at all
|
|
216
|
+
EVEN: tl.constexpr = EVEN_T and not MD # no store of this program can run past T or D
|
|
217
|
+
|
|
218
|
+
px = x + i_b.to(tl.int64) * sxn + o_d * sxd
|
|
219
|
+
py = y + i_b.to(tl.int64) * syn + o_d
|
|
220
|
+
|
|
221
|
+
w0 = _tap(w, o_d, m_d, W, 0)
|
|
222
|
+
w1 = _tap(w, o_d, m_d, W, 1)
|
|
223
|
+
w2 = _tap(w, o_d, m_d, W, 2)
|
|
224
|
+
w3 = _tap(w, o_d, m_d, W, 3)
|
|
225
|
+
|
|
226
|
+
# the ring: x[t-1], x[t-2], x[t-3] (zeros left of the sequence)
|
|
227
|
+
xm1 = _ld_ring(px, t0 - 1, sxt, m_d, T)
|
|
228
|
+
xm2 = _ld_ring(px, t0 - 2, sxt, m_d, T)
|
|
229
|
+
xm3 = _ld_ring(px, t0 - 3, sxt, m_d, T)
|
|
230
|
+
|
|
231
|
+
# Rows in groups of G: every load of a group is issued before any of its stores (Triton
|
|
232
|
+
# cannot prove x and y do not alias, so a load after a store in program order can never be
|
|
233
|
+
# hoisted above it). A software-prefetch variant — next group's loads before this group's
|
|
234
|
+
# math — was measured slower at every config (NOTES.md, 2026-09-01): it doubles the
|
|
235
|
+
# registers the in-flight rows cost, and registers are what bound this kernel.
|
|
236
|
+
# MT: rows can run past T only in a ragged final segment (T % TS != 0), and then every
|
|
237
|
+
# program masks — a runtime "does this group run past T" branch was measured to cost
|
|
238
|
+
# registers (168 vs 122 on the bwd) and speed.
|
|
239
|
+
MT: tl.constexpr = not EVEN_T
|
|
240
|
+
for t in range(t0, t1, G):
|
|
241
|
+
xa = _ld(px, t, sxt, m_d, T, MD, MT)
|
|
242
|
+
xb = _ld(px, t + 1, sxt, m_d, T, MD, MT)
|
|
243
|
+
xc = _ld(px, t + 2, sxt, m_d, T, MD, MT)
|
|
244
|
+
xd = _ld(px, t + 3, sxt, m_d, T, MD, MT)
|
|
245
|
+
if G == 8:
|
|
246
|
+
xe = _ld(px, t + 4, sxt, m_d, T, MD, MT)
|
|
247
|
+
xf = _ld(px, t + 5, sxt, m_d, T, MD, MT)
|
|
248
|
+
xg = _ld(px, t + 6, sxt, m_d, T, MD, MT)
|
|
249
|
+
xh = _ld(px, t + 7, sxt, m_d, T, MD, MT)
|
|
250
|
+
xm1, xm2, xm3 = _fwd4(py, t, syt, xa, xb, xc, xd, xm1, xm2, xm3, w0, w1, w2, w3, m_d, T, EVEN)
|
|
251
|
+
if G == 8:
|
|
252
|
+
xm1, xm2, xm3 = _fwd4(py, t + 4, syt, xe, xf, xg, xh, xm1, xm2, xm3, w0, w1, w2, w3, m_d, T, EVEN)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
# --------------------------------------------------------------------------------------------
|
|
256
|
+
# backward
|
|
257
|
+
# --------------------------------------------------------------------------------------------
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@triton.autotune(configs=_configs(), key=["D", "W", "B", "T"])
|
|
261
|
+
@triton.jit
|
|
262
|
+
def cconv_bwd_strip(
|
|
263
|
+
x, dy, dx, dwp, w,
|
|
264
|
+
B, T, TS, NS,
|
|
265
|
+
sxn, sxt, sxd,
|
|
266
|
+
syn, syt, syd,
|
|
267
|
+
sdn, sdt,
|
|
268
|
+
D: tl.constexpr,
|
|
269
|
+
W: tl.constexpr,
|
|
270
|
+
EVEN_T: tl.constexpr,
|
|
271
|
+
BD: tl.constexpr,
|
|
272
|
+
G: tl.constexpr,
|
|
273
|
+
):
|
|
274
|
+
i_d, i_s, i_b = tl.program_id(0), tl.program_id(1), tl.program_id(2)
|
|
275
|
+
o_d = i_d * BD + tl.arange(0, BD)
|
|
276
|
+
m_d = o_d < D
|
|
277
|
+
t0 = i_s * TS
|
|
278
|
+
t1 = t0 + TS
|
|
279
|
+
MD: tl.constexpr = D % BD != 0
|
|
280
|
+
EVEN: tl.constexpr = EVEN_T and not MD
|
|
281
|
+
|
|
282
|
+
px = x + i_b.to(tl.int64) * sxn + o_d * sxd
|
|
283
|
+
pdy = dy + i_b.to(tl.int64) * syn + o_d * syd
|
|
284
|
+
pdx = dx + i_b.to(tl.int64) * sdn + o_d
|
|
285
|
+
|
|
286
|
+
w0 = _tap(w, o_d, m_d, W, 0)
|
|
287
|
+
w1 = _tap(w, o_d, m_d, W, 1)
|
|
288
|
+
w2 = _tap(w, o_d, m_d, W, 2)
|
|
289
|
+
w3 = _tap(w, o_d, m_d, W, 3)
|
|
290
|
+
|
|
291
|
+
xm1 = _ld_ring(px, t0 - 1, sxt, m_d, T)
|
|
292
|
+
xm2 = _ld_ring(px, t0 - 2, sxt, m_d, T)
|
|
293
|
+
xm3 = _ld_ring(px, t0 - 3, sxt, m_d, T)
|
|
294
|
+
# g ring: g[t-1], g[t-2], g[t-3]. Rows before t0 are the previous segment's business;
|
|
295
|
+
# their dx is never stored from here, so zeros are fine.
|
|
296
|
+
gm1 = tl.zeros([BD], dtype=tl.float32)
|
|
297
|
+
gm2 = tl.zeros([BD], dtype=tl.float32)
|
|
298
|
+
gm3 = tl.zeros([BD], dtype=tl.float32)
|
|
299
|
+
|
|
300
|
+
dw0 = tl.zeros([BD], dtype=tl.float32)
|
|
301
|
+
dw1 = tl.zeros([BD], dtype=tl.float32)
|
|
302
|
+
dw2 = tl.zeros([BD], dtype=tl.float32)
|
|
303
|
+
dw3 = tl.zeros([BD], dtype=tl.float32)
|
|
304
|
+
|
|
305
|
+
# The loads sit in a runtime if/else on "can this group run past T". That branch is almost
|
|
306
|
+
# never taken (a ragged final segment only), and it is NOT there for the masks: with the
|
|
307
|
+
# loads in their own basic block ptxas issues all 16 before any of the group's math,
|
|
308
|
+
# where the straight-line form interleaves them with the FMAs to save registers. Measured
|
|
309
|
+
# at maxnreg=128: 0.327 ms with the branch vs 0.380 ms without (NOTES.md). Rows past T
|
|
310
|
+
# load as zero; dy = 0 makes their g zero, which every masked-off dx row and the dw
|
|
311
|
+
# accumulation rely on.
|
|
312
|
+
for t in range(t0, t1, G):
|
|
313
|
+
if t + G <= T:
|
|
314
|
+
xa = _ld(px, t, sxt, m_d, T, MD, False)
|
|
315
|
+
xb = _ld(px, t + 1, sxt, m_d, T, MD, False)
|
|
316
|
+
xc = _ld(px, t + 2, sxt, m_d, T, MD, False)
|
|
317
|
+
xd = _ld(px, t + 3, sxt, m_d, T, MD, False)
|
|
318
|
+
da = _ld(pdy, t, syt, m_d, T, MD, False)
|
|
319
|
+
db = _ld(pdy, t + 1, syt, m_d, T, MD, False)
|
|
320
|
+
dc = _ld(pdy, t + 2, syt, m_d, T, MD, False)
|
|
321
|
+
dd = _ld(pdy, t + 3, syt, m_d, T, MD, False)
|
|
322
|
+
if G == 8:
|
|
323
|
+
xe = _ld(px, t + 4, sxt, m_d, T, MD, False)
|
|
324
|
+
xf = _ld(px, t + 5, sxt, m_d, T, MD, False)
|
|
325
|
+
xg = _ld(px, t + 6, sxt, m_d, T, MD, False)
|
|
326
|
+
xh = _ld(px, t + 7, sxt, m_d, T, MD, False)
|
|
327
|
+
de = _ld(pdy, t + 4, syt, m_d, T, MD, False)
|
|
328
|
+
df = _ld(pdy, t + 5, syt, m_d, T, MD, False)
|
|
329
|
+
dg = _ld(pdy, t + 6, syt, m_d, T, MD, False)
|
|
330
|
+
dh = _ld(pdy, t + 7, syt, m_d, T, MD, False)
|
|
331
|
+
else:
|
|
332
|
+
xa = _ld(px, t, sxt, m_d, T, MD, True)
|
|
333
|
+
xb = _ld(px, t + 1, sxt, m_d, T, MD, True)
|
|
334
|
+
xc = _ld(px, t + 2, sxt, m_d, T, MD, True)
|
|
335
|
+
xd = _ld(px, t + 3, sxt, m_d, T, MD, True)
|
|
336
|
+
da = _ld(pdy, t, syt, m_d, T, MD, True)
|
|
337
|
+
db = _ld(pdy, t + 1, syt, m_d, T, MD, True)
|
|
338
|
+
dc = _ld(pdy, t + 2, syt, m_d, T, MD, True)
|
|
339
|
+
dd = _ld(pdy, t + 3, syt, m_d, T, MD, True)
|
|
340
|
+
if G == 8:
|
|
341
|
+
xe = _ld(px, t + 4, sxt, m_d, T, MD, True)
|
|
342
|
+
xf = _ld(px, t + 5, sxt, m_d, T, MD, True)
|
|
343
|
+
xg = _ld(px, t + 6, sxt, m_d, T, MD, True)
|
|
344
|
+
xh = _ld(px, t + 7, sxt, m_d, T, MD, True)
|
|
345
|
+
de = _ld(pdy, t + 4, syt, m_d, T, MD, True)
|
|
346
|
+
df = _ld(pdy, t + 5, syt, m_d, T, MD, True)
|
|
347
|
+
dg = _ld(pdy, t + 6, syt, m_d, T, MD, True)
|
|
348
|
+
dh = _ld(pdy, t + 7, syt, m_d, T, MD, True)
|
|
349
|
+
xm1, xm2, xm3, gm1, gm2, gm3, dw0, dw1, dw2, dw3 = _bwd4(
|
|
350
|
+
pdx, t, sdt, t0, xa, xb, xc, xd, da, db, dc, dd, xm1, xm2, xm3, gm1, gm2, gm3,
|
|
351
|
+
dw0, dw1, dw2, dw3, w0, w1, w2, w3, m_d, T, True, EVEN)
|
|
352
|
+
if G == 8:
|
|
353
|
+
xm1, xm2, xm3, gm1, gm2, gm3, dw0, dw1, dw2, dw3 = _bwd4(
|
|
354
|
+
pdx, t + 4, sdt, t0, xe, xf, xg, xh, de, df, dg, dh, xm1, xm2, xm3, gm1, gm2, gm3,
|
|
355
|
+
dw0, dw1, dw2, dw3, w0, w1, w2, w3, m_d, T, False, EVEN)
|
|
356
|
+
|
|
357
|
+
# Halo: the next segment's first 3 rows of g finish this segment's last 3 dx rows. No dw
|
|
358
|
+
# (those rows belong to the next segment's accumulator); stores only below t1.
|
|
359
|
+
t = t1
|
|
360
|
+
xa = _f(_ld(px, t, sxt, m_d, T, MD, True))
|
|
361
|
+
xb = _f(_ld(px, t + 1, sxt, m_d, T, MD, True))
|
|
362
|
+
xc = _f(_ld(px, t + 2, sxt, m_d, T, MD, True))
|
|
363
|
+
da = _f(_ld(pdy, t, syt, m_d, T, MD, True))
|
|
364
|
+
db = _f(_ld(pdy, t + 1, syt, m_d, T, MD, True))
|
|
365
|
+
dc = _f(_ld(pdy, t + 2, syt, m_d, T, MD, True))
|
|
366
|
+
ga = _dsilu(w0 * xa + w1 * xm1 + w2 * xm2 + w3 * xm3, da)
|
|
367
|
+
gb = _dsilu(w0 * xb + w1 * xa + w2 * xm1 + w3 * xm2, db)
|
|
368
|
+
gc = _dsilu(w0 * xc + w1 * xb + w2 * xa + w3 * xm1, dc)
|
|
369
|
+
r = t - 3
|
|
370
|
+
_st(pdx, r, sdt, w3 * ga + w2 * gm1 + w1 * gm2 + w0 * gm3, m_d & (r < T), False)
|
|
371
|
+
_st(pdx, r + 1, sdt, w3 * gb + w2 * ga + w1 * gm1 + w0 * gm2, m_d & (r + 1 < T), False)
|
|
372
|
+
_st(pdx, r + 2, sdt, w3 * gc + w2 * gb + w1 * ga + w0 * gm1, m_d & (r + 2 < T), False)
|
|
373
|
+
|
|
374
|
+
# One fp32 partial per program: dwp[(i_b*NS + i_s), d, W-1-k] = dw_k.
|
|
375
|
+
pp = dwp + (i_b.to(tl.int64) * NS + i_s) * (D * W) + o_d * W
|
|
376
|
+
tl.store(pp + (W - 1), dw0, mask=m_d)
|
|
377
|
+
if W >= 2:
|
|
378
|
+
tl.store(pp + (W - 2), dw1, mask=m_d)
|
|
379
|
+
if W >= 3:
|
|
380
|
+
tl.store(pp + (W - 3), dw2, mask=m_d)
|
|
381
|
+
if W >= 4:
|
|
382
|
+
tl.store(pp + (W - 4), dw3, mask=m_d)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
# --------------------------------------------------------------------------------------------
|
|
386
|
+
# host
|
|
387
|
+
# --------------------------------------------------------------------------------------------
|
|
388
|
+
|
|
389
|
+
# ~14 programs per B300 SM at one BD=1024 strip: enough rows in flight, short tail. The
|
|
390
|
+
# ladder sweeps this through CCONV_TARGET_PROGRAMS; here it is the measured constant.
|
|
391
|
+
TARGET_PROGRAMS = 2048
|
|
392
|
+
SEG_ALIGN = 8 # the largest G, so TS is a multiple of every group size
|
|
393
|
+
MIN_SEG = 64 # below this the backward's 3-row halo re-read is >5% of the segment
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _segment(B: int, T: int, D: int) -> int:
|
|
397
|
+
"""Rows per program. Smaller segments mean more programs (and more halo re-reads: the
|
|
398
|
+
backward re-reads 3 rows per segment, ~2% at TS=128); larger ones mean fewer, longer
|
|
399
|
+
loops. Sized so the grid lands near TARGET_PROGRAMS at one 1024-wide strip."""
|
|
400
|
+
strips = max(1, D // 1024)
|
|
401
|
+
ts = (B * T * strips) // TARGET_PROGRAMS
|
|
402
|
+
ts = max(MIN_SEG, (ts // SEG_ALIGN) * SEG_ALIGN)
|
|
403
|
+
return min(ts, triton.cdiv(T, SEG_ALIGN) * SEG_ALIGN)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def cconv_fwd(x: torch.Tensor, w: torch.Tensor) -> torch.Tensor:
|
|
407
|
+
B, T, D = x.shape
|
|
408
|
+
W = w.shape[1]
|
|
409
|
+
assert w.shape[0] == D and W <= WP.value, f"weight {tuple(w.shape)}: this kernel handles W <= {WP.value}"
|
|
410
|
+
assert w.is_contiguous()
|
|
411
|
+
y = torch.empty((B, T, D), dtype=x.dtype, device=x.device)
|
|
412
|
+
TS = _segment(B, T, D)
|
|
413
|
+
NS = triton.cdiv(T, TS)
|
|
414
|
+
|
|
415
|
+
def grid(meta):
|
|
416
|
+
return (triton.cdiv(D, meta["BD"]), NS, B)
|
|
417
|
+
|
|
418
|
+
cconv_fwd_strip[grid](
|
|
419
|
+
x, y, w,
|
|
420
|
+
B, T, TS,
|
|
421
|
+
x.stride(0), x.stride(1), x.stride(2),
|
|
422
|
+
y.stride(0), y.stride(1),
|
|
423
|
+
D=D, W=W, EVEN_T=(T % TS == 0),
|
|
424
|
+
)
|
|
425
|
+
return y
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def cconv_bwd(x: torch.Tensor, w: torch.Tensor, dy: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
|
|
429
|
+
B, T, D = x.shape
|
|
430
|
+
W = w.shape[1]
|
|
431
|
+
assert w.is_contiguous()
|
|
432
|
+
dx = torch.empty((B, T, D), dtype=x.dtype, device=x.device)
|
|
433
|
+
TS = _segment(B, T, D)
|
|
434
|
+
NS = triton.cdiv(T, TS)
|
|
435
|
+
dwp = torch.empty((B * NS, D, W), dtype=torch.float32, device=x.device)
|
|
436
|
+
|
|
437
|
+
def grid(meta):
|
|
438
|
+
return (triton.cdiv(D, meta["BD"]), NS, B)
|
|
439
|
+
|
|
440
|
+
cconv_bwd_strip[grid](
|
|
441
|
+
x, dy, dx, dwp, w,
|
|
442
|
+
B, T, TS, NS,
|
|
443
|
+
x.stride(0), x.stride(1), x.stride(2),
|
|
444
|
+
dy.stride(0), dy.stride(1), dy.stride(2),
|
|
445
|
+
dx.stride(0), dx.stride(1),
|
|
446
|
+
D=D, W=W, EVEN_T=(T % TS == 0),
|
|
447
|
+
)
|
|
448
|
+
# Deterministic: a fixed-shape reduction over B*NS fp32 partials, never atomics.
|
|
449
|
+
dw = dwp.sum(0).to(w.dtype)
|
|
450
|
+
return dx, dw
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Generated by tools/vendor.py — what this family was cut from.
|
|
2
|
+
|
|
3
|
+
Hand-edited after vendoring (knobs frozen, dead branches removed, call cache
|
|
4
|
+
unified), so these hashes identify the SOURCE, not the files here. drift.py
|
|
5
|
+
compares them against the ladder's current HEAD.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
SOURCE_COMMIT = "6fc6309"
|
|
9
|
+
SOURCE_FILES = {
|
|
10
|
+
"kernels/cconv/ideas/001-strip-onepass/kernel.py": "b8c2d3a246466fc21ddeb66cebac50df3073855649aebd6fd67e241561efc4da",
|
|
11
|
+
}
|