blas/lapack: add wrapper for “alternative”s of BLAS/LAPACK provider

This is based on previous work for switching between BLAS and LAPACK
implementation in Debian[1] and Gentoo[2]. The goal is to have one way
to depend on the BLAS/LAPACK libraries that all packages must use. The
attrs “blas” and “lapack” are used to represent a wrapped BLAS/LAPACK
provider. Derivations that don’t care how BLAS and LAPACK are
implemented can just use blas and lapack directly. If you do care what
you get (perhaps for some CPP), you should verify that blas and lapack
match what you expect with an assertion.

The “blas” package collides with the old “blas” reference
implementation. This has been renamed to “blas-reference”. In
addition, “lapack-reference” is also included, corresponding to
“liblapack” from Netlib.org.

Currently, there are 3 providers of the BLAS and LAPACK interfaces:

- lapack-reference: the BLAS/LAPACK implementation maintained by netlib.org
- OpenBLAS: an optimized version of BLAS and LAPACK
- MKL: Intel’s unfree but highly optimized BLAS/LAPACK implementation

By default, the above implementations all use the “LP64” BLAS and
LAPACK ABI. This corresponds to “openblasCompat” and is the safest way
to use BLAS/LAPACK. You may received some benefits from “ILP64” or
8-byte integer BLAS at the expense of breaking compatibility with some
packages.

This can be switched at build time with an override like:

    import <nixpkgs> {
        config.allowUnfree = true;
        overlays = [(self: super: {
          lapack = super.lapack.override {
            lapackProvider = super.lapack-reference;
          };
          blas = super.blas.override {
            blasProvider = super.lapack-reference;
          };
        })];
      }

or, switched at runtime via LD_LIBRARY_PATH like:

    $ LD_LIBRARY_PATH=$(nix-build -E '(with import <nixpkgs> {}).lapack.override { lapackProvider = pkgs.mkl; is64bit = true; })')/lib:$(nix-build -E '(with import <nixpkgs> {}).blas.override { blasProvider = pkgs.mkl; is64bit = true; })')/lib ./your-blas-linked-binary

By default, we use OpenBLAS LP64 also known in Nixpkgs as
openblasCompat.

[1]: https://wiki.debian.org/DebianScience/LinearAlgebraLibraries
[2]: https://wiki.gentoo.org/wiki/Blas-lapack-switch
This commit is contained in:
Matthew Bauer 2020-03-27 14:50:45 -04:00
parent 90326ba624
commit 43873351ff
11 changed files with 286 additions and 37 deletions

View file

@ -0,0 +1,137 @@
{ lib, stdenv
, lapack-reference, openblasCompat, openblas
, is64bit ? false
, blasProvider ? if is64bit then openblas else openblasCompat }:
let
blasFortranSymbols = [
"caxpy" "ccopy" "cdotc" "cdotu" "cgbmv" "cgemm" "cgemv" "cgerc" "cgeru"
"chbmv" "chemm" "chemv" "cher" "cher2" "cher2k" "cherk" "chpmv" "chpr"
"chpr2" "crotg" "cscal" "csrot" "csscal" "cswap" "csymm" "csyr2k" "csyrk"
"ctbmv" "ctbsv" "ctpmv" "ctpsv" "ctrmm" "ctrmv" "ctrsm" "ctrsv" "dasum"
"daxpy" "dcabs1" "dcopy" "ddot" "dgbmv" "dgemm" "dgemv" "dger" "dnrm2"
"drot" "drotg" "drotm" "drotmg" "dsbmv" "dscal" "dsdot" "dspmv" "dspr"
"dspr2" "dswap" "dsymm" "dsymv" "dsyr" "dsyr2" "dsyr2k" "dsyrk" "dtbmv"
"dtbsv" "dtpmv" "dtpsv" "dtrmm" "dtrmv" "dtrsm" "dtrsv" "dzasum" "dznrm2"
"icamax" "idamax" "isamax" "izamax" "lsame" "sasum" "saxpy" "scabs1"
"scasum" "scnrm2" "scopy" "sdot" "sdsdot" "sgbmv" "sgemm" "sgemv"
"sger" "snrm2" "srot" "srotg" "srotm" "srotmg" "ssbmv" "sscal" "sspmv"
"sspr" "sspr2" "sswap" "ssymm" "ssymv" "ssyr" "ssyr2" "ssyr2k" "ssyrk"
"stbmv" "stbsv" "stpmv" "stpsv" "strmm" "strmv" "strsm" "strsv" "xerbla"
"xerbla_array" "zaxpy" "zcopy" "zdotc" "zdotu" "zdrot" "zdscal" "zgbmv"
"zgemm" "zgemv" "zgerc" "zgeru" "zhbmv" "zhemm" "zhemv" "zher" "zher2"
"zher2k" "zherk" "zhpmv" "zhpr" "zhpr2" "zrotg" "zscal" "zswap" "zsymm"
"zsyr2k" "zsyrk" "ztbmv" "ztbsv" "ztpmv" "ztpsv" "ztrmm" "ztrmv" "ztrsm"
"ztrsv"
];
version = "3";
canonicalExtension = if stdenv.hostPlatform.isLinux
then "${stdenv.hostPlatform.extensions.sharedLibrary}.${version}"
else stdenv.hostPlatform.extensions.sharedLibrary;
is64bit = blasProvider.blas64 or false;
blasImplementation = lib.getName blasProvider;
in
assert is64bit -> (blasImplementation == "openblas" && blasProvider.blas64) || blasImplementation == "mkl";
stdenv.mkDerivation {
pname = "blas";
inherit version;
outputs = [ "out" "dev" ];
meta = (blasProvider.meta or {}) // {
description = "${lib.getName blasProvider} with just the BLAS C and FORTRAN ABI";
};
passthru = {
inherit is64bit;
provider = blasProvider;
implementation = blasImplementation;
};
dontBuild = true;
dontConfigure = true;
unpackPhase = "src=$PWD";
installPhase = (''
mkdir -p $out/lib $dev/include $dev/include/pkgconfig
libblas="${lib.getLib blasProvider}/lib/libblas${stdenv.hostPlatform.extensions.sharedLibrary}"
if ! [ -e "$libblas" ]; then
echo "$libblas does not exist, ${blasProvider.name} does not provide libblas."
exit 1
fi
nm -an "$libblas" | cut -f3 -d' ' > symbols
for symbol in ${toString blasFortranSymbols}; do
grep "^$symbol_$" symbols || { echo "$symbol" was not found in "$libblas"; exit 1; }
done
cp -L "$libblas" $out/lib/libblas${canonicalExtension}
chmod +w $out/lib/libblas${canonicalExtension}
'' + (if stdenv.hostPlatform.parsed.kernel.execFormat.name == "elf" then ''
patchelf --set-soname libblas${canonicalExtension} $out/lib/libblas${canonicalExtension}
patchelf --set-rpath "$(patchelf --print-rpath $out/lib/libblas${canonicalExtension}):${lib.getLib blasProvider}/lib" $out/lib/libblas${canonicalExtension}
'' else if stdenv.hostPlatform.isDarwin then ''
install_name_tool \
-id libblas${canonicalExtension}
-add_rpath ${lib.getLib blasProvider}/lib \
$out/lib/libblas${canonicalExtension}
'' else "") + ''
if [ "$out/lib/libblas${canonicalExtension}" != "$out/lib/libblas${stdenv.hostPlatform.extensions.sharedLibrary}" ]; then
ln -s $out/lib/libblas${canonicalExtension} "$out/lib/libblas${stdenv.hostPlatform.extensions.sharedLibrary}"
fi
cat <<EOF > $dev/lib/pkgconfig/blas.pc
Name: blas
Version: ${version}
Description: BLAS FORTRAN implementation
Libs: -L$out/lib -lblas
Cflags: -I$dev/include
EOF
libcblas="${lib.getLib blasProvider}/lib/libcblas${stdenv.hostPlatform.extensions.sharedLibrary}"
if ! [ -e "$libcblas" ]; then
echo "$libcblas does not exist, ${blasProvider.name} does not provide libcblas."
exit 1
fi
cp -L "$libcblas" $out/lib/libcblas${canonicalExtension}
chmod +w $out/lib/libcblas${canonicalExtension}
'' + (if stdenv.hostPlatform.parsed.kernel.execFormat.name == "elf" then ''
patchelf --set-soname libcblas${canonicalExtension} $out/lib/libcblas${canonicalExtension}
patchelf --set-rpath "$(patchelf --print-rpath $out/lib/libcblas${canonicalExtension}):${lib.getLib blasProvider}/lib" $out/lib/libcblas${canonicalExtension}
'' else if stdenv.hostPlatform.isDarwin then ''
install_name_tool \
-id libcblas${canonicalExtension} \
-add_rpath ${lib.getLib blasProvider}/lib \
$out/lib/libcblas${canonicalExtension}
'' else "") + ''
if [ "$out/lib/libcblas${canonicalExtension}" != "$out/lib/libcblas${stdenv.hostPlatform.extensions.sharedLibrary}" ]; then
ln -s $out/lib/libcblas${canonicalExtension} "$out/lib/libcblas${stdenv.hostPlatform.extensions.sharedLibrary}"
fi
cp ${lib.getDev lapack-reference}/include/cblas{,_mangling}.h $dev/include
cat <<EOF > $dev/lib/pkgconfig/cblas.pc
Name: cblas
Version: ${version}
Description: BLAS C implementation
Cflags: -I$dev/include
Libs: -L$out/lib -lcblas
EOF
'' + stdenv.lib.optionalString (blasImplementation == "mkl") ''
mkdir -p $out/nix-support
echo 'export MKL_INTERFACE_LAYER=${lib.optionalString is64bit "I"}LP64,GNU' > $out/nix-support/setup-hook
'');
}

View file

@ -0,0 +1,111 @@
{ lib, stdenv
, lapack-reference, openblasCompat, openblas
, is64bit ? false
, lapackProvider ? if is64bit then openblas else openblasCompat }:
let
version = "3";
canonicalExtension = if stdenv.hostPlatform.isLinux
then "${stdenv.hostPlatform.extensions.sharedLibrary}.${version}"
else stdenv.hostPlatform.extensions.sharedLibrary;
lapackImplementation = lib.getName lapackProvider;
in
assert is64bit -> (lapackImplementation == "openblas" && lapackProvider.blas64) || lapackImplementation == "mkl";
stdenv.mkDerivation {
pname = "lapack";
inherit version;
outputs = [ "out" "dev" ];
meta = (lapackProvider.meta or {}) // {
description = "${lib.getName lapackProvider} with just the LAPACK C and FORTRAN ABI";
};
passthru = {
inherit is64bit;
provider = lapackProvider;
implementation = lapackImplementation;
};
dontBuild = true;
dontConfigure = true;
unpackPhase = "src=$PWD";
installPhase = (''
mkdir -p $out/lib $dev/include $dev/lib/pkgconfig
liblapack="${lib.getLib lapackProvider}/lib/liblapack${stdenv.hostPlatform.extensions.sharedLibrary}"
if ! [ -e "$liblapack" ]; then
echo "$liblapack does not exist, ${lapackProvider.name} does not provide liblapack."
exit 1
fi
cp -L "$liblapack" $out/lib/liblapack${canonicalExtension}
chmod +w $out/lib/liblapack${canonicalExtension}
'' + (if stdenv.hostPlatform.parsed.kernel.execFormat.name == "elf" then ''
patchelf --set-soname liblapack${canonicalExtension} $out/lib/liblapack${canonicalExtension}
patchelf --set-rpath "$(patchelf --print-rpath $out/lib/liblapack${canonicalExtension}):${lapackProvider}/lib" $out/lib/liblapack${canonicalExtension}
'' else if stdenv.hostPlatform.isDarwin then ''
install_name_tool -id liblapack${canonicalExtension} \
-add_rpath ${lib.getLib lapackProvider}/lib \
$out/lib/liblapack${canonicalExtension}
'' else "") + ''
if [ "$out/lib/liblapack${canonicalExtension}" != "$out/lib/liblapack${stdenv.hostPlatform.extensions.sharedLibrary}" ]; then
ln -s $out/lib/liblapack${canonicalExtension} "$out/lib/liblapack${stdenv.hostPlatform.extensions.sharedLibrary}"
fi
install -D ${lib.getDev lapack-reference}/include/lapack.h $dev/include/lapack.h
cat <<EOF > $dev/lib/pkgconfig/lapack.pc
Name: lapack
Version: ${version}
Description: LAPACK FORTRAN implementation
Cflags: -I$dev/include
Libs: -L$out/lib -llapack
EOF
liblapacke="${lib.getLib lapackProvider}/lib/liblapacke${stdenv.hostPlatform.extensions.sharedLibrary}"
if ! [ -e "$liblapacke" ]; then
echo "$liblapacke does not exist, ${lapackProvider.name} does not provide liblapacke."
exit 1
fi
cp -L "$liblapacke" $out/lib/liblapacke${canonicalExtension}
chmod +w $out/lib/liblapacke${canonicalExtension}
'' + (if stdenv.hostPlatform.parsed.kernel.execFormat.name == "elf" then ''
patchelf --set-soname liblapacke${canonicalExtension} $out/lib/liblapacke${canonicalExtension}
patchelf --set-rpath "$(patchelf --print-rpath $out/lib/liblapacke${canonicalExtension}):${lib.getLib lapackProvider}/lib" $out/lib/liblapacke${canonicalExtension}
'' else if stdenv.hostPlatform.isDarwin then ''
install_name_tool -id liblapacke${canonicalExtension} \
-add_rpath ${lib.getLib lapackProvider}/lib \
$out/lib/liblapacke${canonicalExtension}
'' else "") + ''
if [ -f "$out/lib/liblapacke.so.3" ]; then
ln -s $out/lib/liblapacke.so.3 $out/lib/liblapacke.so
fi
cp ${lib.getDev lapack-reference}/include/lapacke{,_mangling,_config}.h $dev/include
cat <<EOF > $dev/lib/pkgconfig/lapacke.pc
Name: lapacke
Version: ${version}
Description: LAPACK C implementation
Cflags: -I$dev/include
Libs: -L$out/lib -llapacke
EOF
'' + stdenv.lib.optionalString (lapackImplementation == "mkl") ''
mkdir -p $out/nix-support
echo 'export MKL_INTERFACE_LAYER=${lib.optionalString is64bit "I"}LP64,GNU' > $out/nix-support/setup-hook
'');
}

View file

@ -1,6 +1,9 @@
{ stdenv, fetchFromGitHub, autoreconfHook, givaro, pkgconfig, blas
{ stdenv, fetchFromGitHub, autoreconfHook, givaro, pkgconfig, blas, lapack
, gmpxx
}:
assert (!blas.is64bit) && (!lapack.is64bit);
stdenv.mkDerivation rec {
pname = "fflas-ffpack";
version = "2.4.3";
@ -23,11 +26,11 @@ stdenv.mkDerivation rec {
pkgconfig
] ++ stdenv.lib.optionals doCheck checkInputs;
buildInputs = [ givaro blas ];
buildInputs = [ givaro blas lapack ];
configureFlags = [
"--with-blas-libs=-l${blas.linkName}"
"--with-lapack-libs=-l${blas.linkName}"
"--with-blas-libs=-lcblas"
"--with-lapack-libs=-llapacke"
] ++ stdenv.lib.optionals stdenv.isx86_64 {
# disable SIMD instructions (which are enabled *when available* by default)
# for now we need to be careful to disable *all* relevant versions of an instruction set explicitly (https://github.com/linbox-team/fflas-ffpack/issues/284)

View file

@ -4,10 +4,14 @@
, givaro
, pkgconfig
, blas
, lapack
, fflas-ffpack
, gmpxx
, withSage ? false # sage support
}:
assert (!blas.is64bit) && (!lapack.is64bit);
stdenv.mkDerivation rec {
pname = "linbox";
version = "1.6.3";
@ -33,7 +37,7 @@ stdenv.mkDerivation rec {
];
configureFlags = [
"--with-blas-libs=-l${blas.linkName}"
"--with-blas-libs=-lblas"
"--disable-optimization"
] ++ stdenv.lib.optionals stdenv.isx86_64 {
# disable SIMD instructions (which are enabled *when available* by default)

View file

@ -68,10 +68,4 @@ EOF
homepage = "http://www.netlib.org/blas/";
platforms = stdenv.lib.platforms.unix;
};
# We use linkName to pass a different name to --with-blas-libs for
# fflas-ffpack and linbox, because we use blas on darwin but openblas
# elsewhere.
# See see https://github.com/NixOS/nixpkgs/pull/45013.
passthru.linkName = "blas";
}

View file

@ -42,7 +42,4 @@ stdenv.mkDerivation {
license = licenses.bsd3;
platforms = platforms.all;
};
passthru.libblasName = "libblas";
passthru.libcblasName = "libcblas";
}

View file

@ -111,7 +111,14 @@ in stdenvNoCC.mkDerivation {
cp -r opt/intel/compilers_and_libraries_${version}/linux/compiler/lib/intel64_lin/*.so* $out/lib/
cp -r opt/intel/compilers_and_libraries_${version}/linux/mkl/lib/intel64_lin/*.so* $out/lib/
cp -r opt/intel/compilers_and_libraries_${version}/linux/mkl/bin/pkgconfig/*dynamic*.pc $out/lib/pkgconfig
'');
'') + ''
# Setup symlinks for blas / lapack
ln -s $out/lib/libmkl_rt${stdenvNoCC.hostPlatform.extensions.sharedLibrary} $out/lib/libblas${stdenvNoCC.hostPlatform.extensions.sharedLibrary}
ln -s $out/lib/libmkl_rt${stdenvNoCC.hostPlatform.extensions.sharedLibrary} $out/lib/libcblas${stdenvNoCC.hostPlatform.extensions.sharedLibrary}
ln -s $out/lib/libmkl_rt${stdenvNoCC.hostPlatform.extensions.sharedLibrary} $out/lib/liblapack${stdenvNoCC.hostPlatform.extensions.sharedLibrary}
ln -s $out/lib/libmkl_rt${stdenvNoCC.hostPlatform.extensions.sharedLibrary} $out/lib/liblapacke${stdenvNoCC.hostPlatform.extensions.sharedLibrary}
'';
# fixDarwinDylibName fails for libmkl_cdft_core.dylib because the
# larger updated load commands do not fit. Use install_name_tool

View file

@ -176,6 +176,12 @@ Cflags: -I$out/include
Libs: -L$out/lib -lopenblas
EOF
done
# Setup symlinks for blas / lapack
ln -s $out/lib/libopenblas${stdenv.hostPlatform.extensions.sharedLibrary} $out/lib/libblas${stdenv.hostPlatform.extensions.sharedLibrary}
ln -s $out/lib/libopenblas${stdenv.hostPlatform.extensions.sharedLibrary} $out/lib/libcblas${stdenv.hostPlatform.extensions.sharedLibrary}
ln -s $out/lib/libopenblas${stdenv.hostPlatform.extensions.sharedLibrary} $out/lib/liblapack${stdenv.hostPlatform.extensions.sharedLibrary}
ln -s $out/lib/libopenblas${stdenv.hostPlatform.extensions.sharedLibrary} $out/lib/liblapacke${stdenv.hostPlatform.extensions.sharedLibrary}
'';
meta = with stdenv.lib; {
@ -185,10 +191,4 @@ EOF
platforms = platforms.unix;
maintainers = with maintainers; [ ttuegel ];
};
# We use linkName to pass a different name to --with-blas-libs for
# fflas-ffpack and linbox, because we use blas on darwin but openblas
# elsewhere.
# See see https://github.com/NixOS/nixpkgs/pull/45013.
passthru.linkName = "openblas";
}

View file

@ -237,7 +237,7 @@ mapAliases ({
libGL_driver = mesa.drivers;
libintlOrEmpty = stdenv.lib.optional (!stdenv.isLinux || stdenv.hostPlatform.libc != "glibc") gettext; # added 2018-03-14
libjson_rpc_cpp = libjson-rpc-cpp; # added 2017-02-28
liblapackWithoutAtlas = liblapack; # added 2018-11-05
liblapackWithoutAtlas = lapack-reference; # added 2018-11-05
liblrdf = lrdf; # added 2018-04-25
libqrencode = qrencode; # added 2019-01-01
librdf = lrdf; # added 2020-03-22

View file

@ -11479,20 +11479,11 @@ in
ffcast = callPackage ../tools/X11/ffcast { };
fflas-ffpack = callPackage ../development/libraries/fflas-ffpack {
# We need to use blas instead of openblas on darwin,
# see https://github.com/NixOS/nixpkgs/pull/45013.
blas = if stdenv.isDarwin then blas else openblas;
};
fflas-ffpack = callPackage ../development/libraries/fflas-ffpack { };
forge = callPackage ../development/libraries/forge { };
linbox = callPackage ../development/libraries/linbox {
# We need to use blas instead of openblas on darwin, see
# https://github.com/NixOS/nixpkgs/pull/45013 and
# https://github.com/NixOS/nixpkgs/pull/45015.
blas = if stdenv.isDarwin then blas else openblas;
};
linbox = callPackage ../development/libraries/linbox { };
ffmpeg_2_8 = callPackage ../development/libraries/ffmpeg/2.8.nix {
inherit (darwin.apple_sdk.frameworks) Cocoa;
@ -24428,7 +24419,9 @@ in
arpack = callPackage ../development/libraries/science/math/arpack { };
blas = callPackage ../development/libraries/science/math/blas { };
blas = callPackage ../build-support/alternatives/blas { };
blas-reference = callPackage ../development/libraries/science/math/blas { };
brial = callPackage ../development/libraries/science/math/brial { };
@ -24448,12 +24441,15 @@ in
jags = callPackage ../applications/science/math/jags { };
lapack = callPackage ../build-support/alternatives/lapack { };
lapack-reference = callPackage ../development/libraries/science/math/liblapack { };
liblapack = lapack-reference;
libbraiding = callPackage ../development/libraries/science/math/libbraiding { };
libhomfly = callPackage ../development/libraries/science/math/libhomfly { };
liblapack = callPackage ../development/libraries/science/math/liblapack {};
liblbfgs = callPackage ../development/libraries/science/math/liblbfgs { };
lrs = callPackage ../development/libraries/science/math/lrs { };

View file

@ -78,9 +78,9 @@ with import ./release-lib.nix { inherit supportedSystems; };
kvm = linux;
qemu = linux;
qemu_kvm = linux;
lapack-reference = linux;
less = all;
lftp = all;
liblapack = linux;
libtool = all;
libtool_2 = all;
libxml2 = all;