kernel-fun 0.2.0.dev1__tar.gz
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-0.2.0.dev1/.github/workflows/release.yml +95 -0
- kernel_fun-0.2.0.dev1/.gitignore +31 -0
- kernel_fun-0.2.0.dev1/ALGORITHM.md +229 -0
- kernel_fun-0.2.0.dev1/INTEGRATION.md +222 -0
- kernel_fun-0.2.0.dev1/LICENSE +201 -0
- kernel_fun-0.2.0.dev1/NOTICE +60 -0
- kernel_fun-0.2.0.dev1/PKG-INFO +347 -0
- kernel_fun-0.2.0.dev1/README.md +314 -0
- kernel_fun-0.2.0.dev1/THIRD_PARTY_NOTICES.md +175 -0
- kernel_fun-0.2.0.dev1/pyproject.toml +120 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/__init__.py +67 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/_common/__init__.py +11 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/_common/cache.py +99 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/_common/compat.py +168 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/_common/support.py +267 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/cconv/__init__.py +24 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/cconv/_kernels/__init__.py +6 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/cconv/_kernels/strip.py +450 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/cconv/_provenance.py +11 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/cconv/ops.py +256 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/kda/__init__.py +23 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/kda/_kernels/__init__.py +7 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/kda/_kernels/bwd_dhu.py +994 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/kda/_kernels/bwd_intra.py +1089 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/kda/_kernels/bwd_intra_triton.py +328 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/kda/_kernels/bwd_scan.py +1105 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/kda/_kernels/bwd_wy.py +320 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/kda/_kernels/bwd_wy_t.py +309 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/kda/_kernels/fwd_intra_triton.py +101 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/kda/_kernels/fwd_state.py +1065 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/kda/_provenance.py +18 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/kda/autograd.py +114 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/kda/chain.py +150 -0
- kernel_fun-0.2.0.dev1/src/kernel_fun/kda/ops.py +342 -0
- kernel_fun-0.2.0.dev1/tests/__init__.py +0 -0
- kernel_fun-0.2.0.dev1/tests/_inputs.py +105 -0
- kernel_fun-0.2.0.dev1/tests/conftest.py +46 -0
- kernel_fun-0.2.0.dev1/tests/test_cconv.py +368 -0
- kernel_fun-0.2.0.dev1/tests/test_cta_policy.py +172 -0
- kernel_fun-0.2.0.dev1/tests/test_fallback.py +250 -0
- kernel_fun-0.2.0.dev1/tests/test_parity.py +116 -0
- kernel_fun-0.2.0.dev1/tests/test_runtime.py +187 -0
- kernel_fun-0.2.0.dev1/tools/drift.py +135 -0
- kernel_fun-0.2.0.dev1/tools/graphprobe.py +168 -0
- kernel_fun-0.2.0.dev1/tools/prodtime.py +118 -0
- kernel_fun-0.2.0.dev1/tools/vendor.py +234 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Publishing kernel-fun to PyPI.
|
|
2
|
+
#
|
|
3
|
+
# Auth is PyPI Trusted Publishing (OIDC): no API token is stored in this repo or in GitHub
|
|
4
|
+
# secrets. PyPI matches four claims out of the token GitHub mints for this job -- owner,
|
|
5
|
+
# repository, THIS FILENAME, and the environment named below. Renaming this file, or
|
|
6
|
+
# renaming an environment, breaks publishing with a claims mismatch until the publisher
|
|
7
|
+
# entry on PyPI is edited to match. See README.md "Releasing".
|
|
8
|
+
#
|
|
9
|
+
# TestPyPI workflow_dispatch, target=testpypi -> environment `testpypi`
|
|
10
|
+
# PyPI push a `v*` tag (or dispatch) -> environment `pypi`
|
|
11
|
+
#
|
|
12
|
+
# A version on PyPI can never be reused, so the build job refuses a tag that disagrees with
|
|
13
|
+
# `__version__`; the tag is the thing humans read, and the metadata is what actually ships.
|
|
14
|
+
name: Release
|
|
15
|
+
|
|
16
|
+
on:
|
|
17
|
+
push:
|
|
18
|
+
tags: ["v*"]
|
|
19
|
+
workflow_dispatch:
|
|
20
|
+
inputs:
|
|
21
|
+
target:
|
|
22
|
+
description: "Index to publish to"
|
|
23
|
+
type: choice
|
|
24
|
+
options: [testpypi, pypi]
|
|
25
|
+
default: testpypi
|
|
26
|
+
|
|
27
|
+
permissions: {}
|
|
28
|
+
|
|
29
|
+
jobs:
|
|
30
|
+
build:
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v7
|
|
34
|
+
- uses: astral-sh/setup-uv@v10.1.0
|
|
35
|
+
with:
|
|
36
|
+
python-version: "3.12"
|
|
37
|
+
|
|
38
|
+
- name: Build sdist and wheel
|
|
39
|
+
run: uv build
|
|
40
|
+
|
|
41
|
+
# Metadata that fails here fails AFTER upload otherwise, and by then the version is
|
|
42
|
+
# burned. Cheap gate: PyPI's own renderer/validator, run before anything is sent.
|
|
43
|
+
- name: Check metadata
|
|
44
|
+
run: uvx twine check --strict dist/*
|
|
45
|
+
|
|
46
|
+
# The wheel's version comes from src/kernel_fun/__init__.py. If someone tags v0.3.0
|
|
47
|
+
# without bumping it, the upload succeeds and ships 0.2.0 under a 0.3.0 tag -- an
|
|
48
|
+
# inconsistency that cannot be corrected in place. Refuse instead.
|
|
49
|
+
- name: Tag agrees with __version__
|
|
50
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
51
|
+
run: |
|
|
52
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
53
|
+
built=$(ls dist/*.whl | sed -E 's/.*kernel_fun-(.+)-py3-none-any\.whl/\1/')
|
|
54
|
+
echo "tag=$tag built=$built"
|
|
55
|
+
[ "$tag" = "$built" ] || { echo "::error::tag v$tag != __version__ $built"; exit 1; }
|
|
56
|
+
|
|
57
|
+
- uses: actions/upload-artifact@v7
|
|
58
|
+
with:
|
|
59
|
+
name: dist
|
|
60
|
+
path: dist/
|
|
61
|
+
if-no-files-found: error
|
|
62
|
+
|
|
63
|
+
testpypi:
|
|
64
|
+
needs: build
|
|
65
|
+
if: github.event_name == 'workflow_dispatch' && inputs.target == 'testpypi'
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
environment:
|
|
68
|
+
name: testpypi
|
|
69
|
+
url: https://test.pypi.org/p/kernel-fun
|
|
70
|
+
permissions:
|
|
71
|
+
id-token: write # mint the OIDC token PyPI trades for a short-lived upload token
|
|
72
|
+
steps:
|
|
73
|
+
- uses: actions/download-artifact@v8
|
|
74
|
+
with:
|
|
75
|
+
name: dist
|
|
76
|
+
path: dist/
|
|
77
|
+
- uses: pypa/gh-action-pypi-publish@v1.14.2
|
|
78
|
+
with:
|
|
79
|
+
repository-url: https://test.pypi.org/legacy/
|
|
80
|
+
|
|
81
|
+
pypi:
|
|
82
|
+
needs: build
|
|
83
|
+
if: startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && inputs.target == 'pypi')
|
|
84
|
+
runs-on: ubuntu-latest
|
|
85
|
+
environment:
|
|
86
|
+
name: pypi
|
|
87
|
+
url: https://pypi.org/p/kernel-fun
|
|
88
|
+
permissions:
|
|
89
|
+
id-token: write
|
|
90
|
+
steps:
|
|
91
|
+
- uses: actions/download-artifact@v8
|
|
92
|
+
with:
|
|
93
|
+
name: dist
|
|
94
|
+
path: dist/
|
|
95
|
+
- uses: pypa/gh-action-pypi-publish@v1.14.2
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Build artifacts. This repo, unlike the research ladder, IS installable, so these are
|
|
2
|
+
# reachable here in a way they were not there.
|
|
3
|
+
dist/
|
|
4
|
+
build/
|
|
5
|
+
*.egg-info/
|
|
6
|
+
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.py[cod]
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.ruff_cache/
|
|
11
|
+
|
|
12
|
+
# Generated Triton artifacts. The training image points TRITON_CACHE_DIR outside the tree;
|
|
13
|
+
# a native run on a workstation would land here.
|
|
14
|
+
.triton-cache/
|
|
15
|
+
|
|
16
|
+
# A .venv inside the tree is how the old repo lost a day: it shadowed the container's conda
|
|
17
|
+
# python and an image that definitely had torch reported "No module named torch". Install
|
|
18
|
+
# this package INTO the image's python (`pip install .`), or put its src on PYTHONPATH.
|
|
19
|
+
.venv/
|
|
20
|
+
.python-version
|
|
21
|
+
|
|
22
|
+
# macOS Finder droppings.
|
|
23
|
+
.DS_Store
|
|
24
|
+
*.swp
|
|
25
|
+
|
|
26
|
+
# Profiler captures: 5-50MB each and git history is forever. tools/prodtime.py and
|
|
27
|
+
# tools/graphprobe.py print their numbers; the captures leave a session by rsync.
|
|
28
|
+
*.ncu-rep
|
|
29
|
+
*.nsys-rep
|
|
30
|
+
*.qdrep
|
|
31
|
+
*.sqlite
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# KDA — the math these kernels implement
|
|
2
|
+
|
|
3
|
+
Everything below is fla 0.5.2's own decomposition (`fla/ops/kda/{chunk_fwd.py,chunk_bwd.py,
|
|
4
|
+
chunk_intra.py,wy_fast.py}` and `fla/ops/common/chunk_delta_h.py`), stated precisely so each
|
|
5
|
+
port has one written contract. All logs are base-2 (fla multiplies the raw log-space gate by
|
|
6
|
+
`RCP_LN2 = 1/ln2` in the cumsum), all gates are ≤ 0 and decreasing down a chunk, and `BT` is
|
|
7
|
+
the chunk size (64), `BC = 16` the sub-chunk. Per (batch b, value-head hv); q/k live at head
|
|
8
|
+
`h = hv // (HV//H)`. `scale = K^-0.5`.
|
|
9
|
+
|
|
10
|
+
This package ports the forward scan+readout and four of the backward's seven stages out of
|
|
11
|
+
fla. Everything else is fla's, called stage by stage from `kda/chain.py` — which is the
|
|
12
|
+
authoritative table of who owns what; this file is the math each stage implements.
|
|
13
|
+
|
|
14
|
+
| where | module | replaces |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| fwd, Aqk zero-fill | `kda/_kernels/fwd_intra_triton.py` | a `masked_fill` over the whole tile |
|
|
17
|
+
| fwd, scan + o | `kda/_kernels/fwd_state.py` | `chunk_gated_delta_rule_fwd_h` + the o stage |
|
|
18
|
+
| bwd 2, re-scan (B1) | `kda/_kernels/bwd_scan.py` | `chunk_gated_delta_rule_fwd_h` |
|
|
19
|
+
| bwd 4, dhu (B2a) | `kda/_kernels/bwd_dhu.py` | `chunk_gated_delta_rule_bwd_dhu` |
|
|
20
|
+
| bwd 5, wy_dqkg (B2b) | `kda/_kernels/bwd_wy.py` | `chunk_kda_bwd_wy_dqkg_fused` |
|
|
21
|
+
| bwd 6, intra | `kda/_kernels/bwd_intra.py` (+`bwd_intra_triton.py`) | `chunk_kda_bwd_intra` |
|
|
22
|
+
| bwd 7, dg cumsum | — | folded into stage 6's epilogue |
|
|
23
|
+
|
|
24
|
+
The gate activation (`-exp(A_log)·softplus(g + dt_bias)`, when `use_gate_in_kernel`) is
|
|
25
|
+
fused into stage 1's cumsum via fla's `kda_gate_chunk_cumsum`, not run as eager fp32 torch
|
|
26
|
+
ops. At prod8192 the eager form is four passes over a [B,T,HV,K] fp32 tensor — ~2ms and
|
|
27
|
+
~2GiB of saved activations *per layer*, against 1.9ms for the entire fused stage.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
# Forward
|
|
32
|
+
|
|
33
|
+
## Stage 1 — cumsum (fla, stays Triton)
|
|
34
|
+
|
|
35
|
+
g2[t, d] = (1/ln2) * sum_{s <= t, s in chunk(t)} g[s, d] fp32 [B, T, HV, K]
|
|
36
|
+
|
|
37
|
+
Chunk-local inclusive cumsum, per dimension. `G[c, d] = g2[last row of chunk c, d]` is the
|
|
38
|
+
per-chunk total decay — a K-vector, where a scalar-gate op (gdn) would have a scalar.
|
|
39
|
+
|
|
40
|
+
## Stage 2 — intra / WY (fla, stays Triton)
|
|
41
|
+
|
|
42
|
+
`chunk_kda_fwd_intra` produces, per chunk (row indices i, j within the chunk):
|
|
43
|
+
|
|
44
|
+
Aqk[i, j] = scale * sum_d q[i,d] * k[j,d] * exp2(g2[i,d] - g2[j,d]) for i >= j, else 0
|
|
45
|
+
Akk = the solved lower-triangular WY inverse, beta folded in
|
|
46
|
+
w[i, :] = (Akk @ (beta * exp2(g2) * k))[i, :] bf16 [B, T, HV, K]
|
|
47
|
+
u[i, :] = (Akk @ (beta * v))[i, :] bf16 [B, T, HV, V]
|
|
48
|
+
kg[i, d] = k[i,d] * exp2(G[d] - g2[i,d]) bf16 [B, T, HV, K]
|
|
49
|
+
qg = q * exp2(g2) (only materialized when disable_recompute; None on our path)
|
|
50
|
+
|
|
51
|
+
Two things to internalize:
|
|
52
|
+
|
|
53
|
+
- **`scale` ships inside Aqk** (the intra kernels multiply it in), while the `q @ h` readout
|
|
54
|
+
term gets `scale` applied in the o stage. Don't apply it twice.
|
|
55
|
+
- **The per-dim gate sits INSIDE Aqk's dot product.** `exp2(g2_i - g2_j)` would be a scalar
|
|
56
|
+
under a per-head gate, so it could be factored out of one MMA and applied afterwards in
|
|
57
|
+
SIMT. Per-dim, that factorization needs `exp2(+g2_j)` on one operand — unbounded — so fla
|
|
58
|
+
computes Aqk in sub-chunks (BC=16) where relative gates stay bounded. That is Triton work
|
|
59
|
+
worth keeping: the CuTe kernel LOADS Aqk instead of computing it.
|
|
60
|
+
|
|
61
|
+
## Stage 3+4 — scan + o (`kda/_kernels/fwd_state.py`, fused)
|
|
62
|
+
|
|
63
|
+
Serial over chunks c, state h [K, V] fp32:
|
|
64
|
+
|
|
65
|
+
v'_c = u_c - w_c @ h_c (MMA "WH" + subtract)
|
|
66
|
+
o_c = scale * (q_c * exp2(g2_c)) @ h_c + Aqk_c @ v'_c (MMAs "OH" + "OI")
|
|
67
|
+
h_{c+1}[d, :] = exp2(G_c[d]) * h_c[d, :] + (kg_c^T @ v'_c)[d, :] (MMA "DH" + decay)
|
|
68
|
+
|
|
69
|
+
after the last chunk, `ht = h` (fp32, TMA store). fla stores v' (`v_new`) and per-chunk h to
|
|
70
|
+
HBM between its two kernels; the fusion keeps both in smem/tmem — that traffic is the win.
|
|
71
|
+
|
|
72
|
+
Notes that cost time to rediscover: the state decay multiply indexes the tmem fragment's row
|
|
73
|
+
(Ld16x256b lanes span rows r and r+8), and OH's q operand must be pre-gated in SIMT
|
|
74
|
+
(`qg = q·exp2(g2)`, per-dim) from the q tile plus a g2 tile (fp32 [BT,K], 32KB/stage smem).
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
# Backward
|
|
79
|
+
|
|
80
|
+
Residuals from the forward: `(q, k, v, g2, beta, Aqk, Akk, h0)` — exactly what fla's own
|
|
81
|
+
autograd Function saves when `disable_recompute=False`. The forward must preserve that
|
|
82
|
+
rounding contract: Aqk/Akk stored bf16 by intra, g2 the fla cumsum, v_new/h recomputed by
|
|
83
|
+
the backward itself.
|
|
84
|
+
|
|
85
|
+
One deliberate deviation, and it is fla's own behaviour rather than ours: under
|
|
86
|
+
`use_gate_in_kernel`, **g2 is not saved**. The backward recomputes it from the raw (and
|
|
87
|
+
half-size) `g` with one kernel launch, which is what fla's recompute path does — saving it
|
|
88
|
+
would cost 1GiB per layer at prod8192 for something a single launch reproduces.
|
|
89
|
+
|
|
90
|
+
## The seven launches
|
|
91
|
+
|
|
92
|
+
1. **recompute** (`recompute_w_u_fwd`): w = Akk @ (β·exp2(g2)·k), u = Akk @ (β·v),
|
|
93
|
+
qg = q·exp2(g2), kg = k·exp2(G−g2). All bf16 [B,T,HV,K/V].
|
|
94
|
+
2. **re-scan** (`chunk_gated_delta_rule_fwd_h`): the forward recurrence again, this time
|
|
95
|
+
materializing every per-chunk state: h_c [K,V] checkpoints (bf16, [B,NT,HV,K,V]) and
|
|
96
|
+
v'_c = u_c − w_c @ h_c (`v_new`). Serial over chunks.
|
|
97
|
+
3. **dAv** (`chunk_kda_bwd_dAv`): dAqk[r,s] = scale·Σ_v do[r,v]·v'[s,v] (lower tri, fp32
|
|
98
|
+
[B,T,HV,BT]); dv[r,:] = Σ_{s≥r} Aqk[s,r]·do[s,:] (Aqk^T @ do, upper-masked).
|
|
99
|
+
4. **dhu** (`chunk_gated_delta_rule_bwd_dhu`): the reverse scan. State dh [K,V] fp32; fla
|
|
100
|
+
folds the per-chunk decay on the K axis, updates dv (dv2 = dv + w-side term) per chunk,
|
|
101
|
+
and emits dh checkpoints (bf16 [B,NT,HV,K,V]) + dh0. Serial, reverse.
|
|
102
|
+
5. **wy_dqkg** (`chunk_kda_bwd_wy_dqkg_fused`), per chunk, consuming h_c and dh_c:
|
|
103
|
+
- dq[r,d] = scale·exp2(g2[r,d])·Σ_v do[r,v]·h_c[d,v] (h consumer)
|
|
104
|
+
- dk[r,d] = exp2(G[d]−g2[r,d])·Σ_v v'[r,v]·dh_c[d,v] + WY corrections (dh consumer)
|
|
105
|
+
- dw[r,d] = −Σ_v dv[r,v]·h_c[d,v]; folded via Akk into dk/db/dAkk (h consumer)
|
|
106
|
+
- dgk[d] += exp2(G[d])·Σ_v h_c[d,v]·dh_c[d,v] (last row of chunk) (both)
|
|
107
|
+
- dv2 = β·(Akk^T @ dv); db += rowsum terms; dAkk (strict lower, fp32)
|
|
108
|
+
- dg[r,d] = q·dq − k·dk + last-row dgk + kg·(Akk@dw)·β (the inter-chunk part)
|
|
109
|
+
6. **intra** (`chunk_kda_bwd_intra`): the intra-chunk dAqk/dAkk consumers — **the dominant
|
|
110
|
+
stage, half of fla's backward**; see below.
|
|
111
|
+
7. **dg cumsum**: dg = chunk-local REVERSE cumsum of dg (fp32).
|
|
112
|
+
|
|
113
|
+
Ours are 2, 4, 5 and 6, and 7 disappears into 6's epilogue. Stages 2/4/5 are the "B1/B2"
|
|
114
|
+
fusions listed as standing headroom in the first version of this file: B1 re-runs the
|
|
115
|
+
forward scan with h checkpointed via TMA store off the critical path, B2a keeps dh^T
|
|
116
|
+
tmem-resident through the reverse scan, and B2b is a full-K restructure of fla's fused
|
|
117
|
+
kernel — the same math over one K slab instead of NK, so v_new/do/dv are read once each
|
|
118
|
+
instead of four times.
|
|
119
|
+
|
|
120
|
+
GVA (HV > H): dq/dk are produced at HV and reduced to H after stage 6.
|
|
121
|
+
|
|
122
|
+
## Where the time and bytes go (prod8192 = B16 × T8192 × H16, K128/V256, b300)
|
|
123
|
+
|
|
124
|
+
Stage times measured 2026-08-18: 25.3ms total — intra 12.62, wy_dqkg 6.15, dhu 2.22,
|
|
125
|
+
fwd_h 1.88, recompute 1.18, dAv 0.77, dg cumsum 0.51. Inter-stage HBM: h 2.1GB + dh 2.1GB
|
|
126
|
+
+ v_new 1.1GB + w/u/qg/kg 2.7GB + dv 2×1.1GB + fp32 dq/dk/dg 3.2GB + dAqk/dAkk 1.1GB.
|
|
127
|
+
|
|
128
|
+
## Stage 6, bwd_intra — half the backward
|
|
129
|
+
|
|
130
|
+
### What it computes
|
|
131
|
+
|
|
132
|
+
Adds the intra-chunk gradient parts, given dAqk and dAkk (both fp32, strictly-masked). With
|
|
133
|
+
r, s chunk-local rows, all per (b, hv, chunk), and every gate factor per-dim d:
|
|
134
|
+
|
|
135
|
+
dq[r,d] += Σ_{s ≤ r} dAqk[r,s] · k[s,d] · exp2(g2[r,d] − g2[s,d])
|
|
136
|
+
dwk[r,d] = Σ_{s ≤ r} dAkk[r,s] · k[s,d] · exp2(g2[r,d] − g2[s,d])
|
|
137
|
+
db[r] += Σ_d dwk[r,d] · k[r,d]
|
|
138
|
+
dk[s,d] += Σ_{r ≥ s} (dAqk[r,s]·q[r,d] + dAkk[r,s]·β_r·k[r,d]) · exp2(g2[r,d] − g2[s,d])
|
|
139
|
+
+ β_s · dwk[s,d]
|
|
140
|
+
dg[r,d] += q[r,d]·dq_intra[r,d] + β_r·dwk[r,d]·k[r,d] − dkt[r,d]·k[r,d]
|
|
141
|
+
(dkt = the Σ_{r ≥ s} term above, i.e. the column-side accumulation)
|
|
142
|
+
|
|
143
|
+
### The numerics law (do not relitigate)
|
|
144
|
+
|
|
145
|
+
Every exp2 argument above is `g2[r,d] − g2[s,d]` with r ≥ s — one-sided, ≤ 0, safe at any
|
|
146
|
+
gate magnitude. KDA's real init makes decay ~16 log2 units PER STEP (`exp(A_log) ∈ [1,16]`),
|
|
147
|
+
so any factorization through a reference row m — `exp2(g_r − g_m)·exp2(g_m − g_s)` — has one
|
|
148
|
+
factor of the pair unbounded unless m sits between r and s. On a diagonal block no single m
|
|
149
|
+
does, which is why fla's diagonals are scalar j-loops and why the SAFE_GATE midpoint trick
|
|
150
|
+
carries a bounded-gate contract this op does not satisfy. Cross-sub-chunk blocks ARE safe
|
|
151
|
+
(any boundary between the blocks separates all (r,s) pairs) — fla already exploits that with
|
|
152
|
+
MMAs there. Falsified before: two-sided midpoint/binary-tree diagonal forms — they overflow
|
|
153
|
+
(a production NaN, 2026-08-17) or don't win. `test_kimi_delta_attention_cute_extreme_decay`
|
|
154
|
+
in `src/test/nn/attention/kda_test.py` is the guard for exactly this class.
|
|
155
|
+
|
|
156
|
+
### Why fla's version costs 12.6ms
|
|
157
|
+
|
|
158
|
+
Grid (NK·NC, NT, B·HV) = (16, 128, 256) → **524k CTAs of [BC=16, BK=32] work**:
|
|
159
|
+
|
|
160
|
+
- **Stream multiplicity.** Each of the NK=4 K-slabs re-reads the same [BC,BC] dAqk/dAkk
|
|
161
|
+
tiles (fp32 → 4× the 1.1GB), and each of the NC=4 sub-chunk owners re-reads its j-loop
|
|
162
|
+
partners' q/k/g tiles. db is written as an NK-slab and reduced afterwards.
|
|
163
|
+
- **exp2 volume.** ~1.1e10 exp2 at prod8192 → ~2.5ms of pure SFU even at perfect
|
|
164
|
+
utilization, and utilization is poor at [16,32] granularity. The *inherent* one-sided
|
|
165
|
+
minimum is ~4× smaller (~2.3e9, ~0.5–0.6ms) IF each factor is computed once and shared
|
|
166
|
+
between the dAqk/dAkk products AND between the row-side (dq) and column-side (dk/dg)
|
|
167
|
+
passes. fla shares the first pair but recomputes across passes — they live in different
|
|
168
|
+
CTAs.
|
|
169
|
+
- **Latency.** 16-iteration serial scalar loops of tiny loads with nothing to overlap them.
|
|
170
|
+
|
|
171
|
+
Floors: traffic ~7.5GB at one-read-one-write → ~1.1ms; exp2 ~0.6ms (overlappable). The
|
|
172
|
+
honest target is ~2.5–3ms; fla is 5× above it.
|
|
173
|
+
|
|
174
|
+
### What `kda/_kernels/bwd_intra.py` does instead (6.34ms SIMT; ~5.0ms with the MMA path)
|
|
175
|
+
|
|
176
|
+
One CTA per (chunk, b·hv), 512 threads as (32 d-lanes) × (16 row-lanes) — one warp spans a
|
|
177
|
+
full row. Each thread walks one row per 16-block with mirror pairing (even blocks r0+rlane,
|
|
178
|
+
odd blocks r0+15−rlane) so diagonal work is uniform per thread, and owns 4 **strided**
|
|
179
|
+
columns d = lane + 32c (consecutive-column ownership was 4-way bank-conflicted on every
|
|
180
|
+
scalar load — 27ms). q/k/g2/dq/dk/dg and the [64,64] dA tiles are read once, dq2/dk2/dg2
|
|
181
|
+
written once, db completed in-CTA: the 4× multiplicities are gone.
|
|
182
|
+
|
|
183
|
+
Cross-block pairs factor through the **s-block end** boundary e(s) = 16(j+1):
|
|
184
|
+
`exp2(g_r − g_s) = exp2(g_r − g_e)·exp2(g_e − g_s)`, both exponents ≤ 0 at any gate
|
|
185
|
+
magnitude (r ≥ e > s on a decreasing g) — the same safety class as fla's own `kg` operand.
|
|
186
|
+
Diagonal pairs (same 16-block) keep exactly one one-sided exp2 per (r,s,d): nothing inside a
|
|
187
|
+
diagonal block is ever factorized. All three prescale arrays (kb, qb, kbb) are built in one
|
|
188
|
+
phase up front, so the two sweeps fuse per block with no inter-sweep barrier.
|
|
189
|
+
|
|
190
|
+
Outputs land ~1e-7..2e-6 abs of a fp64 reference, where fla's tf32 `tl.dot`s land 3e-4..1.3e-3.
|
|
191
|
+
|
|
192
|
+
The shipped kernel additionally runs the **cross-sub-chunk** blocks on tcgen05 MMAs rather
|
|
193
|
+
than SIMT (the diagonals stay scalar j-loops — see the numerics law above, which is not
|
|
194
|
+
negotiable), and folds two things into its epilogue that fla spends separate work on: the
|
|
195
|
+
chunk-local reverse cumsum of dg (deleting stage 7 entirely) and the bf16 cast of dq/dk,
|
|
196
|
+
which makes the wrapper's casts no-ops. The dg fold is unconditional; the bf16 emit is
|
|
197
|
+
gated on `HV == H`, because the GVA reduction that follows must sum in fp32.
|
|
198
|
+
|
|
199
|
+
Constraints: K=128, BT=64, fixed-length, no `safe_gate`, and a grid of at least 1024 CTAs
|
|
200
|
+
(smaller grids underfill a 148-SM box and the per-call marshaling isn't amortized — T512
|
|
201
|
+
regressed to 0.90× without the gate). Off that box the wrapper falls back to
|
|
202
|
+
`bwd_intra_triton.py` (the same math restructured in Triton, 9.26ms, K ≤ 128), which falls
|
|
203
|
+
back to fla's kernel for varlen/`safe_gate`. **That floor is per stage, not per call**: a
|
|
204
|
+
shape can clear `is_supported` and still run this one stage on Triton, which is why the
|
|
205
|
+
test arms in `src/test/nn/attention/kda_test.py` are sized to clear it explicitly.
|
|
206
|
+
|
|
207
|
+
### Register-pressure notes for whoever edits the CuTe kernel
|
|
208
|
+
|
|
209
|
+
ptxas left alone targets 64 registers (dynamic smem hides the 1-CTA/SM cap from it) and
|
|
210
|
+
spills 1–2KB/thread. `min_blocks_per_mp=1` plus `--maxrregcount=128` (frozen as `_MAXREG`)
|
|
211
|
+
and *un*-unrolling the inner 16-iteration loops (`cutlass.range(unroll=4..8)`; full unroll
|
|
212
|
+
lets ptxas hoist whole load batches) gets `LOCAL_SIZE_BYTES=0`. The incoming-grad gmem reads
|
|
213
|
+
cost 3.25ms read-at-use; prefetching them a compute-section ahead is worth ~3ms. 1024
|
|
214
|
+
threads was falsified twice (0.45×, 64-reg spills), as was holding P1/P2 in smem (27.5ms).
|
|
215
|
+
|
|
216
|
+
## Standing headroom
|
|
217
|
+
|
|
218
|
+
The B1/B2 fusions listed here originally are now stages 2, 4 and 5. What is left, in the
|
|
219
|
+
order the ladder is working on it:
|
|
220
|
+
|
|
221
|
+
- **recompute_w_u** (stage 1, 2 × 1.17ms) and the forward cumsum (0.32ms) — both fla's,
|
|
222
|
+
both counter-blocked on the profiling box.
|
|
223
|
+
- **wy_dqkg** at 4.99ms is now the largest single stage.
|
|
224
|
+
- Remaining intra headroom is ~4ms (latency-bound loads/prescale/epilogue at 16 warps/SM).
|
|
225
|
+
|
|
226
|
+
None of these are edited here. This directory is a vendored snapshot; kernel work happens
|
|
227
|
+
in the `kernel-fun-2` ladder, where a bench row can say whether an idea was worth keeping —
|
|
228
|
+
three of idea 004's did not survive that test.
|
|
229
|
+
- `recompute_w_u`/`dAv`/cumsum (2.4ms combined) are near their traffic floor — leave them.
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Landing this in OLMo-core
|
|
2
|
+
|
|
3
|
+
The last time kernels from this repo landed in the training repo, the ladder reported −3%
|
|
4
|
+
tokens/sec and it took two days to establish that there was no steady-state regression at
|
|
5
|
+
all (the ladder repo's `kernels/gnorm/LADDER-REGRESSION.md`). Almost none of that time went
|
|
6
|
+
into the kernels.
|
|
7
|
+
This checklist exists so that does not happen twice.
|
|
8
|
+
|
|
9
|
+
## 1. The call site
|
|
10
|
+
|
|
11
|
+
**The unit of sharing is this package** (state as of 2026-09-08; the repo split below is
|
|
12
|
+
2026-09-09). OLMo-core gets it one of two ways, on two branches that are otherwise
|
|
13
|
+
identical:
|
|
14
|
+
|
|
15
|
+
- `caleb/cute-kda-vendored` — the package tree copied to `src/olmo_core/kernel_fun/`,
|
|
16
|
+
byte-identical to this repo's `src/kernel_fun/` except the top-level `__init__.py`, which
|
|
17
|
+
carries `VENDORED_FROM`. No private dependency, so no tokens. scaling-ladders'
|
|
18
|
+
`caleb/cute-kda` points here.
|
|
19
|
+
- `caleb/cute-kda` (PR #837) — installs the package as a `kernel-fun` extra pinned to a sha.
|
|
20
|
+
Needs a GitHub token everywhere the lock resolves.
|
|
21
|
+
|
|
22
|
+
> **The shas in both places changed meaning on 2026-09-09**, when this package moved out of
|
|
23
|
+
> the ladder repo — which held the name `allenai/kernel-fun` until that day and is now
|
|
24
|
+
> `allenai/kernel-fun-dev`, and where this tree was `packages/kernel-fun/` — into the repo
|
|
25
|
+
> that took the name, **`allenai/kernel-fun`** (this one). `VENDORED_FROM` and the pinned
|
|
26
|
+
> extra now name a commit HERE, and the extra's URL drops
|
|
27
|
+
> `#subdirectory=packages/kernel-fun`. The last pre-split vendoring was ladder sha
|
|
28
|
+
> `7a6983b`, which exists only in `kernel-fun-dev`; the ladder sha a family's *kernels*
|
|
29
|
+
> were cut from lives on in `src/kernel_fun/<family>/_provenance.py::SOURCE_COMMIT`
|
|
30
|
+
> (`6fc6309` today) and is a different number. When re-vendoring, bump `VENDORED_FROM` to a
|
|
31
|
+
> full kernel-fun sha and say so in the commit message — a bare sha is now ambiguous.
|
|
32
|
+
|
|
33
|
+
The extra's URL, for reference:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
kernel-fun @ git+https://github.com/allenai/kernel-fun.git@<sha>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Three rules for that extra, from reading OLMo-core's build (2026-09-09):
|
|
40
|
+
|
|
41
|
+
- **Bare `kernel-fun`, no `[cu13]`/`[cu12]`.** OLMo-core's images already carry the CuTe
|
|
42
|
+
DSL through the `fa4` extra (flash-attn-4 -> nvidia-cutlass-dsl), and the CUDA major is
|
|
43
|
+
decided once, in the Dockerfile (`CUDA_VERSION_PATH`, and `FLASH_ATTN_4_EXTRAS='[cu13]'`
|
|
44
|
+
on the B300 image). One extra then works on both the cu128 and cu130 images; the
|
|
45
|
+
package's own CUDA extras are for environments that have no DSL at all. Nothing in the
|
|
46
|
+
bare install replaces the image's torch/triton/fla — the floors are below both images.
|
|
47
|
+
- **The git URL cannot sit in the extra** if `ai2-olmo-core` is to keep publishing: PyPI
|
|
48
|
+
rejects metadata with a direct-URL requirement (the pyproject says so at `dion`). Put
|
|
49
|
+
the source in `[tool.uv.sources]` as `dion` does, or publish kernel-fun to PyPI and pin
|
|
50
|
+
a version.
|
|
51
|
+
- **fla must be 0.5.2 on the branch that merges.** The package requires
|
|
52
|
+
`fla-core>=0.5.2`; a branch still on flash-linear-attention 0.4.1 (the B300 image
|
|
53
|
+
branch, today) would end up with a mixed `fla/` tree.
|
|
54
|
+
|
|
55
|
+
If a cu130 image is ever built with FA4's bare requirement, the package logs a
|
|
56
|
+
`CUDA 12 one (nvidia-cutlass-dsl-libs-cu12 without -cu13)` warning at first use — see
|
|
57
|
+
`support.cute_cuda_mismatch`. It warns rather than falls back, because the wrong-build DSL
|
|
58
|
+
may still compile; treat the line as a build-arg bug.
|
|
59
|
+
|
|
60
|
+
Both route `flash_linear_attn_api.py::dispatch_chunk_kda` and `dispatch_causal_conv1d` to
|
|
61
|
+
the package when `KimiDeltaAttentionConfig.use_cute_kernel=True`; that one flag drives BOTH
|
|
62
|
+
families (it is plumbed to the three Q/K/V `CausalConv1d`s). There is no other copy of
|
|
63
|
+
these kernels in OLMo-core — the old frozen `nn/attention/kda_cute/` tree went three ideas
|
|
64
|
+
stale before it was deleted, which is why the package exists. Do not reintroduce one.
|
|
65
|
+
|
|
66
|
+
**To ship a release:** commit/push **kernel-fun** (this repo — tag it if the version
|
|
67
|
+
moved); on OLMo-core's vendored branch re-copy the changed files, bump `VENDORED_FROM` to
|
|
68
|
+
the full kernel-fun sha, bump the scaling-ladders submodule; on the package branch bump
|
|
69
|
+
the sha in the extra and re-lock. Then §2–§3 below. Nothing in this flow touches the ladder
|
|
70
|
+
repo — a ladder commit only ever enters through `tools/vendor.py` (README, "Two repos").
|
|
71
|
+
|
|
72
|
+
The kda call site, for reference:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
# flash_linear_attn_api.py
|
|
76
|
+
if use_cute_kernel:
|
|
77
|
+
from kernel_fun.kda import chunk_kda as cute_chunk_kda
|
|
78
|
+
|
|
79
|
+
return cute_chunk_kda(
|
|
80
|
+
q=q, k=k, v=v, g=g, beta=beta, A_log=A_log, dt_bias=dt_bias,
|
|
81
|
+
scale=scale, initial_state=initial_state,
|
|
82
|
+
output_final_state=output_final_state,
|
|
83
|
+
use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel,
|
|
84
|
+
use_gate_in_kernel=use_gate_in_kernel,
|
|
85
|
+
cu_seqlens=cu_seqlens,
|
|
86
|
+
)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The package decides internally and falls back to fla itself, so a packed-document batch or
|
|
90
|
+
a non-Blackwell node needs no branch in the caller (the old `cute_kda_supported` is gone).
|
|
91
|
+
|
|
92
|
+
**The conv call site**, same file, is the second family. `CausalConv1d.forward`
|
|
93
|
+
(`src/olmo_core/nn/convolution.py`) reaches the package through `dispatch_causal_conv1d`:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
# flash_linear_attn_api.py
|
|
97
|
+
def dispatch_causal_conv1d(x, weight, bias, activation, backend="triton", cu_seqlens=None):
|
|
98
|
+
from kernel_fun.cconv import causal_conv1d
|
|
99
|
+
|
|
100
|
+
return causal_conv1d(
|
|
101
|
+
x=x, weight=weight, bias=bias, activation=activation, backend=backend,
|
|
102
|
+
cu_seqlens=cu_seqlens,
|
|
103
|
+
)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
No flag: the package forwards anything off its box (bias, cu_seqlens, `activation=None`,
|
|
107
|
+
`backend="cuda"`) to fla itself, and `KERNEL_FUN_CCONV_DISABLE=1` is the whole-family
|
|
108
|
+
switch. Like the kda entry point, this one IS `torch.compiler.disable`d — as of
|
|
109
|
+
2026-09-04. It was not, on the theory that fla's conv is plain Python around an
|
|
110
|
+
autograd.Function and so is ours, so Dynamo would treat the two alike. The 30M mainline
|
|
111
|
+
ladder falsified that: Dynamo speculated OUR Function's backward (two tensor arguments is
|
|
112
|
+
the shape it agrees to trace; fla's eleven-argument one is not) and asserted inside
|
|
113
|
+
`cconv_bwd` on a symbolic `dy.stride(0)`. So this entry point costs ONE graph break that
|
|
114
|
+
fla does not — expect §3's break diff to show it, at three convs per KDA layer, and judge
|
|
115
|
+
it against the ~36 ms/step the kernels return rather than against zero.
|
|
116
|
+
|
|
117
|
+
Three things the caller should still do:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
kernel_fun.kda.warmup(K=head_dim, V=head_v_dim, HV=n_v_heads) # before step 1
|
|
121
|
+
kernel_fun.cconv.warmup(B=microbatch, T=seq_len, D=(key_dim, value_dim)) # ditto
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Do NOT call `versions()` from the model's `forward` to log it. Both entry points log it
|
|
125
|
+
themselves, once, the first time either family engages — and they are
|
|
126
|
+
`torch.compiler.disable`d, so the line costs no graph break. The caller-side version of
|
|
127
|
+
this cost two breaks in the KDA layer's compiled block and raised
|
|
128
|
+
`TypeError: unhashable type: 'dict'` through olmo-core's `lru_cache`-based `log_once`
|
|
129
|
+
(2026-09-04). `versions()` stays public for a startup log outside any compiled region.
|
|
130
|
+
|
|
131
|
+
The cconv autotune key is `(D, W, B, T)`, so its warmup must see the REAL microbatch and
|
|
132
|
+
sequence length and every channel count the layer convolves (q/k at `n_heads*head_dim`,
|
|
133
|
+
v at `expand_v` times that). A shape it has not seen re-autotunes at first use: a few
|
|
134
|
+
seconds, once, per shape — fine for training, but it is what a step-1 "regression" looks
|
|
135
|
+
like.
|
|
136
|
+
|
|
137
|
+
The old wrapper computed the gate activation in eager torch. Do not reintroduce that: pass
|
|
138
|
+
`use_gate_in_kernel=True` and let the package fuse it into the cumsum. At prod8192 the
|
|
139
|
+
eager form costs ~2 ms and ~2 GiB of saved activations per layer, which is a third of what
|
|
140
|
+
these kernels win.
|
|
141
|
+
|
|
142
|
+
## 2. Before the first real run
|
|
143
|
+
|
|
144
|
+
- [ ] `pytest -q` from this repo on a B300, green (`-m slow` adds the prod-shaped rows;
|
|
145
|
+
the cconv tests also run on an H100).
|
|
146
|
+
- [ ] `PYTHONPATH=src python tools/prodtime.py` on that B300 — the package's own numbers at
|
|
147
|
+
the production shapes, next to fla's, from the installed copy. (The ladder's
|
|
148
|
+
`python -m loop bench kda --impl 000,005 --case prod8192_gate` and
|
|
149
|
+
`... bench cconv --impl 000,001 --case prod8192_qk` are the recorded originals.)
|
|
150
|
+
- [ ] Confirm fla's Triton autotune cache directory is persistent and pre-warmed in the
|
|
151
|
+
training image. Otherwise every rank re-autotunes at step 1 and the startup cost you
|
|
152
|
+
just moved with `warmup()` comes back through another door.
|
|
153
|
+
- [ ] Grep the startup log for the `kernel-fun kda: engaged` line (and `cconv: engaged`).
|
|
154
|
+
If either says `falling back` instead, the reason is on the same line — read it
|
|
155
|
+
before looking at anything else. The `kernel-fun {...}` versions line appears just
|
|
156
|
+
above the first of them; if it is missing, neither family ever engaged.
|
|
157
|
+
|
|
158
|
+
## 3. The A/B that settles it
|
|
159
|
+
|
|
160
|
+
Three arms, same GPU, same node, first step discarded, enough metric windows to be real
|
|
161
|
+
(the 1.4b gnorm run was killed after 7 windows, and half the "−3%" came from reading the
|
|
162
|
+
average anyway):
|
|
163
|
+
|
|
164
|
+
1. fla (`use_cute_kernel=False`, conv routed to fla)
|
|
165
|
+
2. kernel-fun, both families
|
|
166
|
+
3. kernel-fun with `KERNEL_FUN_DISABLE=1`
|
|
167
|
+
|
|
168
|
+
Arm 3 is the control that separates *the wrapper* from *the kernels*: it runs the same
|
|
169
|
+
call path and the same fallback logic but fla's kernels underneath. If arm 3 is not within
|
|
170
|
+
noise of arm 1, the problem is integration, not numerics, and no amount of kernel work will
|
|
171
|
+
fix it. If arm 2 disappoints, `KERNEL_FUN_KDA_DISABLE=1` / `KERNEL_FUN_CCONV_DISABLE=1`
|
|
172
|
+
split it by family without a config change.
|
|
173
|
+
|
|
174
|
+
A fourth arm exists only for a model whose KDA grid is below the chain-level floor —
|
|
175
|
+
`B * HV * (V//64) < 256`, which the small OLMoE3 candidate's B4/HV8/V256 is, at 128.
|
|
176
|
+
`KERNEL_FUN_KDA_MIN_CTAS=128` moves that floor so those calls dispatch here instead of
|
|
177
|
+
reporting `grid too small` and going to fla. It is a measured opt-in for one shape on one
|
|
178
|
+
box, not a better default: the b1 scan and dhu backwards keep their own 256-CTA floors and
|
|
179
|
+
stay on fla at that grid, so the arm is worth running only against arm 1, on the model you
|
|
180
|
+
intend to train, and only after the log stops saying `grid too small`. Set it for
|
|
181
|
+
`warmup()` too — it warms one grid per floor in play, and the stages this arm adds are
|
|
182
|
+
autotuned in step 1 otherwise.
|
|
183
|
+
|
|
184
|
+
Then:
|
|
185
|
+
|
|
186
|
+
- [ ] **`TORCH_LOGS=recompiles,graph_breaks`, diff arm 1 against arm 2.** Free — the Beaker
|
|
187
|
+
launcher sets it already. At the kda call the counts must be EQUAL: fla's own
|
|
188
|
+
`chunk_kda` is `@torch.compiler.disable`d too, so that one MOVES a break rather than
|
|
189
|
+
adding one. At the conv, expect arm 2 to show exactly ONE more break per conv call
|
|
190
|
+
(three per KDA layer) — see §1; more than that, or a recompile loop, is a bug.
|
|
191
|
+
Do this first; it is the cheapest way to catch the failure mode that cost two days.
|
|
192
|
+
- [ ] **torch-profile ~20 steps, compare CPU gap time between GPU kernels** across arms.
|
|
193
|
+
That is where a host-side regression shows up and no benchmark in this repo can see
|
|
194
|
+
it. While you are in the profile, check that no `softplus` / `mul` / `to_copy` at
|
|
195
|
+
`[B,T,HV,K]` survives — if they do, the gate is not fused and you are paying twice.
|
|
196
|
+
And check the conv: `cconv_fwd_strip` / `cconv_bwd_strip` should be the only conv
|
|
197
|
+
kernels, and `causal_conv1d_fwd_kernel` should appear NOWHERE in the backward (fla
|
|
198
|
+
re-ran the forward there to rematerialize the pre-activation; ours does not).
|
|
199
|
+
- [ ] **`torch.cuda.memory_summary()` after ~5 steps**, both arms. Active memory should be
|
|
200
|
+
within a few hundred MiB. The call caches used to pin ~24 GiB/rank at these shapes;
|
|
201
|
+
that is fixed and tested, and this is the check that keeps it fixed.
|
|
202
|
+
- [ ] Report `throughput/device/TPS (actual avg)` with step 1 discarded.
|
|
203
|
+
|
|
204
|
+
## 4. What to expect
|
|
205
|
+
|
|
206
|
+
**kda.** At the production call the op measures **1.540x** (23.74 ms vs fla's 36.56 at
|
|
207
|
+
B=16, T=8192, H=HV=16, K=128, V=256; 2026-09-02). The 1.288x port was worth 1.037x
|
|
208
|
+
end-to-end on the 1.4b mainline ladder, which puts KDA at ~16% of step time; the same
|
|
209
|
+
arithmetic gives **~1.06x** here, i.e. about +6% tokens/sec. Relative to the branch's
|
|
210
|
+
current 1.545x chain the new wy stage is +0.03x on the op — real, small, and the same
|
|
211
|
+
arithmetic says ~+0.3 points of TPS. Do not expect to see it in a noisy A/B.
|
|
212
|
+
|
|
213
|
+
**cconv.** The 810m/B300 trace (the ladder's `profiles/810m-b300-20260901.md`) put the three conv calls
|
|
214
|
+
at ~50 ms of a ~520 ms step: bwd 35.0 + fwd 7.7 + the backward's own forward re-run 6.9.
|
|
215
|
+
At the measured 4.65x / 1.62x that is ~13 ms — **~36 ms/step back, about 7% of the step,
|
|
216
|
+
~1.075x tokens/sec at 810m**. The conv channel count is set by head count, which the
|
|
217
|
+
ladder pins from 810m up, so the fraction survives scale. This is the larger of the two
|
|
218
|
+
predictions and the easier one to see.
|
|
219
|
+
|
|
220
|
+
Both predictions assume the ops' step-time fractions are unchanged, and they are the
|
|
221
|
+
numbers to check against the A/B rather than to quote from. Judge the port on its fraction
|
|
222
|
+
of real step time — that is the one durable lesson from last time.
|