Repository navigation
feat: Remove Distributions, DataStructures, and MLJModelInterface/Tables dependencies - #177
Conversation
|
MCMCDiagnosticTools.jl documentation for PR #177 is available at: |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #177 +/- ##
==========================================
+ Coverage 97.01% 97.05% +0.04%
==========================================
Files 11 13 +2
Lines 937 952 +15
==========================================
+ Hits 909 924 +15
Misses 28 28 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Coverage Report for CI Build 40316420566Warning No base build found for commit Coverage: 97.059%Details
Uncovered Changes
Coverage RegressionsRequires a base build to compare against. How to fix this → Coverage Stats
💛 - Coveralls |
| function _rstar_distribution end | ||
|
|
||
| return scaled_distribution | ||
| function _rstar_error_hint(io, exc, argtypes, kwargs) |
There was a problem hiding this comment.
In practice this is unreachable, because MMI just defines the interface functions we call but they are implemented in MLJBase (see https://github.com/JuliaAI/MLJModelInterface.jl/blob/f18e0e5692327eda5352caa1430bd5e938898dbe/src/data_utils.jl#L2-L15), which depends on Distributions. So if the user did not yet load MLJBase, they would have already errored before getting to _rstar_distribution, and if they did already load MLJBase, then Distributions would be in the environment, as would our package extension, and _rstar_distribution would not throw a method error.
However, MLJBase could make a breaking release dropping Distributions as a direct dependency without MLJModelInterface requiring a breaking release, so this error hint should still exist for that hypothetical future.
There was a problem hiding this comment.
Another breaking option would be to define our own PoissonBinomial object and return that, documenting only its fields and that it can be converted to a Distributions object, which could be in the extension. Then no error is ever needed.
There was a problem hiding this comment.
Yet another option, since MMI can't do anything until MLJBase is loaded anyways, would be to move the rstar implementations to an extension weakly depending on either MMI or MLJBase directly. The latter allows us to forego any requirement that users load Distributions, since then we can specify via the compat MLJBase versions that have Distributions as a direct dependency. While MMI is lightweight, removing it and Tables as a direct dependency reduces package load time on my machine from .2s in a script to .06s, I assume because MMI depends on and loads REPL.
devmotion
left a comment
There was a problem hiding this comment.
I think it's generally good to drop unnnecessary dependencies (as DataStructures seems to be) and move optional functionality to extensions if it doesn't break typical workflows or makes them significantly less convenient.
I also think that it's a bit simpler to directly use StatsFuns functions instead of going through Distributions.
However, usually I think it's a bad sign if one starts to reimplement functionality available somewhere else, which seems to be the case with the StatsBase functionality we are using. I would also assume that most people using MCMCDiagnosticTools already have StatsBase (and likely also Distributions?) available in their environment anyway, so load time of these packages cannot be attributed solely to MCMCDiagnosticTools. Additionally, <0.4 seconds seems already rather fast.
How much of the load time can be attributed to each dependency (in an environment with only MCMCDiagnosticTools), did you check it with @time_using? Moreover, I wonder if we could make use of https://julialang.github.io/PrecompileTools.jl to reduce TTFX?
As a side remark, I recently learnt that __init__ can slow down load times quite a bit: JuliaMath/InverseFunctions.jl#67
(A second side remark: Skimming through the changes made me wonder whether simulate_DAR1! would benefit from the alias-sampler functionality in StatsBase - which is based on AliasTables)
Agreed, I had thought so as well. AliasTables adds almost nothing to the load time, so I've switched to it here. Also, for typical values of
Yes I did.
So the biggest contributors in interactive sessions are SparseArrays, FillArrays, and Distributions itself. StatsBase itself doesn't contribute much to load time, but by pulling in DataStructures and SparseArrays, it accounts for about .2s, or almost half of the package load time. In non-interactive sessions, MMI indirectly accounts for .14s, or about 25% of package load time.
I would typically agree with this when the functionality being reimplemented is sufficiently complex. In this case (with the recent commits), it's just using Random, MCMCDiagnosticTools, BenchmarkTools
rng = Xoshiro(42)
draws = randn(rng, 1_000, 4, 1_000)
@btime ess_rhat($draws);main: here:
It's hard to know about users, but 18 registered packages depend directly on MCMCDT. All of them also depend on StatsBase, and 16 depend on Distributions (ParetoSmooth and FlexiChains after this PR would no longer indirectly import Distributions, which I think is good). While I agree that most users will already have StatsBase in their envs, it's certainly not all. Historically, Soss/Tilde tried to get rid of Distributions as a dependency. StanBlocks.jl builds probabilistic models ready-for-sampling without depending on either. Other PPLs that opt out of Distributions/StatsBase could want to include our diagnostics as well. But there's actually already an example of a package that wants MCMCDT w/o StatsBase. PSIS is intentionally extremely lightweight to encourage any package that does importance sampling or Monte Carlo estimation to use its diagnostics internally, regardless of their stack. In the convergence diagnostics coming shortly, it really should by default use relative ESS to avoid silently doing the wrong thing for MCMC draws, but currently it wouldn't because it would increase the load time by ~30x. Agreed that <0.4s is fast, but more than once, packages with quicker load times have replicated functionality from small packages I've maintained to avoid increasing by a few hundred ms.
Interesting, I had not seen that. I attempted to replicate that here but did not see any slowdown whether we used
Absolutely, and at least |
|
Following my suggestion in #177 (comment), I've also removed MLJModelInterface and Tables as dependencies and instead moved the |
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
@devmotion does this PR look fine now to you? |
After more time working on the PSIS use-case, I decided it actually doesn't make sense for it to depend on MCMCDiagnosticTools after all, so that need is removed. As a result, I've reverted the removal of StatsBase a dependency. The load time improvements are now much more modest; down to .25s in an interactive session from the original .36s. Most of the remaining load time is from StatsBase. When it's removed as a dependency, load time is .04s. |
|
The StatsBase loadtime seems a bit extreme, do you know why it takes so much time to load? I wonder if we could improve it. |
StatsBase's load time is mostly (~80%) due to it loading SparseArrays: julia> @time using StatsBase
0.221406 seconds (177.65 k allocations: 11.268 MiB)julia> @time_imports using StatsBase
0.5 ms DataAPI
4.9 ms OrderedCollections
15.5 ms DataStructures
0.8 ms SortingAlgorithms
3.2 ms Missings
┌ 0.0 ms DocStringExtensions.__init__()
2.9 ms DocStringExtensions
7.9 ms IrrationalConstants
0.6 ms LogExpFunctions
1.0 ms Statistics
┌ 0.2 ms CompilerSupportLibraries_jll.__init__()
0.7 ms CompilerSupportLibraries_jll
┌ 0.7 ms SuiteSparse_jll.__init__()
1.0 ms SuiteSparse_jll
1.0 ms Serialization
171.3 ms SparseArrays
0.4 ms Statistics → SparseArraysExt
0.4 ms StatsAPI
0.4 ms PtrArrays
1.2 ms AliasTables
8.9 ms StatsBaseThis issue has been raised several times in StatsBase (JuliaStats/StatsBase.jl#885 and JuliaStats/StatsBase.jl#943). I noticed you contributed to both discussions. Claude Opus 5.5 noted that SparseArrays' long load time is not due to invalidation but rather due to spending a lot of time in I also asked Claude to bring back the change JuliaStats/StatsBase.jl#885 and redo our loading times here with that version of StatsBase as a dependency, and it measured a .07s load time. So there's really nothing we can do or need to do here to get the load time any lower; it needs to happen upstream in StatsBase. |
|
Thanks, that matches what I found. I opened JuliaStats/StatsBase.jl#1016 (deprecates |
This package removes a number of dependencies that we primarily only needed for a few utilities that are easily reproduced here. In return, on my machine, package load time is reduced from 0.36s to
0.05s0.04s0.25s in an interactive session and from .5s to .27s in a non-interactive session.Making this package lighter weight allows it to be a dependency of other lightweight packages, e.g. PSIS, which currently has a load time of 0.01s.Distributions
This is our heaviest dependency, and in most places we just use it to call StatsFuns functions anyways, so now we call StatsFuns. In a few places we sample from a categorical distribution. That can be implemented efficiently in 14 LOC, so now we roll that ourselves. The only place where Distributions integration is required is when passing a probabilistic sampler to
rstar, which then returns aPoissonBinomial. In this PR, that has been moved to an extension,and the user is now required to load Distributions if they want to use a probabilistic classifier. That being said, sinceMLJBase depends on Distributions,in most cases users won't need to do that. Nevertheless, this makes this a breaking change.StatsBaseWe just neededwsample(implemented above),tiedrank, andcounts, which again require just 22 and 10 LOC, respectively, so they are included here.DataStructures
SortedDictwas never really necessary but just allowed just to remove asortcall at the end ofunique_indices. Previously DataStructures was an indirect dependency,but with the above 2 removals, it no longer is, so this PR also removes it.and it still is via StatsBase, but this PR removes it as a direct dependency.MLJModelInterface/Tables
While MMI is itself lightweight, in noninteractive sessions it pulled in REPL and increased package load time by about 25%. While how we use MMI is the intended behavior, it is useless apart from MLJBase which contains its implementations. This split predates the introduction of package extensions, and it seems more natural here to have an MLJBase extensions so that we can pin compat to MLJBase versions that include Tables and Distributions as dependencies (a breaking change to MLJBase would be required for it to drop these deps). That allows
rstarto return a Distributions object when passed a probabilistic classifier with absolutely no deviation from current behavior and therefore no breaking release required.