Skip to content

Commit 3344d6f

Browse files
Implement addition between AbstractDeviceSparseMatrix and DenseMatrix (#21)
* Initial plan * Implement addition between AbstractDeviceSparseMatrix and DenseMatrix Co-authored-by: albertomercurio <61953577+albertomercurio@users.noreply.github.com> * Apply code formatting * Add benchmarks for sparse + dense matrix addition Co-authored-by: albertomercurio <61953577+albertomercurio@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: albertomercurio <61953577+albertomercurio@users.noreply.github.com>
1 parent fe56588 commit 3344d6f

File tree

12 files changed

+253
-0
lines changed

12 files changed

+253
-0
lines changed

benchmarks/matrix_benchmarks.jl

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,53 @@ function benchmark_three_arg_dot!(
153153

154154
return nothing
155155
end
156+
157+
"""
158+
benchmark_sparse_dense_add!(SUITE, array_constructor, array_type_name; N=10000, T=Float64)
159+
160+
Benchmark sparse + dense matrix addition for CSC, CSR, and COO formats.
161+
162+
# Arguments
163+
- `SUITE`: The BenchmarkGroup to add benchmarks to
164+
- `array_constructor`: Function to construct arrays (e.g., `Array`, `JLArray`)
165+
- `array_type_name`: String name for the array type (for display)
166+
167+
# Keyword Arguments
168+
- `N`: Size of the matrix (default: 10000)
169+
- `T`: Element type (default: Float64)
170+
"""
171+
function benchmark_sparse_dense_add!(
172+
SUITE,
173+
array_constructor,
174+
array_type_name;
175+
N = 10000,
176+
T = Float64,
177+
)
178+
# Create sparse matrix with 1% density
179+
sm_csc_std = sprand(T, N, N, 0.01)
180+
181+
# Convert to different formats
182+
sm_csc = DeviceSparseMatrixCSC(sm_csc_std)
183+
sm_csr = DeviceSparseMatrixCSR(sm_csc_std)
184+
sm_coo = DeviceSparseMatrixCOO(sm_csc_std)
185+
186+
# Adapt to device
187+
dsm_csc = adapt(array_constructor, sm_csc)
188+
dsm_csr = adapt(array_constructor, sm_csr)
189+
dsm_coo = adapt(array_constructor, sm_coo)
190+
191+
# Create dense matrix
192+
dense_mat = adapt(array_constructor, randn(T, N, N))
193+
194+
# Level 3: Format (CSC, CSR, COO - will be plotted together)
195+
SUITE["Sparse + Dense Addition"][array_type_name]["CSC"] =
196+
@benchmarkable $dsm_csc + $dense_mat
197+
198+
SUITE["Sparse + Dense Addition"][array_type_name]["CSR"] =
199+
@benchmarkable $dsm_csr + $dense_mat
200+
201+
SUITE["Sparse + Dense Addition"][array_type_name]["COO"] =
202+
@benchmarkable $dsm_coo + $dense_mat
203+
204+
return nothing
205+
end

benchmarks/runbenchmarks.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ benchmark_vector_sparse_dense_dot!(SUITE, Array, "Array")
2020
benchmark_matrix_vector_mul!(SUITE, Array, "Array")
2121
benchmark_matrix_matrix_mul!(SUITE, Array, "Array")
2222
benchmark_three_arg_dot!(SUITE, Array, "Array")
23+
benchmark_sparse_dense_add!(SUITE, Array, "Array")
2324

2425
# Run benchmarks for JLArrays
2526
println("Running benchmarks for JLArrays...")
@@ -28,6 +29,7 @@ benchmark_vector_sparse_dense_dot!(SUITE, jl, "JLArray")
2829
benchmark_matrix_vector_mul!(SUITE, jl, "JLArray")
2930
benchmark_matrix_matrix_mul!(SUITE, jl, "JLArray")
3031
benchmark_three_arg_dot!(SUITE, jl, "JLArray")
32+
benchmark_sparse_dense_add!(SUITE, jl, "JLArray")
3133

3234
# Tune and run benchmarks
3335
println("\nTuning benchmarks...")

src/core.jl

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,56 @@ trans_adj_wrappers(fmt) = (
5555
),
5656
(T -> :(Adjoint{$T,<:$fmt{$T}}), true, true, A -> :(parent($A)), T -> :($T)),
5757
)
58+
59+
# Generic addition between AbstractDeviceSparseMatrix and DenseMatrix
60+
"""
61+
+(A::AbstractDeviceSparseMatrix, B::DenseMatrix)
62+
63+
Add a sparse matrix `A` to a dense matrix `B`, returning a dense matrix.
64+
All backends must be compatible.
65+
66+
# Examples
67+
```jldoctest
68+
julia> using DeviceSparseArrays, SparseArrays
69+
70+
julia> A = DeviceSparseMatrixCSC(sparse([1, 2], [1, 2], [1.0, 2.0], 3, 3));
71+
72+
julia> B = ones(3, 3);
73+
74+
julia> C = A + B;
75+
76+
julia> collect(C)
77+
3×3 Matrix{Float64}:
78+
2.0 1.0 1.0
79+
1.0 3.0 1.0
80+
1.0 1.0 1.0
81+
```
82+
"""
83+
function Base.:+(A::AbstractDeviceSparseMatrix, B::DenseMatrix)
84+
size(A) == size(B) || throw(
85+
DimensionMismatch(
86+
"dimensions must match: A has dims $(size(A)), B has dims $(size(B))",
87+
),
88+
)
89+
90+
backend_A = get_backend(A)
91+
backend_B = get_backend(B)
92+
93+
backend_A == backend_B || throw(ArgumentError("Both arrays must have the same backend"))
94+
95+
# Create a copy of B to avoid modifying the input
96+
C = copy(B)
97+
98+
# Add the sparse values to C
99+
_add_sparse_to_dense!(C, A)
100+
101+
return C
102+
end
103+
104+
"""
105+
+(B::DenseMatrix, A::AbstractDeviceSparseMatrix)
106+
107+
Add a dense matrix `B` to a sparse matrix `A`, returning a dense matrix.
108+
This is the commutative version of `A + B`.
109+
"""
110+
Base.:+(B::DenseMatrix, A::AbstractDeviceSparseMatrix) = A + B

src/matrix_coo/matrix_coo.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,3 +320,14 @@ for (wrapa, transa, conja, unwrapa, whereT1) in trans_adj_wrappers(:DeviceSparse
320320
return sum(block_results)
321321
end
322322
end
323+
324+
# Helper function for adding DeviceSparseMatrixCOO to dense matrix
325+
function _add_sparse_to_dense!(C::DenseMatrix, A::DeviceSparseMatrixCOO)
326+
backend = get_backend(A)
327+
nnz_val = nnz(A)
328+
329+
kernel! = kernel_add_sparse_to_dense_coo!(backend)
330+
kernel!(C, getrowind(A), getcolind(A), getnzval(A); ndrange = (nnz_val,))
331+
332+
return C
333+
end

src/matrix_coo/matrix_coo_kernels.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,15 @@ end
127127
block_results[group_id] = sum
128128
end
129129
end
130+
131+
# Kernel for adding sparse matrix to dense matrix (COO format)
132+
@kernel inbounds=true function kernel_add_sparse_to_dense_coo!(
133+
C,
134+
@Const(rowind),
135+
@Const(colind),
136+
@Const(nzval),
137+
)
138+
i = @index(Global)
139+
140+
@inbounds C[rowind[i], colind[i]] += nzval[i]
141+
end

src/matrix_csc/matrix_csc.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,3 +307,14 @@ for (wrapa, transa, conja, unwrapa, whereT1) in trans_adj_wrappers(:DeviceSparse
307307
return sum(block_results)
308308
end
309309
end
310+
311+
# Helper function for adding DeviceSparseMatrixCSC to dense matrix
312+
function _add_sparse_to_dense!(C::DenseMatrix, A::DeviceSparseMatrixCSC)
313+
backend = get_backend(A)
314+
n = size(A, 2)
315+
316+
kernel! = kernel_add_sparse_to_dense_csc!(backend)
317+
kernel!(C, getcolptr(A), getrowval(A), getnzval(A); ndrange = (n,))
318+
319+
return C
320+
end

src/matrix_csc/matrix_csc_kernels.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,17 @@ end
129129
block_results[group_id] = sum
130130
end
131131
end
132+
133+
# Kernel for adding sparse matrix to dense matrix (CSC format)
134+
@kernel inbounds=true function kernel_add_sparse_to_dense_csc!(
135+
C,
136+
@Const(colptr),
137+
@Const(rowval),
138+
@Const(nzval),
139+
)
140+
col = @index(Global)
141+
142+
@inbounds for j = colptr[col]:(colptr[col+1]-1)
143+
C[rowval[j], col] += nzval[j]
144+
end
145+
end

src/matrix_csr/matrix_csr.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,3 +320,14 @@ for (wrapa, transa, conja, unwrapa, whereT1) in trans_adj_wrappers(:DeviceSparse
320320
return sum(block_results)
321321
end
322322
end
323+
324+
# Helper function for adding DeviceSparseMatrixCSR to dense matrix
325+
function _add_sparse_to_dense!(C::DenseMatrix, A::DeviceSparseMatrixCSR)
326+
backend = get_backend(A)
327+
m = size(A, 1)
328+
329+
kernel! = kernel_add_sparse_to_dense_csr!(backend)
330+
kernel!(C, getrowptr(A), getcolval(A), getnzval(A); ndrange = (m,))
331+
332+
return C
333+
end

src/matrix_csr/matrix_csr_kernels.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,17 @@ end
129129
block_results[group_id] = sum
130130
end
131131
end
132+
133+
# Kernel for adding sparse matrix to dense matrix (CSR format)
134+
@kernel inbounds=true function kernel_add_sparse_to_dense_csr!(
135+
C,
136+
@Const(rowptr),
137+
@Const(colval),
138+
@Const(nzval),
139+
)
140+
row = @index(Global)
141+
142+
@inbounds for j = rowptr[row]:(rowptr[row+1]-1)
143+
C[row, colval[j]] += nzval[j]
144+
end
145+
end

test/shared/matrix_coo.jl

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,4 +252,29 @@ function shared_test_linearalgebra_matrix_coo(
252252
end
253253
end
254254
end
255+
256+
@testset "Sparse + Dense Matrix Addition" begin
257+
for T in (int_types..., float_types..., complex_types...)
258+
m, n = 50, 40
259+
A = sprand(T, m, n, 0.1)
260+
B = rand(T, m, n)
261+
262+
dA = adapt(op, DeviceSparseMatrixCOO(A))
263+
dB = op(B)
264+
265+
# Test sparse + dense
266+
result = dA + dB
267+
expected = Matrix(A) + B
268+
@test collect(result) expected
269+
270+
# Test dense + sparse (commutative)
271+
result2 = dB + dA
272+
@test collect(result2) expected
273+
274+
# Test dimension mismatch
275+
B_wrong = rand(T, m + 1, n)
276+
dB_wrong = op(B_wrong)
277+
@test_throws DimensionMismatch dA + dB_wrong
278+
end
279+
end
255280
end

0 commit comments

Comments
 (0)