torchsparsegradutils 0.2.1__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. {torchsparsegradutils-0.2.1/torchsparsegradutils.egg-info → torchsparsegradutils-0.2.2}/PKG-INFO +19 -10
  2. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/README.md +11 -7
  3. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/docs/source/conf.py +3 -3
  4. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/pyproject.toml +8 -2
  5. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/cupy/__init__.py +11 -2
  6. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/cupy/cupy_bindings.py +25 -11
  7. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/cupy/cupy_sparse_solve.py +92 -95
  8. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/indexed_matmul.py +3 -2
  9. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/sparse_matmul.py +3 -3
  10. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/sparse_solve.py +2 -2
  11. torchsparsegradutils-0.2.2/torchsparsegradutils/tests/conftest.py +48 -0
  12. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_bicgstab.py +8 -11
  13. torchsparsegradutils-0.2.2/torchsparsegradutils/tests/test_config.py +62 -0
  14. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_cupy_bindings.py +9 -12
  15. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_cupy_sparse_solve.py +45 -39
  16. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_dist_stats_helpers.py +1 -12
  17. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_distributions.py +22 -42
  18. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_encoders.py +2 -1
  19. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_indexed_matmul.py +30 -5
  20. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_integration_pairwise_sparse_mvn.py +66 -16
  21. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_jax_bindings.py +1 -5
  22. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_jax_sparse_solve.py +19 -27
  23. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_linear_cg.py +0 -19
  24. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_lsmr.py +19 -23
  25. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_minres.py +3 -11
  26. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_random.py +9 -10
  27. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_sparse_lstsq.py +3 -7
  28. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_sparse_matmul.py +7 -14
  29. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_sparse_solve.py +28 -37
  30. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_sparse_triangular_solve.py +8 -17
  31. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_utils.py +2 -7
  32. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/utils/utils.py +6 -8
  33. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2/torchsparsegradutils.egg-info}/PKG-INFO +19 -10
  34. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils.egg-info/SOURCES.txt +2 -0
  35. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils.egg-info/requires.txt +10 -3
  36. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/LICENSE +0 -0
  37. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/MANIFEST.in +0 -0
  38. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/setup.cfg +0 -0
  39. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/setup.py +0 -0
  40. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/__init__.py +0 -0
  41. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/benchmarks/__init__.py +0 -0
  42. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/benchmarks/batched_sparse_mm_rand.py +0 -0
  43. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/benchmarks/benchmark_suite.py +0 -0
  44. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/benchmarks/benchmark_utils.py +0 -0
  45. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/benchmarks/sparse_generic_solve_rand.py +0 -0
  46. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/benchmarks/sparse_generic_solve_suite.py +0 -0
  47. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/benchmarks/sparse_mm_rand.py +0 -0
  48. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/benchmarks/sparse_mm_suite.py +0 -0
  49. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/benchmarks/sparse_triangular_solve_rand.py +0 -0
  50. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/benchmarks/sparse_triangular_solve_suitesparse.py +0 -0
  51. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/benchmarks/visualize_benchmark_results.py +0 -0
  52. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/distributions/__init__.py +0 -0
  53. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/distributions/constraints.py +0 -0
  54. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/distributions/sparse_multivariate_normal.py +0 -0
  55. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/encoders/__init__.py +0 -0
  56. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/encoders/pairwise_encoder.py +0 -0
  57. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/encoders/pairwise_voxel_encoder.py +0 -0
  58. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/jax/__init__.py +0 -0
  59. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/jax/jax_bindings.py +0 -0
  60. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/jax/jax_sparse_solve.py +0 -0
  61. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/sparse_lstsq.py +0 -0
  62. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/__init__.py +0 -0
  63. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_doctests.py +0 -0
  64. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_params/czyx_shifts.yaml +0 -0
  65. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_params/pairwise_coo_indices.yaml +0 -0
  66. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_params/xyz_coords.yaml +0 -0
  67. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/tests/test_quickstart_guide.py +0 -0
  68. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/utils/__init__.py +0 -0
  69. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/utils/bicgstab.py +0 -0
  70. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/utils/dist_stats_helpers.py +0 -0
  71. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/utils/linear_cg.py +0 -0
  72. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/utils/lsmr.py +0 -0
  73. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/utils/minres.py +0 -0
  74. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils/utils/random_sparse.py +0 -0
  75. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils.egg-info/dependency_links.txt +0 -0
  76. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils.egg-info/not-zip-safe +0 -0
  77. {torchsparsegradutils-0.2.1 → torchsparsegradutils-0.2.2}/torchsparsegradutils.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: torchsparsegradutils
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: A collection of utility functions to work with PyTorch sparse tensors
5
5
  Author-email: CAI4CAI research group <contact@cai4cai.uk>
6
6
  License-Expression: Apache-2.0
@@ -19,9 +19,14 @@ Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
20
  Requires-Dist: torch>=2.5
21
21
  Requires-Dist: scipy
