Skip to content

Add some sparse ops and rename others to have more explicit names - #2469

Open
tomicapretto wants to merge 9 commits into
pymc-devs:mainfrom
tomicapretto:sparse-names
Open

tomicapretto wants to merge 9 commits into
pymc-devs:mainfrom
tomicapretto:sparse-names

Conversation

@tomicapretto

@tomicapretto tomicapretto commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Description

This PR adds two Ops for sparse matrices, implements those and a few others in Numba, and gives more explicit names to several Ops.

Renames

Renamed all Ops that contained names such as AddSS or AddSD to be more explicit, now they are AddSparseSparse and AddSparseDense, respectively.

Implementation

  • Add HasCanonicalFormat, a new Op for sparse matrices that determines whether indices are unique and sorted. It is used to then implement the has_canonical_format attribute in _sparse_py_operators which is inherited by sparse matrices in PyTensor. This attribute is used in the Numba backend to compute the sum between two sparse mamtrices. However, I am not 100% sure a new Op should be added for this need in one of the backends. Thus I'm raising the question here.
  • Add SumDuplicates, a new Op for sparse matrices that eliminates entries by adding them together. It is not used when the matrix is already in the canonical format. In SciPy this is an in-place operation, but we return a new one. This Op is used in a few places in the Numba backend to support the implementations of the Ops mentioned below.
  • Numba implementations: AddSparseSparse, MultiplySparseSparse, AddSparseDense, AddSparseSparseData, StructuredAddSparseDenseVector.

Comment about numba implementations: I've discovered that one can get more performance if:

  1. Variables used to index arrays are marked as unsigned.
  2. Casts are performed at the scalar level, not at the array level (which requires a larger allocation).

In a following PR (or this one?) I can incorporate these ideas in the previously implemented ops.

Other

Updated test_sparse_creation_refcount and test_sparse_passthrough_refcount in tests/link/numba/sparse/test_basic.py to use the effective initial reference count, which can differ between Python versions. Right now it's hardcoded to 3, but it is 2 in Python 3.14+.

Checklist

Type of change

  • New feature / enhancement
  • Bug fix
  • Documentation
  • Maintenance
  • Other (please specify):

Comment thread tests/sparse/test_basic.py Outdated

@pytest.mark.parametrize("format", ["csr", "csc"])
def test_inplace(self, format):
x = getattr(sparse, format + "_matrix")("x", dtype="float64")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why don't you pass it directly in the parametrize, instead of retrieving by getattr?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's updated now

Comment thread pytensor/sparse/basic.py
self.inplace = inplace
if self.inplace:
self.view_map = {0: [0]}
self.destroy_map = {0: [0]}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

But is it a view when not inplace?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@ricardoV94 ricardoV94 Oct 14, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

then it's never inplace / aliased either?

@tomicapretto tomicapretto Oct 14, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

When inplace, it uses .sort_indices(), which mutates x inplace, and it then returns x. When not inplace it used sorted_indices(), which returns a copy. It's like list.sort and sorted(list), right?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

omg sort vs sorted is the difference. ok

Comment thread tests/sparse/test_basic.py Outdated
self.op = ensure_sorted_indices

@pytest.mark.parametrize("format", ["csr", "csc"])
def test_inplace(self, format):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

how is this testing inplace (mutation of the original input)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've updated it now

verify_grad_sparse(self.op, data, structured=False)


class TestSumDuplicates(utt.InferShapeTester):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

missing a perform test (gradient tests perform, but can't validate it by itself?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed

@tomicapretto
tomicapretto force-pushed the sparse-names branch 2 times, most recently from 8de2d0d to 9c63961 Compare October 14, 2026 13:02
@ricardoV94

Copy link
Copy Markdown
Member

can you add a summary of this PR in the top message. It seems it's not just renaming but creating new Ops as well?

@tomicapretto

tomicapretto commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor Author

can you add a summary of this PR in the top message. It seems it's not just renaming but creating new Ops as well?

Yes, but it's not done yet (that's why I am keeping it as a draft). I'll add a summary and mark as ready once it's good for review. But of course thanks for the feedback already given!

@tomicapretto tomicapretto changed the title Rename sparse ops to have more explicit names Implement some sparse ops and others to have more explicit names Oct 14, 2026
@tomicapretto tomicapretto changed the title Implement some sparse ops and others to have more explicit names Add some sparse ops and rename others to have more explicit names Oct 14, 2026
@tomicapretto
tomicapretto force-pushed the sparse-names branch 4 times, most recently from f3d3bd5 to 9552550 Compare October 16, 2026 17:35
@tomicapretto
tomicapretto marked this pull request as ready for review October 16, 2026 21:08

This branch has not been deployed

No deployments
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.

2 participants