From 4d54b64f9cb13532813a93741dd1a315bd2cd015 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Thu, 10 Aug 2023 16:16:06 +0100 Subject: [PATCH 01/57] added CUDA extension --- Project.toml | 7 +++++++ ext/NormalizingFlowsCUDAExt.jl | 29 +++++++++++++++++++++++++++++ test/Project.toml | 1 + test/cuda.jl | 18 ++++++++++++++++++ 4 files changed, 55 insertions(+) create mode 100644 ext/NormalizingFlowsCUDAExt.jl create mode 100644 test/cuda.jl diff --git a/Project.toml b/Project.toml index 50db953e..1544563c 100644 --- a/Project.toml +++ b/Project.toml @@ -18,9 +18,16 @@ ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" +[weakdeps] +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" + +[extensions] +NormalizingFlowsCUDAExt = "CUDA" + [compat] ADTypes = "0.1" Bijectors = "0.12.6" +CUDA = "3, 4" Distributions = "0.25" DiffResults = "1" ForwardDiff = "0.10.25" diff --git a/ext/NormalizingFlowsCUDAExt.jl b/ext/NormalizingFlowsCUDAExt.jl new file mode 100644 index 00000000..63b7ed03 --- /dev/null +++ b/ext/NormalizingFlowsCUDAExt.jl @@ -0,0 +1,29 @@ +module NormalizingFlowsCUDAExt + +using CUDA +using NormalizingFlows: Random, Distributions + +# FIXME: These overloads are not triggered. +# TODO: Are we even allowed these sorts of overloads in extensions? + +# Make allocation of output array live on GPU. +function Distributions.rand( + rng::CUDA.RNG, + s::Distributions.Sampleable{<:Distributions.ArrayLikeVariate,Distributions.Continuous} +) + return @inbounds Distributions.rand!(rng, Distributions.sampler(s), CuArray{float(eltype(s))}(undef, size(s))) +end + +function Distributions._rand!(rng::CUDA.RNG, d::Distributions.MvNormal, x::AbstractVector) + # Replaced usage of scalar indexing. + Random.randn!(rng, x) + Distributions.unwhiten!(d.Σ, x) + x .+= d.μ + return x +end + +function Distributions.insupport(::Type{D},x::CuVector{T}) where {T<:Real,D<:Distributions.AbstractMvLogNormal} + return all(0 .< x .< Inf) +end + +end diff --git a/test/Project.toml b/test/Project.toml index 0bed305d..7bdd43a7 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,6 +1,7 @@ [deps] ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b" Bijectors = "76274a88-744f-5084-9051-94815aaf08c4" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" DiffResults = "163ba53b-c6d8-5494-b064-1a9d43ac40c5" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" diff --git a/test/cuda.jl b/test/cuda.jl new file mode 100644 index 00000000..5b05b215 --- /dev/null +++ b/test/cuda.jl @@ -0,0 +1,18 @@ +using CUDA, Test, LinearAlgebra, Distributions + +if CUDA.functional() + @testset "rand with CUDA" begin + dists = [ + MvNormal(CUDA.zeros(2), I), + MvNormal(CUDA.zeros(2), cu([1.0 0.5; 0.5 1.0])), + MvLogNormal(CUDA.zeros(2), I), + MvLogNormal(CUDA.zeros(2), cu([1.0 0.5; 0.5 1.0])), + ] + + @testset "$dist" for dist in dists + x = rand(CUDA.default_rng(), dist) + @info logpdf(dist, x) + @test x isa CuArray + end + end +end From d9f3f1acdb055878d59a210777b4e1a8e33552b5 Mon Sep 17 00:00:00 2001 From: Tor Erlend Fjelde Date: Thu, 10 Aug 2023 16:20:22 +0100 Subject: [PATCH 02/57] fixed merge issue --- Project.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 55c54173..4580549e 100644 --- a/Project.toml +++ b/Project.toml @@ -30,9 +30,8 @@ NormalizingFlowsCUDAExt = "CUDA" [compat] ADTypes = "0.1" -Bijectors = "0.12.6" +Bijectors = "0.12.6, 0.13" CUDA = "3, 4" -Distributions = "0.25" DiffResults = "1" Distributions = "0.25" DocStringExtensions = "0.9" From a3619cab6046a448637b98c3aa172cd2d5990de8 Mon Sep 17 00:00:00 2001 From: Zuheng Date: Tue, 15 Aug 2023 11:33:30 -0700 Subject: [PATCH 03/57] rm extra weakdeps --- Project.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Project.toml b/Project.toml index 4580549e..c0f5aef5 100644 --- a/Project.toml +++ b/Project.toml @@ -22,8 +22,6 @@ ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" -[weakdeps] -CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" [extensions] NormalizingFlowsCUDAExt = "CUDA" From 0dde183283607157c035d3aa0186930002fcf85e Mon Sep 17 00:00:00 2001 From: Zuheng Date: Tue, 15 Aug 2023 11:42:09 -0700 Subject: [PATCH 04/57] edit toml --- Project.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Project.toml b/Project.toml index c0f5aef5..d38a101f 100644 --- a/Project.toml +++ b/Project.toml @@ -24,6 +24,10 @@ Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" [extensions] +NormalizingFlowsEnzymeExt = "Enzyme" +NormalizingFlowsForwardDiffExt = "ForwardDiff" +NormalizingFlowsReverseDiffExt = "ReverseDiff" +NormalizingFlowsZygoteExt = "Zygote" NormalizingFlowsCUDAExt = "CUDA" [compat] @@ -48,3 +52,4 @@ Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" \ No newline at end of file From 1f795a1525c86f477ac0ea24bbde3922d9f11c26 Mon Sep 17 00:00:00 2001 From: Zuheng Date: Tue, 15 Aug 2023 12:07:38 -0700 Subject: [PATCH 05/57] @require cuda ext --- src/NormalizingFlows.jl | 3 +++ test/runtests.jl | 1 + 2 files changed, 4 insertions(+) diff --git a/src/NormalizingFlows.jl b/src/NormalizingFlows.jl index 2d708929..7b7c2b91 100644 --- a/src/NormalizingFlows.jl +++ b/src/NormalizingFlows.jl @@ -94,6 +94,9 @@ function __init__() @require Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" include( "../ext/NormalizingFlowsZygoteExt.jl" ) + @require CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" include( + "../ext/NormalizingFlowsCUDAExt.jl" + ) end end end diff --git a/test/runtests.jl b/test/runtests.jl index e050a645..574b7622 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -7,6 +7,7 @@ using ADTypes, DiffResults using ForwardDiff, Zygote, Enzyme, ReverseDiff using Test +include("cuda.jl") include("ad.jl") include("objectives.jl") include("interface.jl") \ No newline at end of file From 847a520d0cfecb4436ff35be69e07d4292c1030c Mon Sep 17 00:00:00 2001 From: Zuheng Date: Tue, 15 Aug 2023 13:30:36 -0700 Subject: [PATCH 06/57] minor update tests --- test/ad.jl | 2 +- test/interface.jl | 4 ++-- test/objectives.jl | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/ad.jl b/test/ad.jl index a394d806..ca444c39 100644 --- a/test/ad.jl +++ b/test/ad.jl @@ -33,7 +33,7 @@ end target = MvNormal(μ, Σ) logp(z) = logpdf(target, z) - q₀ = MvNormal(zeros(T, 2), ones(T, 2)) + q₀ = MvNormal(zeros(T, 2), I) flow = Bijectors.transformed(q₀, Bijectors.Shift(zero.(μ))) sample_per_iter = 10 diff --git a/test/interface.jl b/test/interface.jl index a3540979..dee7645a 100644 --- a/test/interface.jl +++ b/test/interface.jl @@ -13,7 +13,7 @@ target = MvNormal(μ, Σ) logp(z) = logpdf(target, z) - q₀ = MvNormal(zeros(T, 2), ones(T, 2)) + q₀ = MvNormal(zeros(T, 2), I) flow = Bijectors.transformed( q₀, Bijectors.Shift(zero.(μ)) ∘ Bijectors.Scale(ones(T, 2)) ) @@ -27,7 +27,7 @@ logp, sample_per_iter; max_iters=5_000, - optimiser=Optimisers.ADAM(0.01 * one(T)), + optimiser=Optimisers.Adam(0.01 * one(T)), ADbackend=adtype, show_progress=false, callback=cb, diff --git a/test/objectives.jl b/test/objectives.jl index 072286d1..dc4fb42d 100644 --- a/test/objectives.jl +++ b/test/objectives.jl @@ -5,7 +5,7 @@ target = MvNormal(μ, Σ) logp(z) = logpdf(target, z) - q₀ = MvNormal(zeros(T, 2), ones(T, 2)) + q₀ = MvNormal(zeros(T, 2), I) flow = Bijectors.transformed(q₀, Bijectors.Shift(μ) ∘ Bijectors.Scale(sqrt.(Σ))) x = randn(T, 2) From 51dc577122bd304e19189615e98a101b83dfc2a5 Mon Sep 17 00:00:00 2001 From: Zuheng Date: Tue, 15 Aug 2023 16:02:50 -0700 Subject: [PATCH 07/57] try to fix ambiguity --- example/Project.toml | 1 + example/test.jl | 24 ++++++++++++++++++++++++ ext/NormalizingFlowsCUDAExt.jl | 27 +++++++++++++++++++-------- 3 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 example/test.jl diff --git a/example/Project.toml b/example/Project.toml index d462c5ee..4b30be0c 100644 --- a/example/Project.toml +++ b/example/Project.toml @@ -15,3 +15,4 @@ Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" +cuDNN = "02a925ec-e4fe-4b08-9a7e-0d78e3d38ccd" diff --git a/example/test.jl b/example/test.jl new file mode 100644 index 00000000..05fa70cc --- /dev/null +++ b/example/test.jl @@ -0,0 +1,24 @@ +using CUDA +using LinearAlgebra +using FunctionChains +using Bijectors +using Flux +using NormalizingFlows + +rng = CUDA.default_rng() +T = Float32 +q0 = MvNormal(ones(T, 2)) +q0_g = MvNormal(CUDA.zeros(T, 2), I) + +ts = reduce(∘, [f32(Bijectors.PlanarLayer(2)) for _ in 1:2]) +flow = transformed(q0, ts) + +# gpu +CUDA.functional() +ts_g = gpu(ts) +flow_g = transformed(q0_g, ts_g) + +xs = rand(rng, flow_g.dist, 10) # on cpu +ys_g = transform(ts_g, cu(xs)) # good +logpdf(flow_g, ys_g[:, 1]) # good +rand(flow_g) # bug diff --git a/ext/NormalizingFlowsCUDAExt.jl b/ext/NormalizingFlowsCUDAExt.jl index 63b7ed03..a7015aa9 100644 --- a/ext/NormalizingFlowsCUDAExt.jl +++ b/ext/NormalizingFlowsCUDAExt.jl @@ -3,26 +3,37 @@ module NormalizingFlowsCUDAExt using CUDA using NormalizingFlows: Random, Distributions -# FIXME: These overloads are not triggered. -# TODO: Are we even allowed these sorts of overloads in extensions? - # Make allocation of output array live on GPU. function Distributions.rand( rng::CUDA.RNG, - s::Distributions.Sampleable{<:Distributions.ArrayLikeVariate,Distributions.Continuous} + s::Distributions.Sampleable{<:Distributions.ArrayLikeVariate,Distributions.Continuous}, +) + return @inbounds Distributions.rand!( + rng, Distributions.sampler(s), CuArray{float(eltype(s))}(undef, size(s)) + ) +end + +function Distributions.rand( + rng::CUDA.RNG, + s::Distributions.Sampleable{<:Distributions.ArrayLikeVariate,Distributions.Continuous}, + n::Int, ) - return @inbounds Distributions.rand!(rng, Distributions.sampler(s), CuArray{float(eltype(s))}(undef, size(s))) + return @inbounds Distributions.rand!( + rng, Distributions.sampler(s), CuArray{float(eltype(s))}(undef, length(s), n) + ) end -function Distributions._rand!(rng::CUDA.RNG, d::Distributions.MvNormal, x::AbstractVector) +function Distributions._rand!(rng::CUDA.RNG, d::Distributions.MvNormal, x::CuVecOrMat) # Replaced usage of scalar indexing. - Random.randn!(rng, x) + CUDA.randn!(rng, x) Distributions.unwhiten!(d.Σ, x) x .+= d.μ return x end -function Distributions.insupport(::Type{D},x::CuVector{T}) where {T<:Real,D<:Distributions.AbstractMvLogNormal} +function Distributions.insupport( + ::Type{D}, x::CuVector{T} +) where {T<:Real,D<:Distributions.AbstractMvLogNormal} return all(0 .< x .< Inf) end From 7a56c05e756fe65dfa72eed4625cfafa5c013236 Mon Sep 17 00:00:00 2001 From: Zuheng Date: Tue, 15 Aug 2023 16:05:55 -0700 Subject: [PATCH 08/57] rm tmp test file --- example/test.jl | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 example/test.jl diff --git a/example/test.jl b/example/test.jl deleted file mode 100644 index 05fa70cc..00000000 --- a/example/test.jl +++ /dev/null @@ -1,24 +0,0 @@ -using CUDA -using LinearAlgebra -using FunctionChains -using Bijectors -using Flux -using NormalizingFlows - -rng = CUDA.default_rng() -T = Float32 -q0 = MvNormal(ones(T, 2)) -q0_g = MvNormal(CUDA.zeros(T, 2), I) - -ts = reduce(∘, [f32(Bijectors.PlanarLayer(2)) for _ in 1:2]) -flow = transformed(q0, ts) - -# gpu -CUDA.functional() -ts_g = gpu(ts) -flow_g = transformed(q0_g, ts_g) - -xs = rand(rng, flow_g.dist, 10) # on cpu -ys_g = transform(ts_g, cu(xs)) # good -logpdf(flow_g, ys_g[:, 1]) # good -rand(flow_g) # bug From 5ec4317baff2394002d2fb247c1b55ac876d658e Mon Sep 17 00:00:00 2001 From: Zuheng Date: Mon, 21 Aug 2023 12:56:51 -0700 Subject: [PATCH 09/57] wip on randdevice interface --- example/Project.toml | 1 - example/test.jl | 48 +++++++++++++++++++ ext/NormalizingFlowsCUDAExt.jl | 15 ++++-- src/NormalizingFlows.jl | 3 -- src/objectives/elbo.jl | 4 +- src/sampler.jl | 88 ++++++++++++++++++++++++++++++++++ test/Project.toml | 15 ++++++ 7 files changed, 164 insertions(+), 10 deletions(-) create mode 100644 example/test.jl create mode 100644 src/sampler.jl diff --git a/example/Project.toml b/example/Project.toml index 4b30be0c..d462c5ee 100644 --- a/example/Project.toml +++ b/example/Project.toml @@ -15,4 +15,3 @@ Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" -cuDNN = "02a925ec-e4fe-4b08-9a7e-0d78e3d38ccd" diff --git a/example/test.jl b/example/test.jl new file mode 100644 index 00000000..3fb7fc09 --- /dev/null +++ b/example/test.jl @@ -0,0 +1,48 @@ +using CUDA +using LinearAlgebra +using FunctionChains +using Bijectors +using Flux +using NormalizingFlows + +rng = CUDA.default_rng() +T = Float32 +q0 = MvNormal(ones(T, 2)) +q0_g = MvNormal(CUDA.zeros(T, 2), I) + +ts = reduce(∘, [f32(Bijectors.PlanarLayer(2)) for _ in 1:2]) +flow = transformed(q0, ts) + +# gpu +CUDA.functional() +ts_g = gpu(ts) +flow_g = transformed(q0_g, ts_g) + +xs = rand(rng, flow_g.dist, 10) # on cpu +ys_g = transform(ts_g, cu(xs)) # good +logpdf(flow_g, ys_g[:, 1]) # good +rand(flow_g) # bug + +using CUDA +using LinearAlgebra +using Distributions, Random +using Bijectors + +rng = CUDA.default_rng() +T = Float32 +q0_g = MvNormal(CUDA.zeros(T, 2), I) + +CUDA.functional() +ts_g = gpu(ts) +flow_g = transformed(q0_g, ts_g) + +x = rand(rng, q0_g) # good +xs = rand(rng, q0_g) # ambiguous + +y = rand_device(rng, flow_g, 10) # ambiguous + +rand(gpu(flow)) + +gpu(q0) +using Functors +@functor MvNormal diff --git a/ext/NormalizingFlowsCUDAExt.jl b/ext/NormalizingFlowsCUDAExt.jl index a7015aa9..0e6e2e3e 100644 --- a/ext/NormalizingFlowsCUDAExt.jl +++ b/ext/NormalizingFlowsCUDAExt.jl @@ -1,12 +1,15 @@ module NormalizingFlowsCUDAExt using CUDA -using NormalizingFlows: Random, Distributions +using NormalizingFlows: Random, Distributions, Bijectors # Make allocation of output array live on GPU. function Distributions.rand( rng::CUDA.RNG, - s::Distributions.Sampleable{<:Distributions.ArrayLikeVariate,Distributions.Continuous}, + s::Distributions.Sampleable{ + <:Union{Distributions.Multivariate,Distributions.Univariate}, + Distributions.Continuous, + }, ) return @inbounds Distributions.rand!( rng, Distributions.sampler(s), CuArray{float(eltype(s))}(undef, size(s)) @@ -15,7 +18,7 @@ end function Distributions.rand( rng::CUDA.RNG, - s::Distributions.Sampleable{<:Distributions.ArrayLikeVariate,Distributions.Continuous}, + s::Distributions.Sampleable{Distributions.Multivariate,Distributions.Continuous}, n::Int, ) return @inbounds Distributions.rand!( @@ -25,12 +28,16 @@ end function Distributions._rand!(rng::CUDA.RNG, d::Distributions.MvNormal, x::CuVecOrMat) # Replaced usage of scalar indexing. - CUDA.randn!(rng, x) + Random.randn!(rng, x) Distributions.unwhiten!(d.Σ, x) x .+= d.μ return x end +function Distributions.rand(rng::CUDA.RNG, td::Bijectors.MultivariateTransformed) + return td.transform(rand(rng, td.dist)) +end + function Distributions.insupport( ::Type{D}, x::CuVector{T} ) where {T<:Real,D<:Distributions.AbstractMvLogNormal} diff --git a/src/NormalizingFlows.jl b/src/NormalizingFlows.jl index 7b7c2b91..2d708929 100644 --- a/src/NormalizingFlows.jl +++ b/src/NormalizingFlows.jl @@ -94,9 +94,6 @@ function __init__() @require Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" include( "../ext/NormalizingFlowsZygoteExt.jl" ) - @require CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" include( - "../ext/NormalizingFlowsCUDAExt.jl" - ) end end end diff --git a/src/objectives/elbo.jl b/src/objectives/elbo.jl index 30a491eb..fc8cc534 100644 --- a/src/objectives/elbo.jl +++ b/src/objectives/elbo.jl @@ -33,11 +33,11 @@ function elbo(flow::Bijectors.MultivariateTransformed, logp, xs::AbstractMatrix) end function elbo(rng::AbstractRNG, flow::Bijectors.MultivariateTransformed, logp, n_samples) - return elbo(flow, logp, rand(rng, flow.dist, n_samples)) + return elbo(flow, logp, rand_device(rng, flow.dist, n_samples)) end function elbo(rng::AbstractRNG, flow::Bijectors.UnivariateTransformed, logp, n_samples) - return elbo(flow, logp, rand(rng, flow.dist, n_samples)) + return elbo(flow, logp, rand_device(rng, flow.dist, n_samples)) end function elbo(flow::Bijectors.TransformedDistribution, logp, n_samples) diff --git a/src/sampler.jl b/src/sampler.jl new file mode 100644 index 00000000..63589269 --- /dev/null +++ b/src/sampler.jl @@ -0,0 +1,88 @@ +# this file defines rand_device function to sample from a Distribution or a +# flow<:Bijectors.TranformedDistribution +# this is mainly for resolving the issue of sampling from a distribution on GPU + +function rand_device( + rng::AbstractRNG, + s::Distributions.Sampleable{<:Distributions.ArrayLikeVariate,Distributions.Continuous}, +) + if !(rng isa CUDA.RNG) + return nothing + else + return rand_cuda(rng, s) + end +end + +function rand_device( + rng::AbstractRNG, + s::Distributions.Sampleable{Distributions.Multivariate,Distributions.Continuous}, + n::Int, +) + if !(rng isa CUDA.RNG) + return Distributions.rand(rng, s, n) + else + return rand_cuda(rng, s, n) + end +end + +function rand_cuda( + rng::CUDA.RNG, + s::Distributions.Sampleable{<:Distributions.ArrayLikeVariate,Distributions.Continuous}, +) + return @inbounds Distributions.rand!( + rng, Distributions.sampler(s), CuArray{float(eltype(s))}(undef, size(s)) + ) +end + +function rand_cuda( + rng::CUDA.RNG, + s::Distributions.Sampleable{Distributions.Multivariate,Distributions.Continuous}, + n::Int, +) + return @inbounds Distribution.rand!( + rng, Distributions.sampler(s), CuArray{float(eltype(s))}(undef, length(s), n) + ) +end + +function Distributions._rand!(rng::CUDA.RNG, d::Distributions.MvNormal, x::CuVecOrMat) + # Replaced usage of scalar indexing. + Random.randn!(rng, x) + Distributions.unwhiten!(d.Σ, x) + x .+= d.μ + return x +end + +######################### +# for Bijectors.jl +########################## + +function rand_device(rng::AbstractRNG, td::Bijectors.TransformedDistribution) + if !(rng isa CUDA.RNG) + return Distributions.rand(rng, td) + else + return cuda_rand(rng, td) + end +end + +function rand_device(rng::AbstractRNG, td::Bijectors.TransformedDistribution, n::Int) + if !(rng isa CUDA.RNG) + return Distributions.rand(rng, td, n) + else + return cuda_rand(rng, td, n) + end +end + +function cuda_rand(rng::CUDA.RNG, td::Bijectors.TransformedDistribution) + return td.transform(cuda_rand(rng, td.dist)) +end + +function cuda_rand(rng::CUDA.RNG, td::Bijectors.TranformedDistribution, num_samples::Int) + samples = cuda_rand(rng, td.dist, num_samples) + res = reduce( + hcat, + map(axes(samples, 2)) do i + return td.transform(view(samples, :, i)) + end, + ) + return res +end diff --git a/test/Project.toml b/test/Project.toml index a3d7b4b5..56973a26 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -12,3 +12,18 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" + +[compat] +ADTypes = "0.1" +Bijectors = "0.12.6, 0.13" +CUDA = "3, 4" +DiffResults = "1" +Distributions = "0.25" +Enzyme = "0.11" +ForwardDiff = "0.10.25" +Optimisers = "0.2.16" +ProgressMeter = "1.0.0" +Requires = "1" +ReverseDiff = "1.14" +StatsBase = "0.33, 0.34" +Zygote = "0.6" From e1fa4b974a7b164020e8d0f25fe2e596d51c61a4 Mon Sep 17 00:00:00 2001 From: zuhengxu Date: Mon, 21 Aug 2023 22:17:09 -0700 Subject: [PATCH 10/57] move cuda part in ext --- ext/NormalizingFlowsCUDAExt.jl | 63 +++++++++++++++++++++++-------- src/sample.jl | 69 ++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 15 deletions(-) create mode 100644 src/sample.jl diff --git a/ext/NormalizingFlowsCUDAExt.jl b/ext/NormalizingFlowsCUDAExt.jl index 0e6e2e3e..285576f1 100644 --- a/ext/NormalizingFlowsCUDAExt.jl +++ b/ext/NormalizingFlowsCUDAExt.jl @@ -1,31 +1,48 @@ module NormalizingFlowsCUDAExt using CUDA +using NormalizingFlows using NormalizingFlows: Random, Distributions, Bijectors -# Make allocation of output array live on GPU. -function Distributions.rand( +# to enable `rand_device(rng:CUDA.RNG, dist[, num_samples])` +function NormalizingFlows.rand_device( rng::CUDA.RNG, - s::Distributions.Sampleable{ - <:Union{Distributions.Multivariate,Distributions.Univariate}, - Distributions.Continuous, - }, + s::Distributions.Sampleable{<:Distributions.ArrayLikeVariate,Distributions.Continuous}, +) + println("gpu rand") + return rand_cuda(rng, s) +end + +function NormalizingFlows.rand_device( + rng::CUDA.RNG, + s::Distributions.Sampleable{<:Distributions.ArrayLikeVariate,Distributions.Continuous}, + n::Int, +) + println("gpu rand") + return rand_cuda(rng, s, n) +end + +function rand_cuda( + rng::CUDA.RNG, + s::Distributions.Sampleable{<:Distributions.ArrayLikeVariate,Distributions.Continuous}, ) return @inbounds Distributions.rand!( rng, Distributions.sampler(s), CuArray{float(eltype(s))}(undef, size(s)) ) end -function Distributions.rand( +function rand_cuda( rng::CUDA.RNG, - s::Distributions.Sampleable{Distributions.Multivariate,Distributions.Continuous}, + s::Distributions.Sampleable{<:Distributions.ArrayLikeVariate,Distributions.Continuous}, n::Int, ) return @inbounds Distributions.rand!( - rng, Distributions.sampler(s), CuArray{float(eltype(s))}(undef, length(s), n) + rng, Distributions.sampler(s), CuArray{float(eltype(s))}(undef, size(s)..., n) ) end +# Question: is this type piracy okay? +# (it's probably not ideal but this is sensible enough for now ) function Distributions._rand!(rng::CUDA.RNG, d::Distributions.MvNormal, x::CuVecOrMat) # Replaced usage of scalar indexing. Random.randn!(rng, x) @@ -34,14 +51,30 @@ function Distributions._rand!(rng::CUDA.RNG, d::Distributions.MvNormal, x::CuVec return x end -function Distributions.rand(rng::CUDA.RNG, td::Bijectors.MultivariateTransformed) - return td.transform(rand(rng, td.dist)) +# to enable `rand_device(rng:CUDA.RNG, flow[, num_samples])` +function NormalizingFlows.rand_device(rng::CUDA.RNG, td::Bijectors.TransformedDistribution) + return rand_cuda(rng, td) +end + +function NormalizingFlows.rand_device( + rng::CUDA.RNG, td::Bijectors.TransformedDistribution, num_samples::Int +) + return rand_cuda(rng, td, num_samples) end -function Distributions.insupport( - ::Type{D}, x::CuVector{T} -) where {T<:Real,D<:Distributions.AbstractMvLogNormal} - return all(0 .< x .< Inf) +function rand_cuda(rng::CUDA.RNG, td::Bijectors.TransformedDistribution) + return td.transform(rand_cuda(rng, td.dist)) +end + +function rand_cuda(rng::CUDA.RNG, td::Bijectors.TransformedDistribution, num_samples::Int) + samples = rand_cuda(rng, td.dist, num_samples) + res = reduce( + hcat, + map(axes(samples, 2)) do i + return td.transform(view(samples, :, i)) + end, + ) + return res end end diff --git a/src/sample.jl b/src/sample.jl new file mode 100644 index 00000000..a5cba7a2 --- /dev/null +++ b/src/sample.jl @@ -0,0 +1,69 @@ +# this file defines rand_device function to sample from a Distribution or a +# flow<:Bijectors.TranformedDistribution +# this is mainly for resolving the issue of sampling from a distribution on GPU + +# function rand_device( +# rng::AbstractRNG, +# s::Distributions.Sampleable{<:Distributions.ArrayLikeVariate,Distributions.Continuous}, +# ) +# if !(rng isa CUDA.RNG) +# return Distributions.rand(rng, s) +# else +# return rand_cuda(rng, s) +# end +# end + +function rand_device( + rng::AbstractRNG, + s::Distributions.Sampleable{<:Distributions.ArrayLikeVariate,Distributions.Continuous}, +) + return Distributions.rand(rng, s) +end + +function rand_device( + rng::AbstractRNG, + s::Distributions.Sampleable{<:Distributions.ArrayLikeVariate,Distributions.Continuous}, + n::Int, +) + return Distributions.rand(rng, s, n) +end + +# function rand_device( +# rng::AbstractRNG, +# s::Distributions.Sampleable{Distributions.Multivariate,Distributions.Continuous}, +# n::Int, +# ) +# if !(rng isa CUDA.RNG) +# return Distributions.rand(rng, s, n) +# else +# return rand_cuda(rng, s, n) +# end +# end + +######################### +# for Bijectors.jl +########################## + +function rand_device(rng::AbstractRNG, td::Bijectors.TransformedDistribution) + return Distributions.rand(rng, td) +end + +function rand_device(rng::AbstractRNG, td::Bijectors.TransformedDistribution, n::Int) + return Distributions.rand(rng, td, n) +end + +# function rand_device(rng::AbstractRNG, td::Bijectors.TransformedDistribution) +# if !(rng isa CUDA.RNG) +# return Distributions.rand(rng, td) +# else +# return rand_cuda(rng, td) +# end +# end + +# function rand_device(rng::AbstractRNG, td::Bijectors.TransformedDistribution, n::Int) +# if !(rng isa CUDA.RNG) +# return Distributions.rand(rng, td, n) +# else +# return rand_cuda(rng, td, n) +# end +# end From fb0737288da6d4fc1ac34e8994e14ac3a0599e36 Mon Sep 17 00:00:00 2001 From: zuhengxu Date: Mon, 21 Aug 2023 22:18:00 -0700 Subject: [PATCH 11/57] rename sampler.jl to sample.jl --- src/sampler.jl | 88 -------------------------------------------------- 1 file changed, 88 deletions(-) delete mode 100644 src/sampler.jl diff --git a/src/sampler.jl b/src/sampler.jl deleted file mode 100644 index 63589269..00000000 --- a/src/sampler.jl +++ /dev/null @@ -1,88 +0,0 @@ -# this file defines rand_device function to sample from a Distribution or a -# flow<:Bijectors.TranformedDistribution -# this is mainly for resolving the issue of sampling from a distribution on GPU - -function rand_device( - rng::AbstractRNG, - s::Distributions.Sampleable{<:Distributions.ArrayLikeVariate,Distributions.Continuous}, -) - if !(rng isa CUDA.RNG) - return nothing - else - return rand_cuda(rng, s) - end -end - -function rand_device( - rng::AbstractRNG, - s::Distributions.Sampleable{Distributions.Multivariate,Distributions.Continuous}, - n::Int, -) - if !(rng isa CUDA.RNG) - return Distributions.rand(rng, s, n) - else - return rand_cuda(rng, s, n) - end -end - -function rand_cuda( - rng::CUDA.RNG, - s::Distributions.Sampleable{<:Distributions.ArrayLikeVariate,Distributions.Continuous}, -) - return @inbounds Distributions.rand!( - rng, Distributions.sampler(s), CuArray{float(eltype(s))}(undef, size(s)) - ) -end - -function rand_cuda( - rng::CUDA.RNG, - s::Distributions.Sampleable{Distributions.Multivariate,Distributions.Continuous}, - n::Int, -) - return @inbounds Distribution.rand!( - rng, Distributions.sampler(s), CuArray{float(eltype(s))}(undef, length(s), n) - ) -end - -function Distributions._rand!(rng::CUDA.RNG, d::Distributions.MvNormal, x::CuVecOrMat) - # Replaced usage of scalar indexing. - Random.randn!(rng, x) - Distributions.unwhiten!(d.Σ, x) - x .+= d.μ - return x -end - -######################### -# for Bijectors.jl -########################## - -function rand_device(rng::AbstractRNG, td::Bijectors.TransformedDistribution) - if !(rng isa CUDA.RNG) - return Distributions.rand(rng, td) - else - return cuda_rand(rng, td) - end -end - -function rand_device(rng::AbstractRNG, td::Bijectors.TransformedDistribution, n::Int) - if !(rng isa CUDA.RNG) - return Distributions.rand(rng, td, n) - else - return cuda_rand(rng, td, n) - end -end - -function cuda_rand(rng::CUDA.RNG, td::Bijectors.TransformedDistribution) - return td.transform(cuda_rand(rng, td.dist)) -end - -function cuda_rand(rng::CUDA.RNG, td::Bijectors.TranformedDistribution, num_samples::Int) - samples = cuda_rand(rng, td.dist, num_samples) - res = reduce( - hcat, - map(axes(samples, 2)) do i - return td.transform(view(samples, :, i)) - end, - ) - return res -end From 84fce4c1bd30652feb1d1fc614cbc6193319f3fb Mon Sep 17 00:00:00 2001 From: zuhengxu Date: Mon, 21 Aug 2023 22:18:55 -0700 Subject: [PATCH 12/57] update objectives/elbo.jl to use rand_device --- src/NormalizingFlows.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/NormalizingFlows.jl b/src/NormalizingFlows.jl index 2d708929..39ff281c 100644 --- a/src/NormalizingFlows.jl +++ b/src/NormalizingFlows.jl @@ -9,6 +9,7 @@ using ADTypes, DiffResults using DocStringExtensions export train_flow, elbo, loglikelihood, value_and_gradient! +export rand_device using ADTypes using DiffResults @@ -72,6 +73,7 @@ function train_flow( end include("train.jl") +include("sampler.jl") include("objectives.jl") # optional dependencies @@ -94,6 +96,9 @@ function __init__() @require Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" include( "../ext/NormalizingFlowsZygoteExt.jl" ) + @require CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" include( + "../ext/NormalizingFlowsCUDAExt.jl" + ) end end end From b573508f3345fb8cb7a932cbb2f6e809e3e367d5 Mon Sep 17 00:00:00 2001 From: zuhengxu Date: Mon, 21 Aug 2023 22:19:23 -0700 Subject: [PATCH 13/57] update test/cuda.jl --- test/Project.toml | 3 --- test/cuda.jl | 23 ++++++++++++++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/test/Project.toml b/test/Project.toml index 56973a26..b8d9b933 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -22,8 +22,5 @@ Distributions = "0.25" Enzyme = "0.11" ForwardDiff = "0.10.25" Optimisers = "0.2.16" -ProgressMeter = "1.0.0" -Requires = "1" ReverseDiff = "1.14" -StatsBase = "0.33, 0.34" Zygote = "0.6" diff --git a/test/cuda.jl b/test/cuda.jl index 5b05b215..e4ce4735 100644 --- a/test/cuda.jl +++ b/test/cuda.jl @@ -1,18 +1,31 @@ using CUDA, Test, LinearAlgebra, Distributions +using Flux if CUDA.functional() @testset "rand with CUDA" begin dists = [ - MvNormal(CUDA.zeros(2), I), - MvNormal(CUDA.zeros(2), cu([1.0 0.5; 0.5 1.0])), - MvLogNormal(CUDA.zeros(2), I), - MvLogNormal(CUDA.zeros(2), cu([1.0 0.5; 0.5 1.0])), + MvNormal(CUDA.zeros(2), I), MvNormal(CUDA.zeros(2), cu([1.0 0.5; 0.5 1.0])) ] @testset "$dist" for dist in dists - x = rand(CUDA.default_rng(), dist) + x = rand_device(CUDA.default_rng(), dist) + xs = rand_device(CUDA.default_rng(), dist, 100) @info logpdf(dist, x) @test x isa CuArray + @test xs isa CuArray + end + + @testset "$dist" for dist in dists + CUDA.allowscalar(true) + ts = reduce(∘, [Bijectors.PlanarLayer(2) for _ in 1:2]) + ts_g = gpu(ts) + flow = Bijectors.transformed(dist, ts_g) + + y = rand_device(CUDA.default_rng(), flow) + ys = rand_device(CUDA.default_rng(), flow, 100) + @info logpdf(flow, y) + @test y isa CuArray + @test ys isa CuArray end end end From e8457d4db848a02b8a13e24a70a976ca04ea0a05 Mon Sep 17 00:00:00 2001 From: zuhengxu Date: Mon, 21 Aug 2023 22:19:50 -0700 Subject: [PATCH 14/57] rm exmaple/test.jl --- example/test.jl | 48 ------------------------------------------------ 1 file changed, 48 deletions(-) delete mode 100644 example/test.jl diff --git a/example/test.jl b/example/test.jl deleted file mode 100644 index 3fb7fc09..00000000 --- a/example/test.jl +++ /dev/null @@ -1,48 +0,0 @@ -using CUDA -using LinearAlgebra -using FunctionChains -using Bijectors -using Flux -using NormalizingFlows - -rng = CUDA.default_rng() -T = Float32 -q0 = MvNormal(ones(T, 2)) -q0_g = MvNormal(CUDA.zeros(T, 2), I) - -ts = reduce(∘, [f32(Bijectors.PlanarLayer(2)) for _ in 1:2]) -flow = transformed(q0, ts) - -# gpu -CUDA.functional() -ts_g = gpu(ts) -flow_g = transformed(q0_g, ts_g) - -xs = rand(rng, flow_g.dist, 10) # on cpu -ys_g = transform(ts_g, cu(xs)) # good -logpdf(flow_g, ys_g[:, 1]) # good -rand(flow_g) # bug - -using CUDA -using LinearAlgebra -using Distributions, Random -using Bijectors - -rng = CUDA.default_rng() -T = Float32 -q0_g = MvNormal(CUDA.zeros(T, 2), I) - -CUDA.functional() -ts_g = gpu(ts) -flow_g = transformed(q0_g, ts_g) - -x = rand(rng, q0_g) # good -xs = rand(rng, q0_g) # ambiguous - -y = rand_device(rng, flow_g, 10) # ambiguous - -rand(gpu(flow)) - -gpu(q0) -using Functors -@functor MvNormal From c21400af342fe9d6b6fe5db6f8714261eb59e809 Mon Sep 17 00:00:00 2001 From: Zuheng Date: Mon, 21 Aug 2023 22:39:54 -0700 Subject: [PATCH 15/57] fix CI test error --- src/NormalizingFlows.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NormalizingFlows.jl b/src/NormalizingFlows.jl index 39ff281c..62d9140c 100644 --- a/src/NormalizingFlows.jl +++ b/src/NormalizingFlows.jl @@ -73,7 +73,7 @@ function train_flow( end include("train.jl") -include("sampler.jl") +include("sample.jl") include("objectives.jl") # optional dependencies From 0ced915ef36b51eec039c4ef92326f3bfd3fc04b Mon Sep 17 00:00:00 2001 From: Zuheng Date: Mon, 21 Aug 2023 22:50:41 -0700 Subject: [PATCH 16/57] fix CI error --- test/Project.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/Project.toml b/test/Project.toml index b8d9b933..fa672b26 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -5,6 +5,7 @@ CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" DiffResults = "163ba53b-c6d8-5494-b064-1a9d43ac40c5" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" +Flux = "587475ba-b771-5e3f-ad9e-33799f191a9c" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Optimisers = "3bd65402-5787-11e9-1adc-39752487f4e2" @@ -24,3 +25,4 @@ ForwardDiff = "0.10.25" Optimisers = "0.2.16" ReverseDiff = "1.14" Zygote = "0.6" +Flux = "0.14" From 45c8e0a5b6195c0d10c540b59deea2024a011143 Mon Sep 17 00:00:00 2001 From: zuhengxu Date: Tue, 22 Aug 2023 11:21:57 -0700 Subject: [PATCH 17/57] update flux compat for test/ --- example/test.jl | 23 +++++++++++++++++++++++ test/Project.toml | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 example/test.jl diff --git a/example/test.jl b/example/test.jl new file mode 100644 index 00000000..cd2dae3c --- /dev/null +++ b/example/test.jl @@ -0,0 +1,23 @@ +using CUDA +using LinearAlgebra +using Distributions, Random +using Bijectors +using Flux +import NormalizingFlows as NF + +CUDA.functional() +rng = CUDA.default_rng() +T = Float32 +q0_g = MvNormal(CUDA.zeros(T, 2), I) +# construct gpu flow +ts = reduce(∘, [f32(Bijectors.PlanarLayer(2)) for _ in 1:2]) +ts_g = gpu(ts) +flow_g = transformed(q0_g, ts_g) + +# sample from GPU MvNormal +x = NF.rand_device(rng, q0_g) # good +xs = NF.rand_device(rng, q0_g, 100) # ambiguous + +# sample from GPU flow +y = NF.rand_device(rng, flow_g) # ambiguous +ys = NF.rand_device(rng, flow_g, 100) # ambiguous diff --git a/test/Project.toml b/test/Project.toml index fa672b26..9bda81c5 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -25,4 +25,4 @@ ForwardDiff = "0.10.25" Optimisers = "0.2.16" ReverseDiff = "1.14" Zygote = "0.6" -Flux = "0.14" +Flux = "0.13, 0.14" From 4be9588af0c5c7f2e18ced2c2863c768567cf515 Mon Sep 17 00:00:00 2001 From: Zuheng Date: Tue, 22 Aug 2023 19:26:02 -0700 Subject: [PATCH 18/57] rm tmp test file --- example/test.jl | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 example/test.jl diff --git a/example/test.jl b/example/test.jl deleted file mode 100644 index cd2dae3c..00000000 --- a/example/test.jl +++ /dev/null @@ -1,23 +0,0 @@ -using CUDA -using LinearAlgebra -using Distributions, Random -using Bijectors -using Flux -import NormalizingFlows as NF - -CUDA.functional() -rng = CUDA.default_rng() -T = Float32 -q0_g = MvNormal(CUDA.zeros(T, 2), I) -# construct gpu flow -ts = reduce(∘, [f32(Bijectors.PlanarLayer(2)) for _ in 1:2]) -ts_g = gpu(ts) -flow_g = transformed(q0_g, ts_g) - -# sample from GPU MvNormal -x = NF.rand_device(rng, q0_g) # good -xs = NF.rand_device(rng, q0_g, 100) # ambiguous - -# sample from GPU flow -y = NF.rand_device(rng, flow_g) # ambiguous -ys = NF.rand_device(rng, flow_g, 100) # ambiguous From 1e72b9df793c7aea84f47e4316fb686eb3091a6a Mon Sep 17 00:00:00 2001 From: Zuheng Date: Fri, 25 Aug 2023 14:36:55 -0700 Subject: [PATCH 19/57] fix cuda err diffresults.gradient_result --- src/train.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/train.jl b/src/train.jl index 5edd9a02..a648c3de 100644 --- a/src/train.jl +++ b/src/train.jl @@ -126,7 +126,7 @@ function optimize( opt_stats = [] θ = copy(θ₀) - diff_result = DiffResults.GradientResult(θ) + diff_result = DiffResults.DiffResult(zero(eltype(θ)), similar(θ)) # initialise optimiser state st = Optimisers.setup(optimiser, θ) @@ -140,6 +140,7 @@ function optimize( # Save stats ls = DiffResults.value(diff_result) g = DiffResults.gradient(diff_result) + stat = (iteration=i, loss=ls, gradient_norm=norm(g)) push!(opt_stats, stat) From 6a899f2aee0b3c7a1befd95478557f79d014456c Mon Sep 17 00:00:00 2001 From: David Xu <42751767+zuhengxu@users.noreply.github.com> Date: Wed, 23 Aug 2023 04:35:43 -0700 Subject: [PATCH 20/57] Documentation (#27) * add NF intro * set up doc files * add gitignore * minor update to readme * update home page * update docs for each funciton * update docs * src * update function docs * update docs * fix readme math rendering issue * update docs * update example doc * update customize layer docs * finish docs * finish docs * Update README.md Co-authored-by: Cameron Pfiffer * Update README.md Co-authored-by: Cameron Pfiffer * Update README.md Co-authored-by: Cameron Pfiffer * Update docs/src/index.md Co-authored-by: Xianda Sun <5433119+sunxd3@users.noreply.github.com> * Update README.md Co-authored-by: Xianda Sun <5433119+sunxd3@users.noreply.github.com> * Update docs/src/index.md Co-authored-by: Xianda Sun <5433119+sunxd3@users.noreply.github.com> * Update docs/src/customized_layer.md Co-authored-by: Xianda Sun <5433119+sunxd3@users.noreply.github.com> * Update docs/src/customized_layer.md Co-authored-by: Xianda Sun <5433119+sunxd3@users.noreply.github.com> * Update docs/src/customized_layer.md Co-authored-by: Xianda Sun <5433119+sunxd3@users.noreply.github.com> * Update docs/src/customized_layer.md Co-authored-by: Xianda Sun <5433119+sunxd3@users.noreply.github.com> * Update docs/src/customized_layer.md Co-authored-by: Xianda Sun <5433119+sunxd3@users.noreply.github.com> * Update docs/src/index.md Co-authored-by: Xianda Sun <5433119+sunxd3@users.noreply.github.com> * Update docs/src/index.md Co-authored-by: Xianda Sun <5433119+sunxd3@users.noreply.github.com> * Update docs/src/index.md Co-authored-by: Xianda Sun <5433119+sunxd3@users.noreply.github.com> * Update docs/src/example.md Co-authored-by: Xianda Sun <5433119+sunxd3@users.noreply.github.com> * Update docs/src/example.md Co-authored-by: Xianda Sun <5433119+sunxd3@users.noreply.github.com> * Update docs/src/example.md Co-authored-by: Xianda Sun <5433119+sunxd3@users.noreply.github.com> * Update docs/src/example.md Co-authored-by: Xianda Sun <5433119+sunxd3@users.noreply.github.com> * Update docs/src/example.md Co-authored-by: Xianda Sun <5433119+sunxd3@users.noreply.github.com> * Update docs/src/example.md Co-authored-by: Xianda Sun <5433119+sunxd3@users.noreply.github.com> * Update docs/src/example.md Co-authored-by: Xianda Sun <5433119+sunxd3@users.noreply.github.com> * Update docs/src/customized_layer.md Co-authored-by: Xianda Sun <5433119+sunxd3@users.noreply.github.com> * Update docs/src/customized_layer.md Co-authored-by: Xianda Sun <5433119+sunxd3@users.noreply.github.com> * Update docs/src/customized_layer.md Co-authored-by: Xianda Sun <5433119+sunxd3@users.noreply.github.com> * Update docs/src/customized_layer.md Co-authored-by: Xianda Sun <5433119+sunxd3@users.noreply.github.com> * minor ed * minor ed to fix latex issue * minor update --------- Co-authored-by: Cameron Pfiffer Co-authored-by: Xianda Sun <5433119+sunxd3@users.noreply.github.com> --- README.md | 86 +++++++++++++++ docs/.gitignore | 2 + docs/make.jl | 7 +- docs/src/api.md | 93 +++++++++++++++++ docs/src/banana.png | Bin 0 -> 44156 bytes docs/src/comparison.png | Bin 0 -> 41109 bytes docs/src/customized_layer.md | 180 ++++++++++++++++++++++++++++++++ docs/src/elbo.png | Bin 0 -> 23236 bytes docs/src/example.md | 119 +++++++++++++++++++++ docs/src/index.md | 79 +++++++++++++- src/NormalizingFlows.jl | 11 +- src/objectives/elbo.jl | 4 +- src/objectives/loglikelihood.jl | 8 +- src/train.jl | 3 +- 14 files changed, 579 insertions(+), 13 deletions(-) create mode 100644 docs/.gitignore create mode 100644 docs/src/api.md create mode 100644 docs/src/banana.png create mode 100644 docs/src/comparison.png create mode 100644 docs/src/customized_layer.md create mode 100644 docs/src/elbo.png create mode 100644 docs/src/example.md diff --git a/README.md b/README.md index f9fbf663..5e7cac89 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,89 @@ [![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://turinglang.github.io/NormalizingFlows.jl/dev/) [![Build Status](https://github.com/TuringLang/NormalizingFlows.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/TuringLang/NormalizingFlows.jl/actions/workflows/CI.yml?query=branch%3Amain) + + +A normalizing flow library for Julia. + +The purpose of this package is to provide a simple and flexible interface for variational inference (VI) and normalizing flows (NF) for Bayesian computation or generative modeling. +The key focus is to ensure modularity and extensibility, so that users can easily +construct (e.g., define customized flow layers) and combine various components +(e.g., choose different VI objectives or gradient estimates) +for variational approximation of general target distributions, +without being tied to specific probabilistic programming frameworks or applications. + +See the [documentation](https://turinglang.org/NormalizingFlows.jl/dev/) for more. + +## Installation +To install the package, run the following command in the Julia REPL: +```julia +] # enter Pkg mode +(@v1.9) pkg> add git@github.com:TuringLang/NormalizingFlows.jl.git +``` +Then simply run the following command to use the package: +```julia +using NormalizingFlows +``` + +## Quick recap of normalizing flows +Normalizing flows transform a simple reference distribution $q_0$ (sometimes known as base distribution) to +a complex distribution $q$ using invertible functions. + +In more details, given the base distribution, usually a standard Gaussian distribution, i.e., $q_0 = \mathcal{N}(0, I)$, +we apply a series of parameterized invertible transformations (called flow layers), $T_{1, \theta_1}, \cdots, T_{N, \theta_k}$, yielding that +```math +Z_N = T_{N, \theta_N} \circ \cdots \circ T_{1, \theta_1} (Z_0) , \quad Z_0 \sim q_0,\quad Z_N \sim q_{\theta}, +``` +where $\theta = (\theta_1, \dots, \theta_N)$ is the parameter to be learned, and $q_{\theta}$ is the variational distribution (flow distribution). This describes **sampling procedure** of normalizing flows, which requires sending draws through a forward pass of these flow layers. + +Since all the transformations are invertible (techinically [diffeomorphic](https://en.wikipedia.org/wiki/Diffeomorphism)), we can evaluate the density of a normalizing flow distribution $q_{\theta}$ by the change of variable formula: +```math +q_\theta(x)=\frac{q_0\left(T_1^{-1} \circ \cdots \circ +T_N^{-1}(x)\right)}{\prod_{n=1}^N J_n\left(T_n^{-1} \circ \cdots \circ +T_N^{-1}(x)\right)} \quad J_n(x)=\left|\operatorname{det} \nabla_x +T_n(x)\right|. +``` +Here we drop the subscript $\theta_n, n = 1, \dots, N$ for simplicity. +Density evaluation of normalizing flow requires computing the **inverse** and the +**Jacobian determinant** of each flow layer. + +Given the feasibility of i.i.d. sampling and density evaluation, normalizing flows can be trained by minimizing some statistical distances to the target distribution $p$. The typical choice of the statistical distance is the forward and backward Kullback-Leibler (KL) divergence, which leads to the following optimization problems: +```math +\begin{aligned} +\text{Reverse KL:}\quad +&\argmin _{\theta} \mathbb{E}_{q_{\theta}}\left[\log q_{\theta}(Z)-\log p(Z)\right] \\ +&= \argmin _{\theta} \mathbb{E}_{q_0}\left[\log \frac{q_\theta(T_N\circ \cdots \circ T_1(Z_0))}{p(T_N\circ \cdots \circ T_1(Z_0))}\right] \\ +&= \argmax _{\theta} \mathbb{E}_{q_0}\left[ \log p\left(T_N \circ \cdots \circ T_1(Z_0)\right)-\log q_0(X)+\sum_{n=1}^N \log J_n\left(F_n \circ \cdots \circ F_1(X)\right)\right] +\end{aligned} +``` +and +```math +\begin{aligned} +\text{Forward KL:}\quad +&\argmin _{\theta} \mathbb{E}_{p}\left[\log q_{\theta}(Z)-\log p(Z)\right] \\ +&= \argmin _{\theta} \mathbb{E}_{p}\left[\log q_\theta(Z)\right] +\end{aligned} +``` +Both problems can be solved via standard stochastic optimization algorithms, +such as stochastic gradient descent (SGD) and its variants. + +Reverse KL minimization is typically used for **Bayesian computation**, where one +wants to approximate a posterior distribution $p$ that is only known up to a +normalizing constant. +In contrast, forward KL minimization is typically used for **generative modeling**, where one wants to approximate a complex distribution $p$ that is known up to a normalizing constant. + +## Current status and TODOs + +- [x] general interface development +- [x] documentation +- [ ] including more flow examples +- [ ] GPU compatibility +- [ ] benchmarking + +## Related packages +- [Bijectors.jl](https://github.com/TuringLang/Bijectors.jl): a package for defining bijective transformations, which can be used for defining customized flow layers. +- [Flux.jl](https://fluxml.ai/Flux.jl/stable/) +- [Optimisers.jl](https://github.com/FluxML/Optimisers.jl) +- [AdvancedVI.jl](https://github.com/TuringLang/AdvancedVI.jl) + + diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..da3d3379 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,2 @@ +build/ +site/ \ No newline at end of file diff --git a/docs/make.jl b/docs/make.jl index 7202aa7d..e0c87646 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -10,7 +10,12 @@ makedocs(; repo="https://github.com/TuringLang/NormalizingFlows.jl/blob/{commit}{path}#{line}", sitename="NormalizingFlows.jl", format=Documenter.HTML(), - pages=["Home" => "index.md"], + pages=[ + "Home" => "index.md", + "API" => "api.md", + "Example" => "example.md", + "Customize your own flow layer" => "customized_layer.md", + ], ) deploydocs(; repo="github.com/TuringLang/NormalizingFlows.jl", devbranch="main") diff --git a/docs/src/api.md b/docs/src/api.md new file mode 100644 index 00000000..f8028b91 --- /dev/null +++ b/docs/src/api.md @@ -0,0 +1,93 @@ +## API + +```@index +``` + + +## Main Function + +```@docs +NormalizingFlows.train_flow +``` + +The flow object can be constructed by `transformed` function in `Bijectors.jl` package. +For example of Gaussian VI, we can construct the flow as follows: +```@julia +using Distributions, Bijectors +T= Float32 +q₀ = MvNormal(zeros(T, 2), ones(T, 2)) +flow = Bijectors.transformed(q₀, Bijectors.Shift(zeros(T,2)) ∘ Bijectors.Scale(ones(T, 2))) +``` +To train the Gaussian VI targeting at distirbution $p$ via ELBO maiximization, we can run +```@julia +using NormalizingFlows + +sample_per_iter = 10 +flow_trained, stats, _ = train_flow( + elbo, + flow, + logp, + sample_per_iter; + max_iters=2_000, + optimiser=Optimisers.ADAM(0.01 * one(T)), +) +``` +## Variational Objectives +We have implemented two variational objectives, namely, ELBO and the log-likelihood objective. +Users can also define their own objective functions, and pass it to the [`train_flow`](@ref) function. +`train_flow` will optimize the flow parameters by maximizing `vo`. +The objective function should take the following general form: +```julia +vo(rng, flow, args...) +``` +where `rng` is the random number generator, `flow` is the flow object, and `args...` are the +additional arguments that users can pass to the objective function. + +#### Evidence Lower Bound (ELBO) +By maximizing the ELBO, it is equivalent to minimizing the reverse KL divergence between $q_\theta$ and $p$, i.e., +```math +\begin{aligned} +&\min _{\theta} \mathbb{E}_{q_{\theta}}\left[\log q_{\theta}(Z)-\log p(Z)\right] \quad \text{(Reverse KL)}\\ +& = \max _{\theta} \mathbb{E}_{q_0}\left[ \log p\left(T_N \circ \cdots \circ +T_1(Z_0)\right)-\log q_0(X)+\sum_{n=1}^N \log J_n\left(F_n \circ \cdots \circ +F_1(X)\right)\right] \quad \text{(ELBO)} +\end{aligned} +``` +Reverse KL minimization is typically used for **Bayesian computation**, +where one only has access to the log-(unnormalized)density of the target distribution $p$ (e.g., a Bayesian posterior distribution), +and hope to generate approximate samples from it. + +```@docs +NormalizingFlows.elbo +``` +#### Log-likelihood + +By maximizing the log-likelihood, it is equivalent to minimizing the forward KL divergence between $q_\theta$ and $p$, i.e., +```math +\begin{aligned} +& \min_{\theta} \mathbb{E}_{p}\left[\log q_{\theta}(Z)-\log p(Z)\right] \quad \text{(Forward KL)} \\ +& = \max_{\theta} \mathbb{E}_{p}\left[\log q_{\theta}(Z)\right] \quad \text{(Expected log-likelihood)} +\end{aligned} +``` +Forward KL minimization is typically used for **generative modeling**, +where one is given a set of samples from the target distribution $p$ (e.g., images) +and aims to learn the density or a generative process that outputs high quality samples. + +```@docs +NormalizingFlows.loglikelihood +``` + + +## Training Loop + +```@docs +NormalizingFlows.optimize +``` + + +## Utility Functions for Taking Gradient +```@docs +NormalizingFlows.grad! +NormalizingFlows.value_and_gradient! +``` + diff --git a/docs/src/banana.png b/docs/src/banana.png new file mode 100644 index 0000000000000000000000000000000000000000..785e53b4aa9186716bf22d8a9abd7a8ad4719e83 GIT binary patch literal 44156 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 literal 0 HcmV?d00001 diff --git a/docs/src/comparison.png b/docs/src/comparison.png new file mode 100644 index 0000000000000000000000000000000000000000..777f2fdc92d79a0d1b9aa6c801fe2550a949837b GIT binary patch literal 41109 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