@@ -294,6 +294,48 @@ def test_channelwise_gated_delta_rule_multichunk_matches_reference(self):
294294 torch .allclose (actual_state , expected_state , atol = 1e-3 , rtol = 1e-3 )
295295 )
296296
297+ def test_channelwise_gated_delta_rule_prefill_tiny_decay_is_finite (self ):
298+ # Regression test for the chunked-prefill overflow NaN in op_sdpa.cpp.
299+ # The prefill kernel forms eg_inv = exp(-cumsum(log decay)) per
300+ # CHUNK_SIZE (32) tokens; for tiny positive decay the within-chunk
301+ # cumulative -log(decay) exceeds the float expf overflow limit (~88.7),
302+ # so eg_inv overflows to +inf and eg * eg_inv becomes 0 * inf = NaN,
303+ # poisoning the whole head's output. The
304+ # channelwise_gated_delta_rule_chunked_is_safe guard must route such
305+ # inputs to the exact recurrence, which stays finite (and correct) for
306+ # tiny decay; without it the isfinite assertions below fail.
307+ torch .manual_seed (0 )
308+
309+ # seq_len > CHUNK_SIZE (32) so the prefill route runs the internal chunk
310+ # loop; a single chunk of tiny decay overflows the unguarded kernel.
311+ query , key , value , decay , beta , initial_state = self ._make_inputs (seq_len = 64 )
312+ # decay.min ~6.5e-12 was observed in a real GDN2 checkpoint;
313+ # -log(1e-12) ~27.6 per token, so a few tokens blow past the ~80 safe
314+ # bound and exp(-gc) would overflow to +inf -> NaN.
315+ decay = torch .full_like (decay , 1e-12 )
316+
317+ expected_output , expected_state = self ._reference_channelwise_gated_delta_rule (
318+ query , key , value , decay , beta , initial_state
319+ )
320+ actual_output , actual_state = torch .ops .llama .channelwise_gated_delta_rule (
321+ query , key , value , decay , beta , initial_state
322+ )
323+
324+ self .assertTrue (
325+ torch .isfinite (actual_output ).all (),
326+ "prefill output has NaN/inf for tiny decay" ,
327+ )
328+ self .assertTrue (
329+ torch .isfinite (actual_state ).all (),
330+ "prefill final_state has NaN/inf for tiny decay" ,
331+ )
332+ self .assertTrue (
333+ torch .allclose (actual_output , expected_output , atol = 1e-3 , rtol = 1e-3 )
334+ )
335+ self .assertTrue (
336+ torch .allclose (actual_state , expected_state , atol = 1e-3 , rtol = 1e-3 )
337+ )
338+
297339 def test_channelwise_gated_delta_rule_exports (self ):
298340 class Module (torch .nn .Module ):
299341 def forward (self , query , key , value , decay , beta , initial_state ):
0 commit comments