Repository navigation
Cached treetwister for twist!
#599
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
54cb605
1357725
8d89927
5cb33fe
45f0ebd
29f5a17
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -186,6 +186,144 @@ function EnzymeRules.forward( | |
| end | ||
| end | ||
|
|
||
| function EnzymeRules.augmented_primal( | ||
| config::EnzymeRules.RevConfigWidth{1}, | ||
| func::Const{typeof(TK.twist!)}, | ||
| ::Type{RT}, | ||
| C::Annotation{<:AbstractTensorMap}, | ||
| A::Annotation{<:AbstractTensorMap}, | ||
| inds::Const, | ||
| α::Annotation{<:Number}, | ||
| β::Annotation{<:Number}, | ||
| ba::Const...; | ||
| inv::Bool = false | ||
| ) where {RT} | ||
| C_cache = !isa(β, Const) ? copy(C.val) : nothing | ||
| A_cache = EnzymeRules.overwritten(config)[3] ? copy(A.val) : nothing | ||
| # if we need to compute Δa, it is faster to allocate an intermediate twisted A | ||
| # and store that instead of repeating the twist in the pullback each time. | ||
| # effectively, we replace `twist!` by `add ∘ twist`. | ||
| Ap = if !isa(α, Const) | ||
| Ap = twist(A.val, inds.val; inv, copy = true) | ||
|
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. Should this |
||
| add!(C.val, Ap, α.val, β.val) | ||
| Ap | ||
| else | ||
| bavs = map(a -> a.val, ba) | ||
| TK.twist!(C.val, A.val, inds.val, α.val, β.val, bavs...; inv) | ||
| nothing | ||
| end | ||
| cache = (C_cache, A_cache, Ap) | ||
| primal = EnzymeRules.needs_primal(config) ? C.val : nothing | ||
| shadow = EnzymeRules.needs_shadow(config) ? C.dval : nothing | ||
| return EnzymeRules.AugmentedReturn(primal, shadow, cache) | ||
| end | ||
| function EnzymeRules.reverse( | ||
|
lkdvos marked this conversation as resolved.
Resolved
|
||
| config::EnzymeRules.RevConfigWidth{1}, | ||
| func::Const{typeof(TK.twist!)}, | ||
| ::Type{RT}, | ||
| cache, | ||
| C::Annotation{<:AbstractTensorMap}, | ||
| A::Annotation{<:AbstractTensorMap}, | ||
| inds::Const, | ||
| α::Annotation{<:Number}, | ||
| β::Annotation{<:Number}, | ||
| ba::Const...; | ||
| inv::Bool = false | ||
| ) where {RT} | ||
| C_cache, A_cache, Ap = cache | ||
| Cval = something(C_cache, C.val) | ||
| Aval = something(A_cache, A.val) | ||
| bavs = map(a -> a.val, ba) | ||
| # ΔA | ||
| if !isa(A, Const) && !isa(C, Const) | ||
| TK.twist_pb(A.dval, Aval, C.dval, C.val, inds.val, α.val, bavs...; inv) | ||
| end | ||
| Δαr = pullback_dα(α, C, Ap) | ||
| Δβr = pullback_dβ(β, C, Cval) | ||
| !isa(C, Const) && pullback_dC!(C.dval, β.val) | ||
| return nothing, nothing, nothing, Δαr, Δβr, map(Returns(nothing), ba)... | ||
| end | ||
| function EnzymeRules.forward( | ||
| config::EnzymeRules.FwdConfigWidth{1}, | ||
| func::Const{typeof(TK.twist!)}, | ||
| ::Type{RT}, | ||
| C::Annotation{<:AbstractTensorMap}, | ||
| A::Annotation{<:AbstractTensorMap}, | ||
| inds::Annotation, | ||
| α::Annotation{<:Number}, | ||
| β::Annotation{<:Number}, | ||
| ba::Const...; | ||
| inv::Bool = false | ||
| ) where {RT} | ||
| bavs = map(a -> a.val, ba) | ||
| if !isa(C, Const) | ||
| if isa(β, Const) | ||
| scale!(C.dval, β.val) | ||
| else | ||
| add!(C.dval, C.val, β.dval, β.val) | ||
| end | ||
| !isa(α, Const) && TK.twist!(C.dval, A.val, inds.val, α.dval, One(), bavs...; inv) | ||
| !isa(A, Const) && TK.twist!(C.dval, A.dval, inds.val, α.val, One(), bavs...; inv) | ||
| end | ||
| TK.twist!(C.val, A.val, inds.val, α.val, β.val, bavs...; inv) | ||
| if EnzymeRules.needs_primal(config) && EnzymeRules.needs_shadow(config) | ||
| return C | ||
| elseif EnzymeRules.needs_primal(config) | ||
| return C.val | ||
| elseif EnzymeRules.needs_shadow(config) | ||
| return C.dval | ||
| else | ||
| return nothing | ||
| end | ||
| end | ||
|
|
||
| # in-place twist: the source and destination are the same tensor | ||
| function EnzymeRules.augmented_primal( | ||
| config::EnzymeRules.RevConfigWidth{1}, | ||
| func::Const{typeof(TK.twist!)}, | ||
| ::Type{RT}, | ||
| t::Annotation{<:AbstractTensorMap}, | ||
| inds::Const; | ||
| inv::Bool = false | ||
| ) where {RT} | ||
| TK.twist!(t.val, inds.val; inv) | ||
| primal = EnzymeRules.needs_primal(config) ? t.val : nothing | ||
| shadow = EnzymeRules.needs_shadow(config) ? t.dval : nothing | ||
| return EnzymeRules.AugmentedReturn(primal, shadow, nothing) | ||
| end | ||
| function EnzymeRules.reverse( | ||
| config::EnzymeRules.RevConfigWidth{1}, | ||
| func::Const{typeof(TK.twist!)}, | ||
| ::Type{RT}, | ||
| cache, | ||
| t::Annotation{<:AbstractTensorMap}, | ||
| inds::Const; | ||
| inv::Bool = false | ||
| ) where {RT} | ||
| !isa(t, Const) && TK.twist!(t.dval, inds.val; inv = !inv) | ||
| return (nothing, nothing) | ||
| end | ||
| function EnzymeRules.forward( | ||
| config::EnzymeRules.FwdConfigWidth{1}, | ||
| func::Const{typeof(TK.twist!)}, | ||
| ::Type{RT}, | ||
| t::Annotation{<:AbstractTensorMap}, | ||
| inds::Annotation; | ||
| inv::Bool = false | ||
| ) where {RT} | ||
| TK.twist!(t.val, inds.val; inv) | ||
| !isa(t, Const) && TK.twist!(t.dval, inds.val; inv) | ||
| if EnzymeRules.needs_primal(config) && EnzymeRules.needs_shadow(config) | ||
| return t | ||
| elseif EnzymeRules.needs_primal(config) | ||
| return t.val | ||
| elseif EnzymeRules.needs_shadow(config) | ||
| return t.dval | ||
| else | ||
| return nothing | ||
| end | ||
| end | ||
|
|
||
| function EnzymeRules.augmented_primal( | ||
| config::EnzymeRules.RevConfigWidth{1}, | ||
| func::Const{typeof(flip)}, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -160,6 +160,93 @@ function Mooncake.rrule!!( | |
| return t_Δt, twist_pullback | ||
| end | ||
|
|
||
| # both are needed for correctly capturing every dispatch | ||
| @is_primitive( | ||
| DefaultCtx, | ||
| ReverseMode, | ||
| Tuple{ | ||
| typeof(TK.twist!), | ||
| AbstractTensorMap, | ||
| AbstractTensorMap, Any, | ||
| Number, Number, Vararg{Any}, | ||
| } | ||
| ) | ||
| @is_primitive( | ||
| DefaultCtx, | ||
| ReverseMode, | ||
| Tuple{ | ||
| typeof(Core.kwcall), @NamedTuple{inv::Bool}, typeof(TK.twist!), | ||
| AbstractTensorMap, | ||
| AbstractTensorMap, Any, | ||
| Vararg{Any}, | ||
| } | ||
| ) | ||
|
|
||
| function Mooncake.rrule!!( | ||
| ::CoDual{typeof(TK.twist!)}, | ||
| C_ΔC::CoDual{<:AbstractTensorMap}, | ||
| A_ΔA::CoDual{<:AbstractTensorMap}, inds_Δinds::CoDual, | ||
| α_Δα::CoDual{<:Number}, β_Δβ::CoDual{<:Number}, | ||
| ba_Δba::CoDual... | ||
| ) | ||
| C_ΔC, pullback = twist!_rrule(C_ΔC, A_ΔA, inds_Δinds, false, (α_Δα, β_Δβ, ba_Δba...)) | ||
| twist!_pullback(Δ::NoRData) = (NoRData(), pullback(Δ)...) | ||
| return C_ΔC, twist!_pullback | ||
| end | ||
| function Mooncake.rrule!!( | ||
| ::CoDual{typeof(Core.kwcall)}, kwargs_Δkwargs::CoDual{@NamedTuple{inv::Bool}}, ::CoDual{typeof(TK.twist!)}, | ||
| C_ΔC::CoDual{<:AbstractTensorMap}, | ||
| A_ΔA::CoDual{<:AbstractTensorMap}, inds_Δinds::CoDual, | ||
| args_Δargs::CoDual... | ||
| ) | ||
| inv = primal(kwargs_Δkwargs).inv | ||
| C_ΔC, pullback = twist!_rrule(C_ΔC, A_ΔA, inds_Δinds, inv, args_Δargs) | ||
| twist!_pullback(Δ::NoRData) = (NoRData(), NoRData(), NoRData(), pullback(Δ)...) | ||
| return C_ΔC, twist!_pullback | ||
| end | ||
|
|
||
| # `args_Δargs` holds the optional `α`, `β`, backend and allocator, where a missing `α` or `β` is constant | ||
| function twist!_rrule(C_ΔC, A_ΔA, inds_Δinds, inv, args_Δargs) | ||
| # prepare arguments | ||
| C, ΔC = arrayify(C_ΔC) | ||
| A, ΔA = arrayify(A_ΔA) | ||
| inds = primal(inds_Δinds) | ||
| args = primal.(args_Δargs) | ||
| α = length(args) >= 1 ? args[1] : One() | ||
| β = length(args) >= 2 ? args[2] : Zero() | ||
| ba = length(args) >= 3 ? args[3:end] : () | ||
|
|
||
| C_cache = copy(C) | ||
|
|
||
| # if we need to compute Δa, it is faster to allocate an intermediate twisted A | ||
| # and store that instead of repeating the twist in the pullback each time. | ||
| # effectively, we replace `twist!` by `add ∘ twist`. | ||
| Ap = if _needs_tangent(α) | ||
| Ap = twist(A, inds; inv, copy = true) | ||
|
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. Same comment about |
||
| add!(C, Ap, α, β) | ||
| Ap | ||
| else | ||
| TK.twist!(C, A, inds, α, β, ba...; inv) | ||
| nothing | ||
| end | ||
|
|
||
| function pullback(::NoRData) | ||
| copy!(C, C_cache) | ||
|
|
||
| TK.twist_pb(ΔA, A, ΔC, C, inds, α, ba...; inv) | ||
| ΔAr = NoRData() | ||
|
|
||
| Δαr = length(args) >= 1 ? pullback_dα(α, ΔC, Ap) : nothing | ||
| Δβr = length(args) >= 2 ? pullback_dβ(β, ΔC, C) : nothing | ||
| ΔCr = pullback_dC!(ΔC, β) # this typically returns NoRData() | ||
|
|
||
| Δargsr = (Δαr, Δβr, map(Returns(NoRData()), ba)...)[1:length(args)] | ||
| return ΔCr, ΔAr, NoRData(), Δargsr... | ||
| end | ||
|
|
||
| return C_ΔC, pullback | ||
| end | ||
|
|
||
| # both are needed for correctly capturing every dispatch | ||
| @is_primitive DefaultCtx ReverseMode Tuple{typeof(flip), AbstractTensorMap, Any} | ||
| @is_primitive DefaultCtx ReverseMode Tuple{typeof(Core.kwcall), @NamedTuple{inv::Bool}, typeof(flip), AbstractTensorMap, Any} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -367,6 +367,23 @@ function permute(src::Union{FusionTreePair, FusionTreeBlock}, p::Index2Tuple) | |
| return braid(src, p, (levels1, levels2)) | ||
| end | ||
|
|
||
| """ | ||
| twist(src::Union{FusionTreePair, FusionTreeBlock}, inds; inv::Bool = false) -> (src, θ) | ||
|
|
||
| Apply a twist to the uncoupled legs `inds` of the fusion-splitting tree pair or block `src`, | ||
| where `inds` is either a collection of leg indices or an `NTuple{N, Bool}` mask over all legs. | ||
| The twist is diagonal in the fusion tree basis, so this returns `src` itself together with the | ||
| scalar factor `θ`, which is shared by all trees in a block. If `inv = true`, use the inverse twist. | ||
| """ | ||
| function twist(src::Union{FusionTreePair{I}, FusionTreeBlock{I}}, inds; inv::Bool = false) where {I} | ||
|
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. I see why you want to accept an |
||
| uncoupled₁, uncoupled₂ = src isa FusionTreeBlock ? src.uncoupled : (src[1].uncoupled, src[2].uncoupled) | ||
| uncoupled = (uncoupled₁..., uncoupled₂...) | ||
| N = length(uncoupled) | ||
| mask = inds isa NTuple{N, Bool} ? inds : ntuple(i -> i in inds, N) | ||
| θ = prod(map((m, a) -> m ? twist(a) : one(twist(a)), mask, uncoupled); init = one(sectorscalartype(I))) | ||
| return src, inv ? θ' : θ | ||
| end | ||
|
|
||
| """ | ||
| flip((f₁, f₂)::FusionTreePair, i::Int; inv::Bool = false) | ||
| -> SingletonDict{FusionTreePair, <:Number} | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.