Skip to content

Add sparse input support to ElasticNet/Lasso - #7943

Merged
rapids-bot[bot] merged 6 commits into
NVIDIA:mainfrom
jcrist:sparse-elastic-net
Apr 7, 2026
Merged

Add sparse input support to ElasticNet/Lasso#7943
rapids-bot[bot] merged 6 commits into
NVIDIA:mainfrom
jcrist:sparse-elastic-net

Conversation

@jcrist

@jcrist jcrist commented Apr 1, 2026

Copy link
Copy Markdown
Member

This:

  • Adds sparse input support to ElasticNet and Lasso, based on the existing QN solver. To accomplish this, we change the default of solver to 'auto', which will use 'cd' when dense and 'qn' when sparse.
  • Consolidates tests for ElasticNet and Lasso in test_elastic_net.py. Previously these were duplicated and spread among a few files.
  • Adds new tests for the new sparse functionality
  • Updates the cuml.accel integration and docs accordingly. Most new xfails are due to numerical equivalences or lack of support for dual_gap_.

Fixes #7912.

@jcrist jcrist self-assigned this Apr 1, 2026
@jcrist
jcrist requested a review from a team as a code owner April 1, 2026 18:46
@jcrist jcrist added feature request New feature or request non-breaking Non-breaking change labels Apr 1, 2026
@jcrist
jcrist requested a review from divyegala April 1, 2026 18:46
@github-actions github-actions Bot added the Cython / Python Cython or Python issue label Apr 1, 2026
@coderabbitai

coderabbitai Bot commented Apr 1, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b934e1e3-a901-43d8-ab5e-f1093edc5cc5

📥 Commits

Reviewing files that changed from the base of the PR and between 21497bd and 198932a.

📒 Files selected for processing (1)
  • python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Sparse input support for ElasticNet and Lasso.
    • New "auto" solver default that selects solver based on input sparsity.
    • New sparse_coef_ property to return coefficients in sparse format.
  • Bug Fixes

    • GPU fitting now validates targets and rejects multi-output targets with a clear error.
  • Documentation

    • Updated limitations doc (removed prior CPU-fallback note for sparse inputs).
  • Tests

    • Expanded sparse/regression tests and updated expected-failure entries for sparse scenarios.

Walkthrough

ElasticNet and Lasso were extended to handle sparse inputs: added sparse-input tagging and a reflected sparse_coef_ property, changed solver default to "auto" (resolves to "qn" for sparse, "cd" for dense), tightened solver validation, updated GPU-fit target coercion/validation, and adjusted tests, docs, and xfails.

Changes

Cohort / File(s) Summary
Documentation
docs/source/cuml-accel/limitations.rst
Removed the documented CPU-fallback condition "If X is sparse" for ElasticNet and Lasso.
Public models
python/cuml/cuml/linear_model/elastic_net.py, python/cuml/cuml/linear_model/lasso.py
Added sparse-input support (added SparseInputTagMixin to ElasticNet), changed solver default to "auto" with auto-resolution ("qn" for sparse, "cd" for dense), made "cd" explicitly reject sparse inputs, improved solver error messages, and added a reflected sparse_coef_ property.
Accelerated sklearn overrides
python/cuml/cuml/accel/_overrides/sklearn/linear_model.py
Coerce y using input_to_cuml_array(..., convert_to_mem_type=False)[0] in _gpu_fit and raise on multi-output targets before invoking GPU fit.
Tests — new/expanded
python/cuml/tests/test_elastic_net.py
Added Hypothesis-driven solver-equivalence tests, sparse-regression tests comparing cuML to scikit-learn, assertions for sparse_coef_, and solver error message tests.
Tests — removed/updated
python/cuml/tests/test_linear_model.py, python/cuml/tests/test_exceptions.py
Removed legacy ElasticNet tests and removed ElasticNet/Lasso from the sparse-exception parametrization; test coverage reorganized into new/updated tests.
CI / xfails
python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml, python/cuml/tests/test_sklearn_compatibility.py
Expanded and re-targeted xfails for sparse coordinate-descent cases; added sample-weight / sparse-data xfails for ElasticNet/Lasso QN solver; adjusted multiple sparse-input xfail entries.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • divyegala
  • dantegd
  • viclafargue
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add sparse input support to ElasticNet/Lasso' directly and clearly describes the primary change: adding sparse input support to these two estimators.
Description check ✅ Passed The description is well-related to the changeset, detailing sparse input support addition, solver default change to 'auto', test consolidation, new sparse tests, and cuml.accel updates.
Linked Issues check ✅ Passed The PR fully implements the objective of adding sparse input support to ElasticNet/Lasso using QN solver, with appropriate solver selection logic and comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are directly related to sparse input support for ElasticNet/Lasso: solver updates, sparse handling, test consolidation, and cuml.accel integration aligned with the PR objective.

✏️ 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 and usage tips.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
python/cuml/cuml/linear_model/lasso.py (1)

33-46: ⚠️ Potential issue | 🟡 Minor

Clarify that selection only affects coordinate descent.

solver='auto' now resolves to qn for sparse inputs, but the selection paragraph still reads as if it always changes fitting behavior. Please mirror ElasticNet here and state that selection is only used when the resolved solver is 'cd'.

As per coding guidelines, "Missing docstrings for public methods, undocumented hyperparameters, or missing scikit-learn compatibility notes in documentation must be addressed."

Also applies to: 125-125

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/cuml/cuml/linear_model/lasso.py` around lines 33 - 46, Update the
Lasso docstring to clarify that the selection parameter only applies when the
coordinate descent solver is used: explicitly state that when solver resolves to
'cd' (including when solver='auto' resolves to 'cd' for dense inputs) the
selection option ('cyclic' or 'random') affects coefficient updates, and that
selection is ignored when the resolved solver is 'qn' (including when
solver='auto' resolves to 'qn' for sparse inputs); mirror the wording used in
ElasticNet's docstring so the behavior is consistent and also update any other
Lasso method docstring mentioning selection to the same wording.
🧹 Nitpick comments (1)
python/cuml/tests/test_elastic_net.py (1)

369-390: Please exercise at least one sparse-array input here.

This only covers scipy.sparse.csr_matrix, but the new code path is keyed off generic sparse detection and the same PR adds separate sparse-array / other sparse-format xfails upstream. Adding a csr_array case here would give us a local guardrail for the sparse-array path too.

As per coding guidelines, "Test files must validate numerical correctness by comparing with scikit-learn, include edge case coverage (empty datasets, single sample, high-dimensional data), test fit/predict/transform consistency, and test different input types (cuDF, pandas, NumPy)."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/cuml/tests/test_elastic_net.py` around lines 369 - 390, The
test_sparse function currently only exercises scipy.sparse.csr_matrix; add a
sparse-array case to exercise the new sparse-array code path by parameterizing
or branching on an input_type and passing a scipy.sparse.csr_array into the
training/prediction code. Concretely, update the test_sparse parametrize to
include an input_type (e.g., ["csr_matrix", "csr_array"]) or add a small loop
inside test_sparse that converts X to scipy.sparse.csr_array when the csr_array
case is selected, then proceed to construct cu_model/sk_model, fit, and compare
coef_/intercept_/score as before (leave test names and assertions unchanged) so
the same numerical comparisons cover both csr_matrix and csr_array inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@python/cuml/cuml/linear_model/elastic_net.py`:
- Around line 30-35: The current SparseInputTagMixin together with is_sparse(X)
routes all scipy/cupyx sparse inputs into the QN/GPU path even though we still
mark some sparse cases as xfail (e.g., sample_weight on sparse inputs and
specific csc_*/lil_* cases in upstream/scikit-learn/xfail-list.yaml); update
ElasticNet's sparse handling to either (a) narrow the sparse contract by
changing the gating logic in SparseInputTagMixin/is_sparse(X) so only supported
sparse formats (e.g., CSR/CSC without sample_weight) are routed to the QN GPU
path, or (b) add explicit guards in ElasticNet (or the QN entrypoint) that
detect the unsupported combinations (sparse sample_weight, csc/lil formats) and
fall back to the CPU/scikit-learn code path; reference and modify the
SparseInputTagMixin, is_sparse(X) checks and the ElasticNet QN dispatch to
ensure parity with scikit-learn edge-case behavior and avoid sending xfailed
cases to the GPU path.

---

Outside diff comments:
In `@python/cuml/cuml/linear_model/lasso.py`:
- Around line 33-46: Update the Lasso docstring to clarify that the selection
parameter only applies when the coordinate descent solver is used: explicitly
state that when solver resolves to 'cd' (including when solver='auto' resolves
to 'cd' for dense inputs) the selection option ('cyclic' or 'random') affects
coefficient updates, and that selection is ignored when the resolved solver is
'qn' (including when solver='auto' resolves to 'qn' for sparse inputs); mirror
the wording used in ElasticNet's docstring so the behavior is consistent and
also update any other Lasso method docstring mentioning selection to the same
wording.

---

Nitpick comments:
In `@python/cuml/tests/test_elastic_net.py`:
- Around line 369-390: The test_sparse function currently only exercises
scipy.sparse.csr_matrix; add a sparse-array case to exercise the new
sparse-array code path by parameterizing or branching on an input_type and
passing a scipy.sparse.csr_array into the training/prediction code. Concretely,
update the test_sparse parametrize to include an input_type (e.g.,
["csr_matrix", "csr_array"]) or add a small loop inside test_sparse that
converts X to scipy.sparse.csr_array when the csr_array case is selected, then
proceed to construct cu_model/sk_model, fit, and compare coef_/intercept_/score
as before (leave test names and assertions unchanged) so the same numerical
comparisons cover both csr_matrix and csr_array inputs.
🪄 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: Pro

Run ID: a72e33dd-6a9a-49eb-81be-ea08f41b8910

📥 Commits

Reviewing files that changed from the base of the PR and between ea4d5a2 and 84b564d.

📒 Files selected for processing (8)
  • docs/source/cuml-accel/limitations.rst
  • python/cuml/cuml/accel/_overrides/sklearn/linear_model.py
  • python/cuml/cuml/linear_model/elastic_net.py
  • python/cuml/cuml/linear_model/lasso.py
  • python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml
  • python/cuml/tests/test_elastic_net.py
  • python/cuml/tests/test_exceptions.py
  • python/cuml/tests/test_linear_model.py
💤 Files with no reviewable changes (3)
  • python/cuml/tests/test_exceptions.py
  • docs/source/cuml-accel/limitations.rst
  • python/cuml/tests/test_linear_model.py

Comment thread python/cuml/cuml/linear_model/elastic_net.py
@jcrist
jcrist force-pushed the sparse-elastic-net branch from 84b564d to 21497bd Compare April 6, 2026 16:35

@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.

♻️ Duplicate comments (1)
python/cuml/cuml/linear_model/elastic_net.py (1)

249-275: ⚠️ Potential issue | 🔴 Critical

Fail fast on sparse sample_weight until the QN path is correct.

This branch now accepts fit(X_sparse, y, sample_weight=...), but the same PR has to xfail check_sample_weight_equivalence_on_sparse_data for both ElasticNet and Lasso. That turns a previously unsupported combination into a silently wrong one. Please reject sparse weighted fits here (or route them through a verified CPU fallback) instead of sending them to fit_qn().

🛡️ Proposed guard
-        solver = self.solver
-        if solver == "auto":
-            solver = "qn" if is_sparse(X) else "cd"
+        is_sparse_x = is_sparse(X)
+        solver = self.solver
+        if solver == "auto":
+            solver = "qn" if is_sparse_x else "cd"
+
+        if is_sparse_x and sample_weight is not None:
+            raise ValueError(
+                "sample_weight with sparse inputs is not supported yet; "
+                "use dense inputs or omit sample_weight"
+            )
 
         if solver == "qn":
             coef, intercept, n_iter, _ = fit_qn(
                 X,
                 y,
@@
-        elif solver == "cd":
-            if is_sparse(X):
+        elif solver == "cd":
+            if is_sparse_x:
                 raise ValueError(

As per coding guidelines, "Logic errors in ML algorithm implementations, incorrect distance metrics, kernels, or loss function implementations, numerical instability causing wrong results, and incorrect model parameter initialization must be corrected" and "Silent data corruption from type coercion, incorrect handling of cuDF vs pandas vs NumPy inputs, or missing validation causing crashes on invalid input must be addressed".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/cuml/cuml/linear_model/elastic_net.py` around lines 249 - 275, The
code currently allows sparse sample_weight to be passed into the QN path
(fit_qn) which can yield silently incorrect results; add a guard that detects a
sparse sample_weight (using the same is_sparse check) before calling fit_qn (and
when solver=="auto" that resolves to "qn") and raise a ValueError instructing
the user that sparse sample_weight is unsupported for ElasticNet (or suggest
using solver='cd' or a dense sample_weight/CPU fallback). Specifically, update
the branch that sets solver (and the subsequent if solver == "qn" block) to
check if sample_weight is not None and is_sparse(sample_weight) and then raise
an explanatory ValueError prior to invoking fit_qn; reference the symbols
solver, is_sparse, sample_weight, and fit_qn so the change is easy to locate.
🧹 Nitpick comments (1)
python/cuml/tests/test_elastic_net.py (1)

393-400: Cover Lasso in the solver-error regression too.

The new solver contract is public on both estimators, but this only exercises ElasticNet. Parameterizing the test over cuml.Lasso as well will keep Lasso.__init__/param forwarding from drifting away from the inherited ElasticNet.fit() checks.

♻️ Proposed change
-def test_solver_errors():
+@pytest.mark.parametrize("cls", [cuml.ElasticNet, cuml.Lasso])
+def test_solver_errors(cls):
     X, y = make_sparse_regression()
 
     with pytest.raises(ValueError, match="solver='bad' is not supported"):
-        cuml.ElasticNet(solver="bad").fit(X, y)
+        cls(solver="bad").fit(X, y)
 
     with pytest.raises(ValueError, match="solver='cd' doesn't support sparse"):
-        cuml.ElasticNet(solver="cd").fit(X, y)
+        cls(solver="cd").fit(X, y)

As per coding guidelines, "Update unit tests when making code changes".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/cuml/tests/test_elastic_net.py` around lines 393 - 400, The test only
checks solver-related errors for cuml.ElasticNet but should also cover
cuml.Lasso to ensure Lasso.__init__/parameter forwarding enforces the same
solver contract; update test_solver_errors to parameterize the test over the
estimator class (e.g., using a loop or pytest.mark.parametrize) and run the same
two assertions (bad solver and cd-with-sparse) against both cuml.ElasticNet and
cuml.Lasso so both estimators raise the same ValueErrors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@python/cuml/cuml/linear_model/elastic_net.py`:
- Around line 249-275: The code currently allows sparse sample_weight to be
passed into the QN path (fit_qn) which can yield silently incorrect results; add
a guard that detects a sparse sample_weight (using the same is_sparse check)
before calling fit_qn (and when solver=="auto" that resolves to "qn") and raise
a ValueError instructing the user that sparse sample_weight is unsupported for
ElasticNet (or suggest using solver='cd' or a dense sample_weight/CPU fallback).
Specifically, update the branch that sets solver (and the subsequent if solver
== "qn" block) to check if sample_weight is not None and
is_sparse(sample_weight) and then raise an explanatory ValueError prior to
invoking fit_qn; reference the symbols solver, is_sparse, sample_weight, and
fit_qn so the change is easy to locate.

