main
py 132 lines 4.71 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Unit tests for sparse tensor validation.
4
5 Simple, fast unit tests that can run without server fixtures.
6 Run with: pytest tests/multimodal/test_sparse_tensor_validation_unit.py -v
7 """
8
9 import io
10
11 import pytest
12 import torch
13
14
15 class TestSparseTensorValidationContextManager:
16 """Test that torch.sparse.check_sparse_tensor_invariants() works as expected."""
17
18 def test_valid_sparse_tensor_passes(self):
19 """Valid sparse tensors should pass validation."""
20 indices = torch.tensor([[0, 1], [0, 1]])
21 values = torch.tensor([1.0, 2.0])
22 shape = (2, 2)
23
24 with torch.sparse.check_sparse_tensor_invariants():
25 tensor = torch.sparse_coo_tensor(indices, values, shape)
26 dense = tensor.to_dense()
27
28 assert dense.shape == shape
29
30 def test_out_of_bounds_indices_rejected(self):
31 """Sparse tensors with out-of-bounds indices should be rejected."""
32 indices = torch.tensor([[5], [5]]) # Out of bounds for 2x2
33 values = torch.tensor([1.0])
34 shape = (2, 2)
35
36 with pytest.raises(RuntimeError) as exc_info: # noqa: SIM117
37 with torch.sparse.check_sparse_tensor_invariants():
38 tensor = torch.sparse_coo_tensor(indices, values, shape)
39 tensor.to_dense()
40
41 assert (
42 "index" in str(exc_info.value).lower()
43 or "bound" in str(exc_info.value).lower()
44 )
45
46 def test_negative_indices_rejected(self):
47 """Sparse tensors with negative indices should be rejected."""
48 indices = torch.tensor([[-1], [0]])
49 values = torch.tensor([1.0])
50 shape = (2, 2)
51
52 with pytest.raises(RuntimeError): # noqa: SIM117
53 with torch.sparse.check_sparse_tensor_invariants():
54 tensor = torch.sparse_coo_tensor(indices, values, shape)
55 tensor.to_dense()
56
57 def test_without_context_manager_allows_invalid(self):
58 """WITHOUT validation, invalid tensors may not immediately error.
59
60 This demonstrates the vulnerability: PyTorch 2.8.0+ doesn't validate
61 by default, which can lead to memory corruption.
62 """
63 indices = torch.tensor([[100], [100]]) # Way out of bounds
64 values = torch.tensor([1.0])
65 shape = (2, 2)
66
67 # Without validation context, this might create an invalid tensor
68 # (actual behavior depends on PyTorch version)
69 tensor = torch.sparse_coo_tensor(indices, values, shape)
70
71 # The tensor object is created, but it's invalid
72 assert tensor.is_sparse
73
74
75 class TestTorchLoadWithValidation:
76 """Test torch.load() with sparse tensor validation."""
77
78 def test_load_valid_sparse_tensor_with_validation(self):
79 """Valid sparse tensors should load successfully with validation."""
80 # Create and save a valid sparse tensor
81 indices = torch.tensor([[0, 1], [0, 1]])
82 values = torch.tensor([1.0, 2.0])
83 tensor = torch.sparse_coo_tensor(indices, values, (2, 2))
84
85 buffer = io.BytesIO()
86 torch.save(tensor, buffer)
87 buffer.seek(0)
88
89 # Load with validation
90 with torch.sparse.check_sparse_tensor_invariants():
91 loaded = torch.load(buffer, weights_only=True)
92 dense = loaded.to_dense()
93
94 assert dense.shape == (2, 2)
95
96 def test_load_invalid_sparse_tensor_rejected(self):
97 """Invalid sparse tensors should be caught when loaded with validation."""
98 # Create an invalid sparse tensor (out of bounds)
99 indices = torch.tensor([[10], [10]])
100 values = torch.tensor([1.0])
101 tensor = torch.sparse_coo_tensor(indices, values, (2, 2))
102
103 buffer = io.BytesIO()
104 torch.save(tensor, buffer)
105 buffer.seek(0)
106
107 # Load with validation - should fail on to_dense()
108 with pytest.raises(RuntimeError): # noqa: SIM117
109 with torch.sparse.check_sparse_tensor_invariants():
110 loaded = torch.load(buffer, weights_only=True)
111 loaded.to_dense()
112
113 def test_load_dense_tensor_unaffected(self):
114 """Dense tensors should work normally with the validation context."""
115 # Create and save a dense tensor
116 tensor = torch.randn(10, 20)
117
118 buffer = io.BytesIO()
119 torch.save(tensor, buffer)
120 buffer.seek(0)
121
122 # Load with validation (should have no effect on dense tensors)
123 with torch.sparse.check_sparse_tensor_invariants():
124 loaded = torch.load(buffer, weights_only=True)
125
126 assert loaded.shape == (10, 20)
127 assert not loaded.is_sparse
128
129
130 if __name__ == "__main__":
131 # Allow running directly for quick testing
132 pytest.main([__file__, "-v", "--tb=short"])