Module neuralops.tests.test_triton_gdn_scan

Comprehensive test suite for neuralops.core.triton_kernels.gdn_scan.

Coverage: - Numerical correctness vs PyTorch reference - Multiple dtypes: fp32, fp16, bf16 - Multiple shapes: batch, seq, head, dim variations - Edge cases: head_dim bounds - Fallback path & error handling - Determinism - Gradient correctness

Functions

def device()
Expand source code
@pytest.fixture
def device():
    if torch.cuda.is_available():
        return torch.device("cuda")
    return torch.device("cpu")
def reference_gdn_scan_pytorch(q, k, v, beta, alpha)
Expand source code
def reference_gdn_scan_pytorch(q, k, v, beta, alpha):
    """
    Pure PyTorch sequential reference for Gated DeltaNet scan.
    Recurrence:
        inter_v_t = k_t @ S_{t-1}
        err_t     = v_t - inter_v_t
        norm_sq_t = sum(k_t^2)
        step_t    = alpha_t / max(1 + norm_sq_t, 1)
        S_t       = beta_t * S_{t-1} + step_t * k_t^T @ err_t
        y_t       = q_t @ S_t
    """
    B, H, L, D = q.shape
    q_f = q.float()
    k_f = k.float()
    v_f = v.float()
    beta_f = beta.float()
    alpha_f = alpha.float()

    y = torch.zeros(B, H, L, D, device=q.device, dtype=torch.float32)

    for b in range(B):
        for h in range(H):
            S = torch.zeros(D, D, device=q.device, dtype=torch.float32)
            for t in range(L):
                qt = q_f[b, h, t]          # (D,)
                kt = k_f[b, h, t]          # (D,)
                vt = v_f[b, h, t]          # (D,)
                bt = beta_f[b, h, t]       # scalar
                at = alpha_f[b, h, t]      # scalar

                inter_v = torch.matmul(kt, S)  # (D,)
                err = vt - inter_v
                norm_sq = torch.dot(kt, kt)
                step = at / max(1.0 + norm_sq.item(), 1.0)

                S = bt * S + step * torch.outer(kt, err)
                y[b, h, t] = torch.matmul(qt, S)

    return y.to(q.dtype)

Pure PyTorch sequential reference for Gated DeltaNet scan.

Recurrence -----= inter_v_t = k_t @ S_{t-1} err_t = v_t - inter_v_t norm_sq_t = sum(k_t^2) step_t = alpha_t / max(1 + norm_sq_t, 1) S_t = beta_t * S_{t-1} + step_t * k_t^T @ err_t y_t = q_t @ S_t

def test_gdn_scan_triton_backward(device)
Expand source code
@pytest.mark.skipif(not HAS_TRITON or not torch.cuda.is_available(), reason="Requires Triton and CUDA")
def test_gdn_scan_triton_backward(device):
    """Verify backward gradient computation."""
    B, H, L, D = 1, 2, 16, 32
    q = torch.randn(B, H, L, D, device=device, requires_grad=True)
    k = torch.randn(B, H, L, D, device=device, requires_grad=True)
    v = torch.randn(B, H, L, D, device=device, requires_grad=True)
    beta = torch.sigmoid(torch.randn(B, H, L, device=device, requires_grad=True))
    alpha = torch.sigmoid(torch.randn(B, H, L, device=device, requires_grad=True))

    out = gdn_scan_triton(q, k, v, beta, alpha)
    loss = out.sum()
    loss.backward()

    assert q.grad is not None and torch.isfinite(q.grad).all()
    assert k.grad is not None and torch.isfinite(k.grad).all()
    assert v.grad is not None and torch.isfinite(v.grad).all()
    assert beta.grad is not None and torch.isfinite(beta.grad).all()
    assert alpha.grad is not None and torch.isfinite(alpha.grad).all()

Verify backward gradient computation.

def test_gdn_scan_triton_correctness(device, dtype, D)
Expand source code
@pytest.mark.skipif(not HAS_TRITON or not torch.cuda.is_available(), reason="Requires Triton and CUDA")
@pytest.mark.parametrize("dtype", [torch.float32, torch.float16, torch.bfloat16])
@pytest.mark.parametrize("D", [16, 32, 64])
def test_gdn_scan_triton_correctness(device, dtype, D):
    """Verify Triton GDN matches PyTorch reference on CUDA."""
    B, H, L = 2, 2, 16
    q = torch.randn(B, H, L, D, device=device, dtype=dtype)
    k = torch.randn(B, H, L, D, device=device, dtype=dtype)
    v = torch.randn(B, H, L, D, device=device, dtype=dtype)
    beta = torch.sigmoid(torch.randn(B, H, L, device=device, dtype=dtype))
    alpha = torch.sigmoid(torch.randn(B, H, L, device=device, dtype=dtype))

    out = gdn_scan_triton(q, k, v, beta, alpha)
    ref = reference_gdn_scan_pytorch(q, k, v, beta, alpha)

    tol = 1e-2 if dtype != torch.float32 else 1e-4
    assert torch.allclose(out, ref, atol=tol, rtol=tol)

Verify Triton GDN matches PyTorch reference on CUDA.

def test_gdn_scan_triton_dim_exceeded(device)
Expand source code
@pytest.mark.skipif(not HAS_TRITON or not torch.cuda.is_available(), reason="Requires Triton and CUDA")
def test_gdn_scan_triton_dim_exceeded(device):
    """Verify exception raised when D > MAX_REGISTER_DIM."""
    B, H, L, D = 1, 1, 8, MAX_REGISTER_DIM + 16
    q = torch.randn(B, H, L, D, device=device)
    k = torch.randn(B, H, L, D, device=device)
    v = torch.randn(B, H, L, D, device=device)
    beta = torch.sigmoid(torch.randn(B, H, L, device=device))
    alpha = torch.sigmoid(torch.randn(B, H, L, device=device))

    with pytest.raises(ValueError, match="head_dim"):
        gdn_scan_triton(q, k, v, beta, alpha)

Verify exception raised when D > MAX_REGISTER_DIM.

def test_reference_gdn_scan_pytorch_basic()
Expand source code
def test_reference_gdn_scan_pytorch_basic():
    """Verify PyTorch reference runs and outputs correct shape on CPU."""
    B, H, L, D = 2, 4, 16, 32
    q = torch.randn(B, H, L, D)
    k = torch.randn(B, H, L, D)
    v = torch.randn(B, H, L, D)
    beta = torch.sigmoid(torch.randn(B, H, L))
    alpha = torch.sigmoid(torch.randn(B, H, L))

    out = reference_gdn_scan_pytorch(q, k, v, beta, alpha)
    assert out.shape == (B, H, L, D)
    assert torch.isfinite(out).all()

Verify PyTorch reference runs and outputs correct shape on CPU.