Search code examples
macosfortranaccelerate-framework

Calling sparse matrix product in Accelerate


I often use Accelerate framework on macOS to speedup my calculations with matrices. The main program is written in Fortran, and the call to dgemm is essentially identical to that of the documentation on netlib.

In my current project, great performance improvement is expected from the use of sparse-dense matrix multiplication. I found that Accelerate contains a sparse_matrix_product_dense_double function (documentation). According to the documentation, the function should work on macOS 10.11+ (I have 13.2). Here is my question: is it possible to directly call this function from fortran? My naive tests with gfortran

program dgemm_demo
  integer, parameter :: m = 3, n = 4, k = 5 

  double precision, allocatable :: A(:,:), B(:,:), C(:,:)

  allocate(A(m,k), B(k, n), C(m, n))

  A = 1.0D0
  B = 2.0D0

  call dgemm ('N', 'N', M, N, K, 1.0D0, A, m, B, k, 0.0D0, C, m)

  call sparse_matrix_product_dense_double('CblasColMajor', 'CblasTrans', n, 1.0D0,&
    sparse_matrix_create_double(m, k), B, n, C, n)
  
end program dgemm_demo

return linking error

Undefined symbols for architecture arm64


Solution

  • It is possible. Accelerate exposes an API very similar to Sparse BLAS as part of vecLib. (The Sparse BLAS API is defined by the BLAS Technical Forum.)

    Search for BLAS.h using Finder to get a full overview of the available routines. You can also locate the header using the command: $ find /Library/Developer -name BLAS.h.

    As a vendor-agnostic, yet performant, cross-platform Sparse BLAS alternative, you might also consider librsb.

    Here's an example of multiplying a sparse matrix A with a dense matrix B using the Sparse BLAS in Accelerate:

    ! accelerate.f90
    !
    !   Example of calling Sparse BLAS from Accelerate framework on MacOS
    ! 
    !   Compile using:
    ! 
    !      gfortran -Wall -O2 accelerate.f90 -framework Accelerate
    !
    program accelerate
    
    use, intrinsic :: iso_c_binding
    implicit none
    
    integer, parameter :: dp = c_double
    
    integer, parameter :: spdim = c_int64_t   ! Sparse Dimension (uint64_t)
    integer, parameter :: spstr = c_int64_t   ! Sparse Stride
    integer, parameter :: spind = c_int64_t   ! Sparse Index
    
    integer, parameter :: cblas_order = c_int
    integer, parameter :: cblas_transpose = c_int
    
    ! Transpose flags
    integer(cblas_transpose), parameter :: NOTRANS   = 111
    integer(cblas_transpose), parameter :: TRANS     = 112
    integer(cblas_transpose), parameter :: CONJTRANS = 113
    
    ! Order flags
    integer(cblas_order), parameter :: ROWMAJOR = 101
    integer(cblas_order), parameter :: COLMAJOR = 102
    
    ! Sparse Status
    integer(c_int), parameter :: SPARSE_SUCCESS             =     0
    integer(c_int), parameter :: SPARSE_ILLEGAL_PARAMETER   = -1000
    integer(c_int), parameter :: SPARSE_CANNOT_SET_PROPERTY = -1001
    integer(c_int), parameter :: SPARSE_SYSTEM_ERROR        = -1002
    
    interface
    
        !> C = alpha * op(A) * B + C, where A is sparse, B and C are dense
        function dspmm(order,transa,n,alpha,A,B,ldb,C,ldc) &
                bind(c,name="sparse_matrix_product_dense_double")
            import c_int, c_int64_t, c_double, c_ptr
            implicit none
            integer(c_int), value :: order
            integer(c_int), value :: transa
            integer(c_int64_t), value :: n
            real(c_double), value :: alpha
            type(c_ptr), value :: A
            real(c_double) :: B(ldb,*)
            integer(c_int64_t), value :: ldb
            real(c_double) :: C(ldc,*)
            integer(c_int64_t), value :: ldc
            integer(c_int) :: dspmm
        end function
    
        ! Create sparse matrix of doubles
        function dspcreate(m,n) bind(c,name="sparse_matrix_create_double")
            import c_int64_t, c_ptr
            integer(c_int64_t), value :: m, n
            type(c_ptr) :: dspcreate
        end function
    
        ! Finalize entry phase
        function spcommit(A) bind(c,name="sparse_commit")
            import c_ptr, c_int
            type(c_ptr), value :: A
            integer(c_int) :: spcommit
        end function
    
        ! Destroy sparse matrix
        function spdestroy(A) bind(c,name="sparse_matrix_destroy")
            import c_ptr, c_int
            type(c_ptr), value :: A
            integer(c_int) :: spdestroy
        end function
    
        ! Insert into array in triplet format
        function dspinsert_entries(A,n,val,indx,jndx) &
                bind(c,name="sparse_insert_entries_double")
            import c_ptr, c_int64_t, c_double, c_int
            type(c_ptr), value :: A
            integer(c_int64_t), value :: n
            real(c_double), intent(in) :: val(n)
            integer(c_int64_t), intent(in) :: indx(n), jndx(n)
            integer(c_int) :: dspinsert_entries
        end function
    
    end interface
    
    real(dp) :: A(3,3), B(3,3)
    integer :: i
    
    real(dp) :: VA(5), C(3,3)
    integer(spdim) :: IA(5), JA(5), nnzA
    type(c_ptr) :: spA
    
    !
    ! Dense matrix multiplication
    !
    
    A = reshape([1,0,1,1,1,0,0,0,1],[3,3])
    B = reshape([(i,i=1,9)],[3,3])
    
    write(*,'(A,/,3(F10.3,2X))') "Dense result = ", matmul(A,B)
    
    !
    ! Sparse matrix multiplication
    !
    
    nnzA = 5
    VA = [ 1, 1,    &
              1,    &
           1,    1]
    
    IA = [1,1,2,3,3] - 1    ! Zero-based indexing expected!
    JA = [1,2,2,1,3] - 1
    
    ! Initialize matrix of size nrows x ncols
    spA = dspcreate(3_spdim,3_spdim)
    if (.not. c_associated(spA)) error stop "Sparse matrix creation failed."
    
    ! Insert entries from a list of triplets
    call spcheck( dspinsert_entries(spA,nnzA,VA,IA,JA) )
    
    ! Finalize entry phase by committing
    call spcheck( spcommit(spA) )
    
    C = 0.0_dp
    
    ! Multiply sparse matrix A with dense matrix B
    ! Result is stored in C
    !
    call spcheck( dspmm(COLMAJOR,NOTRANS,3_spdim,1.0_dp,spA,B,3_spdim,C,3_spdim) )
    call spcheck( spdestroy(spA) )
    
    write(*,'(/,A,/,3(F10.3,2X))') "Sparse result = ", C
    
    contains
    
        subroutine spcheck(ierr)
            integer(c_int), intent(in) :: ierr
            
            if (ierr == SPARSE_SUCCESS) &
                return
    
            write(*,'(A,I0)') 'Error encountered: IERR = ', ierr
            select case(ierr)
            case(SPARSE_ILLEGAL_PARAMETER)
                write(*,'(A)') '  Operation was not completed because one or more'
                write(*,'(A)') '  of the arguments had an illegal value.'
            case(SPARSE_CANNOT_SET_PROPERTY)
                write(*,'(A)') '  Matrix properties can only be set before any'
                write(*,'(A)') '  values are inserted into the matrix.'
            case(SPARSE_SYSTEM_ERROR)
                write(*,'(A)') '  An internal error has occured, such as'
                write(*,'(A)') '  not enough memory.'
            case default
                write(*,'(A)') '  Unknown error code.'
                error stop 1
            end select
    
        end subroutine
    
    end program