-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Support in code loading and precompilation for weak dependencies #47040
Conversation
First of all, thanks for addressing this issue!
Do I understand correctly that in large environments this may lead to accidentally increase loading time, as the weak dependency may be pulled in by some other random packages, instead of being added directly by the user to the environment? As seen in https://github.com/IanButterworth/WeakDepsExamples/blob/adb19bec21d763566c4419bf1d459fcf130ee9ae/HasWeakDeps.jl/src/HasWeakDeps.jl#L9-L19, the code one would typically write is if Base.@hasdep CUDA
using CUDA
# do stuff with CUDA
else
# do stuff without CUDA
end so CUDA has always to be loaded if it just happens to be present in the environment. On the other hand I appreciate this works better with precompilation and makes code loading more deterministic. |
This is lovely! As a style nit, the pattern if Base.@hasdep CUDA
using CUDA seems redundant. Is there any case where one would call if Base.@optional using CUDA
# do stuff with CUDA
else
# without
end or if Base.@optional using CUDA: something_from_cuda
# do stuff with CUDA
else
# without
end or if Base.@optional import CUDA.something
# do stuff with CUDA
else
# without
end
|
For large environments where only a small amount of the environment is typically loaded in a session, yes. But generally, that is not a great idea in the first place.
Kind of yes. But I think the current one is so much simpler that a bit of redundancy is not too bad. And who knows, maybe you don't want to load the package in that block for some reason. |
9e9d3e4
to
ff09903
Compare
Such awesome work! This is really exciting. Would it be possible to spell out "dependency," e.g. |
Yeah, that needs some bike shedding. I just went with something quick as to not get stuck thinking about it. |
To elaborate on my comment above, my ultimate question is: how about defining "weak dependency" a dependency which is explicitly added to the environment (i.e., it's in the project file) and doesn't simply happen to be in the environment because it's been pulled in by some other packages? |
I agree with mose. Since we always load the package if it's in the environment, I guess requiring it to be explicitily added makes sense. |
I don't really understand this. If you have a project with only package A in it, A depends on B and C, inside A you call |
To give a clear example, imagine package x has an optional CUDA.jl dep and it internally could use GPU processing in a function even if it returns a normal julia array. |
I am not sure why you didn't comment on my example at all but anyway.
To rephrase my previous example. Types can "flow" between packages that do not have a dependency relationship by moving up the dependency chain (by returning objects of that type) and then downwards again (by calling functions with those objects) thanks to generic code. You may want to be able to specialize code on the chance that you will be called with some specific type, without having to take on a full dependency on the package that defines the type for that value. This is true even if all of the packages here being considered are all somewhere "deep" in the dependency graph. I don't think you would use weak dependencies for your example but probably something more like the Preference system and then say |
I think the split where you're talking past each other is about whether I'd say that yes, these functions should be available - without large static analysis, it'd be quite hard to determine whether a callchain involving So "opting into" |
The implications of this (awesome) pr for this particular use case go over my head, but I, and most people I know, exclusively use one big environment. I know that's probably not optimal and feel vaguely guilty for doing so, but it's the simplest solution and it works sufficiently well that I've never bothered to change. Generally speaking people commenting here probably use more advanced workflows than the average julia user, so I just wanted to point out that yes, this is a prevalent use case. |
I should mention that there is one alternative possible implementation that works better with large environments where typically only a small part of the environment is loaded (but it has other drawbacks). It does this by pushing the loading of conditional code to runtime based don't the loading of other packages, (similarly described in #43119 but with some tweaks to support precompilation) which is also more similar to Requires.jl. The implementation would roughly be:
The advantages of this are:
The disadvantages are:
I know @vtjnash prefers this implementation :P |
Is this likely to make it into 1.9? |
Hello, sharing a few words about CUDA. Some time ago we found an issue with CUDA drivers on a machine without NVidia hardware. We had a problem with a build agent located on AWS. https://github.com/jw3126/ONNXRunTime.jl/pull/23/files We used ONNXRunTime. And the initial code of that package looked like that: function __init__()
@require CUDA="052768ef-5323-5732-b1bb-66c8b64840ba" include("cuda.jl")
end The issue was we couldn't control the usage of CUDA in 3-rd party packages. And The correct way is to do an additional check: function __init__()
@require CUDA="052768ef-5323-5732-b1bb-66c8b64840ba" begin
CUDA.functional() && include("cuda.jl")
end
end But CUDA is already loaded in that case. And we should use And, an additional question is binary compiling with CUDA support. If we want to prepare a docker image with CUDA support for production use, the build agent must be able to activate CUDA drivers too. But that might not be true with AWS or another cloud platform. The more preferable way is to build a package on a machine without NVidia hardware/CUDA support but use the docker image with a production cluster with NVidia hardware. |
@KristofferC Can we close this since package extensions got merged? |
Yes |
Weak dependencies are a solution to the problem where you want to be able to extend some package (e.g. via a method overload) but you don't really use the package itself so taking on a full dependency can be too expensive (in terms of e.g. load time).
Weak dependencies are a bit similar to optional dependencies in Rust (https://doc.rust-lang.org/cargo/reference/features.html#optional-dependencies), with the difference that the optional dependency is not explicitly opted into, but is based on the presence of the optional dependency in the current environment.
The problem weak dependencies solves is very similar to what Requires.jl solves but it has some advantages:
include
s and serializes a string of those files so that it can be evaluated during runtime even when the file does not exist.compat
of the weak dependency is applied).In summary, there is virtually no disadvantage of a dependency being weak. If it can be weak it pretty much should. The end goal is to reduce artificial "dissection" of packages (c.f. StaticArraysCore.jl) and to make the size of the average dependency graph significantly smaller, leading to smaller load times and smaller artifacts from e.g. PackageCompiler.
An example of packages using weak dependencies can be found in https://github.com/IanButterworth/WeakDepsExamples (where the syntax in the package and the TOML files schema can be seen). The registry format can be seen in https://github.com/IanButterworth/General/tree/ib/weak_deps/H/HasWeakDeps.
For simplicity of testing, this PR also contains a change to the Pkg version that suports weak dependencies (JuliaLang/Pkg.jl#3216).
This work has been done in collaboration with @IanButterworth
Implementation
Base
Pkg
pkg> st --weak
)These are implemented in JuliaLang/Pkg.jl#3216.
Registrator