Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial implementation of lazy JLLs for LinearAlgebra #57719

Merged
merged 5 commits into from
Mar 16, 2025

Conversation

staticfloat
Copy link
Member

@staticfloat staticfloat commented Mar 11, 2025

This alters CompilerSupportLibraries_jll, OpenBLAS_jll and
libblastrampoline_jll to use LazyLibrary objects and thereby be loaded
only upon first dlopen() or ccall() to the individual library
objects. Note that this is one of the more complicated cases, as
libblastrampoline must have OpenBLAS_jll added as a dynamic dependency
(as it does not actually have it listed in its shared object headers)
and also has some on-load callbacks that must be invoked.

Long-term, I would like to replace the bespoke JLLs here with actual JLL
source code, and vendor a version of LazyJLLWrappers in-tree to do the
actual code generation even for Base. That is left as future work.

[0] JuliaLang/LinearAlgebra.jl#1235

staticfloat added a commit to JuliaLang/LinearAlgebra.jl that referenced this pull request Mar 11, 2025
This alters CompilerSupportLibraries_jll, OpenBLAS_jll and
libblastrampoline_jll to use `LazyLibrary` objects and thereby be loaded
only upon first `dlopen()` or `ccall()` to the individual library
objects.  Note that this is one of the more complicated cases, as
`libblastrampoline` must have OpenBLAS_jll added as a dynamic dependency
(as it does not actually have it listed in its shared object headers)
and also has some on-load callbacks that must be invoked.

This must be paired with the appropriate base Julia changes [0].

[0] JuliaLang/julia#57719
@staticfloat staticfloat force-pushed the sf/lazy_linear_algebra branch from 85eb73d to d537c45 Compare March 11, 2025 13:51
staticfloat added a commit to JuliaLang/LinearAlgebra.jl that referenced this pull request Mar 11, 2025
This alters CompilerSupportLibraries_jll, OpenBLAS_jll and
libblastrampoline_jll to use `LazyLibrary` objects and thereby be loaded
only upon first `dlopen()` or `ccall()` to the individual library
objects.  Note that this is one of the more complicated cases, as
`libblastrampoline` must have OpenBLAS_jll added as a dynamic dependency
(as it does not actually have it listed in its shared object headers)
and also has some on-load callbacks that must be invoked.

This must be paired with the appropriate base Julia changes [0].

[0] JuliaLang/julia#57719
@staticfloat staticfloat force-pushed the sf/lazy_linear_algebra branch from d537c45 to b43712b Compare March 11, 2025 14:42
@topolarity topolarity force-pushed the sf/lazy_linear_algebra branch from b43712b to 3db34a3 Compare March 11, 2025 14:59
@staticfloat staticfloat force-pushed the sf/lazy_linear_algebra branch 2 times, most recently from d21736d to 24bdde7 Compare March 11, 2025 21:02
@staticfloat staticfloat requested a review from topolarity March 11, 2025 21:07
@staticfloat
Copy link
Member Author

This is working locally now, I can confirm that Libdl.dllist() shows that libgfortran, libopenblas64_ and libblastrampoline are all not loaded until the first Linear Algebra call!

Note that this is attempting to conform to the LazyJLLWrappers API, which includes things like eager_mode(), which is called by JLLWrappers.jl. This is to provide backwards-compatibility with JLLs that expect all dependencies from all dependent JLLs to have already dlopen()'ed their libraries.

What this means is that for any realistic workload where there are JLLs that depend on LBT/OpenBLAS, everything is going to get eagerly loaded upon loading of that JLL anyway, until those JLLs are built to use LazyJLLWrappers instead of JLLWrappers, which functionally means waiting for BB2 to be ready, as it contains the new analysis passes that track things like library interdependencies, etc... However, for workloads that never use linear algebra workloads, this will be a nice savings, as the libraries will now never get loaded!

@topolarity
Copy link
Member

The --trim failure should be fixed up by #57734

@topolarity topolarity added the backport 1.12 Change should be backported to release-1.12 label Mar 11, 2025
@staticfloat staticfloat marked this pull request as draft March 11, 2025 23:56
@staticfloat
Copy link
Member Author

I've converted this to draft because I realized that while "real" JLLs that use JLLWrappers will correctly call eager_mode() on these lazy stdlib JLLs, other stdlib JLLs that are not lazy yet but that depend on any of these new lazy stdlib JLLs won't, I need to add that in here.

staticfloat added a commit to JuliaLang/LinearAlgebra.jl that referenced this pull request Mar 12, 2025
This alters CompilerSupportLibraries_jll, OpenBLAS_jll and
libblastrampoline_jll to use `LazyLibrary` objects and thereby be loaded
only upon first `dlopen()` or `ccall()` to the individual library
objects.  Note that this is one of the more complicated cases, as
`libblastrampoline` must have OpenBLAS_jll added as a dynamic dependency
(as it does not actually have it listed in its shared object headers)
and also has some on-load callbacks that must be invoked.

This must be paired with the appropriate base Julia changes [0].

[0] JuliaLang/julia#57719
@staticfloat staticfloat force-pushed the sf/lazy_linear_algebra branch 3 times, most recently from b85e778 to 6109346 Compare March 12, 2025 00:25
@staticfloat staticfloat marked this pull request as ready for review March 12, 2025 00:30
@topolarity
Copy link
Member

It's also worth noting that once we land this PR, --trim will not support loading BLAS until we fix #57707

@staticfloat staticfloat force-pushed the sf/lazy_linear_algebra branch 4 times, most recently from 94382bd to afc71c7 Compare March 12, 2025 15:36
@topolarity
Copy link
Member

I think you might need a rebase for the trimming test to pass, and the following patch:

diff --git i/base/libdl.jl w/base/libdl.jl
index 77f608e77e..d94a2fd0e2 100644
--- i/base/libdl.jl
+++ w/base/libdl.jl
@@ -333,7 +333,7 @@ struct LazyLibraryPath
     pieces::Tuple{Vararg{Any}}
     LazyLibraryPath(pieces...) = new(pieces)
 end
-Base.string(llp::LazyLibraryPath) = joinpath(String[string(p) for p in llp.pieces])
+@inline Base.string(llp::LazyLibraryPath) = joinpath(String[string(p) for p in llp.pieces])
 Base.cconvert(::Type{Cstring}, llp::LazyLibraryPath) = Base.cconvert(Cstring, string(llp))
 # Define `print` so that we can wrap this in a `LazyString`
 Base.print(io::IO, llp::LazyLibraryPath) = print(io, string(llp))

The robust solution to this will require a callable of some kind, but for now massaging is required to make sure that the components of the path are visible to the optimizer

@staticfloat staticfloat force-pushed the sf/lazy_linear_algebra branch from 7dcb242 to b4cc2f8 Compare March 12, 2025 21:10
staticfloat added a commit to JuliaLang/LinearAlgebra.jl that referenced this pull request Mar 13, 2025
This alters CompilerSupportLibraries_jll, OpenBLAS_jll and
libblastrampoline_jll to use `LazyLibrary` objects and thereby be loaded
only upon first `dlopen()` or `ccall()` to the individual library
objects.  Note that this is one of the more complicated cases, as
`libblastrampoline` must have OpenBLAS_jll added as a dynamic dependency
(as it does not actually have it listed in its shared object headers)
and also has some on-load callbacks that must be invoked.

This must be paired with the appropriate base Julia changes [0].

[0] JuliaLang/julia#57719
@staticfloat staticfloat force-pushed the sf/lazy_linear_algebra branch 2 times, most recently from 44bd276 to 41e8fb4 Compare March 13, 2025 14:28
@staticfloat staticfloat force-pushed the sf/lazy_linear_algebra branch 4 times, most recently from 3433d57 to f77f026 Compare March 16, 2025 04:33
staticfloat added a commit to JuliaLang/LinearAlgebra.jl that referenced this pull request Mar 16, 2025
This alters CompilerSupportLibraries_jll, OpenBLAS_jll and
libblastrampoline_jll to use `LazyLibrary` objects and thereby be loaded
only upon first `dlopen()` or `ccall()` to the individual library
objects.  Note that this is one of the more complicated cases, as
`libblastrampoline` must have OpenBLAS_jll added as a dynamic dependency
(as it does not actually have it listed in its shared object headers)
and also has some on-load callbacks that must be invoked.

This must be paired with the appropriate base Julia changes [0].

[0] JuliaLang/julia#57719
staticfloat and others added 5 commits March 16, 2025 14:08
This alters CompilerSupportLibraries_jll, OpenBLAS_jll and
libblastrampoline_jll to use `LazyLibrary` objects and thereby be loaded
only upon first `dlopen()` or `ccall()` to the individual library
objects.  Note that this is one of the more complicated cases, as
`libblastrampoline` must have OpenBLAS_jll added as a dynamic dependency
(as it does not actually have it listed in its shared object headers)
and also has some on-load callbacks that must be invoked.

Long-term, I would like to replace the bespoke JLLs here with actual JLL
source code, and vendor a version of `LazyJLLWrappers` in-tree to do the
actual code generation even for Base.  That is left as future work.

This must be paired with the appropriate `LinearAlgebra.jl` changes [0].

[0] JuliaLang/LinearAlgebra.jl#1235
This makes more sense, reduces work, and avoids that perennial thorn in
my side, that in-tree builds store libraries in `${prefix}/lib`, whereas
installed builds store libraries in `${prefix}/lib/julia`.

Co-authored-by: Cody Tapscott <[email protected]>
We can no longer depend on the fact that these libraries will be eagerly
loaded (huzzah!) so we must teach these helper functions to either
inspect the lazy CSL JLL for the necessary information, or just directly
load the library.
This includes the changes needed for lazy LinearAlgebra JLLs.
@staticfloat staticfloat force-pushed the sf/lazy_linear_algebra branch from f77f026 to f38a3b9 Compare March 16, 2025 21:08
@staticfloat
Copy link
Member Author

Tests passed, so I merged the LinearAlgebra PR, adjusted the final commit here to bump the LinearAlgebra stdlib, and once this goes green, we're good to merge!

@staticfloat staticfloat added the merge me PR is reviewed. Merge when all tests are passing label Mar 16, 2025
@staticfloat staticfloat merged commit 0d363a8 into master Mar 16, 2025
6 of 8 checks passed
@staticfloat staticfloat deleted the sf/lazy_linear_algebra branch March 16, 2025 23:04
@nanosoldier
Copy link
Collaborator

Your job submission was not accepted: invalid argument to vs keyword.

@topolarity
Copy link
Member

@nanosoldier runtests(vs = "@04922e7e205237ab01cb009b2684bcbb97b053cf")

@topolarity
Copy link
Member

It looks like this might have broken a test on FreeBSD: https://buildkite.com/julialang/julia-master/builds/45925#0195a580-58c7-45e6-a297-b1adcc81d8b6/770-1576

The commits just prior this on master are passing this test, in comparison

@giordano
Copy link
Contributor

#57808

@topolarity
Copy link
Member

Fix in #57811

@nanosoldier
Copy link
Collaborator

The package evaluation job you requested has completed - possible new issues were detected.
The full report is available.

Report summary

❗ Packages that crashed

1 packages crashed only on the current version.

  • An internal error was encountered: 1 packages

4 packages crashed on the previous version too.

✖ Packages that failed

31 packages failed only on the current version.

  • Package fails to precompile: 5 packages
  • Illegal method overwrites during precompilation: 2 packages
  • Package has test failures: 5 packages
  • Package tests unexpectedly errored: 2 packages
  • Package requires a missing binary dependency: 4 packages
  • There were unidentified errors: 1 packages
  • Test duration exceeded the time limit: 12 packages

1518 packages failed on the previous version too.

✔ Packages that passed tests

21 packages passed tests only on the current version.

  • Other: 21 packages

5084 packages passed tests on the previous version too.

~ Packages that at least loaded

7 packages successfully loaded only on the current version.

  • Other: 7 packages

2817 packages successfully loaded on the previous version too.

➖ Packages that were skipped altogether

1 packages were skipped only on the current version.

  • Package could not be installed: 1 packages

909 packages were skipped on the previous version too.

@topolarity
Copy link
Member

topolarity commented Mar 18, 2025

Some of the PkgEval failures (thankfully, not many!) look relevant, such as FastPolynomialRoots.jl and StateSpaceLearning.jl:

Failed to precompile GLMNet [8d5ece8b-de18-5317-b113-243142960cc6] to "/home/pkgeval/.julia/compiled/v1.13/GLMNet/jl_6YhP1U" (ProcessExited(1)).
ERROR: LoadError: InitError: could not load library "/home/pkgeval/.julia/artifacts/e80442d59af73a1877a108eb3fab2cd12d25dc56/lib/libglmnet.so"
libgfortran.so.5: cannot open shared object file: No such file or directory
Stacktrace:
  [1] #dlopen#3
    @ ./libdl.jl:120 [inlined]
  [2] dlopen
    @ ./libdl.jl:119 [inlined]
  [3] dlopen(s::String)
    @ Base.Libc.Libdl ./libdl.jl:119
  [4] __init__()
    @ glmnet_jll ~/.julia/packages/glmnet_jll/en9XD/src/wrappers/x86_64-linux-gnu-libgfortran5.jl:37

@topolarity
Copy link
Member

Looks like the common theme is that the JLL's involved are so old (≥5 years) that they predate JLLWrappers

const LIBPATH = Ref("")
const LIBPATH_list = String[]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it intentional that this dropped PATH_list?

Some very old JLL's (e.g. NL2sol_jll) fail now with:

ERROR: LoadError: InitError: UndefVarError: `PATH_list` not defined in `CompilerSupportLibraries_jll`

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not good, that's documented: https://docs.binarybuilder.org/stable/jll/#The-wrappers

@gitboy16
Copy link
Contributor

Just a note that between this PR and the previous one 57752, the size of Julia windows installation folder jumped from 1.1G to 1.5G.

@KristofferC KristofferC removed the backport 1.12 Change should be backported to release-1.12 label Mar 20, 2025
@KristofferC
Copy link
Member

I removed the backport here because this is very much like a feature and feature freeze was many months ago.

@giordano giordano removed the merge me PR is reviewed. Merge when all tests are passing label Mar 21, 2025
@topolarity
Copy link
Member

Just a note that between this PR and the previous one 57752, the size of Julia windows installation folder jumped from 1.1G to 1.5G.

Any idea which files / folders experienced the difference?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants