Skip to content

Type stability in ADNLPModels #352

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

Open
wants to merge 18 commits into
base: main
Choose a base branch
from

Conversation

MaxenceGollier
Copy link

#347

@amontoison @tmigot This is where I am so far.

Attached are my benchmark results...
grad_benchmarks.zip

@MaxenceGollier MaxenceGollier changed the title Type stab Type stability in ADNLPModels Jun 5, 2025
args = []
for field in fieldnames(ADNLPModels.ADModelBackend)
push!(args, if field in keys(kwargs) && typeof(kwargs[field]) <: ADBackend
kwargs[field]
elseif field in keys(kwargs) && typeof(kwargs[field]) <: DataType
elseif field in keys(kwargs) && typeof(kwargs[field]) <: Union{DataType, UnionAll}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MaxenceGollier Why do you need UnionAll?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the type ReverseDiffADGradient or ForwardDiffADGradient are now parametric types and it is no longer true that ReverseDiffADGradient <: DataType...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tmigot Do you remember why you tested typeof(kwargs[field]) <: DataType ?
The condition typeof(kwargs[field]) <: Type is always true so I don't know why we are testing that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, it doesn't make sense as is. Maybe it should be kwargs[field] <: Type to make sure the constructor call next make sense.

Copy link
Author

@MaxenceGollier MaxenceGollier Jun 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not kwargs[field] <: ADBackend ?? This seems to work locally, we are really trying to see if we can create a backend just from kwargs[field] as per the next line...
It would also make more sense with the previous check where we check if kwargs[field] is already an ADBackend by using typeof...

@amontoison
Copy link
Member

@tmigot Are you fine with the new set_adbackend ?

@tmigot
Copy link
Member

tmigot commented Jun 6, 2025

@tmigot Are you fine with the new set_adbackend ?

I will check that today

test/utils.jl Outdated
@test typeof(get_adbackend(newer_nlp).hessian_backend) <: ADNLPModels.ReverseDiffADHessian
end

function test_allocations(nlp)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you create an issue in NLPModelsTest.jl to add this in all models?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MaxenceGollier can you put the link here once it's done, thanks!

args = []
for field in fieldnames(ADNLPModels.ADModelBackend)
push!(args, if field in keys(kwargs) && typeof(kwargs[field]) <: ADBackend
kwargs[field]
elseif field in keys(kwargs) && typeof(kwargs[field]) <: DataType
elseif field in keys(kwargs) && typeof(kwargs[field]) <: Union{DataType, UnionAll}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, it doesn't make sense as is. Maybe it should be kwargs[field] <: Type to make sure the constructor call next make sense.

@tmigot
Copy link
Member

tmigot commented Jun 7, 2025

Why the tests are failing? Is it just because NLS grad! function uses other function behind and should not be tested or is there some typing issue in NLPModels?

Your benchmarks are indeed surprising @MaxenceGollier , what were the tests problems?

@tmigot
Copy link
Member

tmigot commented Jun 7, 2025

@MaxenceGollier @amontoison I moved the PR from draft to review to run the package benchmark by curiosity

@tmigot tmigot added the do not merge This is an experiment or work in progress label Jun 7, 2025
@MaxenceGollier
Copy link
Author

Why the tests are failing? Is it just because NLS grad! function uses other function behind and should not be tested or is there some typing issue in NLPModels?

That's what I have been trying to figure out, some Hessian (with JET I mean) tests also seem to fail for some reason so there might be other unstabilities hidden in this repo...

what were the tests problems?

What do you mean ? as I said, I still have to figure things out, I have a lot of urgent work to do on something else next week, I will be available to work this out the week after that...

Can you create an issue in NLPModelsTest.jl to add this in all models?

Yes good idea, I found similar issues on QuadraticModels.jl... (still need to open an issue but I don't have much time currently)

@MaxenceGollier
Copy link
Author

What do you think of this version of set_adbackend ? @amontoison @tmigot. I tried to implement all your suggestions

@MaxenceGollier
Copy link
Author

I think tests keep failing due to inherent type instabilities in ForwardDiff.jl. See in ForwardDiff/docs/advanced.md :

If your input dimension is constant across calls, you should explicitly select a chunk size rather than relying on ForwardDiff's heuristic. There are two reasons for this. The first is that ForwardDiff's heuristic depends only on the input dimension, whereas in reality the optimal chunk size will also depend on the target function. The second is that ForwardDiff's heuristic is inherently type-unstable, which can cause the entire call to be type-unstable.

The remaining type instabilities should be solved by having a closer look to those of ForwardDiff.

@amontoison amontoison requested a review from tmigot June 29, 2025 22:42
@amontoison
Copy link
Member

@tmigot
Can you take care of the review? I am still traveling this week and won't be able to have a look before 2 weeks at least.

Copy link
Member

@tmigot tmigot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot @MaxenceGollier for the great work! I made some comments, but we are going in a good direction.

Comment on lines +277 to 291
function ADNLPModel(nlp::ADNLPModel, new_adbackend::ADModelBackend)
return _set_adbackend(nlp, new_adbackend)
end

function ADNLPModel(nlp::ADNLPModel; kwargs...)
return _set_adbackend(nlp; kwargs...)
end

function ADNLSModel(nlp::ADNLSModel; kwargs...)
return _set_adbackend(nlp; kwargs...)
end

function ADNLSModel(nlp::ADNLSModel, new_adbackend::ADModelBackend)
return _set_adbackend(nlp, new_adbackend)
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, but it doesn't work with this

function ADNLPModel!(model::AbstractNLPModel; kwargs...)
.
Because, we are already building mixed models.

Do we really need the ADNLPModel(nlp::ADNLPModel; kwargs...) and ADNLSModel(nlp::ADNLSModel; kwargs...) ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No perhaps we don't.
We need to remove the test_getter_setter test then which basically test these functions (these are causing the tests to fail on my branch).

I am not sure though if it is a good idea to remove the functions. Let me know what you think. I we do remove them, I will juste remove the test_getter_setter thing and tests should pass.

@tmigot
Copy link
Member

tmigot commented Jun 30, 2025

I think tests keep failing due to inherent type instabilities in ForwardDiff.jl. See in ForwardDiff/docs/advanced.md :

If your input dimension is constant across calls, you should explicitly select a chunk size rather than relying on ForwardDiff's heuristic. There are two reasons for this. The first is that ForwardDiff's heuristic depends only on the input dimension, whereas in reality the optimal chunk size will also depend on the target function. The second is that ForwardDiff's heuristic is inherently type-unstable, which can cause the entire call to be type-unstable.

The remaining type instabilities should be solved by having a closer look to those of ForwardDiff.

I am unfortunately not too surprised by this. Thanks to your analysis we will improve our part of the code.
We should try to think of a way to still test that our code is good.

@amontoison
Copy link
Member

bump

@MaxenceGollier
Copy link
Author

I am unfortunately not too surprised by this. Thanks to your analysis we will improve our part of the code. We should try to think of a way to still test that our code is good.

I agree. Perhaps the best we can do is to call the report_opt macro but let it ignore all the issues from the ForwardDiff package.
If our code is stable with all other backends then we'd consider that our code is good enough.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants