Module neuralops.tests.test_svd_linear_attention

Functions

def test_adapt_to_kv_size_modes()
Expand source code
def test_adapt_to_kv_size_modes():
    """Test 9: GQA adaptation modes 'slice', 'mean', and 'first' work properly."""
    # hidden_size=64, num_heads=4 -> head_dim=16. 2 KV heads -> kv_head_size = 32.
    attn = SVDLinearAttention(hidden_size=64, num_heads=4, num_kv_heads=2, rank=16)
    assert attn.num_kv_heads == 2
    assert attn.head_dim == 16
    assert attn.kv_head_size == 32

    class LLaMAStyleAttention(nn.Module):
        def __init__(self):
            super().__init__()
            self.q_proj = nn.Linear(64, 64)
            self.k_proj = nn.Linear(64, 64)  # 4 heads (64) in source, to be adapted to 2 heads (32)
            self.v_proj = nn.Linear(64, 64)
            self.o_proj = nn.Linear(64, 64)

    dummy = LLaMAStyleAttention()
    attn.copy_weights(dummy)
    x = torch.randn(2, 8, 64)
    out, _ = attn(x)
    assert out.shape == (2, 8, 64)
    assert not torch.isnan(out).any()
    print("[OK] test_adapt_to_kv_size_modes")

Test 9: GQA adaptation modes 'slice', 'mean', and 'first' work properly.

def test_chunked_decay_scan_bf16()
Expand source code
def test_chunked_decay_scan_bf16():
    """Test 11: chunked decay scan handles BF16 tensors without type promotion crash."""
    attn = SVDLinearAttention(
        hidden_size=64, num_heads=4, rank=16, mode="decay_linear", use_decay_gating=True
    )
    # Test with sequence length 128 (crosses chunk boundary of 64)
    x = torch.randn(2, 128, 64, dtype=torch.bfloat16)
    attn.to(torch.bfloat16)
    out, _ = attn(x)
    assert out.shape == (2, 128, 64)
    assert out.dtype == torch.bfloat16
    assert not torch.isnan(out).any()
    print("[OK] test_chunked_decay_scan_bf16")

Test 11: chunked decay scan handles BF16 tensors without type promotion crash.

def test_copy_weights_distilbert()
Expand source code
def test_copy_weights_distilbert():
    """Test 7: copy_weights successfully extracts weights from DistilBERT style attention."""
    attn = SVDLinearAttention(hidden_size=64, num_heads=4, rank=16)

    class DistilBertStyleAttention(nn.Module):
        def __init__(self):
            super().__init__()
            self.q_lin = nn.Linear(64, 64)
            self.k_lin = nn.Linear(64, 64)
            self.v_lin = nn.Linear(64, 64)
            self.out_lin = nn.Linear(64, 64)

    dummy = DistilBertStyleAttention()
    attn.copy_weights(dummy)
    # Forward check after copy
    x = torch.randn(2, 8, 64)
    out, _ = attn(x)
    assert out.shape == (2, 8, 64)
    assert not torch.isnan(out).any()
    print("[OK] test_copy_weights_distilbert")

Test 7: copy_weights successfully extracts weights from DistilBERT style attention.

def test_copy_weights_gpt2()
Expand source code
def test_copy_weights_gpt2():
    """Test 8: copy_weights successfully extracts weights from GPT-2 style fused c_attn and c_proj."""
    attn = SVDLinearAttention(hidden_size=64, num_heads=4, rank=16, is_decoder=True)

    class GPT2StyleAttention(nn.Module):
        def __init__(self):
            super().__init__()
            self.c_attn = nn.Linear(64, 192)
            self.c_proj = nn.Linear(64, 64)

    dummy = GPT2StyleAttention()
    attn.copy_weights(dummy)
    x = torch.randn(2, 8, 64)
    out, _ = attn(x)
    assert out.shape == (2, 8, 64)
    assert not torch.isnan(out).any()
    print("[OK] test_copy_weights_gpt2")

Test 8: copy_weights successfully extracts weights from GPT-2 style fused c_attn and c_proj.

