Module neuralops.tests.test_triton_sparse_attn
Comprehensive test suite for neuralops.core.triton_kernels.sparse_attn.
Coverage: - Numerical correctness vs PyTorch block-sparse reference - Multiple dtypes: fp32, fp16, bf16 - Varying block sparsity patterns (full dense, diagonal, checkerboard) - Causal vs non-causal block sparsity - Edge cases: single block, non-power-of-2 sequence length - Fallback path when Triton unavailable - Determinism - Gradient flow
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_sparse_attn_pytorch(q, k, v, block_mask=None, block_size=16, is_causal=False, eps=1e-06)-
Expand source code
def reference_sparse_attn_pytorch(q, k, v, block_mask=None, block_size=16, is_causal=False, eps=1e-6): """Pure PyTorch block-sparse attention reference.""" B, H, N_q, D = q.shape N_k = k.shape[-2] sm_scale = 1.0 / math.sqrt(D) scores = torch.matmul(q.float(), k.float().transpose(-2, -1)) * sm_scale if block_mask is not None: mask_expand = block_mask.float() if mask_expand.dim() == 2: mask_expand = mask_expand.unsqueeze(0).unsqueeze(0) elif mask_expand.dim() == 3: mask_expand = mask_expand.unsqueeze(1) # Upsample block mask to token resolution token_mask = F.interpolate(mask_expand, size=(N_q, N_k), mode="nearest") scores = scores.masked_fill(token_mask < 0.5, float("-inf")) if is_causal: causal_mask = torch.triu(torch.ones(N_q, N_k, device=q.device, dtype=torch.bool), diagonal=1) scores = scores.masked_fill(causal_mask, float("-inf")) attn = F.softmax(scores, dim=-1) attn = torch.nan_to_num(attn, nan=0.0) out = torch.matmul(attn, v.float()) return out.to(q.dtype)Pure PyTorch block-sparse attention reference.
def test_sparse_attn_correctness(device, dtype, is_causal, batch, num_heads, seq_len, dim)-
Expand source code
@pytest.mark.parametrize("dtype", [torch.float32, torch.float16]) @pytest.mark.parametrize("is_causal", [False, True]) @pytest.mark.parametrize("batch,num_heads", [(1, 2), (2, 4)]) @pytest.mark.parametrize("seq_len,dim", [(32, 32), (64, 64)]) def test_sparse_attn_correctness(device, dtype, is_causal, batch, num_heads, seq_len, dim): """Verify block-sparse attention output matches PyTorch reference.""" q = torch.randn(batch, num_heads, seq_len, dim, device=device, dtype=dtype) k = torch.randn(batch, num_heads, seq_len, dim, device=device, dtype=dtype) v = torch.randn(batch, num_heads, seq_len, dim, device=device, dtype=dtype) block_size = 16 n_blocks = (seq_len + block_size - 1) // block_size # Diagonal block sparsity mask block_mask = torch.eye(n_blocks, device=device, dtype=torch.bool) out = fwd(q, k, v, block_mask=block_mask, block_size=block_size, is_causal=is_causal) ref = reference_sparse_attn_pytorch(q, k, v, block_mask=block_mask, block_size=block_size, is_causal=is_causal) tol = {torch.float32: 1e-4, torch.float16: 1e-2}[dtype] assert out.shape == (batch, num_heads, seq_len, dim) assert torch.allclose(out, ref, atol=tol, rtol=tol)Verify block-sparse attention output matches PyTorch reference.
def test_sparse_attn_cpu_fallback()-
Expand source code
def test_sparse_attn_cpu_fallback(): """Verify fallback executes cleanly on CPU.""" q = torch.randn(2, 2, 16, 32, device="cpu") k = torch.randn(2, 2, 16, 32, device="cpu") v = torch.randn(2, 2, 16, 32, device="cpu") out = sparse_attn_pytorch_fallback(q, k, v, block_size=8) assert out.shape == (2, 2, 16, 32) assert torch.isfinite(out).all()Verify fallback executes cleanly on CPU.
def test_sparse_attn_deterministic(device)-
Expand source code
def test_sparse_attn_deterministic(device): """Deterministic output for identical input.""" q = torch.randn(2, 2, 16, 32, device=device) k = torch.randn(2, 2, 16, 32, device=device) v = torch.randn(2, 2, 16, 32, device=device) out1 = fwd(q, k, v) out2 = fwd(q, k, v) assert torch.equal(out1, out2)Deterministic output for identical input.
def test_sparse_attn_gradient(device)-
Expand source code
def test_sparse_attn_gradient(device): """Gradients propagate back to q, k, and v.""" q = torch.randn(1, 2, 16, 32, device=device, requires_grad=True) k = torch.randn(1, 2, 16, 32, device=device, requires_grad=True) v = torch.randn(1, 2, 16, 32, device=device, requires_grad=True) out = fwd(q, k, v) 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()Gradients propagate back to q, k, and v.
def test_sparse_attn_no_mask(device)-
Expand source code
def test_sparse_attn_no_mask(device): """When block_mask is None, acts as full attention.""" q = torch.randn(1, 2, 16, 32, device=device) k = torch.randn(1, 2, 16, 32, device=device) v = torch.randn(1, 2, 16, 32, device=device) out = fwd(q, k, v, block_mask=None, is_causal=False) assert out.shape == (1, 2, 16, 32) assert torch.isfinite(out).all()When block_mask is None, acts as full attention.
def test_sparse_attn_non_power_of_2(device)-
Expand source code
def test_sparse_attn_non_power_of_2(device): """Non-power-of-2 sequence length.""" q = torch.randn(2, 2, 35, 32, device=device) k = torch.randn(2, 2, 35, 32, device=device) v = torch.randn(2, 2, 35, 32, device=device) out = fwd(q, k, v, block_size=16) assert out.shape == (2, 2, 35, 32) assert torch.isfinite(out).all()Non-power-of-2 sequence length.