Conversation
The sparse support required back-ends to implement a cluster of
type-returning functions -- `dense_array_type`, `dense_vector_type`,
`sparse_array_type`, `csc_type`, `csr_type`, `coo_type` -- that generic
code called to name a related type (the dense counterpart, the
unparameterized sparse wrapper, a sibling format). That is unidiomatic:
Julia expresses these with value-level verbs (`float`, `complex`,
`sparse`) and `similar`, not type-level mapping functions.
Replace that machinery:
* Format conversion is now the value verbs `sparse_csc`/`sparse_csr`/
`sparse_coo`. Converting to the format already held is the generic
identity; back-ends implement only the cross-format cases (which they
already had as constructors). `csc_type`/`csr_type`/`coo_type` are gone.
* Broadcast and `similar` outputs are built with `similar` dispatched on a
representative sparse value (plus an internal `_stored_inds` field
accessor for filling), mirroring `SparseArrays`' `_allocres`. No more
reconstructing a sparse type from a type, so `sparse_array_type` is gone.
* `dense_array_type`/`dense_vector_type` had no consumer in the generic
algorithms (a dense result comes from `similar(nonzeros(A), ...)`); they
are removed, and the test suite threads the dense array type explicitly.
* `sparse_from_dense` now yields COO directly (the natural format when
scanning a dense array); CSR/CSC are derived with the conversion verbs,
removing the last type-level `coo_type(ST)` use.
A back-end's obligation shrinks to: the storage structs, their
constructors, `similar`, and the three conversion verbs. JLArrays is
updated as the reference; the GPUArrays test suite passes for both JLArray
and Array.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…supported
`mapreduce(abs, …)` over a `Complex{<:Integer}` sparse matrix has result
element type `Float64` (since `abs(::Complex{<:Integer})::Float64`). Back-ends
that can't allocate that type (e.g. Metal, which has no `Float64`) errored on
these cases. Gate the `abs`-reductions on the result type being among the
back-end's supported eltypes; `sum`, which preserves the input eltype, stays
unconditional.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Spell out the parallel sparse hierarchy, the storage structs and their conventional field names, and the methods a back-end implements (constructors, `similar`, and the `sparse_csc`/`sparse_csr`/`sparse_coo` conversion verbs), plus the generic functionality that comes for free. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Densifying a sparse array had no generic on-device path: back-ends did it with a host round-trip (`Array(collect(SparseMatrixCSC(x)))`). Add `to_dense(A)`, which scatters the stored entries into a dense array of the same back-end with a kernel (allocated via `similar(nonzeros(A), …)`), no host transfer. Coordinates are taken to be unique -- true for CSC/CSR and for COO produced by conversion. Rename the existing `sparse_from_dense` to `to_sparse` so the two on-device conversions form a symmetric, discoverable pair (`to_sparse` needs a target format; `to_dense` does not). JLArrays uses `to_dense` for `JLArray(::JLSparse…)`, and the interface docs are updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reorganize the sparse section of the interface docs into three parts -- the storage types a back-end provides, the methods it implements to integrate them, and the functionality it gets for free -- and round it out (device-struct `Adapt` rule, `get_backend`, `_sptranspose`/`_spadjoint`, the `to_sparse`/ `to_dense` conversions, and the full list of generic operations). Also give `to_sparse` a docstring to match `to_dense`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Also add the missing JLArrays COO identity constructor. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mandate the undef constructor as the empty-of-shape primitive; similar delegates to it. JLArrays now provides spzeros + undef constructors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the sparse branch's sparse_csc/csr/coo value-verbs with main's type-level format hooks, used as coo_type(A)(A); keeps the generic on-device convert algorithm as the engine. Re-aligns the conversion API with main. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
|
||
| A sparse array can't share the `AbstractGPUArray` supertype — that is a `DenseArray`, | ||
| whereas a sparse array must be an `AbstractSparseArray` — so GPUArrays keeps a parallel | ||
| sparse hierarchy with its own generic functionality. Integrating a back-end has three |
There was a problem hiding this comment.
Weird phrasing here, let me think a bit about how to make this more natural
| | `AbstractGPUSparseMatrixCSR{Tv,Ti}` | `rowPtr`, `colVal`, `nzVal`, `dims`, `nnz` | | ||
| | `AbstractGPUSparseMatrixCOO{Tv,Ti}` | `rowInd`, `colInd`, `nzVal`, `dims`, `nnz` | | ||
|
|
||
| The pointer/index/value arrays are the back-end's own dense vector type. Provide only the |
There was a problem hiding this comment.
Should we give an example (e.g. CuVector for CUDA)
|
|
||
| ### Interface to implement | ||
|
|
||
| * **Constructors** — from component arrays (`MyCSR(rowPtr, colVal, nzVal, dims)`), |
There was a problem hiding this comment.
What about to/from dense arrays?
| JLArray(x::JLSparseVector) = JLArray(collect(SparseVector(x))) | ||
| JLArray(x::JLSparseMatrixCSC) = JLArray(collect(SparseMatrixCSC(x))) | ||
| JLArray(x::JLSparseMatrixCSR) = JLArray(collect(SparseMatrixCSC(x))) | ||
| JLArray(x::JLSparseVector) = GPUArrays.to_dense(x) |
There was a problem hiding this comment.
should we make this an eval block?
| SparseArrays.SparseMatrixCSC(x::AbstractGPUSparseMatrixCSC) = SparseMatrixCSC(size(x)..., Array(SparseArrays.getcolptr(x)), Array(SparseArrays.rowvals(x)), Array(SparseArrays.nonzeros(x))) | ||
|
|
||
| function check_sparse_target(::Type{ST}, ::Type{Tv}, ::Type{Ti}) where {ST,Tv,Ti} | ||
| Tv isa Type && Ti isa Type || |
There was a problem hiding this comment.
This looks really funky to me. Shouldn't Ti also check it's a <: Integer?
|
|
||
| function check_sparse_target(::Type{ST}, ::Type{Tv}, ::Type{Ti}) where {ST,Tv,Ti} | ||
| Tv isa Type && Ti isa Type || | ||
| throw(ArgumentError("sparse target type $ST must specify value and index eltypes")) |
There was a problem hiding this comment.
Should we include Tv and Ti in the error message?
| @kernel function densify_vector_kernel!(out, iPtr, nzVal) | ||
| k = @index(Global, Linear) | ||
| if k <= length(nzVal) | ||
| @inbounds out[iPtr[k]] = nzVal[k] |
There was a problem hiding this comment.
it's funny how Claude made this a one liner here but not above
|
Yeah this hasn't passed my personal sniff test yet, hence draft, but thanks for the review. FWIW, the initial motivation was Metal support + SpMV/SpMM (which happens to be easier to express in COO), with the rest following out of it. The API simplification, replacing *_array_type / dense_array_type / sparse_array_type with something like |
|
Ah I can eff off until it's ready if you prefer, sorry! |
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
I'd love to review this one too once it's ready! |
Builds out GPUArrays' generic sparse-array support so a back-end gets formats, conversions, and a handful of linear algebra ops by implementing a smaller interface. Validated on JLArrays (reference) and Metal (WIP)
Formats & interface
_sptranspose/_spadjoint.
Conversions (on-device, no host round-trips)
Linear algebra & ops (generic, COO-normalized kernels)
Breaking / notes