-
Notifications
You must be signed in to change notification settings - Fork 11
Drop Transducers/Folds in favor of OhMyThreads and drop Optimization umbrella #369
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
Changes from all commits
04f6153
b1d5e86
83df232
749ef22
4270ee7
e6e5e44
c4da1f6
6723ba3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -101,15 +101,13 @@ $(_ARGUMENT_DOCSTRING) | |
| - `importance::Bool=true`: Perform Pareto smoothed importance resampling of draws. | ||
| - `rng::AbstractRNG=Random.default_rng()`: Pseudorandom number generator. It is recommended to | ||
| use a parallelization-friendly PRNG like the default PRNG on Julia 1.7 and up. | ||
| - `executor::Transducers.Executor`: Transducers.jl executor that determines if and how to | ||
| run the single-path runs in parallel, defaulting to | ||
| [`Transducers.SequentialEx()`](@extref `Transducers.SequentialEx`). If a transducer for | ||
| multi-threaded computation is selected, you must first verify that `rng` and the log | ||
| density function are thread-safe. | ||
| - `executor_per_run::Transducers.Executor`: Transducers.jl executor used within each run to | ||
| parallelize PRNG calls, defaulting to | ||
| [`Transducers.SequentialEx()`](@extref `Transducers.SequentialEx`). See | ||
| [`pathfinder`](@ref) for further description. | ||
| - `ntasks::Int=1`: maximum number of parallel tasks used to run the single-path | ||
|
sethaxen marked this conversation as resolved.
|
||
| Pathfinder runs and to evaluate the target log density across draws. The default | ||
| `ntasks = 1` runs sequentially; larger values parallelize across runs and across | ||
| importance-resampling log-density evaluations, in which case the log-density function | ||
| must be thread-safe. Results are reproducible regardless of `ntasks`. | ||
| - `ntasks_per_run::Int=1`: same as `ntasks`, but applied within each single-path run for | ||
| parallelizing the ELBO evaluation. See [`pathfinder`](@ref) for details. | ||
| - `kwargs...` : Remaining keywords are forwarded to [`pathfinder`](@ref). | ||
|
|
||
| # Returns | ||
|
|
@@ -141,64 +139,92 @@ function multipathfinder( | |
| rng::Random.AbstractRNG=Random.default_rng(), | ||
| history_length::Int=DEFAULT_HISTORY_LENGTH, | ||
| optimizer=default_optimizer(history_length), | ||
| executor::Transducers.Executor=Transducers.SequentialEx(), | ||
| executor_per_run=Transducers.SequentialEx(), | ||
| ntasks::Int=1, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here and for single-path Pathfinder, would it make sense to warn the user if any
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe? I guess it could be surprising to users that do not know about how to start Julia multithreaded if |
||
| ntasks_per_run::Int=1, | ||
| importance::Bool=true, | ||
| kwargs..., | ||
| ) | ||
| if init === nothing | ||
| _init = if init === nothing | ||
| nruns > 0 || throw( | ||
| ArgumentError("A positive `nruns` must be set or `init` must be provided.") | ||
| ) | ||
| _init = fill(init, nruns) | ||
| fill(init, nruns) | ||
| else | ||
| _init = init | ||
| init | ||
| end | ||
| nruns = length(_init) | ||
| if ndraws > ndraws_per_run * nruns | ||
| @warn "More draws requested than total number of draws across replicas. Draws will not be unique." | ||
| end | ||
| logp(x) = -optim_fun.f(x, nothing) | ||
|
|
||
| # run pathfinder independently from each starting point | ||
| trans = Transducers.Map() do (init_i, optimizer_i) | ||
| return pathfinder( | ||
| optim_fun; | ||
| rng, | ||
| history_length, | ||
| optimizer=optimizer_i, | ||
| ndraws=ndraws_per_run, | ||
| init=init_i, | ||
| executor=executor_per_run, | ||
| ndraws_elbo, | ||
| kwargs..., | ||
| ) | ||
| run_seeds = rand!(rng, similar(_init, UInt64)) | ||
|
|
||
| pathfinder_results = ProgressLogging.@withprogress name = "Multi-path Pathfinder" begin | ||
| progress_taskref = Ref{Task}() | ||
| progress_channel = Channel{Bool}( | ||
| min(nruns, 1_000); spawn=true, taskref=progress_taskref | ||
| ) do ch | ||
| # Throttle progress logs as generally the logging system is not super performant: | ||
| # - At most 1 progress log every 0.1 seconds | ||
| # - At most 1 progress log every 0.5% progress | ||
| progress_step = max(1, cld(nruns, 200)) | ||
| next_logged = progress_step | ||
| next_time = time() + 0.1 | ||
|
|
||
| completed = 0 | ||
| while take!(ch) | ||
| completed += 1 | ||
| now = time() | ||
| if completed >= next_logged && now >= next_time | ||
| ProgressLogging.@logprogress completed / nruns | ||
| next_logged = completed + progress_step | ||
| next_time = now + 0.1 | ||
| end | ||
| end | ||
| end | ||
|
|
||
| try | ||
| # `Optim` optimizers may carry mutable state, so each chunk gets its own copy. | ||
| _chunk_tmap( | ||
| _init, run_seeds; ntasks, setup=() -> (copy(rng), deepcopy(optimizer)) | ||
| ) do (chunk_rng, chunk_optimizer), init, seed | ||
| Random.seed!(chunk_rng, seed) | ||
| result = pathfinder( | ||
| optim_fun; | ||
| rng=chunk_rng, | ||
| history_length, | ||
| optimizer=chunk_optimizer, | ||
| ndraws=ndraws_per_run, | ||
| init, | ||
| ntasks=ntasks_per_run, | ||
| ndraws_elbo, | ||
| kwargs..., | ||
| ) | ||
| put!(progress_channel, true) | ||
| yield() | ||
| return result | ||
| end | ||
| finally | ||
| put!(progress_channel, false) | ||
| close(progress_channel) | ||
| wait(progress_taskref[]) | ||
| end | ||
| end | ||
| iter_optimizers = fill(optimizer, nruns) | ||
| iter_sp = | ||
| if executor isa Folds.ThreadedEx | ||
| # temporary workaround due to | ||
| # https://github.com/JuliaFolds2/Transducers.jl/issues/10 | ||
| # also support optimizers that store state | ||
| zip(_init, Iterators.map(deepcopy, iter_optimizers)) | ||
| else | ||
| Transducers.withprogress(zip(_init, iter_optimizers); interval=1e-3) | ||
| end |> trans | ||
| pathfinder_results = Folds.collect(iter_sp, executor) | ||
| fit_distributions = | ||
| pathfinder_results |> Transducers.Map(x -> x.fit_distribution) |> collect | ||
| draws_all = reduce(hcat, pathfinder_results |> Transducers.Map(x -> x.draws)) | ||
| fit_distributions = map(x -> x.fit_distribution, pathfinder_results) | ||
| draws_all = mapreduce(x -> x.draws, hcat, pathfinder_results) | ||
|
|
||
| # draw samples from augmented mixture model | ||
| inds = axes(draws_all, 2) | ||
| sample_inds, psis_result = if importance | ||
| log_densities_fit = | ||
| pathfinder_results |> | ||
| Transducers.MapCat() do x | ||
| return Distributions.logpdf(x.fit_distribution, x.draws) | ||
| end |> | ||
| collect | ||
| iter_logp = eachcol(draws_all) |> Transducers.Map(logp) | ||
| log_densities_target = Folds.collect(iter_logp, executor) | ||
| log_densities_fit = _maybe_tmapreduce( | ||
| x -> Distributions.logpdf(x.fit_distribution, x.draws), | ||
| vcat, | ||
| pathfinder_results, | ||
| ntasks, | ||
| ) | ||
| log_densities_target = _maybe_tmap(logp, eachcol(draws_all), ntasks) | ||
| log_densities_ratios = log_densities_target - log_densities_fit | ||
| resample(rng, inds, log_densities_ratios, ndraws) | ||
| else | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.