diff --git a/Project.toml b/Project.toml index 8c6b1ce..90c28fa 100644 --- a/Project.toml +++ b/Project.toml @@ -7,10 +7,12 @@ Artifacts = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MKL_jll = "856f044c-d86e-5d09-b602-aeab76dc8ba7" +Preferences = "21216c6a-2e73-6563-6e65-726566657250" [compat] MKL_jll = "2022.1" julia = "1.8" +Preferences = "1.4" [extras] Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" diff --git a/README.md b/README.md index 8c1bc67..0a81ff7 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,23 @@ MKL.jl is a Julia package that allows users to use the Intel MKL library for Julia's underlying BLAS and LAPACK, instead of OpenBLAS, which Julia ships with by default. Julia includes [libblastrampoline](https://github.com/staticfloat/libblastrampoline), which enables picking a BLAS and LAPACK library at runtime. A [JuliaCon 2021 talk](https://www.youtube.com/watch?v=t6hptekOR7s) provides details on this mechanism. -This package requires Julia 1.7+ +This package requires Julia 1.7+. -## Usage - -If you want to use `MKL.jl` in your project, make sure it is the first package you load before any other package. It is essential that MKL be loaded before other packages so that it can find the Intel OMP library and avoid [issues resulting out of GNU OMP being loaded first](https://github.com/JuliaPackaging/BinaryBuilder.jl/issues/700). +## Installation -## To Install: +To install the package execute -Adding the package will replace the system BLAS and LAPACK with MKL provided ones at runtime. Note that the MKL package has to be loaded in every new Julia process. Upon quitting and restarting, Julia will start with the default OpenBLAS. ```julia julia> using Pkg; Pkg.add("MKL") ``` -## To Check Installation: +## Usage + +Loading the package (`using MKL`) will replace the system BLAS and LAPACK with MKL provided ones at runtime. Make sure it is the first package you load before any other package. It is essential that MKL be loaded before other packages so that it can find the Intel OMP library and avoid [issues resulting out of GNU OMP being loaded first](https://github.com/JuliaPackaging/BinaryBuilder.jl/issues/700). + +Note that the MKL package has to be loaded in every new Julia process. Upon quitting and restarting, Julia will start with the default OpenBLAS. + +## Check ```julia julia> using LinearAlgebra @@ -35,6 +38,16 @@ Libraries: └ [ILP64] libmkl_rt.1.dylib ``` +Note that you can put `using MKL` into your `startup.jl` to make Julia automatically use Intel MKL in every session. + ## Using the 64-bit vs 32-bit version of MKL We use ILP64 by default on 64-bit systems, and LP64 on 32-bit systems. + +## Using a system-provided Intel MKL + +If you want to use a system-provided Intel MKL installation, you can set the [preference](https://github.com/JuliaPackaging/Preferences.jl) `mkl_path` to hint MKL.jl to the corresponding `libmkl_rt` library. Specifically, the options are: + +* `mkl_jll` (default): Download and install MKL via [MKL_jll.jl](https://github.com/JuliaBinaryWrappers/MKL_jll.jl). +* `system`: The package will try to automatically locate the system-provided libmkl_rt library (i.e. find it on the linker search path). +* `path/to/my/libmkl_rt.`: Explicit path to the `libmkl_rt.` where `` is the shared library extension of the system at hand (e.g. `.so`, `.dll`, `.dylib`) diff --git a/src/MKL.jl b/src/MKL.jl index b3c7ac4..80eb60e 100644 --- a/src/MKL.jl +++ b/src/MKL.jl @@ -1,9 +1,36 @@ module MKL -using MKL_jll - +using Preferences +using Libdl using LinearAlgebra +# Choose an MKL path; taking an explicit preference as the first choice, +# but if nothing is set as a preference, fall back to the default choice of `MKL_jll`. +const mkl_path = something( + @load_preference("mkl_path", nothing), + "mkl_jll", +)::String + +if lowercase(mkl_path) == "mkl_jll" + # Only load MKL_jll if we are suppoed to use it as the MKL source + # to avoid an unnecessary download of the (lazy) artifact. + import MKL_jll + const mkl_found = MKL_jll.is_available() + const libmkl_rt = mkl_found ? MKL_jll.libmkl_rt : nothing +elseif lowercase(mkl_path) == "system" + # We expect the "system" MKL to already be loaded, + # or be on our linker search path. + libname = string("libmkl_rt", ".", Libdl.dlext) + const libmkl_rt = find_library(libname, [""]) + const mkl_found = libmkl_rt != "" + mkl_found || @warn("Couldn't find $libname. Try to specify the path to `libmkl_rt` explicitly.") +else + # mkl_path should be a valid path to libmkl_rt. + const libmkl_rt = mkl_path + const mkl_found = isfile(libmkl_rt) + mkl_found || @warn("Couldn't find MKL library at $libmkl_rt.") +end + if Base.USE_BLAS64 const MKLBlasInt = Int64 else @@ -24,6 +51,14 @@ end INTERFACE_GNU end +function set_mkl_path(path) + if lowercase(path) ∉ ("mkl_jll", "system") && !isfile(path) + error("The provided argument $path neither seems to be a valid path to libmkl_rt nor \"mkl_jll\" or \"system\".") + end + @set_preferences!("mkl_path" => path) + @info("New MKL preference set; please restart Julia to see this take effect", path) +end + function set_threading_layer(layer::Threading = THREADING_SEQUENTIAL) err = ccall((:MKL_Set_Threading_Layer, libmkl_rt), Cint, (Cint,), layer) err == -1 && throw(ErrorException("MKL_Set_Threading_Layer() returned -1")) @@ -36,29 +71,36 @@ function set_interface_layer(interface::Interface = INTERFACE_LP64) return nothing end -function __init__() - if MKL_jll.is_available() - if Sys.isapple() - set_threading_layer(THREADING_SEQUENTIAL) - end - # MKL 2022 and onwards have 64_ for ILP64 suffixes. The LP64 interface - # includes LP64 APIs for the non-suffixed symbols and ILP64 API for the - # 64_ suffixed symbols. LBT4 in Julia is necessary for this to work. - set_interface_layer(INTERFACE_LP64) - if Base.USE_BLAS64 - # Load ILP64 forwards - BLAS.lbt_forward(libmkl_rt; clear=true, suffix_hint="64") - # Load LP64 forward - BLAS.lbt_forward(libmkl_rt; suffix_hint="") - else - BLAS.lbt_forward(libmkl_rt; clear=true, suffix_hint="") - end - end -end - function mklnorm(x::Vector{Float64}) ccall((:dnrm2_, libmkl_rt), Float64, (Ref{MKLBlasInt}, Ptr{Float64}, Ref{MKLBlasInt}), length(x), x, 1) end +function lbt_mkl_forwarding() + if Sys.isapple() + set_threading_layer(THREADING_SEQUENTIAL) + end + # MKL 2022 and onwards have 64_ for ILP64 suffixes. The LP64 interface + # includes LP64 APIs for the non-suffixed symbols and ILP64 API for the + # 64_ suffixed symbols. LBT4 in Julia is necessary for this to work. + set_interface_layer(INTERFACE_LP64) + if Base.USE_BLAS64 + # Load ILP64 forwards + BLAS.lbt_forward(libmkl_rt; clear=true, suffix_hint="64") + # Load LP64 forward + BLAS.lbt_forward(libmkl_rt; suffix_hint="") + else + BLAS.lbt_forward(libmkl_rt; clear=true, suffix_hint="") + end + return nothing +end + +function __init__() + if mkl_found + lbt_mkl_forwarding() + else + @warn("MKL library couldn't be found. Please make sure to set the `mkl_path` preference correctly (e.g. via `MKL.set_mkl_path`).") + end +end + end # module