---

Nitpick comments:
In `@python/cuml/tests/test_elastic_net.py`:
- Around line 393-400: The test only checks solver-related errors for
cuml.ElasticNet but should also cover cuml.Lasso to ensure
Lasso.__init__/parameter forwarding enforces the same solver contract; update
test_solver_errors to parameterize the test over the estimator class (e.g.,
using a loop or pytest.mark.parametrize) and run the same two assertions (bad
solver and cd-with-sparse) against both cuml.ElasticNet and cuml.Lasso so both
estimators raise the same ValueErrors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 404e88fb-b090-4374-b5dc-45440b3d45e9

📥 Commits

Reviewing files that changed from the base of the PR and between 84b564d and 21497bd.

📒 Files selected for processing (9)
  • docs/source/cuml-accel/limitations.rst
  • python/cuml/cuml/accel/_overrides/sklearn/linear_model.py
  • python/cuml/cuml/linear_model/elastic_net.py
  • python/cuml/cuml/linear_model/lasso.py
  • python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml
  • python/cuml/tests/test_elastic_net.py
  • python/cuml/tests/test_exceptions.py
  • python/cuml/tests/test_linear_model.py
  • python/cuml/tests/test_sklearn_compatibility.py
💤 Files with no reviewable changes (3)
  • python/cuml/tests/test_exceptions.py
  • docs/source/cuml-accel/limitations.rst
  • python/cuml/tests/test_linear_model.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • python/cuml/cuml/accel/_overrides/sklearn/linear_model.py
  • python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml

@viclafargue viclafargue 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.

Thanks, LGTM

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

jcrist commented Apr 7, 2026

Copy link
Copy Markdown
Member Author

/merge

@rapids-bot
rapids-bot Bot merged commit b4eab6a into NVIDIA:main Apr 7, 2026
93 checks passed
@jcrist
jcrist deleted the sparse-elastic-net branch April 7, 2026 16:46
rapids-bot Bot pushed a commit that referenced this pull request Apr 8, 2026
- Splits out new test files for `LinearRegression` and `LogisticRegression`. All other linear models now have their own test files (from the old huge `test_linear_models.py`, these were the last two that still weren't split out.
- Plumbs through missing `solver` arg in `test_max_iter_n_iter`, as noticed in #7943 (comment)

Authors:
  - Jim Crist-Harif (https://github.com/jcrist)

Approvers:
  - Dante Gama Dessavre (https://github.com/dantegd)

URL: #7957
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

algo: linear-model Cython / Python Cython or Python issue feature request New feature or request non-breaking Non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sparse matrix support for ElasticNet/Lasso models

4 participants