-
-
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
Monorepo Packages via Submodules/Subpackages: Support in Pkg and Julia Compiler #55516
Comments
This sounds fishy to me. How would this look? |
Maybe a section, somewhat like the [submodules]
Extrapolation = ["NonlinearSolve", "LinearSolve"] where those are given as
then you can use |
So it is a "subpackage" scoped to the "parent package" with us own deps and compat (which would go into the registry) but it always have the same version as the parent? Or is that also separate? |
That's probably a good way to put it. It's a package that's always scoped to be the same version as the parent but can add some extra deps and you can |
This sounds to me like a small extension package that primarily defines extra dependencies with little to no code, meaning it doesn't require frequent updates. If there is any code, it would be minimal, such as defining the solver and configuration structs, which also wouldn’t need regular updates. All the actual functionality resides in the 'main' package under extensions and is only available (and precompiled) if the user explicitly adds this 'thin' package to their environment. In this setup, |
No, not necessarily. If it was small and thin then it wouldn't effect load time all that much. I would think in theory if we had all of this functionality and time was easy to come by, we would probably just make all of SciML one big metapackage so that it's all versioned together, and the LinearSolve.jl, NonlinearSolve.jl, etc. would all just be OrdinaryDiffEq.jl solvers are already rather substantial in size in some cases, like OrdinaryDiffEq.StabilizedRK is a few MBs of code. So I wouldn't use things like "small" or "thin" to describe it, the smallness is definitely not a necessary property nor a requirement for it to be useful. That's one use case it might be used for, for example I could see Makie using this structure and then |
The term ‘thin’ might have caused some confusion. What I meant is that the few megabytes of code are not in the ‘thin’ package itself, but rather in the main package under extensions. This code would only be available (and precompiled) if the ‘thin’ package is explicitly added to the environment. The ‘thin’ package would essentially be empty, with a |
I think this ticket conflates several issues that should perhaps be considered separately. E.g., point one:
Isn't this more of a tooling/infrastructure issue? NB: FTR: the registrator is also available on the JuliaHub site, maybe that's more convenient than using it on GitHub? |
The package infrastructure issue is held in tandem with it because if it was straightforward to keep 50 packages all tied together and updated in lock step then there might not be a need for any submodule features. One solution could be that package infrastructure just gets so much better that registering 50 packages all at the same time is simple enough to be a single command. Right now it's a lot of manual overhead and pretty error prone, so one would consider not using separate packages at all, but that doesn't mean that's the only potential solution. I leave it as an issue to discuss the general phenomena, and suggest one path to resolution but leave it as open for debate as to whether that's the right path. |
This all sound a lot like the |
But it would also mean that if you wanted to make a small bugfix or docfix to LinearSolve users would have to "bump" the version of SciML and redownload / recompile everything? And if you only depend on LinearSolve, it would be annoying to have its version get bumped just because of a bugfix in some other "subpackage"? |
Yes, and I don't know if all of SciML is the right granularity, maybe just DifferentialEquations, I'd have to experiment with it. But definitely for example DelayDiffEq and StochasticDiffEq needs |
CUDA.jl/cuDNN.jl/cuTENSOR.jl/... is another possible candidate for a feature like this. There's currently quite tight coupling between CUDA.jl and its subpackages, so I wouldn't mind having to bump everything in lockstep (we currently already use tight compat bounds to minimize the risk of incompatibilities, https://github.com/JuliaGPU/CUDA.jl/blob/4918437ad8530bbe6f7d4613af2e82d53e968801/lib/cudnn/Project.toml#L14). EDIT: another example is CUDA.jl's profiler, which is fairly heavy to load: JuliaGPU/CUDA.jl#2238 |
While I don't have the expertise to comment on the diagnosis, I recalled reading the SciML Small Grant project for separating OrdinaryDiffEq into subpackages for improving precompilation. I immediately felt something was off with top-level implementations. One of my packages was subpackaged to speed up the development-time precompilation and then combined again to work around obscure dependency issues. I hope this Issue gets enough attention for a permanent fix, so that regardless of how the author structures it, packages can precompile as fast as the current (manual) best practices. |
Just to say that I also have a project (not yet in the registry) which has a ton of (sub-)packages in a monorepo. There, however, I'd rather not have the package versions in lockstep. But it sure would be nice if I could issue a single If such a feature would be in principle acceptable, that'd be great. |
Looking at Cargo's features, some notes
To me this seems like a good mapping to the needs here:
I think this would also be useful for Makie, whose individual backends really make sense as "features" in this way also, and are released in lockstep IIUC. I think "features" are also nice as they fill a different gap from package extensions or workspaces; no separate Project.toml, no separate compat bounds, rather just optional portions of a single main package that are compiled separately.
With this "feature" approach you would definitely need to bump the version of the main package and presumably redownload everything. I wonder though if the "feature" binary caching can be smart enough to not re-precompile everything if only a feature changed. (But I guess that's not safe if a package introspects it's own Project.toml or version number at precompilation time). Here is my attempt to map the "features" of cargo onto the questions from https://hackmd.io/jpsiIxdsSRe4ANi55_F7oA#Questions :
|
Some answers from the specific viewpoint of CUDA.jl and its potential subpackages:
Packages like CUDNN.jl have significant dependencies that only matter there, e.g., CUDNN_jll.jl comes with a 500MB artifact. So if the subpackage doesn't have its own Project.toml, there would need to be a mechanism to prevent downloading the CUDNN_jll.jl from the toplevel Project.toml when only importing CUDA.jl
Not strictly required; the subpackages aren't useful without the
Ideally there'd be support for dependencies within the same main package, as there are dependencies between e.g. CUSOLVER.jl and CUBLAS.jl.
That has happened... For example, at some point NVIDIA had updated CUTENSOR to v2.x which CUTENSOR.jl had updated to, but NVIDIA hadn't updated cuTENSORNET yet so cuTENSORNET.jl was still using an older version of cuTENSOR_jll.jl. However, that seems like a pretty niche requirement, and it's only happened once, for a short period of time.
Not needed.
There is only little opportunity for independent use, because of the aforementioned
Yes.
I haven't worked with workspaces yet.
We already do so in CUDA.jl CI, and yes, it's a little tricky: https://github.com/JuliaGPU/CUDA.jl/blob/0175505361ddc83b9205a60596af3e92e9f069d7/.buildkite/pipeline.yml#L88-L144 |
There are many things to be said about how to grow and scale repositories. However, I think it's clear that OrdinaryDiffEq.jl is a repo that is currently hitting the scaling limits of Julia and thus a good case to ground this discussion. It's a repository which is very performance-minded, has many different solvers (hundreds), and thus has many optimizations around. However, in order to be usable, it needs to load "not so badly". With the changes to v1.9 adding package binaries, we could get precompilation to finally build binaries of solvers and allow for first run times under a second. We need to continue to improve this, but job well done there.
However, that has started to show some cracks around it. In particular, the precompilation times to build the binaries are tremendous. At first they were measured close to an hour, but by tweaking with preferences we got it to around 5-10 minutes. The issue is that, again, there are many solvers, so if you want to make all solvers load fast, you need many binaries. Julia's package binary system is on the package level, so you only have a choice of whether to build the solvers of the package or not. We then setup Preferences.jl and by default tuned it down a bunch, but what that really meant is that while there is a solution to first solve problems, it's simply not usable in the vast majority of the cases by most users.
Also, one of the issues highlighted by @KristofferC was that some of the solvers were still causing unsolvable loading time issues simply by existing in the repo. As highlighted in SciML/OrdinaryDiffEq.jl#2177, for example the implicit extrapolation methods, which are a very rare method to use, caused 1.5 seconds of lowering time. This is something that we had discussed as potentially decreasing when the new lowering would come into play, but not to zero and likely still relatively non-zero, and so it was determined that post v1.10 we were likely to see no more improvements due to Julia Base simply because the repo is too large. Too much stuff = already saturated in load time improvements.
However, since the unit of package binaries is the package, the next solution is to simply make a ton of packages. Thus OrdinaryDiffEq.jl recently did a splitting process that changed it from 1 package into 30 packages. That's somewhat intermediate, I assume we'll get to about 50 packages over the next year as we refine it down to the specific binaries people want. There are a few interdependencies in there as well: everything relies on OrdinaryDiffEqCore, and implicit methods have a chain like OrdinaryDiffEqCore -> OrdinaryDiffEqDifferentiation -> OrdinaryDiffEqNonlinearSolve -> OrdinaryDiffEqBDF. This is all just libs in the same repo, so it's a tens of package monorepo. This effectively parallelizes package binary construction, only causes loading what the user requests (thus fixing the "too much stuff" problem), and allows for solvers to all be set to precompile and has an easy user-level way to pick the subset of the binaries they want (by picking the solver packages).
It also has a nice side benefit that the dependencies of most solvers are decreased. Since for example only the exponential integrators need ExponentialUtiltiies.jl, that's a dependency of the solver package but not the core now, meaning most people get less dependencies.
To an extent, this is simply using monorepos and tons of packages to solve the problem, since our one hammer is package binaries just make everything a package. However, there are several downsides to this approach:
As a result, it's not a great experience, but it's the best that we have to scale today.
What's the real issue and solution?
The real issue here is that we have privileged packages in a way that we have not privileged modules. In a sense, OrdinaryDiffEqCore is a submodule, OrdinaryDiffEqTsit5 is a submodule. They are all submodules of the same package. However, since we only have dependencies, binary building, etc. as package level features, we put all of these submodules into different packages. Then:
using OrdinaryDiffEqCore, OrdinaryDiffEqTsit5
is "the solution", and we have 50 packages roaming around that are actually all submodules of 1 package. In reality, what would be really nice is to use the submodule system for this. For example:
If these were submodules of OrdinaryDiffEq, we could just have one versioned version of the package and all of our issues would go away... if the following features were supported:
The text was updated successfully, but these errors were encountered: