Skip to content
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

clarify: calling prepared with different x type #633

Open
tpapp opened this issue Nov 20, 2024 · 11 comments
Open

clarify: calling prepared with different x type #633

tpapp opened this issue Nov 20, 2024 · 11 comments
Labels
documentation Improvements or additions to documentation

Comments

@tpapp
Copy link

tpapp commented Nov 20, 2024

From the docs it is not clear what happens if the user prepares with a given type of x, then calls with another type. Eg

using DifferentiationInterface, ForwardDiff, StaticArrays

const A = ones(3, 3)
f(x) = A * x
x1 = ones(3)
AD = AutoForwardDiff(; chunksize = 1)
prep = prepare_jacobian(f, AD, x1)
value_and_jacobian(f, prep, AD, ones(SVector{3}))

In practice, it seems to work in the example above and with all backend types I tried. But what does the API guarantee? Some possibilities:

  1. It works just fine, nothing to see here
  2. It works but may be suboptimal
  3. Consequences are undefined, it may error or work
@gdalle
Copy link
Member

gdalle commented Nov 20, 2024

The documentation in https://juliadiff.org/DifferentiationInterface.jl/DifferentiationInterface/stable/explanation/operators/#Reusing-preparation gives necessary conditions for preparation to be reusable. Therefore, if you step outside of these conditions, nothing at all is guaranteed. Depending on 1) the backend 2) the specific operator 3) the function to differentiate and 4) the argument types, DI may execute without error or throw an error. I can't spontaneously imagine a situation where it would silently return the wrong result but I can't definitively exclude it.
Do you want to open a PR to clarify that part of the documentation?

@tpapp
Copy link
Author

tpapp commented Nov 20, 2024

Thanks for the clairification. Yes, I will make a PR, to the effect "don't do that".

@tpapp
Copy link
Author

tpapp commented Nov 20, 2024

Thinking about this, I am wondering it it would be feasible to expose a function that tries to convert the argument to the type accepted by a prepared operation.

For backends that don't care it would be a no-op, for eg ForwardDiff it would convert to the element type obtained from the tag type.

@gdalle
Copy link
Member

gdalle commented Nov 20, 2024

I have thought about it, or more precisely I have thought about checking that the types match and throwing an error if they don't. But it would require a systematic overhaul of every single preparation type to store the function signature it was prepared with (because most backends don't care and so they don't record it natively). It's completely feasible but rather tedious

@tpapp
Copy link
Author

tpapp commented Nov 20, 2024

because most backends don't care and so they don't record it natively

Again, for those backends I would make it a no-op.

@gdalle
Copy link
Member

gdalle commented Nov 20, 2024

An automatic conversion utility would mean that we're voluntarily making efforts to support this functionality, which can never be part of the API because some backends are very strict on the types they accept and automatic conversion has its limits. So I would rather error right away than implicitly support this for some backends while it may not work with others?

@tpapp
Copy link
Author

tpapp commented Nov 20, 2024

I am thinking of a "best effort" solution, eg something along the lines of

value_and_gradient(f, prep, backend, convert_argument(prep, backend, x))

where for most backends (which take everything that makes sense) we would have

convert_argument(prep, backend, x) = x

If convert_argument cannot convert, it would error.

Generally, the reason for this is that I like to write code following the robustness principle, ie accept all kinds of inputs that make sense.

@gdalle
Copy link
Member

gdalle commented Nov 20, 2024

On the other hand, you could argue that these implicit conversions lead to hidden performance pitfalls, which may be a no-go for performance users. As far as AD systems are concerned, one could say that Zygote follows this robustness principle while Enzyme and Mooncake are much more strict in terms of types, and also much more performant. For those backends, adding an implicit conversion if a user happens to pass the wrong array type seems worse than just letting it error?

@tpapp
Copy link
Author

tpapp commented Nov 20, 2024

adding an implicit conversion if a user happens to pass the wrong array type seems worse than just letting it error?

Presicely, I fully agree. Which is why I was not proposing an implicit conversion API, but an explicit one: the caller opts in by using convert_argument.

@gdalle
Copy link
Member

gdalle commented Nov 20, 2024

Oh right, I see now, sorry. How would this handle context arguments? And tangents?

@tpapp
Copy link
Author

tpapp commented Nov 21, 2024

I would have to look into the implementation to answer that, I am not yet familiar with all the details.

@gdalle gdalle added the documentation Improvements or additions to documentation label Nov 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

2 participants