22
- Provides-Extra: extras
23
- Requires-Dist: jax; extra == "extras"
24
- Requires-Dist: cupy; extra == "extras"
22
+ Requires-Dist: packaging
23
+ Provides-Extra: cupy
24
+ Requires-Dist: cupy-cuda12x>=13.0; extra == "cupy"
25
+ Provides-Extra: jax
26
+ Requires-Dist: jax[cuda12]; extra == "jax"
27
+ Provides-Extra: all
28
+ Requires-Dist: cupy-cuda12x>=13.0; extra == "all"
29
+ Requires-Dist: jax[cuda12]; extra == "all"
25
30
  Provides-Extra: docs
26
31
  Requires-Dist: sphinx>=7.0.0; extra == "docs"
27
32
  Requires-Dist: sphinx-rtd-theme>=1.3.0; extra == "docs"
@@ -42,7 +47,7 @@ Dynamic: license-file
42
47
 
43
48
  # torchsparsegradutils: Sparsity-preserving gradient utility tools for PyTorch
44
49
 
45
- [![PyPI](https://img.shields.io/pypi/v/torchsparsegradutils.svg)](https://pypi.org/project/torchsparsegradutils/) [![Python Versions](https://img.shields.io/pypi/pyversions/torchsparsegradutils.svg)](https://pypi.org/project/torchsparsegradutils/) [![Downloads](https://img.shields.io/pypi/dm/torchsparsegradutils.svg)](https://pypi.org/project/torchsparsegradutils/) ![PyTorch 2.5+](https://img.shields.io/badge/PyTorch-2.5%2B-ee4c2c?logo=pytorch) ![Tested 2.5 / 2.9 / nightly](https://img.shields.io/badge/Tested-2.5%20|%202.9%20|%20nightly-ee4c2c?logo=pytorch) [![Build](https://github.com/cai4cai/torchsparsegradutils/actions/workflows/python-package.yml/badge.svg)](https://github.com/cai4cai/torchsparsegradutils/actions/workflows/python-package.yml) [![Docs](https://readthedocs.org/projects/torchsparsegradutils/badge/?version=latest)](https://readthedocs.org/projects/torchsparsegradutils) [![Code Style: Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![License](https://img.shields.io/github/license/cai4cai/torchsparsegradutils)](LICENSE) [![status](https://joss.theoj.org/papers/6da0e92488d06f70c0a03d0a7cbfba7d/status.svg)](https://joss.theoj.org/papers/6da0e92488d06f70c0a03d0a7cbfba7d)
50
+ [![PyPI](https://img.shields.io/pypi/v/torchsparsegradutils.svg)](https://pypi.org/project/torchsparsegradutils/) [![Python Versions](https://img.shields.io/pypi/pyversions/torchsparsegradutils.svg)](https://pypi.org/project/torchsparsegradutils/) [![Downloads](https://img.shields.io/pypi/dm/torchsparsegradutils.svg)](https://pypi.org/project/torchsparsegradutils/) ![PyTorch 2.5+](https://img.shields.io/badge/PyTorch-2.5%2B-ee4c2c?logo=pytorch) ![Tested 2.5 / 2.11 / nightly](https://img.shields.io/badge/Tested-2.5%20|%202.11%20|%20nightly-ee4c2c?logo=pytorch) [![Build](https://github.com/cai4cai/torchsparsegradutils/actions/workflows/python-package.yml/badge.svg)](https://github.com/cai4cai/torchsparsegradutils/actions/workflows/python-package.yml) [![Docs](https://readthedocs.org/projects/torchsparsegradutils/badge/?version=latest)](https://readthedocs.org/projects/torchsparsegradutils) [![Code Style: Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![License](https://img.shields.io/github/license/cai4cai/torchsparsegradutils)](LICENSE) [![status](https://joss.theoj.org/papers/6da0e92488d06f70c0a03d0a7cbfba7d/status.svg)](https://joss.theoj.org/papers/6da0e92488d06f70c0a03d0a7cbfba7d)
46
51
 
47
52
  A comprehensive collection of utility functions to work with PyTorch sparse tensors, ensuring memory efficiency and supporting various sparsity-preserving tensor operations with automatic differentiation. This package addresses fundamental gaps in PyTorch's sparse tensor ecosystem, providing essential operations that preserve sparsity in gradients during backpropagation.
48
53
 
@@ -133,17 +138,21 @@ pip install git+https://github.com/cai4cai/torchsparsegradutils
133
138
  For full functionality, install optional dependencies:
134
139
 
135
140
  ```bash
136
- # For CuPy sparse solver support (GPU acceleration)
137
- pip install cupy-cuda12x # Replace with your CUDA version
141
+ # For CuPy sparse solver support (GPU acceleration, requires CUDA 12.x)
142
+ pip install torchsparsegradutils[cupy]
138
143
 
139
144
  # For JAX sparse solver support
140
- pip install "jax[cpu]" # CPU version
141
- pip install "jax[cuda12]" # GPU version (replace with your CUDA version)
145
+ pip install torchsparsegradutils[jax]
146
+
147
+ # Install all optional dependencies
148
+ pip install torchsparsegradutils[all]
142
149
 
143
150
  # For benchmarking and testing
144
151
  pip install scipy matplotlib pandas tqdm pytest
145
152
  ```
146
153
 
154
+ > **Note:** The CuPy extra installs `cupy-cuda12x>=13.0`. If you are using a different CUDA version, install the appropriate CuPy package manually (e.g. `pip install cupy-cuda11x`).
155
+
147
156
  ### Requirements
148
157
 
149
158
  - **Python**: ≥ 3.10
@@ -160,7 +169,7 @@ Our comprehensive benchmark suite demonstrates significant performance improveme
160
169
 
161
170
  ![Sparse Triangular Solve Suite Performance (int32/float32 COO)](torchsparsegradutils/benchmarks/benchmark_visualizations/triangular_solve_suitesparse_performance_int32_float32_coo.png)
162
171
 
163
- ![Sparse Genertic Solve Suite Performance (int32/float32 COO)](torchsparsegradutils/benchmarks/benchmark_visualizations/sparse_solve_suite_performance_int32_float32_coo.png)
172
+ ![Sparse Generic Solve Suite Performance (int32/float32 COO)](torchsparsegradutils/benchmarks/benchmark_visualizations/sparse_solve_suite_performance_int32_float32_coo.png)
164
173
 
165
174
  ## 🚀 Quick Start
166
175
 
@@ -1,6 +1,6 @@
1
1
  # torchsparsegradutils: Sparsity-preserving gradient utility tools for PyTorch
2
2
 
3
- [![PyPI](https://img.shields.io/pypi/v/torchsparsegradutils.svg)](https://pypi.org/project/torchsparsegradutils/) [![Python Versions](https://img.shields.io/pypi/pyversions/torchsparsegradutils.svg)](https://pypi.org/project/torchsparsegradutils/) [![Downloads](https://img.shields.io/pypi/dm/torchsparsegradutils.svg)](https://pypi.org/project/torchsparsegradutils/) ![PyTorch 2.5+](https://img.shields.io/badge/PyTorch-2.5%2B-ee4c2c?logo=pytorch) ![Tested 2.5 / 2.9 / nightly](https://img.shields.io/badge/Tested-2.5%20|%202.9%20|%20nightly-ee4c2c?logo=pytorch) [![Build](https://github.com/cai4cai/torchsparsegradutils/actions/workflows/python-package.yml/badge.svg)](https://github.com/cai4cai/torchsparsegradutils/actions/workflows/python-package.yml) [![Docs](https://readthedocs.org/projects/torchsparsegradutils/badge/?version=latest)](https://readthedocs.org/projects/torchsparsegradutils) [![Code Style: Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![License](https://img.shields.io/github/license/cai4cai/torchsparsegradutils)](LICENSE) [![status](https://joss.theoj.org/papers/6da0e92488d06f70c0a03d0a7cbfba7d/status.svg)](https://joss.theoj.org/papers/6da0e92488d06f70c0a03d0a7cbfba7d)
3
+ [![PyPI](https://img.shields.io/pypi/v/torchsparsegradutils.svg)](https://pypi.org/project/torchsparsegradutils/) [![Python Versions](https://img.shields.io/pypi/pyversions/torchsparsegradutils.svg)](https://pypi.org/project/torchsparsegradutils/) [![Downloads](https://img.shields.io/pypi/dm/torchsparsegradutils.svg)](https://pypi.org/project/torchsparsegradutils/) ![PyTorch 2.5+](https://img.shields.io/badge/PyTorch-2.5%2B-ee4c2c?logo=pytorch) ![Tested 2.5 / 2.11 / nightly](https://img.shields.io/badge/Tested-2.5%20|%202.11%20|%20nightly-ee4c2c?logo=pytorch) [![Build](https://github.com/cai4cai/torchsparsegradutils/actions/workflows/python-package.yml/badge.svg)](https://github.com/cai4cai/torchsparsegradutils/actions/workflows/python-package.yml) [![Docs](https://readthedocs.org/projects/torchsparsegradutils/badge/?version=latest)](https://readthedocs.org/projects/torchsparsegradutils) [![Code Style: Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![License](https://img.shields.io/github/license/cai4cai/torchsparsegradutils)](LICENSE) [![status](https://joss.theoj.org/papers/6da0e92488d06f70c0a03d0a7cbfba7d/status.svg)](https://joss.theoj.org/papers/6da0e92488d06f70c0a03d0a7cbfba7d)
4
4
 
5
5
  A comprehensive collection of utility functions to work with PyTorch sparse tensors, ensuring memory efficiency and supporting various sparsity-preserving tensor operations with automatic differentiation. This package addresses fundamental gaps in PyTorch's sparse tensor ecosystem, providing essential operations that preserve sparsity in gradients during backpropagation.
6
6
 
@@ -91,17 +91,21 @@ pip install git+https://github.com/cai4cai/torchsparsegradutils
91
91
  For full functionality, install optional dependencies:
92
92
 
93
93
  ```bash
94
- # For CuPy sparse solver support (GPU acceleration)
95
- pip install cupy-cuda12x # Replace with your CUDA version
94
+ # For CuPy sparse solver support (GPU acceleration, requires CUDA 12.x)
95
+ pip install torchsparsegradutils[cupy]
96
96
 
97
97
  # For JAX sparse solver support
98
- pip install "jax[cpu]" # CPU version
99
- pip install "jax[cuda12]" # GPU version (replace with your CUDA version)
98
+ pip install torchsparsegradutils[jax]
99
+
100
+ # Install all optional dependencies
101
+ pip install torchsparsegradutils[all]
100
102
 
101
103
  # For benchmarking and testing
102
104
  pip install scipy matplotlib pandas tqdm pytest
103
105
  ```
104
106
 
107
+ > **Note:** The CuPy extra installs `cupy-cuda12x>=13.0`. If you are using a different CUDA version, install the appropriate CuPy package manually (e.g. `pip install cupy-cuda11x`).
108
+
105
109
  ### Requirements
106
110
 
107
111
  - **Python**: ≥ 3.10
@@ -118,7 +122,7 @@ Our comprehensive benchmark suite demonstrates significant performance improveme
118
122
 
119
123
  ![Sparse Triangular Solve Suite Performance (int32/float32 COO)](torchsparsegradutils/benchmarks/benchmark_visualizations/triangular_solve_suitesparse_performance_int32_float32_coo.png)
120
124
 
121
- ![Sparse Genertic Solve Suite Performance (int32/float32 COO)](torchsparsegradutils/benchmarks/benchmark_visualizations/sparse_solve_suite_performance_int32_float32_coo.png)
125
+ ![Sparse Generic Solve Suite Performance (int32/float32 COO)](torchsparsegradutils/benchmarks/benchmark_visualizations/sparse_solve_suite_performance_int32_float32_coo.png)
122
126
 
123
127
  ## 🚀 Quick Start
124
128
 
@@ -658,4 +662,4 @@ dist_stable = SparseMultivariateNormal(
658
662
  - **No SPD Constraints**: Doesn't require strict positive definiteness
659
663
  - **Better Conditioning**: Diagonal component can be controlled independently
660
664
 
661
- **Status**: This is a known limitation of the LL^T precision formulation. LDL^T parameterization is the recommended approach for precision matrices.
665
+ **Status**: This is a known limitation of the LL^T precision formulation. LDL^T parameterization is the recommended approach for precision matrices.
@@ -13,10 +13,10 @@ import sys
13
13
  sys.path.insert(0, os.path.abspath("../../"))
14
14
 
15
15
  project = "torchsparsegradutils"
16
- copyright = "2025, CAI4CAI research group"
16
+ copyright = "2026, CAI4CAI research group"
17
17
  author = "CAI4CAI research group"
18
- release = "0.2.1"
19
- version = "0.2.1"
18
+ release = "0.2.2"
19
+ version = "0.2.2"
20
20
 
21
21
  # -- General configuration ---------------------------------------------------
22
22
  # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "torchsparsegradutils"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "A collection of utility functions to work with PyTorch sparse tensors"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -22,10 +22,13 @@ classifiers = [
22
22
  dependencies = [
23
23
  "torch>=2.5",
24
24
  "scipy",
25
+ "packaging",
25
26
  ]
26
27
 
27
28
  [project.optional-dependencies]
28
- extras = ["jax", "cupy"]
29
+ cupy = ["cupy-cuda12x>=13.0"]
30
+ jax = ["jax[cuda12]"]
31
+ all = ["cupy-cuda12x>=13.0", "jax[cuda12]"]
29
32
  docs = [
30
33
  "sphinx>=7.0.0",
31
34
  "sphinx-rtd-theme>=1.3.0",
@@ -99,6 +102,9 @@ addopts = ["-v"]
99
102
  testpaths = [
100
103
  "torchsparsegradutils/tests",
101
104
  ]
105
+ pythonpath = [
106
+ "torchsparsegradutils/tests",
107
+ ]
102
108
  doctest_optionflags = [
103
109
  "NORMALIZE_WHITESPACE",
104
110
  "IGNORE_EXCEPTION_DETAIL",
@@ -11,7 +11,16 @@ if cupy_spec is None:
11
11
  else:
12
12
  have_cupy = True
13
13
 
14
- from .cupy_bindings import _get_array_modules, c2t_coo, c2t_csr, t2c_coo, t2c_csr
14
+ from .cupy_bindings import _backend_to_torch, _get_array_modules, _torch_to_backend, c2t_coo, c2t_csr, t2c_coo, t2c_csr
15
15
  from .cupy_sparse_solve import sparse_solve_c4t
16
16
 
17
- __all__ = ["c2t_coo", "t2c_coo", "c2t_csr", "t2c_csr", "_get_array_modules", "sparse_solve_c4t"]
17
+ __all__ = [
18
+ "c2t_coo",
19
+ "t2c_coo",
20
+ "c2t_csr",
21
+ "t2c_csr",
22
+ "_get_array_modules",
23
+ "_torch_to_backend",
24
+ "_backend_to_torch",
25
+ "sparse_solve_c4t",
26
+ ]
@@ -38,6 +38,20 @@ import scipy.sparse.linalg
38
38
  import torch
39
39
 
40
40
 
41
+ def _torch_to_backend(tensor: torch.Tensor, xp) -> Any:
42
+ r"""Convert a PyTorch tensor to a backend array (NumPy or CuPy) using DLPack for CUDA."""
43
+ if tsgucupy.have_cupy and xp is cp:
44
+ return cp.from_dlpack(tensor)
45
+ return xp.asarray(tensor)
46
+
47
+
48
+ def _backend_to_torch(array) -> torch.Tensor:
49
+ r"""Convert a backend array (NumPy or CuPy) to a PyTorch tensor using DLPack for CUDA."""
50
+ if tsgucupy.have_cupy and isinstance(array, cp.ndarray):
51
+ return torch.from_dlpack(array)
52
+ return torch.as_tensor(array)
53
+
54
+
41
55
  def _get_array_modules(x: Any) -> Tuple[Any, Any]:
42
56
  r"""
43
57
  Select dense & sparse array modules (NumPy/SciPy or CuPy/cupyx.scipy.sparse).
@@ -116,9 +130,9 @@ def t2c_csr(x_torch: torch.Tensor) -> Any:
116
130
  """
117
131
  xp, xsp = _get_array_modules(x_torch)
118
132
 
119
- data_c = xp.asarray(x_torch.values())
120
- col_idx_c = xp.asarray(x_torch.col_indices())
121
- ind_ptr_c = xp.asarray(x_torch.crow_indices())
133
+ data_c = _torch_to_backend(x_torch.values(), xp)
134
+ col_idx_c = _torch_to_backend(x_torch.col_indices(), xp)
135
+ ind_ptr_c = _torch_to_backend(x_torch.crow_indices(), xp)
122
136
  x_cupy = xsp.csr_matrix((data_c, col_idx_c, ind_ptr_c), shape=x_torch.shape)
123
137
  return x_cupy
124
138
 
@@ -150,9 +164,9 @@ def c2t_csr(x_cupy: Any) -> torch.Tensor:
150
164
  >>> x.layout is torch.sparse_csr
151
165
  True
152
166
  """
153
- data_t = torch.as_tensor(x_cupy.data)
154
- idices_t = torch.as_tensor(x_cupy.indices)
155
- ind_ptr_t = torch.as_tensor(x_cupy.indptr)
167
+ data_t = _backend_to_torch(x_cupy.data)
168
+ idices_t = _backend_to_torch(x_cupy.indices)
169
+ ind_ptr_t = _backend_to_torch(x_cupy.indptr)
156
170
  x_torch = torch.sparse_csr_tensor(ind_ptr_t, idices_t, data_t, x_cupy.shape)
157
171
  return x_torch
158
172
 
@@ -205,8 +219,8 @@ def t2c_coo(x_torch: torch.Tensor) -> Any:
205
219
  "Requested a conversion from torch to cupy/numpy on a non-coalesced tensor -> coalescing implicitly"
206
220
  )
207
221
  x_torch = x_torch.coalesce()
208
- data_c = xp.asarray(x_torch.values())
209
- idx_cp = xp.asarray(x_torch.indices())
222
+ data_c = _torch_to_backend(x_torch.values(), xp)
223
+ idx_cp = _torch_to_backend(x_torch.indices(), xp)
210
224
  x_cupy = xsp.coo_matrix((data_c, idx_cp), shape=x_torch.shape)
211
225
  return x_cupy
212
226
 
@@ -238,8 +252,8 @@ def c2t_coo(x_cupy: Any) -> torch.Tensor:
238
252
  >>> x.layout is torch.sparse_coo
239
253
  True
240
254
  """
241
- data_t = torch.as_tensor(x_cupy.data)
242
- row_t = torch.as_tensor(x_cupy.row)
243
- col_t = torch.as_tensor(x_cupy.col)
255
+ data_t = _backend_to_torch(x_cupy.data)
256
+ row_t = _backend_to_torch(x_cupy.row)
257
+ col_t = _backend_to_torch(x_cupy.col)
244
258
  x_torch = torch.sparse_coo_tensor(torch.stack([row_t, col_t], dim=0), data_t, x_cupy.shape)
245
259
  return x_torch
@@ -1,3 +1,4 @@
1
+ import inspect
1
2
  import warnings
2
3
  from typing import Any, Callable, Dict, Optional, Tuple, Union
3
4
 
@@ -8,6 +9,90 @@ import torchsparsegradutils.cupy as tsgucupy
8
9
  # from cupyx.scipy.sparse.linalg import cg, cgs, minres, gmres, spsolve
9
10
 
10
11
 
12
+ def _wrap_iterative_solver(base_solver, backend_type, solver_name=None):
13
+ """Wrap an iterative solver to handle parameter mapping and return format."""
14
+
15
+ # Introspect the actual solver signature to determine accepted parameter names
16
+ try:
17
+ sig_params = set(inspect.signature(base_solver).parameters.keys())
18
+ except (ValueError, TypeError):
19
+ sig_params = set()
20
+
21
+ def wrapped_solver(A, b, **solver_kwargs):
22
+ # Create a copy to avoid modifying the original
23
+ filtered_kwargs = solver_kwargs.copy()
24
+
25
+ # Extract tolerance parameter and map to correct name for the backend
26
+ tolerance = filtered_kwargs.pop("tol", None)
27
+ atol = filtered_kwargs.pop("atol", None)
28
+
29
+ if tolerance is not None:
30
+ # Use introspection to determine correct tolerance parameter name
31
+ if "rtol" in sig_params:
32
+ filtered_kwargs["rtol"] = tolerance
33
+ elif "tol" in sig_params:
34
+ filtered_kwargs["tol"] = tolerance
35
+
36
+ # Handle atol parameter if the solver accepts it
37
+ if atol is not None and "atol" in sig_params:
38
+ filtered_kwargs["atol"] = atol
39
+
40
+ # Filter to only parameters the solver actually accepts
41
+ common_params = {"x0", "M", "callback", "show", "check"}
42
+ if sig_params:
43
+ final_kwargs = {k: v for k, v in filtered_kwargs.items() if k in sig_params or k in common_params}
44
+ else:
45
+ # Fallback: pass everything if we couldn't introspect
46
+ final_kwargs = filtered_kwargs
47
+
48
+ # Call the base solver
49
+ result = base_solver(A, b, **final_kwargs)
50
+
51
+ # Handle return format - some solvers return (solution, info) tuples
52
+ if isinstance(result, tuple):
53
+ return result[0] # Return just the solution
54
+ return result
55
+
56
+ return wrapped_solver
57
+
58
+
59
+ def _wrap_direct_solver(base_solver):
60
+ """Wrap a direct solver to ignore tolerance parameters."""
61
+
62
+ def wrapped_solver(A, b, **solver_kwargs):
63
+ # Direct solvers don't use iterative solver parameters, so ignore them all
64
+ filtered_kwargs = {
65
+ k: v
66
+ for k, v in solver_kwargs.items()
67
+ if k not in ["tol", "tolerance", "atol", "rtol", "maxiter", "matvec_max"]
68
+ }
69
+ return base_solver(A, b, **filtered_kwargs)
70
+
71
+ return wrapped_solver
72
+
73
+
74
+ def _get_solver_function(solver_name, xsp, device):
75
+ """Get the appropriate solver function based on the backend."""
76
+ if solver_name is None or callable(solver_name):
77
+ return solver_name
78
+
79
+ # Determine backend type
80
+ backend_type = "scipy" if device.type == "cpu" else "cupy"
81
+
82
+ solver_map = {
83
+ "cg": _wrap_iterative_solver(xsp.linalg.cg, backend_type, "cg"),
84
+ "cgs": _wrap_iterative_solver(xsp.linalg.cgs, backend_type, "cgs"),
85
+ "minres": _wrap_iterative_solver(xsp.linalg.minres, backend_type, "minres"),
86
+ "gmres": _wrap_iterative_solver(xsp.linalg.gmres, backend_type, "gmres"),
87
+ "spsolve": _wrap_direct_solver(xsp.linalg.spsolve),
88
+ }
89
+
90
+ if solver_name not in solver_map:
91
+ raise ValueError(f"Unknown solver: {solver_name}. Supported solvers: {list(solver_map.keys())}")
92
+
93
+ return solver_map[solver_name]
94
+
95
+
11
96
  def sparse_solve_c4t(
12
97
  A: torch.Tensor,
13
98
  B: torch.Tensor,
@@ -154,94 +239,7 @@ def sparse_solve_c4t(
154
239
  f"Use transpose_solve='spsolve' or transpose_solve=None for multi-RHS problems."
155
240
  )
156
241
 
157
- # Convert string solver names to actual solver functions
158
- def _get_solver_function(solver_name, xsp):
159
- """Get the appropriate solver function based on the backend."""
160
- if solver_name is None or callable(solver_name):
161
- return solver_name
162
-
163
- def _wrap_iterative_solver(base_solver, backend_type, solver_name=None):
164
- """Wrap an iterative solver to handle parameter mapping and return format."""
165
-
166
- def wrapped_solver(A, b, **solver_kwargs):
167
- # Create a copy to avoid modifying the original
168
- filtered_kwargs = solver_kwargs.copy()
169
-
170
- # Extract tolerance parameter and map to correct name for the backend
171
- tolerance = filtered_kwargs.pop("tol", None)
172
- atol = filtered_kwargs.pop("atol", None)
173
-
174
- # Define solver-specific parameter support
175
- solver_params = {
176
- "cg": {"rtol", "maxiter", "atol"} if backend_type == "scipy" else {"tol", "maxiter", "atol"},
177
- "cgs": {"rtol", "maxiter", "atol"} if backend_type == "scipy" else {"tol", "maxiter", "atol"},
178
- "minres": {"rtol", "maxiter", "shift"} if backend_type == "scipy" else {"tol", "maxiter"},
179
- "gmres": {"rtol", "maxiter", "atol"} if backend_type == "scipy" else {"tol", "maxiter", "atol"},
180
- }
181
-
182
- # Get supported parameters for this solver
183
- supported_params = solver_params.get(solver_name, set())
184
-
185
- if tolerance is not None:
186
- # Map tolerance parameter based on backend and solver support
187
- if backend_type == "scipy" and "rtol" in supported_params:
188
- filtered_kwargs["rtol"] = tolerance
189
- elif backend_type == "cupy" and "tol" in supported_params:
190
- filtered_kwargs["tol"] = tolerance
191
-
192
- # Handle atol parameter if supported
193
- if atol is not None and "atol" in supported_params:
194
- filtered_kwargs["atol"] = atol
195
-
196
- # Filter out unsupported parameters
197
- final_kwargs = {
198
- k: v
199
- for k, v in filtered_kwargs.items()
200
- if k in supported_params
201
- or k in {"x0", "M", "callback", "show", "check"} # Always allow common parameters
202
- }
203
-
204
- # Call the base solver
205
- result = base_solver(A, b, **final_kwargs)
206
-
207
- # Handle return format - some solvers return (solution, info) tuples
208
- if isinstance(result, tuple):
209
- return result[0] # Return just the solution
210
- return result
211
-
212
- return wrapped_solver
213
-
214
- def _wrap_direct_solver(base_solver):
215
- """Wrap a direct solver to ignore tolerance parameters."""
216
-
217
- def wrapped_solver(A, b, **solver_kwargs):
218
- # Direct solvers don't use iterative solver parameters, so ignore them all
219
- filtered_kwargs = {
220
- k: v
221
- for k, v in solver_kwargs.items()
222
- if k not in ["tol", "tolerance", "atol", "rtol", "maxiter", "matvec_max"]
223
- }
224
- return base_solver(A, b, **filtered_kwargs)
225
-
226
- return wrapped_solver
227
-
228
- # Determine backend type
229
- backend_type = "scipy" if A.device.type == "cpu" else "cupy"
230
-
231
- solver_map = {
232
- "cg": _wrap_iterative_solver(xsp.linalg.cg, backend_type, "cg"),
233
- "cgs": _wrap_iterative_solver(xsp.linalg.cgs, backend_type, "cgs"),
234
- "minres": _wrap_iterative_solver(xsp.linalg.minres, backend_type, "minres"),
235
- "gmres": _wrap_iterative_solver(xsp.linalg.gmres, backend_type, "gmres"),
236
- "spsolve": _wrap_direct_solver(xsp.linalg.spsolve),
237
- }
238
-
239
- if solver_name not in solver_map:
240
- raise ValueError(f"Unknown solver: {solver_name}. Supported solvers: {list(solver_map.keys())}")
241
-
242
- return solver_map[solver_name]
243
-
244
- # Get the appropriate backend modules
242
+ # Convert string solver names to functions
245
243
  xp, xsp = tsgucupy._get_array_modules(A.data)
246
244
 
247
245
  # Warn about dtype issues with minres on CPU
@@ -262,9 +260,8 @@ def sparse_solve_c4t(
262
260
  stacklevel=2,
263
261
  )
264
262
 
265
- # Convert string solver names to functions
266
- solve_func = _get_solver_function(solve, xsp)
267
- transpose_solve_func = _get_solver_function(transpose_solve, xsp)
263
+ solve_func = _get_solver_function(solve, xsp, A.device)
264
+ transpose_solve_func = _get_solver_function(transpose_solve, xsp, A.device)
268
265
 
269
266
  return SparseSolveC4T.apply(A, B, solve_func, transpose_solve_func, kwargs)
270
267
 
@@ -307,7 +304,7 @@ class SparseSolveC4T(torch.autograd.Function):
307
304
  A_c = tsgucupy.t2c_csr(A.detach())
308
305
  else:
309
306
  raise TypeError(f"Unsupported layout type: {A.layout}")
310
- B_c = xp.asarray(B.detach())
307
+ B_c = tsgucupy._torch_to_backend(B.detach(), xp)
311
308
 
312
309
  # Solve the sparse system
313
310
  ctx.factorisedsolver = None
@@ -329,7 +326,7 @@ class SparseSolveC4T(torch.autograd.Function):
329
326
  # If the solver returns a tuple, we assume the first element is the solution
330
327
  x_c = x_c[0]
331
328
 
332
- x = torch.as_tensor(x_c, device=A.device)
329
+ x = tsgucupy._backend_to_torch(x_c)
333
330
 
334
331
  # Ensure output dtype matches input dtype
335
332
  if x.dtype != A.dtype:
@@ -354,7 +351,7 @@ class SparseSolveC4T(torch.autograd.Function):
354
351
  x = x.unsqueeze(-1)
355
352
  grad = grad.unsqueeze(-1)
356
353
 
357
- grad_c = xp.asarray(grad.detach())
354
+ grad_c = tsgucupy._torch_to_backend(grad.detach(), xp)
358
355
 
359
356
  # Backprop rule: gradB = A^{-T} grad
360
357
  if ctx.transpose_solve is not None:
@@ -369,7 +366,7 @@ class SparseSolveC4T(torch.autograd.Function):
369
366
  # If the solver returns a tuple, we assume the first element is the gradient
370
367
  gradB_c = gradB_c[0]
371
368
 
372
- gradB = torch.as_tensor(gradB_c, device=A.device)
369
+ gradB = tsgucupy._backend_to_torch(gradB_c)
373
370
 
374
371
  # Ensure gradient dtype matches input dtype
375
372
  if gradB.dtype != A.dtype:
@@ -1,4 +1,5 @@
1
1
  import torch
2
+ from packaging.version import parse as parse_version
2
3
 
3
4
  try:
4
5
  import dgl.ops as dglops
@@ -73,7 +74,7 @@ def segment_mm(a: torch.Tensor, b: torch.Tensor, seglen_a: torch.Tensor) -> torc
73
74
  >>> segment_mm(a, b, seglen_a).shape
74
75
  torch.Size([18, 2])
75
76
  """
76
- if torch.__version__ < (2, 4):
77
+ if parse_version(torch.__version__) < parse_version("2.4"):
77
78
  raise NotImplementedError("PyTorch version is too old for nested tensors")
78
79
 
79
80
  if dgl_installed:
@@ -177,7 +178,7 @@ def gather_mm(a: torch.Tensor, b: torch.Tensor, idx_b: torch.Tensor) -> torch.Te
177
178
  tensor([[1., 2.],
178
179
  [6., 8.]])
179
180
  """
180
- if torch.__version__ < (2, 4):
181
+ if parse_version(torch.__version__) < parse_version("2.4"):
181
182
  raise NotImplementedError("PyTorch version is too old for nested tensors")
182
183
 
183
184
  if dgl_installed:
@@ -150,7 +150,7 @@ class SparseMatMul(torch.autograd.Function):
150
150
 
151
151
  if ctx.batch_size is not None:
152
152
  A = sparse_block_diag(*A)
153
- B = torch.cat([*B])
153
+ B = B.reshape(-1, B.size(-1))
154
154
 
155
155
  x = torch.sparse.mm(A, B)
156
156
 
@@ -194,7 +194,7 @@ class SparseMatMul(torch.autograd.Function):
194
194
  raise ValueError(f"Unsupported layout: {A.layout}")
195
195
 
196
196
  if ctx.batch_size is not None:
197
- grad_for_A = torch.cat([*grad])
197
+ grad_for_A = grad.reshape(-1, grad.size(-1))
198
198
  else:
199
199
  grad_for_A = grad
200
200
 
@@ -221,7 +221,7 @@ class SparseMatMul(torch.autograd.Function):
221
221
  # -------- Only compute gradB if needed --------
222
222
  if ctx.needs_input_grad[1]:
223
223
  if ctx.batch_size is not None:
224
- grad_for_B = torch.cat([*grad])
224
+ grad_for_B = grad.reshape(-1, grad.size(-1))
225
225
  else:
226
226
  grad_for_B = grad
227
227
 
@@ -171,7 +171,7 @@ class SparseTriangularSolve(torch.autograd.Function):
171
171
 
172
172
  if ctx.batch_size is not None:
173
173
  A = sparse_block_diag(*A)
174
- B = torch.cat([*B])
174
+ B = B.reshape(-1, B.size(-1))
175
175
 
176
176
  if A.layout == torch.sparse_coo:
177
177
  A = convert_coo_to_csr(A) # NOTE: triangular solve doesn't work with sparse coo
@@ -194,7 +194,7 @@ class SparseTriangularSolve(torch.autograd.Function):
194
194
  @staticmethod
195
195
  def backward(ctx, grad): # type: ignore[override]
196
196
  if ctx.batch_size is not None:
197
- grad = torch.cat([*grad])
197
+ grad = grad.reshape(-1, grad.size(-1))
198
198
 
199
199
  A, x = ctx.saved_tensors
200
200
 
@@ -0,0 +1,48 @@
1
+ import os
2
+ import random
3
+
4
+ import numpy as np
5
+ import pytest
6
+ import torch
7
+
8
+ SEED = 42
9
+
10
+
11
+ def _seed_unlocked() -> bool:
12
+ unlock = os.getenv("TSGU_UNLOCK_SEED", os.getenv("UNLOCK_SEED", "false"))
13
+ return unlock.lower() in {"1", "true", "yes", "on"}
14
+
15
+
16
+ def _seed_all(seed: int = SEED) -> None:
17
+ random.seed(seed)
18
+ np.random.seed(seed)
19
+ torch.manual_seed(seed)
20
+ if torch.cuda.is_available():
21
+ torch.cuda.manual_seed_all(seed)
22
+
23
+
24
+ def pytest_configure(config):
25
+ if not _seed_unlocked():
26
+ _seed_all()
27
+
28
+
29
+ @pytest.fixture(autouse=True)
30
+ def seed_rng():
31
+ if _seed_unlocked():
32
+ yield
33
+ return
34
+
35
+ torch_state = torch.get_rng_state()
36
+ cuda_states = torch.cuda.get_rng_state_all() if torch.cuda.is_available() else None
37
+ random_state = random.getstate()
38
+ numpy_state = np.random.get_state()
39
+
40
+ _seed_all()
41
+ try:
42
+ yield
43
+ finally:
44
+ torch.set_rng_state(torch_state)
45
+ if cuda_states is not None:
46
+ torch.cuda.set_rng_state_all(cuda_states)
47
+ random.setstate(random_state)
48
+ np.random.set_state(numpy_state)