-
Notifications
You must be signed in to change notification settings - Fork 32
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
Eagerly invert plan on formation of AdjointPlan: correct eltype and remove output_size #113
Conversation
Codecov ReportPatch coverage:
Additional details and impacted files@@ Coverage Diff @@
## master #113 +/- ##
==========================================
- Coverage 93.87% 93.70% -0.17%
==========================================
Files 5 5
Lines 441 445 +4
==========================================
+ Hits 414 417 +3
- Misses 27 28 +1
☔ View full report in Codecov by Sentry. |
In the common case where For example: function adjoint_mul(p::Plan{T}, x::AbstractArray, ::FFTAdjointStyle) where {T}
dims = fftdims(p)
N = normalization(T, size(p), dims)
pinv = inv(p)
if pinv isa ScaledPlan
return pinv.scale == N ? pinv.p * x : rmul!(pinv.p * x, pinv.scale / N)
else
return rmul!(pinv * x, inv(N))
end
end |
Implemented in f9b5de3. In the FFT case, a neat way to do the case where the scaling does not cancel was to combine the extra scaling into the scaled plan, which will then do the same AbstractFFTs.jl/src/definitions.jl Line 268 in f9b5de3
map , so we take the scaling out of the scaled plan and into the map.
|
Co-authored-by: Steven G. Johnson <stevenj@mit.edu>
Bump on merge |
if pinv isa ScaledPlan && pinv.scale == N | ||
return pinv.p * x | ||
else | ||
return (1/N * pinv) * x |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gaurav-arya This line introduced a Float64
promotion that was not (necessarily) present in the previous implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't quite follow, could you explain or give an example?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1 / N
will be a Float64
which will promote everything. In the old code this did not happen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apologies that I still don't follow: why does where we divide by N
affect the promotion behavior?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And why is N
a Float64
in the first place, rather than a quantity of type T
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
julia> using FFTW
julia> P = plan_fft(rand(Float32, 3, 3));
julia> P' * rand(Float32, 3, 3)
3×3 Matrix{ComplexF32}:
3.48416+0.0im -0.264397-0.859624im -0.264397+0.859624im
-0.0523138-0.781731im -0.462729-0.257876im -0.902054+0.397495im
-0.0523138+0.781731im -0.902054-0.397495im -0.462729+0.257876im
Could you provide an example of the issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bump
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bump -- I still don't follow your initial comment, since as Int64
and Float32
promote to Float32
. An example would be much appreciated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bump. If this a problem that exists on the main branch, could you make an issue for it, with a reproducer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, somehow this conversation was lost in my stream of Github notifications.
And why is N a Float64 in the first place, rather than a quantity of type T?
I assumed it would be an Int
(and hence 1/N
would be a Float64
) but you're right of course that it is of type T
. Maybe one could even use T = eltype(pinv)
here since the computations are performed with pinv
but I think it should not matter. Possibly inv(N)
would be a tiny bit safer since then no Int
s would be involved but I can't think of any relevant case where it should matter that the inverse is computed in a way that involves an Int
.
pinv = inv(p) | ||
n = size(pinv, halfdim) | ||
# Optimization: when pinv is a ScaledPlan, fuse the scaling into our map to ensure we do not loop over x twice. | ||
scale = pinv isa ScaledPlan ? pinv.scale / 2N : 1 / 2N |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, this introduces Float64
promotions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
julia> using FFTW
julia> P = plan_rfft(rand(Float32, 3, 3));
julia> P' * (P * rand(Float32, 3, 3))
3×3 Matrix{Float32}:
1.35893 3.17063 3.99985
1.15958 6.03116 3.67565
3.10326 5.80633 3.89977
This PR resolves #110. After some thinking I realized that the right way to do this should be to actually call
inv(p)
in the formation of the adjoint plan. This makesadjoint(p)
work the same way asinv(p)
, in that caching occurs in the plan formation rather than plan application.As a consequence, I realized that
output_size
should not actually be job of an adjoint style. TPlan implementers are actually already required to manually figure out output size for their plan when implementingsize(plan_inv(p))
, and hence it didn't actually make sense to have the adjoint style trait provide this. If we want to make to make the output size available for free without forming the inverse plan, that's a separate line of work (that could also simplify the lives of the original plan implementation) But it shouldn't actually be the job of adjoint traits -- the goal of adjoint traits is to make adjoints easy given that all inverse functionality is implemented, and indeed inverses already give the output size for free.Note that this PR is technically breaking for implementers of new adjoint styles (by removing 1 required function). but does not affect users of existing adjoint styles.
Edit: this PR also includes the fusion optimization suggested in #113 (comment).