-
Notifications
You must be signed in to change notification settings - Fork 62
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
Take is_inplaceable_destination
seriously
#577
Conversation
Codecov Report
@@ Coverage Diff @@
## main #577 +/- ##
==========================================
+ Coverage 93.15% 93.18% +0.03%
==========================================
Files 15 15
Lines 891 895 +4
==========================================
+ Hits 830 834 +4
Misses 61 61
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. |
src/accumulation.jl
Outdated
is_inplaceable_destination(::DenseArray)= true | ||
is_inplaceable_destination(::DenseArray{<:Integer}) = false |
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.
The intention was to allow for CuArray
s. But perhaps this is unsafe, as:
julia> Base.Experimental.Const([1,2]) isa DenseArray
true
julia> subtypes(DenseArray)
13-element Vector{Any}:
Array
Base.CodeUnits
Base.Experimental.Const
CUDA.Const
CUDA.CuDeviceArray
GPUArrays.AbstractDeviceArray
GPUArraysCore.AbstractGPUArray
Metal.Const
Metal.MtlLargerDeviceArray
MtlDeviceArray
Random.UnsafeView
SharedArrays.SharedArray
SparseArrays.CHOLMOD.Dense
is_inplaceable_destination(::DenseArray)= true | |
is_inplaceable_destination(::DenseArray{<:Integer}) = false | |
is_inplaceable_destination(::Array)= true | |
is_inplaceable_destination(:: Array{<:Integer}) = false |
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.
Or perhaps:
is_inplaceable_destination(::DenseArray)= true | |
is_inplaceable_destination(::DenseArray{<:Integer}) = false | |
is_inplaceable_destination(x::DenseArray)= ismutable(x) | |
is_inplaceable_destination(x::DenseArray{<:Integer}) = false |
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 think to be truly safe we much only accept Array
Since anyone can subtype DenseArray
, and overload setindex
however they want
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.
A pity to miss CuArrays. (Although previous version also marks them non-writable.) But agree that if the goal is always to fail on the safe side, then is has to be Array.
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.
As far as I understand this PR will make some things work, and other things silently slower, because it will make them not inplaceable. Do we know how big the performance drop will be, and how widespread it will be? I imagine it should be fine as long as we cover most commonly used types here?
Co-authored-by: Miha Zgubic <mzgubic@users.noreply.github.com>
The cost is like I think its real intended use is for InplaceabeThunk which, as far as I know, is not in use at all, since The goal is more to make this function safe to use in code which may meet unknown types. For this it needs to fail to the side which doesn't error, that's much more important than guessing correctly more often. Some discussion of such use here: FluxML/Optimisers.jl#99 Instead of changing this function, it could be deprecated & replaced. |
There is also a question of whether it treats eltypes and structure correctly. The present one is documented to allow The PR makes it return false on integers arrays, as you can't safely write a gradient into such a thing. But it won't stop you trying to write a complex array into a real one. |
You know if you made this PR from a branch of this repo, rather than from your fork, you could just click accept on all the nagging from the autoformatter? |
The intended use is for in I think this will have no performance impacts on anything real |
src/accumulation.jl
Outdated
alpha = is_inplaceable_destination(parent(x)) | ||
beta = x.indices isa Tuple{Vararg{ Union{Integer, Base.Slice, UnitRange}}} | ||
return alpha && beta |
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.
Is it clearer to do
alpha = is_inplaceable_destination(parent(x)) | |
beta = x.indices isa Tuple{Vararg{ Union{Integer, Base.Slice, UnitRange}}} | |
return alpha && beta | |
is_inplaceable_destination(parent(x)) || return falsse | |
return x.indices isa Tuple{Vararg{Union{Integer,Base.Slice,UnitRange}}} |
I will trust your judgement
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 had a few variants. Maybe I don't like that this seems to imply you ought to check one before the other, but in fact it's just dealing with line length.
@eval is_inplaceable_destination(x::Base.$T) = is_inplaceable_destination(parent(x)) | ||
end | ||
for T in [:Adjoint, :Transpose, :Diagonal, :UpperTriangular, :LowerTriangular] | ||
@eval is_inplaceable_destination(x::LinearAlgebra.$T) = is_inplaceable_destination(parent(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.
a bit sad that this won't catch NamedDimsArrays or KeyedArrays for free anymore.
But those need to overload ProjectTo anyway so its fine to make them do this
That wrapper types in Base is most of what we need to deal with
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.
Besides having something you have to opt into, the other approach I can think of would be to ask type inference. But... this doesn't seem like the package's style:
julia> Core.Compiler._return_type(setindex!, typeof(([1,2,3.0], 4.0, 2)))
Vector{Float64} (alias for Array{Float64, 1})
julia> Core.Compiler._return_type(setindex!, typeof((SA[1,2,3.0], 4.0, 2)))
Union{}
julia> Core.Compiler._return_type(setindex!, typeof((1:3.0, 4.0, 2)))
Union{}
julia> which(setindex!, typeof((SA[1,2,3.0], 4.0, 2))) # their own error
setindex!(a::StaticArray, value, i::Int64)
@ StaticArrays ~/.julia/packages/StaticArrays/6QFsp/src/indexing.jl:3
julia> which(setindex!, typeof((1:3.0, 4.0, 2))) # not useful
setindex!(A::AbstractArray, v, I...)
@ Base abstractarray.jl:1374
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.
Sensible reallly.
Only handle that which we know about.
Address remaining comments and then merge when happy
Co-authored-by: Frames Catherine White <oxinabox@ucc.asn.au>
# TODO: uncomment this once https://github.com/JuliaLang/julia/issues/35516 | ||
if VERSION >= v"1.8-" | ||
@test haskey(Tangent{Tuple{Float64}}(2.0), 1) == true | ||
else | ||
@test_broken haskey(Tangent{Tuple{Float64}}(2.0), 1) == true | ||
end | ||
# if VERSION >= v"1.8-" | ||
# @test haskey(Tangent{Tuple{Float64}}(2.0), 1) == true | ||
# else | ||
# @test_broken haskey(Tangent{Tuple{Float64}}(2.0), 1) == true | ||
# end | ||
@test_broken hasproperty(Tangent{Tuple{Float64}}(2.0), 2) == false |
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.
This is unrelated, but was needed to make tests pass on 1.9. I don't know why it says "uncomment" but now that things are commented, it may make sense.
@test_throws MethodError reverse(Tangent{Foo}(; x=1.0, y=2.0)) | ||
if VERSION < v"1.9-" | ||
# can't reverse a named tuple or a dict | ||
@test_throws MethodError reverse(Tangent{Foo}(; x=1.0, y=2.0)) |
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.
Likewise needed for 1.9
This tests that you can't reverse a Tangent, but do we care that you can't? We know that it's meaningless, like the order of fields in a struct. But if it used to be an error and now works, why is that a failure?
To be useful, this function should return true only when it is certain that the array can be written into. A false
false
costs one allocation, but a falsetrue
will lead to an error. At present, it guesses wrong for wrappers like ReadOnlyArrays.Commit c3bf4b0 is just fixing tests to pass on 1.9, could be pulled out.