Operations#
These module-level functions wrap the native sparse primitives. In most cases
the @ operator on COOArray, CSRArray, and
CSCArray is the preferred spelling. These functions exist for
explicit dispatch and for callers who prefer a functional style.
Sparse-dense operations return lazy mlx.core.array values. Sparse-sparse
add / subtract and matmat operations return new sparse arrays and
may synchronize structure to host because their output sparsity pattern is
data-dependent.
add#
- mlx_sparse.add(A, B)[source]#
Add two sparse arrays without densifying.
Computes
A + Bfor rank-2 mlx-sparse arrays with equal shape and matching value dtype. The production path canonicalizes both operands with native sort/sum kernels, merges their CSR structures in native C++ or Metal, sums duplicate coordinates, and removes exact zero cancellations from the result.CSR inputs return a canonical
CSRArray. Homogeneous CSC inputs return a canonicalCSCArrayvia native CSR/CSC conversion. COO and mixed-format inputs return canonical CSR output so no dense matrix is created.Sparse+dense addition is intentionally out of scope for this release: adding a sparse matrix to a dense matrix returns a dense matrix mathematically, and this API does not hide that cost. Add or subtract a Python scalar only when the scalar is exactly zero; nonzero scalar addition is rejected for the same reason.
The output structure depends on the input structures and on exact numerical cancellation, so public sparse addition is treated as a dynamic-topology operation. Gradients through integer structure are unsupported, and no fixed-topology sparse-value autodiff contract is claimed for this dynamic operation.
- Parameters:
A – Left sparse operand, or the scalar
0for0 + B.B – Right sparse operand, or the scalar
0forA + 0.
- Returns:
A canonical sparse array. The result is CSR except for homogeneous CSC inputs, which return CSC.
- Raises:
TypeError – If operands are dense, shapes differ, or value dtypes differ.
NotImplementedError – If nonzero scalar addition would densify.
subtract#
- mlx_sparse.subtract(A, B)[source]#
Subtract two sparse arrays without densifying.
Computes
A - Bfor rank-2 mlx-sparse arrays with equal shape and matching value dtype. Semantics matchadd(): inputs are canonicalized natively, the structural union is merged in CSR form, duplicate coordinates are summed, and exact zero cancellations are pruned from the canonical output. Homogeneous CSC inputs return CSC; all other supported sparse combinations return CSR.Sparse-dense subtraction and nonzero scalar subtraction are rejected because they would produce dense results. The scalar
0is accepted as the additive identity:A - 0returnsAand0 - Areturns-Aas a sparse array with the same structure.The output topology is dynamic because exact cancellation can remove stored entries. Gradients through the public sparse subtraction structure are not claimed in this release.
- Parameters:
A – Left sparse operand, or scalar
0for0 - B.B – Right sparse operand, or scalar
0forA - 0.
- Returns:
A canonical sparse array. The result is CSR except for homogeneous CSC inputs, which return CSC.
- Raises:
TypeError – If operands are dense, shapes differ, or value dtypes differ.
NotImplementedError – If nonzero scalar subtraction would densify.
kron#
- mlx_sparse.kron(A, B, format=None)[source]#
Return the sparse Kronecker product of two rank-2 operands.
kron(A, B)builds the matrix whose stored entries followrow = row_A * B.shape[0] + row_B,col = col_A * B.shape[1] + col_B, anddata = data_A * data_B. COO, CSR, CSC, and dense rank-2 MLX-compatible inputs are accepted; dense inputs are converted with the nativemlx_sparse.fromdense()path before assembly, never with Python loops over entries.formatmay be"coo","csr","csc", orNone. The default is COO, matching the construction-oriented SciPy API. COO output is the direct native fixed-topology product and preserves duplicate structural entries if either input contains duplicates. CSR and CSC output canonicalize through native compressed conversion, summing duplicate products and returning duplicate-free compressed structures. Unsupported SciPy formats such as"bsr","dia","dok", and"lil"are rejected explicitly.Value dtype promotion follows the package’s sparse value constraints:
complex64wins over real dtypes, anyfloat32operand yieldsfloat32, equal low-precision operands keep their dtype, and mixedfloat16/bfloat16promotes tofloat32. Dense integer or boolean operands are converted tofloat32because mlx-sparse sparse containers do not store integer or boolean value buffers in this release.Sparse-value JVP/VJP is implemented for the native COO data product when the input structures are fixed. Gradients through integer coordinates, dense-to-sparse extraction, and duplicate-summing canonicalization are not part of the differentiable contract.
- Parameters:
A – Left COO, CSR, CSC, or dense rank-2 operand.
B – Right COO, CSR, CSC, or dense rank-2 operand.
format – Output format, one of
None,"coo","csr", or"csc".Nonedefaults to"coo".
- Returns:
A
COOArray,CSRArray, orCSCArraywith shape(A.shape[0] * B.shape[0], A.shape[1] * B.shape[1]).- Raises:
ValueError – If an operand is not rank-2, the requested format is unknown, or output dimensions exceed MLX limits.
TypeError – If
formatis not a string orNone.NotImplementedError – If a known unsupported SciPy sparse format is requested.
kronsum#
- mlx_sparse.kronsum(A, B, format=None)[source]#
Return the Kronecker sum of two square sparse or dense matrices.
The Kronecker sum is defined as
kron(I_n, A) + kron(B, I_m)forA.shape == (m, m)andB.shape == (n, n). Inputs may be COO, CSR, CSC, or dense rank-2 arrays. Dense inputs are extracted with nativemlx_sparse.fromdense(); the two Kronecker products are assembled with native COO kernels and the sum is merged with native sparse addition.formatmay be"coo","csr","csc", orNone. The default is COO. The intermediate sum is canonical CSR, so returned CSR and CSC outputs are canonical; returned COO is produced by native CSR-to-COO expansion and is also canonical.- Parameters:
A – Left square COO, CSR, CSC, or dense rank-2 operand.
B – Right square COO, CSR, CSC, or dense rank-2 operand.
format – Output format, one of
None,"coo","csr", or"csc".Nonedefaults to"coo".
- Returns:
A sparse array with shape
(A.shape[0] * B.shape[0], A.shape[1] * B.shape[1]).- Raises:
ValueError – If either operand is not square or if output shape/nnz limits are exceeded.
TypeError – If
formatis not a string orNone.NotImplementedError – If a known unsupported SciPy sparse format is requested.
csr_matvec#
- mlx_sparse.csr_matvec(a, x)[source]#
Multiply a CSR sparse matrix by a dense vector.
Computes
y = A @ xwhereAis aCSRArrayandxis a rank-1 dense array. The result is added to the MLX computation graph and not evaluated eagerly.On Apple Silicon, the Metal backend dispatches a scalar row kernel for short rows and a vector-reduction kernel for long rows. CPU and GPU paths support
float32,float16,bfloat16, andcomplex64values withint32orint64indices.- Parameters:
a (CSRArray) – The sparse matrix, shape
(n_rows, n_cols).x – Dense vector, shape
(n_cols,). Converted tomx.arrayif needed. Must have the same dtype asa.data.
- Returns:
Dense vector of shape
(n_rows,)with the same dtype asa.data.- Raises:
TypeError – If
ais not aCSRArray, or if the dtypes ofa.dataandxdo not match.ValueError – If shape constraints are violated.
- Return type:
mlx.core.array
Example:
import mlx.core as mx import mlx_sparse as ms y = a @ x # preferred via __matmul__ y = ms.csr_matvec(a, x) # explicit call mx.eval(y)
coo_matvec#
csc_matvec#
csc_matvec_transpose#
csr_matmul#
- mlx_sparse.csr_matmul(a, rhs)[source]#
Multiply a CSR sparse matrix by a dense matrix.
Computes
Y = A @ BwhereAis aCSRArrayandBis a rank-2 or batched dense array. The result is added to the MLX computation graph and not evaluated eagerly.On Apple Silicon, the Metal backend dispatches scalar output-element kernels for short rows and vector-reduction kernels for long rows. CPU and GPU paths support
float32,float16,bfloat16, andcomplex64values withint32orint64indices.- Parameters:
a (CSRArray) – The sparse matrix, shape
(n_rows, n_cols).rhs – Dense matrix, shape
(n_cols, k), or batched dense matrix with sparse dimension atrhs.shape[-2]. Converted tomx.arrayif needed. Must have the same dtype asa.data.
- Returns:
Dense matrix or batched dense matrix with sparse dimension replaced by
n_rowsand the same dtype asa.data.- Raises:
TypeError – If
ais not aCSRArray, or if dtype constraints are violated.ValueError – If shape constraints are violated.
- Return type:
mlx.core.array
Example:
import mlx.core as mx import mlx_sparse as ms Y = a @ B # preferred via __matmul__ Y = ms.csr_matmul(a, B) # explicit call mx.eval(Y)
coo_matmul#
csc_matmul#
csr_batched_matvec#
- mlx_sparse.csr_batched_matvec(a, rhs)[source]#
Multiply a CSR sparse matrix by a batch of dense vectors.
Computes
Y[b] = A @ X[b]forXwith shape(..., n_cols)and returns shape(..., n_rows). The implementation uses native batched CPU/Metal kernels after flattening any leading batch dimensions.- Parameters:
a (CSRArray)
- Return type:
mlx.core.array
coo_batched_matvec#
csc_batched_matvec#
csr_batched_matmul#
- mlx_sparse.csr_batched_matmul(a, rhs)[source]#
Multiply a CSR sparse matrix by a batch of dense matrices.
rhsmust have shape(..., n_cols, k)and the result has shape(..., n_rows, k). For rank-2 dense matrices, usecsr_matmul().- Parameters:
a (CSRArray)
- Return type:
mlx.core.array
coo_batched_matmul#
csc_batched_matmul#
csr_matmat#
- mlx_sparse.csr_matmat(a, rhs)[source]#
Multiply two CSR sparse matrices and return a canonical CSR matrix.
Computes
C = A @ Bwhere bothAandBareCSRArrayinstances. The output sparsity pattern is not known at graph-build time, so this operation performs a native C++ structural assembly pass on the host (callingmx.evalon the input arrays internally) and returns a newCSRArraywith canonical format.Because the output size is data-dependent, this operation is not representable as a fixed-shape MLX primitive. It is suitable for one-shot matrix products and matrix-power computations, but is not appropriate inside a JIT-compiled function.
- Parameters:
- Returns:
A canonical
CSRArraywith shape(m, n),has_canonical_format=True, andsorted_indices=True.- Raises:
ValueError – If the inner dimensions do not match (
a.shape[1] != rhs.shape[0]).
- Return type:
Example:
import mlx_sparse as ms # Compute the square of a sparse matrix C = A @ A # dispatches csr_matmat when A is CSRArray C = ms.csr_matmat(A, A) # explicit call # Chain sparse matrix products D = ms.csr_matmat(ms.csr_matmat(A, B), C)
coo_matmat#
csc_matmat#
Reductions#
All sparse containers expose reduction methods as well as module-level helper
functions. row_sums / col_sums return the same dtype as the sparse
values, row_norms / col_norms return float32, and diagonal /
trace sum duplicate diagonal entries.
Format |
Functions |
|---|---|
COO |
|
CSR |
|
CSC |
|
COO and CSC reductions are native C++/Metal paths. Norm reductions use dense
matrix semantics, so non-canonical COO/CSC inputs are canonicalized before
norming to ensure duplicate coordinates are summed before the square is taken.
Sparse-value JVP/VJP is implemented for row_sums, col_sums,
diagonal, and trace on COO, CSR, and CSC. Row/column norms are
forward-only in v0.0.6b0 while zero-norm and complex-norm gradient behavior is
specified.
todense#
- mlx_sparse.todense(array)[source]#
Materialize a sparse array as a dense MLX array.
Convenience wrapper that calls
array.todense()on any sparse container. Duplicate entries are summed, consistent withcanonicalize().todense().- Parameters:
- Returns:
Dense array of shape
(n_rows, n_cols)with the same dtype asarray.data.- Raises:
TypeError – If
arraydoes not have atodensemethod.- Return type:
mlx.core.array
Example:
import mlx_sparse as ms dense = ms.todense(my_csr)
identity_like#
- mlx_sparse.identity_like(x)[source]#
Return a native MLX copy of
x.This function exists as an extension smoke test. It passes
xthrough the native_extmodule (if available) and returns an identical MLX array. For production code, prefermlx.coreoperations directly.- Parameters:
x (mlx.core.array) – Any MLX array.
- Returns:
An MLX array with the same shape, dtype, and values as
x.- Return type:
mlx.core.array
is_available#
- mlx_sparse.is_available()[source]#
Return
Trueif the native C++ extension is loaded.The mlx-sparse native extension (
_ext) provides MLX-primitive implementations of sparse operations with CPU and Metal backends. When it is absent (e.g. a pure-source checkout without a build step), all operations fall back to NumPy-based Python implementations inmlx_sparse._fallback.- Returns:
Trueifmlx_sparse._extwas successfully imported at package load time,Falseotherwise.- Return type:
Example:
import mlx_sparse as ms if not ms.is_available(): print("Native extension not found. Using Python fallback.")
Dispatch summary#
The @ operator on CSRArray dispatches based on the type and rank
of rhs:
C = A @ B # rhs is CSRArray -> csr_matmat(A, B) returns CSRArray
y = A @ x # rhs.ndim == 1 -> csr_matvec(A, x) returns mx.array
Y = A @ X # rhs.ndim == 2 -> csr_matmul(A, X) returns mx.array
Yb = A @ Xb # rhs.ndim > 2 -> csr_matmul(A, Xb) returns mx.array
The explicit function calls accept the same arguments:
y = ms.csr_matvec(A, x)
Y = ms.csr_matmul(A, X)
yb = ms.csr_batched_matvec(A, xb)
Yb = ms.csr_batched_matmul(A, Xb)
C = ms.csr_matmat(A, B)
For COOArray and CSCArray, dense RHS dispatch mirrors CSR:
rank-1 RHS uses format-native matvec, rank-2 RHS uses format-native matmul,
and higher-rank RHS is flattened into the corresponding native batched
primitive. Sparse-sparse @ supports every COO/CSR/CSC format pair. The
operator result follows the left-hand sparse format: COO left operands return
canonical COO, CSR left operands return canonical CSR, and CSC left operands
return canonical CSC. Same-format products dispatch directly to the native
format-specific kernel. Mixed-format products normalize the RHS through native
format conversion before calling the same native kernel, never through dense
materialization or Python loops over stored entries.
Vectorized sparse-dense products use the same native path. For COO, CSR, and
CSC matrices with fixed sparse structure, mx.vmap(lambda x: A @ x)(X)
routes to the format-native batched matvec primitive and
mx.vmap(lambda X: A @ X)(XB) routes to the format-native batched matmul
primitive. The mapped dense RHS axis may be any valid RHS axis, the primitive
normalizes it internally and reports a stable mapped output axis back to MLX so
out_axes is handled by MLX. Mapping sparse data or structural index
buffers is intentionally rejected in v0.0.6b0, use explicit batched dense RHS
helpers when the sparse topology is fixed and only the right-hand side is
batched.
The + and - operators on COOArray, CSRArray, and
CSCArray dispatch to add() and subtract(). Sparse-sparse
addition supports COO, CSR, and CSC operands with equal shape and matching
value dtype. Inputs are canonicalized natively, duplicate coordinates are
summed, exact zero cancellations are removed, and the output is canonical.
CSR inputs return CSR, homogeneous CSC inputs return CSC, and COO or
mixed-format inputs return CSR. Sparse+dense addition and nonzero scalar
addition are rejected because they would produce dense matrices, call
A.todense() explicitly when that is intended. The scalar 0 is accepted
as a sparse-preserving identity.
The kron() and kronsum() structural constructors accept COO, CSR,
CSC, or dense rank-2 inputs and return COO, CSR, or CSC output. Dense operands
are first extracted with the native fromdense() constructor. kron is
assembled by the native COO Kronecker primitive after native compressed-to-COO
conversion for CSR/CSC operands. format="coo" returns the direct fixed
product topology and preserves duplicate coordinates when inputs contain
duplicates, format="csr" and format="csc" canonicalize through native
compressed conversion and sum duplicate products. kronsum builds
kron(I_n, A) + kron(B, I_m) for square inputs and uses the native sparse
addition merge. Unsupported SciPy storage names such as "bsr", "dia",
"dok", and "lil" are rejected explicitly.
All sparse-dense products validate that rhs.dtype == A.data.dtype. There
is no implicit type promotion. See Dtype policy for the
full dtype matrix.
Native dispatch notes#
Sparse-dense matvec and matmul for COO, CSR, and CSC are fixed-output primitives and stay lazy in the MLX graph. Explicit batched helpers use native C++/Metal kernels for leading batch dimensions rather than materializing dense matrices in Python.
Transpose products used by autodiff are also native. On Metal, float32
transpose matvec/matmul use atomic scatter-add kernels. Other GPU value dtypes
lower through native csr_transpose followed by the ordinary native product.
Sparse-sparse add / subtract and matmat are different: their output
shape depends on the input structure and, for addition, on exact cancellation,
so they perform count work and synchronize enough structure to allocate compact
output buffers. Sparse addition canonicalizes to CSR for the native merge and
uses CPU or Metal row-merge kernels. CSR matmat uses row symbolic/numeric
assembly, COO groups coordinate rows without routing through CSR, and CSC walks
right-hand columns against left-hand compressed columns to produce sorted output
columns. Mixed-format @ uses native RHS conversion plus the left-hand
format’s native sparse-sparse product, direct mixed-format kernels should be
added only when benchmarks show conversion overhead is material.
kron is a fixed-output structural product before optional compressed
canonicalization. The native COO kernel writes nnz_A * nnz_B values and
coordinates directly on CPU or Metal without dense masks and without Python
loops over stored entries. Sparse-value JVP/VJP is implemented for the COO data
product when input structures are fixed. Gradients through integer structural
buffers, dense-to-sparse extraction, duplicate-summing canonicalization, and
structural triangular compaction are unsupported.
coo_matvec / coo_matmul are native coordinate scatter products. On
Metal, float32 uses atomic scatter-add over stored coordinates, other
value dtypes stay native through a serial scatter kernel because Metal does
not provide storage-compatible atomic adds for float16, bfloat16, or
complex64.
csc_matvec / csc_matmul are native compressed-column scatter products.
Forward CSC products walk columns and scatter into output rows, on Metal,
float32 uses atomic scatter-add while other value dtypes use native serial
scatter. CSC transpose products are the layout’s reduction fast path: each
output entry is one compressed-column dot product.