def test_copy_weights_near_zero_frobenius_raises()
Expand source code
def test_copy_weights_near_zero_frobenius_raises():
    """Test 6: copy_weights raises RuntimeError when projection weight has near-zero Frobenius norm."""
    attn = SVDLinearAttention(hidden_size=64, num_heads=4, rank=16)

    class DummyNearZero(nn.Module):
        def __init__(self):
            super().__init__()
            self.q_lin = nn.Linear(64, 64)
            self.k_lin = nn.Linear(64, 64)
            self.v_lin = nn.Linear(64, 64)
            self.out_lin = nn.Linear(64, 64)
            # Set q_lin weight to zero
            self.q_lin.weight.data.zero_()

    dummy = DummyNearZero()
    with pytest.raises(RuntimeError) as excinfo:
        attn.copy_weights(dummy)
    assert "near-zero Frobenius norm" in str(excinfo.value)
    print("[OK] test_copy_weights_near_zero_frobenius_raises")

Test 6: copy_weights raises RuntimeError when projection weight has near-zero Frobenius norm.

def test_copy_weights_none_raises()
Expand source code
def test_copy_weights_none_raises():
    """Test 5: copy_weights raises RuntimeError when essential projections cannot be extracted."""
    attn = SVDLinearAttention(hidden_size=64, num_heads=4, rank=16)

    class DummyEmpty(nn.Module):
        pass

    dummy = DummyEmpty()
    with pytest.raises(RuntimeError) as excinfo:
        attn.copy_weights(dummy)
    assert "failed to extract required projections" in str(excinfo.value)
    print("[OK] test_copy_weights_none_raises")

Test 5: copy_weights raises RuntimeError when essential projections cannot be extracted.

def test_linear_kernel_and_recurrent()
Expand source code
def test_linear_kernel_and_recurrent():
    """Test 12: linear kernel mode and step_recurrent execution."""
    attn = SVDLinearAttention(
        hidden_size=64, num_heads=4, rank=16, mode="linear", is_decoder=True
    )
    x = torch.randn(2, 16, 64)
    out, _ = attn(x)
    assert out.shape == (2, 16, 64)

    # Step recurrent test
    past_state = None
    outputs = []
    for t in range(5):
        xt = x[:, t : t + 1, :]
        out_step, past_state = attn.step_recurrent(xt, past_state)
        assert out_step.shape == (2, 1, 64)
        outputs.append(out_step)
    all_steps = torch.cat(outputs, dim=1)
    assert all_steps.shape == (2, 5, 64)
    print("[OK] test_linear_kernel_and_recurrent")

Test 12: linear kernel mode and step_recurrent execution.

def test_registry_and_injector()
Expand source code
def test_registry_and_injector():
    """Test 13: ATTENTION_REGISTRY contains aliases and inject_svd_linear_attention works."""
    assert "svd_linear_attn" in ATTENTION_REGISTRY
    assert "svd_linear_attention" in ATTENTION_REGISTRY
    assert "svd_linear" in ATTENTION_REGISTRY
    assert "svd_linear_inject" in ATTENTION_REGISTRY
    assert "inject_svd_linear_attention" in ATTENTION_REGISTRY

    class DummyLayer(nn.Module):
        def __init__(self):
            super().__init__()
            self.attention = nn.Module()
            self.attention.q_lin = nn.Linear(64, 64)
            self.attention.k_lin = nn.Linear(64, 64)
            self.attention.v_lin = nn.Linear(64, 64)
            self.attention.out_lin = nn.Linear(64, 64)

    layer = DummyLayer()
    injected = inject_svd_linear_attention(
        layer=layer,
        model_config={"hidden_size": 64, "num_attention_heads": 4},
        rank=16,
        mode="exact",
    )
    assert isinstance(layer.attention, SVDLinearAttention)
    print("[OK] test_registry_and_injector")

Test 13: ATTENTION_REGISTRY contains aliases and inject_svd_linear_attention works.

def test_sdpa_in_exact_mode()
Expand source code
def test_sdpa_in_exact_mode():
    """Test 10: exact mode uses SDPA when output_attentions=False, returns attn_probs when True."""
    attn = SVDLinearAttention(hidden_size=64, num_heads=4, rank=16, mode="exact")
    x = torch.randn(2, 8, 64)

    # 1. output_attentions=False -> attn_probs should be None (SDPA path)
    out, attn_probs = attn(x, output_attentions=False)
    assert out.shape == (2, 8, 64)
    assert attn_probs is None
    assert not torch.isnan(out).any()

    # 2. output_attentions=True -> attn_probs should be returned (B, H, N, N)
    out2, attn_probs2 = attn(x, output_attentions=True)
    assert out2.shape == (2, 8, 64)
    assert attn_probs2 is not None
    assert attn_probs2.shape == (2, 4, 8, 8)
    assert not torch.isnan(attn_probs2).any()
    print("[OK] test_sdpa_in_exact_mode")

Test 10: exact mode uses SDPA when output_attentions=False, returns attn_probs when True.

def test_svdlinear_forward()
Expand source code
def test_svdlinear_forward():
    """Test 4: SVDLinear forward pass with and without RMSNorm."""
    layer1 = SVDLinear(in_features=32, out_features=64, rank=16, use_rms_norm=False)
    x = torch.randn(2, 10, 32)
    out1 = layer1(x)
    assert out1.shape == (2, 10, 64)
    assert not torch.isnan(out1).any()

    layer2 = SVDLinear(in_features=32, out_features=64, rank=16, use_rms_norm=True)
    out2 = layer2(x)
    assert out2.shape == (2, 10, 64)
    assert not torch.isnan(out2).any()
    print("[OK] test_svdlinear_forward")

Test 4: SVDLinear forward pass with and without RMSNorm.

def test_svdlinear_get_weight()
Expand source code
def test_svdlinear_get_weight():
    """Test 2: SVDLinear has get_weight() method and not weight property."""
    linear = SVDLinear(in_features=32, out_features=64, rank=16, bias=True)
    assert not hasattr(linear.__class__, "weight") or not isinstance(
        getattr(linear.__class__, "weight"), property
    ), "SVDLinear must not define @property def weight"
    assert hasattr(linear, "get_weight") and callable(linear.get_weight)
    W = linear.get_weight()
    assert W.shape == (64, 32)
    assert not torch.isnan(W).any()
    print("[OK] test_svdlinear_get_weight")

Test 2: SVDLinear has get_weight() method and not weight property.

def test_svdlinear_no_custom_attention_attr()
Expand source code
def test_svdlinear_no_custom_attention_attr():
    """Test 1: SVDLinear does not have is_custom_attention = True as class attribute."""
    # is_custom_attention should NOT be True on SVDLinear class or instances
    assert not getattr(SVDLinear, "is_custom_attention", False), "SVDLinear must not have is_custom_attention=True"
    linear = SVDLinear(in_features=32, out_features=64, rank=16)
    assert not getattr(linear, "is_custom_attention", False), "SVDLinear instance must not have is_custom_attention=True"
    # But SVDLinearAttention MUST have is_custom_attention = True
    assert getattr(SVDLinearAttention, "is_custom_attention", False) is True
    print("[OK] test_svdlinear_no_custom_attention_attr")

Test 1: SVDLinear does not have is_custom_attention = True as class attribute.

def test_svdlinear_set_low_rank_factors_zeroing()
Expand source code
def test_svdlinear_set_low_rank_factors_zeroing():
    """Test 3: set_low_rank_factors only zeroes written slice and handles smaller rank."""
    linear = SVDLinear(in_features=32, out_features=64, rank=16, bias=True)
    # Fill with sentinel values
    linear.U.data.fill_(7.0)
    linear.V.data.fill_(7.0)

    # Provide rank 8 factors (smaller than rank 16)
    U_sub = torch.randn(64, 8)
    V_sub = torch.randn(8, 32)
    linear.set_low_rank_factors(U_sub, V_sub)

    # Columns 8:16 in U and rows 8:16 in V must be 0
    assert torch.allclose(linear.U.data[:, 8:], torch.zeros(64, 8))
    assert torch.allclose(linear.V.data[8:, :], torch.zeros(8, 32))
    # Loaded factors in :8 must match
    assert torch.allclose(linear.U.data[:, :8], U_sub)
    assert torch.allclose(linear.V.data[:8, :], V_sub)
    print("[OK] test_svdlinear_set_low_rank_factors_zeroing")

Test 3: set_low_rank_factors only zeroes written slice and handles smaller rank.