Skip to content

Consolidate sparse utilities - #8288

Merged
rapids-bot[bot] merged 11 commits into
NVIDIA:mainfrom
jcrist:consolidate-sparse
Jun 24, 2026
Merged

Consolidate sparse utilities#8288
rapids-bot[bot] merged 11 commits into
NVIDIA:mainfrom
jcrist:consolidate-sparse

Conversation

@jcrist

@jcrist jcrist commented Jun 24, 2026

Copy link
Copy Markdown
Member

This was motivated as a followup to some recent cleanup work (#8272 and #8269), aiming to consolidate some modules and delete some dead code, but in the process fixed a few bugs and added one feature.

Highlights a user would care about

  • Fixes PCA on sparse inputs submitted in COO format.
  • Adds support for large sparse matrices to PCA. Fixes Support large sparse matrices in PCA #8159.
  • Fixes a long standing bug in sparse PCA where the incorrect ddof was used when computing the covariance.

Details a maintainer would care about

  • Consolidates cuml.common.sparse_utils and cuml.common.sparsefuncs into cuml.common.sparse
  • Moves create_csr_matrix_from_count_df to cuml.feature_extraction._vectorizers, the only module it's used in.
  • Moves cuml.prims.stats.cov to cuml.common.sparse (keeping only the sparse version, as that's the only version we use), and removes the remaining empty cuml.prims module.
  • Some mild code cleanups
  • Improved test coverage for remaining functions in cuml.common.stats
  • Removes duplicate versions of csr_row_normalize_l1 and csr_row_normalize_l2 from cuml.thirdparty_adapters. I ran some tests and benchmarks just to be sure - the versions in cuml.common.sparse using cupy are consistently faster (~15%) and yield equivalent results. Less code to manage, and less numba.cuda usage. We're almost to a point where this won't be a required dep either.
  • Removes buggy/incorrect specialization of cov for COO matrices. This wasn't caught with the previous test, but was caught with a more thorough one on denser data. I spent some time trying to fix the kernel, but decided that relying on the single CSR kernel implementation was simpler, more correct, and as performant.
  • I attempted to remove the fallback grammian computation kernel in cov to rely on X.T.dot(X) instead in cupy >= 14. Removing the fallback in that case resulted in a nice speedup on small inputs, but when tested on larger data I ran into issues - unfortunately there still appears to be a bug there. I opened Incorrect results for sparse gemm cupy/cupy#10033, and updated the inline comment to note the new issue.
  • Fixed a long standing bug in our sparse PCA implementation where the incorrect ddof was used when computing cov. I've improved the test suite to catch the issue going forward. This also fixed 300+ xfails in the sklearn compat test suite.
  • Added support for large (int64 indices) sparse matrices to PCA. This was relatively straightforward to do. Like other cases, this is hard to test in CI (cupy rightfully normalizes indices as int32 when possible), but I can confirm things work properly from local testing.

There are still some dispersed sparse utilities in cuml.thirdparty_adapters/cuml._thirdparty.sklearn.utils.sparsefuncs. In the interest of time I opted to skip these for now (sans relying on any implementations in cuml.common.sparse instead, as noted above). Further migration work may happen in followup(s).

@jcrist jcrist self-assigned this Jun 24, 2026
@jcrist
jcrist requested a review from a team as a code owner June 24, 2026 05:23
@jcrist jcrist added the improvement Improvement / enhancement to an existing function label Jun 24, 2026
@jcrist
jcrist requested a review from dantegd June 24, 2026 05:23
@jcrist jcrist added the non-breaking Non-breaking change label Jun 24, 2026
@github-actions github-actions Bot added the Cython / Python Cython or Python issue label Jun 24, 2026
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Sparse helpers are consolidated into cuml.common.sparse. Legacy sparse helper modules are removed, sparse imports are redirected, PCA uses the new sparse covariance path, vectorizer CSR construction is inlined, and sparse-related tests are updated.

Changes

Sparse utility consolidation

Layer / File(s) Summary
New sparse helper module
python/cuml/cuml/common/sparse.py
Defines sparse detection, CSR row normalization, diagonal CSR scaling, and sparse covariance/mean computation in one module.
Legacy sparse modules removed
python/cuml/cuml/common/sparsefuncs.py, python/cuml/cuml/common/sparse_utils.py, python/cuml/cuml/thirdparty_adapters/sparsefuncs_fast.py, python/cuml/cuml/prims/stats/covariance.py, python/cuml/cuml/prims/stats/__init__.py, python/cuml/cuml/prims/__init__.py, python/cuml/cuml/decomposition/pca.pyx
Removes the previous sparse helper implementations, the old covariance export, and the PCA import of cov.
Vectorizer CSR construction
python/cuml/cuml/feature_extraction/_vectorizers.py, python/cuml/cuml/_thirdparty/sklearn/preprocessing/_data.py, python/cuml/cuml/feature_extraction/_tfidf.py
_vectorizers.py inlines CSR construction helpers and uses them in vectorizer entry points; sparse normalization imports move to cuml.common.sparse.
PCA sparse covariance path
python/cuml/cuml/decomposition/pca.pyx, python/cuml/tests/test_pca.py
Sparse PCA switches to sparse_cov_and_mean, stores the returned mean directly, accepts CSR sparse input, and updates PCA validation coverage.
Imports redirected to cuml.common.sparse
python/cuml/cuml/accel/_overrides/sklearn/..., python/cuml/cuml/accel/estimator_proxy.py, python/cuml/cuml/dask/linear_model/logistic_regression.py, python/cuml/cuml/decomposition/incremental_pca.py, python/cuml/cuml/internals/array.py, python/cuml/cuml/linear_model/elastic_net.py, python/cuml/cuml/manifold/..., python/cuml/cuml/neighbors/nearest_neighbors.pyx, python/cuml/cuml/svm/...
Sparse helper imports move from the removed modules to cuml.common.sparse, and nearest_neighbors.pyx changes its dense-input check to use is_sparse(X).
Tests and xfails updated
python/cuml/tests/test_sparse_utils.py, python/cuml/tests/test_metrics.py, python/cuml/tests/test_adapters.py, python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml
Sparse utility tests switch to cuml.common.sparse, adapter normalization tests are removed, and sparse PCA xfail entries are dropped.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • rapidsai/cuml#7922: Both PRs touch cuml/accel/_overrides/sklearn/linear_model.py sparse handling around is_sparse.
  • rapidsai/cuml#7943: Both PRs use the shared sparse-detection path in ElasticNet-related sparse input handling.
  • rapidsai/cuml#8207: Both PRs modify the upstream scikit-learn xfail list for PCA sparse cases.

Suggested reviewers

  • divyegala
  • betatim
  • viclafargue
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title concisely reflects the main change: consolidating sparse utilities.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description matches the changeset: it describes sparse-utility consolidation, PCA sparse fixes, moved helpers, and test updates.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/cuml/cuml/common/sparse.py`:
- Around line 44-57: Preserve the CSR index width throughout the CUDA sparse
kernels: the row-offset and column-index parameters in the normalizer and
Gram/Covariance paths are currently truncated to int, which breaks int64 CSR
inputs and large nnz offsets. Update the kernels behind the normalizers,
compute_gram, and the L2 normalization path to use the same dtype as
X.indptr/X.indices for indptr and indices, and make the loop counters/offset
arithmetic match that wider type instead of hard-coded int. Keep the fix
consistent across the affected sparse kernel helpers so row boundaries and
column access remain correct for large inputs.

In `@python/cuml/cuml/decomposition/pca.pyx`:
- Line 431: The sparse PCA path in `PCA._fit_sparse` is using
`sparse_cov_and_mean(X)`, which returns population covariance and breaks the
variance/singular value scaling expected by `explained_variance_`,
`singular_values_`, and `noise_variance_`. Update the sparse covariance
computation or its caller so `_fit_sparse` receives sample covariance normalized
by `n_samples - 1`, matching the dense PCA/sklearn behavior; keep the fix
localized around `sparse_cov_and_mean` and the `_fit_sparse` assignment.

In `@python/cuml/tests/test_sparse_utils.py`:
- Around line 49-61: The test_sparse_cov_and_mean case only checks the
covariance result from sparse_cov_and_mean, leaving the mean output unvalidated.
Update this test to also compute the expected mean from X.todense() (matching
the existing ddof/rowvar settings as applicable) and assert the returned mean
matches it with an appropriate tolerance, using the sparse_cov_and_mean symbol
and the existing res, _ unpacking.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 7c967ebc-cb7a-40ee-8ddf-53d5e0ba30b9

📥 Commits

Reviewing files that changed from the base of the PR and between 31d5439 and 90cff93.

📒 Files selected for processing (29)
  • python/cuml/cuml/_thirdparty/sklearn/preprocessing/_data.py
  • python/cuml/cuml/accel/_overrides/sklearn/linear_model.py
  • python/cuml/cuml/accel/_overrides/sklearn/manifold.py
  • python/cuml/cuml/accel/_overrides/sklearn/neighbors.py
  • python/cuml/cuml/accel/estimator_proxy.py
  • python/cuml/cuml/common/sparse.py
  • python/cuml/cuml/common/sparse_utils.py
  • python/cuml/cuml/common/sparsefuncs.py
  • python/cuml/cuml/dask/linear_model/logistic_regression.py
  • python/cuml/cuml/decomposition/incremental_pca.py
  • python/cuml/cuml/decomposition/pca.pyx
  • python/cuml/cuml/feature_extraction/_tfidf.py
  • python/cuml/cuml/feature_extraction/_vectorizers.py
  • python/cuml/cuml/internals/array.py
  • python/cuml/cuml/linear_model/elastic_net.py
  • python/cuml/cuml/manifold/t_sne.pyx
  • python/cuml/cuml/manifold/umap/umap.pyx
  • python/cuml/cuml/manifold/utils.py
  • python/cuml/cuml/neighbors/nearest_neighbors.pyx
  • python/cuml/cuml/prims/__init__.py
  • python/cuml/cuml/prims/stats/__init__.py
  • python/cuml/cuml/prims/stats/covariance.py
  • python/cuml/cuml/svm/svc.py
  • python/cuml/cuml/svm/svm_base.pyx
  • python/cuml/cuml/svm/svr.py
  • python/cuml/cuml/thirdparty_adapters/sparsefuncs_fast.py
  • python/cuml/tests/test_metrics.py
  • python/cuml/tests/test_sparse_utils.py
  • python/cuml/tests/test_stats.py
💤 Files with no reviewable changes (6)
  • python/cuml/cuml/prims/init.py
  • python/cuml/tests/test_stats.py
  • python/cuml/cuml/prims/stats/init.py
  • python/cuml/cuml/prims/stats/covariance.py
  • python/cuml/cuml/common/sparsefuncs.py
  • python/cuml/cuml/common/sparse_utils.py

Comment thread python/cuml/cuml/common/sparse.py Outdated
Comment thread python/cuml/cuml/decomposition/pca.pyx
Comment thread python/cuml/tests/test_sparse_utils.py Outdated

@dantegd dantegd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two small questions, otherwise the changes look good to me

Comment thread python/cuml/cuml/common/sparse.py Outdated
Comment thread python/cuml/cuml/common/sparse.py Outdated
jcrist added 10 commits June 24, 2026 12:16
Moves this function into the only module it's actually used in. This
function was too specific to `CountVectorizer` to make sense outside of
this module.
This module contained only one tiny function.
Also revert workaround for cupyx.scipy.sparse matrix multiplication on
cupy >= 14.
There's still a bug here.
Fixes a longstanding bug in our sparse PCA implementation, where the
denominator was `n_samples` instead of `n_samples - 1`. I've updated the
tests to catch this issue, and fixed the bug.

Also fixed a few other asks in `cuml.common.sparse` from review.
@jcrist
jcrist force-pushed the consolidate-sparse branch from f062d05 to 915d24e Compare June 24, 2026 18:30

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/cuml/tests/test_pca.py`:
- Line 51: The PCA test is comparing components_ directly, which fails when the
valid solution differs only by sign. Update test_pca.py around the PCA
assertions to make the components_ check sign-invariant again, using the
existing PCA result objects (res and sol) so either matching sign is accepted,
while leaving the direct comparisons for sign-stable attributes unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 9799f74c-1894-47cb-b794-b8454962f236

📥 Commits

Reviewing files that changed from the base of the PR and between f062d05 and 915d24e.

📒 Files selected for processing (33)
  • python/cuml/cuml/_thirdparty/sklearn/preprocessing/_data.py
  • python/cuml/cuml/accel/_overrides/sklearn/linear_model.py
  • python/cuml/cuml/accel/_overrides/sklearn/manifold.py
  • python/cuml/cuml/accel/_overrides/sklearn/neighbors.py
  • python/cuml/cuml/accel/estimator_proxy.py
  • python/cuml/cuml/common/sparse.py
  • python/cuml/cuml/common/sparse_utils.py
  • python/cuml/cuml/common/sparsefuncs.py
  • python/cuml/cuml/dask/linear_model/logistic_regression.py
  • python/cuml/cuml/decomposition/incremental_pca.py
  • python/cuml/cuml/decomposition/pca.pyx
  • python/cuml/cuml/feature_extraction/_tfidf.py
  • python/cuml/cuml/feature_extraction/_vectorizers.py
  • python/cuml/cuml/internals/array.py
  • python/cuml/cuml/linear_model/elastic_net.py
  • python/cuml/cuml/manifold/t_sne.pyx
  • python/cuml/cuml/manifold/umap/umap.pyx
  • python/cuml/cuml/manifold/utils.py
  • python/cuml/cuml/neighbors/nearest_neighbors.pyx
  • python/cuml/cuml/prims/__init__.py
  • python/cuml/cuml/prims/stats/__init__.py
  • python/cuml/cuml/prims/stats/covariance.py
  • python/cuml/cuml/svm/svc.py
  • python/cuml/cuml/svm/svm_base.pyx
  • python/cuml/cuml/svm/svr.py
  • python/cuml/cuml/thirdparty_adapters/sparsefuncs_fast.py
  • python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml
  • python/cuml/tests/test_adapters.py
  • python/cuml/tests/test_metrics.py
  • python/cuml/tests/test_pca.py
  • python/cuml/tests/test_sparse_utils.py
  • python/cuml/tests/test_sparsefuncs.py
  • python/cuml/tests/test_stats.py
💤 Files with no reviewable changes (9)
  • python/cuml/cuml/prims/stats/init.py
  • python/cuml/cuml/prims/init.py
  • python/cuml/tests/test_sparsefuncs.py
  • python/cuml/tests/test_stats.py
  • python/cuml/tests/test_adapters.py
  • python/cuml/cuml/common/sparsefuncs.py
  • python/cuml/cuml/common/sparse_utils.py
  • python/cuml/cuml/prims/stats/covariance.py
  • python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml
✅ Files skipped from review due to trivial changes (6)
  • python/cuml/cuml/svm/svr.py
  • python/cuml/cuml/decomposition/incremental_pca.py
  • python/cuml/cuml/linear_model/elastic_net.py
  • python/cuml/cuml/svm/svc.py
  • python/cuml/cuml/manifold/utils.py
  • python/cuml/cuml/manifold/umap/umap.pyx
🚧 Files skipped from review as they are similar to previous changes (15)
  • python/cuml/cuml/svm/svm_base.pyx
  • python/cuml/cuml/feature_extraction/_tfidf.py
  • python/cuml/cuml/accel/_overrides/sklearn/linear_model.py
  • python/cuml/cuml/internals/array.py
  • python/cuml/cuml/accel/estimator_proxy.py
  • python/cuml/cuml/manifold/t_sne.pyx
  • python/cuml/cuml/_thirdparty/sklearn/preprocessing/_data.py
  • python/cuml/tests/test_metrics.py
  • python/cuml/cuml/accel/_overrides/sklearn/neighbors.py
  • python/cuml/cuml/accel/_overrides/sklearn/manifold.py
  • python/cuml/cuml/neighbors/nearest_neighbors.pyx
  • python/cuml/cuml/decomposition/pca.pyx
  • python/cuml/cuml/dask/linear_model/logistic_regression.py
  • python/cuml/cuml/thirdparty_adapters/sparsefuncs_fast.py
  • python/cuml/cuml/feature_extraction/_vectorizers.py

Comment thread python/cuml/tests/test_pca.py
@jcrist

jcrist commented Jun 24, 2026

Copy link
Copy Markdown
Member Author

The bot actually pointed out a long standing bug, which I've now fixed. I've updated the top-level comment accordingly with the new changes. I believe this should be good-to-go.

@jcrist

jcrist commented Jun 24, 2026

Copy link
Copy Markdown
Member Author

/merge

@rapids-bot
rapids-bot Bot merged commit c453fc2 into NVIDIA:main Jun 24, 2026
180 of 182 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Cython / Python Cython or Python issue improvement Improvement / enhancement to an existing function non-breaking Non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support large sparse matrices in PCA

4 participants