# Apple Accelerate Sparse Solvers

**URL:** <https://discourse.julialang.org/t/apple-accelerate-sparse-solvers/110175>\
**Category:** General Usage\
**Created:** [February 13, 2024, 9:56pm UTC](https://discourse.julialang.org/t/apple-accelerate-sparse-solvers/110175 "2024-02-13T21:56:59Z")\
**Posts on this page:** 15\
**Page:** 1

<div class="post-metadata">

**Author:** ![jdlara-berkeley](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jdlara-berkeley/32/4096_2.png) [@jdlara-berkeley](https://discourse.julialang.org/u/jdlara-berkeley)\
**Post date:** [February 13, 2024, 9:56pm UTC](https://discourse.julialang.org/t/apple-accelerate-sparse-solvers/110175/1 "2024-02-13T21:56:59Z")

</div>

I am trying to wrap the calls for Apple’s Accelerate Sparse Solver and it is my first try at wrapping apple routines.

I am following some of the code structure in Accelerate.jl and LinearSolvers.jl but I keep failing at wrapping the SparseMatrixStructure object from Accelerate in Julia.

I am following this docs [Creating sparse matrices | Apple Developer Documentation](https://developer.apple.com/documentation/accelerate/creating_sparse_matrices?language=objc)

I am trying this so far

```Julia
using LinearAlgebra
using Libdl
using SparseArrays

# For now, only use BLAS from Accelerate (that is to say, vecLib)
const global libacc = "/System/Library/Frameworks/Accelerate.framework/Accelerate"
libacc_hdl = Libdl.dlopen_e(libacc)

mat = sprand(10, 10, 0.4)

rowIndices = mat.nzind .- 1
values = mat.nzval
column_starts = mat.colptr .- 1

structure_acc = ccall((:SparseMatrixStructure, libacc), Ptr{Cvoid},
    (Cint, Cint, Ptr{Cint}, Ptr{Cint}, Ptr{Cvoid}, Cint),
    10, 10, column_starts, rowIndices, Ptr{Cvoid}(), 1)

```

I am following the header located at

`/Library/Developer/CommandLineTools/SDKs/MacOSX14.2.sdk/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/Headers/Sparse/Solve.h`

But `ccall` isn’t able to find the symbol. I am not sure if I am calling the correct method or loading the correct library.

Any pointers on how to proceed will be appreciated.

---

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [February 14, 2024, 3:29am UTC](https://discourse.julialang.org/t/apple-accelerate-sparse-solvers/110175/2 "2024-02-14T03:29:00Z")

</div>

This requires a little knowledge of C to understand. There is no function called `SparseMatrixStructure`.

For Julia, you need to redeclare the struct.

Clang.jl can automate the process of recreating the struct for you.

---

<div class="post-metadata">

**Author:** ![jdlara-berkeley](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jdlara-berkeley/32/4096_2.png) [@jdlara-berkeley](https://discourse.julialang.org/u/jdlara-berkeley)\
**Post date:** [February 14, 2024, 4:13am UTC](https://discourse.julialang.org/t/apple-accelerate-sparse-solvers/110175/3 "2024-02-14T04:13:02Z")

</div>

Thanks for your answer. What I don’t get is why the clang call will produce this method for example

```Julia
function SparseMultiply(A, X, Y)
    ccall((:SparseMultiply, libSparse), Cvoid, (SparseMatrix_Double, DenseMatrix_Double, DenseMatrix_Double), A, X, Y)
end

```

and I look for the symbol in the library it can’t find it

```Julia
libacc ="/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparse.dylib"

libacc_hdl = Libdl.dlopen_e(libacc)

dlsym(libacc_hdl, :SparseMultiply)
ERROR: could not load symbol "SparseMultiply":
dlsym(0x7ffe8dc6a948, SparseMultiply): symbol not found
Stacktrace:
 [1] dlsym(hnd::Ptr{Nothing}, s::Symbol; throw_error::Bool)
   @ Base.Libc.Libdl ./libdl.jl:59
 [2] dlsym(hnd::Ptr{Nothing}, s::Symbol)
   @ Base.Libc.Libdl ./libdl.jl:56
 [3] top-level scope
   @ REPL[26]:1

```

---

<div class="post-metadata">

**Author:** ![jdlara-berkeley](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jdlara-berkeley/32/4096_2.png) [@jdlara-berkeley](https://discourse.julialang.org/u/jdlara-berkeley)\
**Post date:** [February 14, 2024, 4:16am UTC](https://discourse.julialang.org/t/apple-accelerate-sparse-solvers/110175/4 "2024-02-14T04:16:21Z")

</div>

This was the code that I ran with clang

```Julia
options = load_options(joinpath(@ __DIR__ , "wrap.toml"))

    args = get_default_args()
    includedir = " /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparse.dylib"
    push!(args, "-I$includedir")

    headers = ["/Library/Developer/CommandLineTools/SDKs/MacOSX14.2.sdk/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/Headers/Sparse/Solve.h"]

    ctx = create_context(headers, args, options)

    build!(ctx, BUILDSTAGE_NO_PRINTING)

    build!(ctx, BUILDSTAGE_PRINTING_ONLY)

```

the warp file is

```toml
[general]
library_name = "libSparse"
output_file_path = "./libSparse.jl"

[codegen]
always_NUL_terminated_string = true

```

---

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [February 14, 2024, 7:09am UTC](https://discourse.julialang.org/t/apple-accelerate-sparse-solvers/110175/5 "2024-02-14T07:09:13Z")

</div>

This was an interesting rabbit hole to go down.

It seems that all the dylibs are now aggregated into some kind of cache as of Big Sur:

> - New in macOS Big Sur 11.0.1, the system ships with a built-in dynamic linker cache of all system-provided libraries. As part of this change, copies of dynamic libraries are no longer present on the filesystem. Code that attempts to check for dynamic library presence by looking for a file at a path or enumerating a directory will fail. Instead, check for library presence by attempting to `dlopen()` the path, which will correctly check for the library in the cache. (62986286)

To actually inspect the dylibs, I needed to use this:

> **[GitHub - keith/dyld-shared-cache-extractor: A CLI for extracting libraries...](https://github.com/keith/dyld-shared-cache-extractor)**
>
> A CLI for extracting libraries from Apple's dyld shared cache file - keith/dyld-shared-cache-extractor

Now that I can actually inspect the dylibs I can see the symbols are actually mangled.

```julia
% cd /tmp/libraries/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A
% nm -gU libSparse.dylib | grep SparseMultiply                                                                      
0000000181087838 T __SparseMultiplySubfactor_Double
00000001810ac724 T __SparseMultiplySubfactor_Float
000000018109a880 T __Z14SparseMultiply18SparseMatrix_Float17DenseMatrix_FloatS0_
000000018109a8e0 T __Z14SparseMultiply18SparseMatrix_Float17DenseVector_FloatS0_
0000000181091694 T __Z14SparseMultiply19SparseMatrix_Double18DenseMatrix_DoubleS0_
00000001810916f4 T __Z14SparseMultiply19SparseMatrix_Double18DenseVector_DoubleS0_
000000018109ed6c T __Z14SparseMultiply27SparseOpaqueSubfactor_Float17DenseMatrix_Float
000000018109f5c4 T __Z14SparseMultiply27SparseOpaqueSubfactor_Float17DenseMatrix_FloatPv
000000018109f008 T __Z14SparseMultiply27SparseOpaqueSubfactor_Float17DenseMatrix_FloatS0_
000000018109f7e8 T __Z14SparseMultiply27SparseOpaqueSubfactor_Float17DenseMatrix_FloatS0_Pv
000000018109fd34 T __Z14SparseMultiply27SparseOpaqueSubfactor_Float17DenseVector_Float
000000018109fdf4 T __Z14SparseMultiply27SparseOpaqueSubfactor_Float17DenseVector_FloatPv
000000018109fd8c T __Z14SparseMultiply27SparseOpaqueSubfactor_Float17DenseVector_FloatS0_
000000018109fe50 T __Z14SparseMultiply27SparseOpaqueSubfactor_Float17DenseVector_FloatS0_Pv
0000000181095b80 T __Z14SparseMultiply28SparseOpaqueSubfactor_Double18DenseMatrix_Double
00000001810963d8 T __Z14SparseMultiply28SparseOpaqueSubfactor_Double18DenseMatrix_DoublePv
0000000181095e1c T __Z14SparseMultiply28SparseOpaqueSubfactor_Double18DenseMatrix_DoubleS0_
00000001810965fc T __Z14SparseMultiply28SparseOpaqueSubfactor_Double18DenseMatrix_DoubleS0_Pv
0000000181096b48 T __Z14SparseMultiply28SparseOpaqueSubfactor_Double18DenseVector_Double
0000000181096c08 T __Z14SparseMultiply28SparseOpaqueSubfactor_Double18DenseVector_DoublePv
0000000181096ba0 T __Z14SparseMultiply28SparseOpaqueSubfactor_Double18DenseVector_DoubleS0_
0000000181096c64 T __Z14SparseMultiply28SparseOpaqueSubfactor_Double18DenseVector_DoubleS0_Pv
0000000181091260 T __Z14SparseMultiplyd19SparseMatrix_Double18DenseMatrix_DoubleS0_
0000000181091594 T __Z14SparseMultiplyd19SparseMatrix_Double18DenseVector_DoubleS0_
000000018109a44c T __Z14SparseMultiplyf18SparseMatrix_Float17DenseMatrix_FloatS0_
000000018109a780 T __Z14SparseMultiplyf18SparseMatrix_Float17DenseVector_FloatS0_
000000018109a918 T __Z17SparseMultiplyAdd18SparseMatrix_Float17DenseMatrix_FloatS0_
000000018109acac T __Z17SparseMultiplyAdd18SparseMatrix_Float17DenseVector_FloatS0_
000000018109172c T __Z17SparseMultiplyAdd19SparseMatrix_Double18DenseMatrix_DoubleS0_
0000000181091ac0 T __Z17SparseMultiplyAdd19SparseMatrix_Double18DenseVector_DoubleS0_
000000018109178c T __Z17SparseMultiplyAddd19SparseMatrix_Double18DenseMatrix_DoubleS0_
0000000181091af8 T __Z17SparseMultiplyAddd19SparseMatrix_Double18DenseVector_DoubleS0_
000000018109a978 T __Z17SparseMultiplyAddf18SparseMatrix_Float17DenseMatrix_FloatS0_
000000018109ace4 T __Z17SparseMultiplyAddf18SparseMatrix_Float17DenseVector_FloatS0_

```

Those can be exposed by `dlsym`.

```julia
julia> libSparse = dlopen("/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/libSparse.dylib", RTLD_NOW | RTLD_GLOBAL)
Ptr{Nothing} @0x00000003b81a5ca0

julia> dlsym(libSparse, :_Z14SparseMultiplyf18SparseMatrix_Float17DenseMatrix_FloatS0_)
Ptr{Nothing} @0x000000018c60e44c

```

This really make this work, I suspect we will need [GitHub - JuliaInterop/CxxWrap.jl: Package to make C++ libraries available in Julia](https://github.com/JuliaInterop/CxxWrap.jl)

---

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [February 14, 2024, 8:28am UTC](https://discourse.julialang.org/t/apple-accelerate-sparse-solvers/110175/6 "2024-02-14T08:28:05Z")

</div>

Here’s a quick and dirty demo.

First, let’s start with the setup.

```julia
struct DenseMatrix_Double
    rowCount::Cint
    columnCount::Cint
    columnStride::Cint
    attributes::UInt16
    data::Ptr{Cdouble}
end

dense_data = zeros(Float64, 3,3)
dense = DenseMatrix_Double(3, 3, 3, 0, pointer(dense_data))

dense2_data = ones(Float64, 3,3)
dense2 = DenseMatrix_Double(3, 3, 3, 0, pointer(dense2_data))

struct SparseMatrixStructure
    rowCount::Cint
    columnCount::Cint
    columnStarts::Ptr{Clong}
    rowIndices::Ptr{Cint}
    attributes::UInt16
    blockSize::UInt8
end

columnStarts = Clong[0, 2, 4, 5]
rowIndices = Cint[0, 2, 0, 1, 2]

matrix_structure = SparseMatrixStructure(
    3, 3, pointer(columnStarts), pointer(rowIndices), 0, 3
)

struct SparseMatrix_Double
    structure::SparseMatrixStructure
    data::Ptr{Cdouble}
end

data = Cdouble[1.0, 0.1, 9.2, 0.3, 0.5, 1.3, 0.2, 1.3, 4.5]
sparse_matrix_double = SparseMatrix_Double(matrix_structure, pointer(data))

using Libdl
libSparse = dlopen("/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/libSparse.dylib")
SparseMultiply = dlsym(libSparse, :_Z14SparseMultiply19SparseMatrix_Double18DenseMatrix_DoubleS0_)

```

Now let’s play with it in the REPL:

```julia-repl
julia> include("accelerate_test.jl");

julia> dense_data
3×3 Matrix{Float64}:
 0.0 0.0 0.0
 0.0 0.0 0.0
 0.0 0.0 0.0

julia> dense2_data
3×3 Matrix{Float64}:
 1.0 1.0 1.0
 1.0 1.0 1.0
 1.0 1.0 1.0

julia> ccall(SparseMultiply, Nothing, (SparseMatrix_Double, DenseMatrix_Double, DenseMatrix_Double), sparse_matrix_double, dense, dense2)

julia> dense2_data
3×3 Matrix{Float64}:
 0.0 0.0 0.0
 0.0 0.0 0.0
 0.0 0.0 0.0

julia> using LinearAlgebra

julia> dense_data .= I(3)
3×3 Matrix{Float64}:
 1.0 0.0 0.0
 0.0 1.0 0.0
 0.0 0.0 1.0

julia> ccall(SparseMultiply, Nothing, (SparseMatrix_Double, DenseMatrix_Double, DenseMatrix_Double), sparse_matrix_double, dense, dense2)

julia> dense2_data
3×3 Matrix{Float64}:
 1.0 9.2 0.0
 0.0 0.3 0.0
 0.1 0.0 0.5

```

---

<div class="post-metadata">

**Author:** ![jdlara-berkeley](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jdlara-berkeley/32/4096_2.png) [@jdlara-berkeley](https://discourse.julialang.org/u/jdlara-berkeley)\
**Post date:** [February 23, 2024, 8:15pm UTC](https://discourse.julialang.org/t/apple-accelerate-sparse-solvers/110175/7 "2024-02-23T20:15:58Z")

</div>

Thanks a lot for the prototype. I wonder how much work would it be to expose the linear solver.

Any suggestions on how to handle the mangling? Or should I just try to work it out manually?

---

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [February 23, 2024, 9:06pm UTC](https://discourse.julialang.org/t/apple-accelerate-sparse-solvers/110175/8 "2024-02-23T21:06:47Z")

</div>

For mangling or demangling there are existing tools such as the following

> **[c++filt (GNU Binary Utilities)](https://sourceware.org/binutils/docs/binutils/c_002b_002bfilt.html)**
>
> c++filt (GNU Binary Utilities)

[https://llvm.org/docs/CommandGuide/llvm-cxxfilt.html](https://llvm.org/docs/CommandGuide/llvm-cxxfilt.html)

The LLVM version seems advisable given that we are dealing with an Apple framework. It may be packaged as part of one of our LLVM JLLs.

---

<div class="post-metadata">

**Author:** ![Zentrik](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/zentrik/32/35409_2.png) [@Zentrik](https://discourse.julialang.org/u/Zentrik)\
**Post date:** [March 28, 2024, 12:59am UTC](https://discourse.julialang.org/t/apple-accelerate-sparse-solvers/110175/9 "2024-03-28T00:59:07Z")

</div>

It seems to be packaged as part of `LLVM_Full_jll` under `tools/llvm-cxxfilt`

---

<div class="post-metadata">

**Author:** ![luke-kiernan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/luke-kiernan/32/214309_2.png) [@luke-kiernan](https://discourse.julialang.org/u/luke-kiernan)\
**Post date:** [June 6, 2025, 3:16pm UTC](https://discourse.julialang.org/t/apple-accelerate-sparse-solvers/110175/10 "2025-06-06T15:16:07Z")

</div>

Progress report in case someone comes across this in the future:

I successfully added Julia bindings for some of libSparse’s functionality ([GitHub link](https://github.com/JuliaLinearAlgebra/AppleAccelerate.jl/tree/libSparse)). My approach requires manually working out the name mangling and individually writing the Julia wrappers. There’s bash script for demangling [here](https://github.com/luke-kiernan/AASparseSolvers.jl/blob/main/demangled-symbols.sh) should someone want to go that route.

If you want to automate the generation of the Julia wrappers, parsing the header as C++ seems like the way to go. Sadly, Clang.jl does not suppport and cannot easily support ([issue](https://github.com/JuliaInterop/Clang.jl/issues/543)) parsing the overloadable attribute, which is used extensively in libSparse.h

The performance of the linear solvers is curiously poor. Profiling an in-place solve in Julia, the C routines taking the most time are related to allocating/deallocating the internal matrix representation. If I repeat the computation in Objective-C, the runtime is the same: calling the libSparse functions as C vs as Objective-C probably isn’t to blame. I [asked](https://developer.apple.com/forums/thread/773450) on the Apple Developer form about this: a dev said “that’s strange,” but no response beyond that.

---

<div class="post-metadata">

**Author:** ![luke-kiernan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/luke-kiernan/32/214309_2.png) [@luke-kiernan](https://discourse.julialang.org/u/luke-kiernan)\
**Post date:** [May 19, 2026, 2:11pm UTC](https://discourse.julialang.org/t/apple-accelerate-sparse-solvers/110175/11 "2026-05-19T14:11:53Z")

</div>

We now have official support! The Julia bindings I made have been integrated with AppleAccelerate.jl, file [here](https://github.com/JuliaLinearAlgebra/AppleAccelerate.jl/blob/master/src/sparse.jl).

Curiously, still no response on the Apple developer forum. I also tried reaching out through side channels, via a friend-of-a-friend who works at Apple, but all they could say was “yeah that’s the right spot for such questions.” It does look like they’re actively working on libSparse: there’s some LU factorization types in the docs that I’m pretty sure weren’t there a year ago.

---

<div class="post-metadata">

**Author:** ![lukem12345](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lukem12345/32/221287_2.png) [@lukem12345](https://discourse.julialang.org/u/lukem12345)\
**Post date:** [May 22, 2026, 8:16pm UTC](https://discourse.julialang.org/t/apple-accelerate-sparse-solvers/110175/12 "2026-05-22T20:16:34Z")

</div>

Thanks for adding those bindings @luke-kiernan ! I’ve been wanting to add some more Metal-backend differential operators to CombinatorialSpaces.jl for a while, so I’m going to take a look at this. For a lot of test matrices that I work with, solves on the CPU are almost always faster than setting up and executing (iterative) solvers on the GPU, but I think that has mostly come down to the particular types of sparse systems that come up in my particular engineering contexts.

Checking out the link you shared on the Apple Developer forum I get a 404, which may be due to some link expansion thing?`https://github.com/JuliaLinearAlgebra/AppleAccelerate.jl/blob/libSparse/src/libSparse/wrappers.jl`

Cheers,  
Luke

---

<div class="post-metadata">

**Author:** ![gdalle](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gdalle/32/27854_2.png) [@gdalle](https://discourse.julialang.org/u/gdalle)\
**Post date:** [May 27, 2026, 8:18am UTC](https://discourse.julialang.org/t/apple-accelerate-sparse-solvers/110175/14 "2026-05-27T08:18:43Z")

</div>

This looks awesome, thank you so much! Do you have any idea whether something similar is possible for Metal.jl?

> [@Sparse matrix multiplication for Metal](https://discourse.julialang.org/t/sparse-matrix-multiplication-for-metal/131088):
>
> I need to perform a multiplication between two fairly large sparse matrices C = A \* B and I would like to try using the GPU for that. The problem arises in a context where A represents a fixed 2D convolution kernel, while B represents a list of 2D images (each of which is a column of B). Therefore, if necessary, I can easily change the storage scheme of A without a significant penalty. I can easily do this operation with CUDA, but I could not find any way to perform sparse matrix products with …

---

<div class="post-metadata">

**Author:** ![gbaraldi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gbaraldi/32/22101_2.png) [@gbaraldi](https://discourse.julialang.org/u/gbaraldi)\
**Post date:** [May 27, 2026, 1:00pm UTC](https://discourse.julialang.org/t/apple-accelerate-sparse-solvers/110175/15 "2026-05-27T13:00:18Z")

</div>

It’s definitely possible, but needs to be implemented. If it’s just KernelAbstractions or CUDA.jl kernels then it’s simple. If it’s calling into CUDA libraries then we’d have to check if MPSGraph supports it

---

<div class="post-metadata">

**Author:** ![luke-kiernan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/luke-kiernan/32/214309_2.png) [@luke-kiernan](https://discourse.julialang.org/u/luke-kiernan)\
**Post date:** [June 1, 2026, 8:24pm UTC](https://discourse.julialang.org/t/apple-accelerate-sparse-solvers/110175/16 "2026-06-01T20:24:04Z")

</div>

> [@lukem12345](#):
>
> Checking out the link you shared on the Apple Developer forum I get a 404

Fixed now.

> Do you have any idea whether something similar is possible for [Metal.jl](https://juliaregistries.github.io/General/packages/redirect_to_repo/Metal)?

Oh! That’s a thing? We could add GPU support for sparse inverses!

Digging up the `.h` files, they use Objective-C features like `@property`, `@interface`. In contrast, the LibSparse stuff was 99% vanilla C. So it’ll require `ObjectiveC.jl`, not `ccall`. Doable? Probably, but not a straightforward task for me.
