Skip to content

Commit 94c6c88

Browse files
authored
Add OSS regression test for GDN channelwise_gated_delta_rule chunked-prefill NaN (pytorch#21439)
Differential Revision: D113943464 Pull Request resolved: pytorch#21439
1 parent 8001512 commit 94c6c88

1 file changed

Lines changed: 42 additions & 0 deletions

File tree

‎extension/llm/custom_ops/test_gated_delta.py‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)