Skip to content

Add opt-in CUDA backend for normalized VCS view matmul - #42

Closed
aarmey wants to merge 2 commits into
vsparse-normalization-recipesfrom
vsparse-cuda-matmul
Closed

aarmey wants to merge 2 commits into
vsparse-normalization-recipesfrom
vsparse-cuda-matmul

Conversation

@aarmey

@aarmey aarmey commented Sep 10, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds GPUNormalizedVCS, a device-resident copy of a VCSCArrayNormalized/VCSRArrayNormalized view, built explicitly via a new .to_gpu() method. Its @/__rmatmul__ mirror _vcs_matmul's baseline + Delta decomposition using hand-written CuPy RawKernels instead of the CPU Numba kernels, computing in float32 for throughput on consumer/GeForce GPUs (return values are still float64, matching the CPU API).
  • Deliberately opt-in: the plain CPU view's @/__rmatmul__ are untouched and always use the existing float64 Numba path. A CUDA device being present never changes existing behavior/precision.
  • Adds an optional cuda dependency group (cupy-cuda12x).
  • Fixes a real import-order footgun affecting any CUDA use in this process: zarr registers a pytest plugin, and anndata's zarr backend both speculatively import cupy (zarr.core.buffer.gpu); CuPy's NVRTC header discovery breaks for the rest of the process if CuPy's first import happens that way instead of through vsparse. vsparse/__init__.py now imports _vcs_matmul_cuda (which configures CUDA_PATH before importing cupy) first; tests/conftest.py does the same for the test suite; pyproject.toml disables zarr's pytest-plugin autoload during tests.
  • Adds CUDA-vs-CPU parity tests (skipped when no working CUDA device/toolkit is available) across all 5 normalization recipes and both VCSCArray/VCSRArray.

Test plan

  • uv run pytest tests/ — 1655 passed, 111 skipped (pre-existing all-zero/shape-too-small skips only) — includes real CUDA execution on 2 physical GPUs (RTX 4090, RTX 5080), verified against the CPU kernels.
  • uv run ruff check . — clean
  • uv run ty check — clean (one benign unused-ignore-comment warning, matching this repo's existing convention for optional-import lines)

aarmey and others added 2 commits September 9, 2026 19:14
GPUNormalizedVCS (built via VCSCArrayNormalized/VCSRArrayNormalized.to_gpu())
mirrors _vcs_matmul's baseline+Delta decomposition with hand-written CuPy
RawKernels instead of Numba, computing in float32. This is opt-in only:
plain `@`/`__rmatmul__` on the CPU view are untouched and always use the
existing float64 Numba path, so precision/behavior don't change based on
whether CUDA happens to be available.

Also fixes an import-order issue affecting any CUDA use in this process:
zarr registers a pytest plugin and anndata's zarr backend both import cupy
speculatively (zarr.core.buffer.gpu), and CuPy's NVRTC header discovery
breaks for the rest of the process if cupy's first import happens that way
rather than through vsparse. vsparse/__init__.py now imports
_vcs_matmul_cuda (which configures CUDA_PATH before importing cupy) first;
tests/conftest.py does the same for the test suite, and pyproject.toml
disables zarr's pytest plugin autoload.

Adds an optional `cuda` extra (cupy-cuda12x) and CUDA-vs-CPU parity tests,
skipped when no working CUDA device/toolkit is available.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
resolve_recipe() (and the docstring) already accept a Recipe instance
directly, but the signature only declared `view: str`, so passing a
custom Recipe (e.g. a stock recipe with center=False for a non-negative
consumer) failed type-checking despite working correctly at runtime.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@aarmey
aarmey deleted the branch vsparse-normalization-recipes September 11, 2026 00:37
@aarmey aarmey closed this Sep 11, 2026
@aarmey
aarmey deleted the vsparse-cuda-matmul branch September 14, 2026 17:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant