Skip to content

Commit 0fd2072

Browse files
committed
test(v4-path-c): lock in GDN+KDA Path C real-kernel parity (unblocked by ffi-NULL fix)
The ffi-NULL fix (tilelang 168877b0) unblocked GDN Path C with NO prim completion needed -- the scaffold's recurrence math was already correct (structural twin of the working KDA Path C kernel). Both GDN + KDA Path C now DISPATCH the real @T.prim_func Metal kernel and MATCH the FLA-naive reference (rel_err 1.9e-7..4.8e-7, allclose True across production shapes, L2-normalized q/k). Tests (test-only, no production code changed): - +test_kda_path_c_real_kernel_parity_with_path_a (the KDA analog the suite lacked). - Fixed stale test_gdn_dispatch_each_path_runs[path_e]: it fed a sign-unconstrained gate (g>0 possible), physically invalid for GDN (decay=exp(g)<=1); Path E correctly RAISED per RULE #1. Changed to a valid non-positive gate (pre-existing failure on origin, not introduced here). The 2 masking tests (test_path_c_forced_returns_valid_output GDN+KDA) now pass via REAL Path C (spy-confirmed, not Path A). Full GDN+KDA suite 109 passed/2 skip. RULE #1 verified: runtime crash in Path C raises "Refusing to silently fall back to Path A" even with allow_fallback=True.
1 parent ce90574 commit 0fd2072

2 files changed

Lines changed: 41 additions & 3 deletions

File tree

tests/v4/test_kda_paths.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from cppmega_v4._tilelang.kda_path_b import kda_forward_path_b
1010
from cppmega_v4._tilelang.kda_path_c import (
1111
_path_c_runtime_status as kda_path_c_runtime,
12+
_tilelang_importable as kda_tilelang_importable,
1213
)
1314
from cppmega_v4._tilelang.kda_path_d import (
1415
_fla_kda_chunk_importable,
@@ -196,6 +197,37 @@ def test_kda_path_c_fallback_matches_path_a():
196197
np.testing.assert_array_equal(np.array(o_disp), np.array(o_ref))
197198

198199

200+
@pytest.mark.skipif(
201+
not kda_tilelang_importable()[0],
202+
reason="tilelang not importable in this env",
203+
)
204+
def test_kda_path_c_real_kernel_parity_with_path_a():
205+
"""Real @T.prim_func KDA Path C parity vs the FLA-naive reference.
206+
207+
Uses L2-normalised q/k (the production conditioning for KDA — q and k are
208+
always normalised in the real architecture) so the delta-rule recurrence
209+
stays bounded and fp32 reassociation between the Metal kernel and the MLX
210+
reference is at the 1e-6 level rather than tripping on an exploded state.
211+
"""
212+
from cppmega_v4._tilelang.kda_path_c import _kda_fwd_path_c_call
213+
214+
B, T, H, HV, K, V = 1, 16, 2, 4, 64, 64
215+
rng = np.random.default_rng(73)
216+
217+
def _l2n(x):
218+
return x / (mx.linalg.norm(x, axis=-1, keepdims=True) + 1e-6)
219+
220+
q = _l2n(mx.array(rng.standard_normal((B, T, H, K)).astype(np.float32)))
221+
k = _l2n(mx.array(rng.standard_normal((B, T, H, K)).astype(np.float32)))
222+
v = mx.array(rng.standard_normal((B, T, HV, V)).astype(np.float32))
223+
g = mx.array(-rng.uniform(0.01, 0.2, (B, T, HV, K)).astype(np.float32))
224+
beta = mx.array(rng.uniform(0.1, 0.9, (B, T, HV)).astype(np.float32))
225+
o_c, _ = _kda_fwd_path_c_call(q, k, v, g, beta)
226+
o_a, _ = naive_recurrent_kda(q, k, v, g, beta)
227+
mx.eval(o_c, o_a)
228+
np.testing.assert_allclose(np.array(o_c), np.array(o_a), atol=1e-4, rtol=1e-3)
229+
230+
199231
# ----- Path D (Triton frontend) -----
200232

201233

tests/v4/test_path_dispatch.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,18 @@ def test_gdn_dispatch_returns_same_as_path_a():
136136

137137
@pytest.mark.parametrize("path", ["path_a", "path_b", "path_c", "path_d", "path_e"])
138138
def test_gdn_dispatch_each_path_runs(path):
139-
"""Each forced path must return finite, correctly-shaped output."""
139+
"""Each forced path must return finite, correctly-shaped output.
140+
141+
The GDN gate is parameterised as decay = exp(g) <= 1, so g must be <= 0
142+
(a g>0 amplifying gate has no real pre-image under softplus_inverse(-g),
143+
and Path E correctly RAISES per RULE #1 rather than silently clamping).
144+
Use a physically-valid non-positive gate so every forced path runs.
145+
"""
140146
q = mx.random.normal((1, 4, 2, 4))
141147
k = mx.random.normal((1, 4, 2, 4))
142148
v = mx.random.normal((1, 4, 2, 4))
143-
beta = mx.random.normal((1, 4, 2))
144-
g = mx.random.normal((1, 4, 2)) * 0.1
149+
beta = mx.sigmoid(mx.random.normal((1, 4, 2)))
150+
g = -mx.abs(mx.random.normal((1, 4, 2)) * 0.1)
145151
o, _ = gated_delta_recurrent_dispatch(q, k, v, beta, g, path=path)
146152
assert o.shape == (1, 4, 2, 4)
147153
assert not bool(mx.any(mx.isnan(o)).item())

0 commit comments

Comments
 (0)