Skip to content

Commit f49a7e4

Browse files
committed
feat(v4): GDN/KDA Path B forward — initial_state + K!=V + custom scale
Lift the three remaining Path-B forward restrictions so production streaming decode no longer silently falls back to Path A: - GDN: split per-thread state into K x V (head_k_dim != head_v_dim). Per-thread register array is length K, grid x-axis is V. State shape becomes [B, H, K, V] (was [B, H, Dh, Dh]). - GDN + KDA: accept optional initial_state of shape [B, H, K, V] (KDA: [B, HV, K, V]). When provided, compile-time pick a kernel variant that pre-loads state[i] from the buffer instead of zeroing. - GDN + KDA: honor a non-default `scale` argument instead of forcing fallback to Path A. Parity tests added for each new capability (incl. streaming chunks = single-shot run). Removed two pytest.skipif-on-tilelang-available gates in path_c fallback tests by monkeypatching the runtime status so the fallback path is verified on every host (was: 2 skipped). Suite: 340 passed / 0 skipped (was 338 / 2).
1 parent 430983d commit f49a7e4

5 files changed

Lines changed: 310 additions & 97 deletions

File tree

cppmega_v4/_tilelang/kda_path_b.py

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
2121
Group expansion (HV / H): each ``hv`` maps to ``h = hv // G`` for indexing
2222
into q and k. The kernel performs that mapping inline.
23+
24+
Supports:
25+
- ``initial_state`` of shape [B, HV, K, V] (streaming decode).
26+
- Custom ``scale`` (defaults to 1/sqrt(K)).
2327
"""
2428

2529
from __future__ import annotations
@@ -35,6 +39,9 @@ def _kda_forward_kernel(
3539
v: mx.array,
3640
g: mx.array,
3741
beta: mx.array,
42+
*,
43+
scale: float | None = None,
44+
h0: mx.array | None = None,
3845
) -> tuple[mx.array, mx.array]:
3946
"""Metal forward for KDA recurrence.
4047
@@ -43,6 +50,8 @@ def _kda_forward_kernel(
4350
v: [B, T, HV, V]
4451
g: [B, T, HV, K] per-K vectorized log-gate
4552
beta: [B, T, HV]
53+
scale: optional float — defaults to 1/sqrt(K)
54+
h0: optional [B, HV, K, V] initial state
4655
4756
Returns:
4857
o: [B, T, HV, V]
@@ -63,7 +72,13 @@ def _kda_forward_kernel(
6372
raise ValueError(f"HV ({hv}) must be divisible by H ({h})")
6473
group = hv // h
6574

66-
q = q.astype(mx.float32) * (kdim ** -0.5)
75+
if h0 is not None and tuple(h0.shape) != (b, hv, kdim, vdim):
76+
raise ValueError(
77+
f"initial_state must be [B={b}, HV={hv}, K={kdim}, V={vdim}]; got {h0.shape}"
78+
)
79+
80+
sc = float(scale) if scale is not None else (kdim ** -0.5)
81+
q = q.astype(mx.float32) * sc
6782
k = k.astype(mx.float32)
6883
v = v.astype(mx.float32)
6984
g = g.astype(mx.float32)
@@ -75,6 +90,24 @@ def _kda_forward_kernel(
7590
g_flat = g.reshape(-1)
7691
beta_flat = beta.reshape(-1)
7792

93+
has_h0 = h0 is not None
94+
if has_h0:
95+
h0_flat = h0.astype(mx.float32).reshape(-1)
96+
97+
init_state_block = (
98+
f"""
99+
// Init from h0[bb, hv_idx, i, vj]
100+
int h0_base = (bb * {hv} + hv_idx) * {kdim * vdim} + vj;
101+
for (int i = 0; i < {kdim}; i++) {{
102+
state[i] = h0[h0_base + i * {vdim}];
103+
}}
104+
"""
105+
if has_h0
106+
else f"""
107+
for (int i = 0; i < {kdim}; i++) state[i] = 0.0f;
108+
"""
109+
)
110+
78111
source = f"""
79112
uint vj = thread_position_in_grid.x;
80113
uint bhv = thread_position_in_grid.y;
@@ -87,7 +120,7 @@ def _kda_forward_kernel(
87120
88121
// Per-thread state column: S[bb, hv_idx, :, vj] size K
89122
float state[{kdim}];
90-
for (int i = 0; i < {kdim}; i++) state[i] = 0.0f;
123+
{init_state_block}
91124
92125
for (int ti = 0; ti < {t}; ti++) {{
93126
// g_base: g[bb, ti, hv_idx, 0]
@@ -134,10 +167,12 @@ def _kda_forward_kernel(
134167
}}
135168
"""
136169

137-
name = f"v4_kda_fwd_{b}_{t}_{h}_{hv}_{kdim}_{vdim}"
170+
h0_tag = "h0" if has_h0 else "noh0"
171+
name = f"v4_kda_fwd_{b}_{t}_{h}_{hv}_{kdim}_{vdim}_{h0_tag}"
172+
input_names = ["q", "k", "v", "g", "beta"] + (["h0"] if has_h0 else [])
138173
kernel = get_or_build_kernel(
139174
name=name,
140-
input_names=["q", "k", "v", "g", "beta"],
175+
input_names=input_names,
141176
output_names=["output", "state_final"],
142177
source=source,
143178
)
@@ -146,8 +181,12 @@ def _kda_forward_kernel(
146181
tg_x = min(vdim, 64)
147182
threadgroup = (tg_x, 1, 1)
148183

184+
inputs = [q_flat, k_flat, v_flat, g_flat, beta_flat]
185+
if has_h0:
186+
inputs.append(h0_flat)
187+
149188
out, sf = kernel(
150-
inputs=[q_flat, k_flat, v_flat, g_flat, beta_flat],
189+
inputs=inputs,
151190
output_shapes=[
152191
(b * t * hv * vdim,),
153192
(b * hv * kdim * vdim,),
@@ -172,22 +211,17 @@ def kda_forward_path_b(
172211
):
173212
"""KDA Path B forward, signature matching ``naive_recurrent_kda``.
174213
175-
Falls back to Path A for unsupported configs (initial_state, custom
176-
scale, or HV not divisible by H) so the dispatch never produces wrong
177-
numerics silently.
214+
Falls back to Path A only when HV is not divisible by H (architectural
215+
mismatch). Supports ``initial_state`` and custom ``scale``.
178216
"""
179-
if (
180-
initial_state is not None
181-
or scale is not None
182-
or v.shape[2] % q.shape[2] != 0
183-
):
217+
if v.shape[2] % q.shape[2] != 0:
184218
from cppmega_v4.nn._external.fla_naive_kda import naive_recurrent_kda
185219
return naive_recurrent_kda(
186220
q, k, v, g, beta,
187221
scale=scale, initial_state=initial_state,
188222
output_final_state=output_final_state,
189223
)
190-
o, sf = _kda_forward_kernel(q, k, v, g, beta)
224+
o, sf = _kda_forward_kernel(q, k, v, g, beta, scale=scale, h0=initial_state)
191225
return o, (sf if output_final_state else None)
192226

193227

0 commit comments

Comments
 (0)