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.
@@ -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
+ }