Conversation
Benchmark Results
Benchmark PlotsA plot of the benchmark results has been uploaded as an artifact at https://github.com/EnzymeAD/Enzyme.jl/actions/runs/36857576061/artifacts/11160036901. |
1b25925 to
3df4ca1
Compare
|
I hit a version of this from a slightly different angle, and it seems one more change would help here: Trigger: On Julia 1.13.1, a struct with inline roots is passed as a pointer to its data half plus its roots. If a function with a custom rule only uses the pointer fields in its primal body, using Enzyme, LinearAlgebra
import Enzyme.EnzymeRules
struct TM; data::Vector{Float64}; n::Int; end
f1(A::TM) = norm(A.data)
function EnzymeRules.forward(config::EnzymeRules.FwdConfigWidth{1}, ::Const{typeof(f1)}, ::Type{RT}, A::Annotation{TM}) where {RT}
@show A.val.n A.dval.n # 7 expected
n = f1(A.val)
return Duplicated(n, dot(A.val.data, A.dval.data) / n)
end
g1(A) = f1(A)
autodiff(Forward, g1, Duplicated(TM(randn(3), 7), TM(ones(3), 7)))On 1.13.1 this prints garbage for both ns. It's fine on 1.12.7, where the store survives even though the parameter is also inferred Routing that call through the wrapper fixes it: function middle_optimize!(second_stage = false)
...
- API.EnzymeDetectReadonlyOrThrow(mod)
+ detect_readonly_or_throw!(mod)
|
3df4ca1 to
5c1be07
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## vc/custom-rule-regression-tests #3610 +/- ##
===================================================================
- Coverage 80.03% 76.53% -3.51%
===================================================================
Files 71 71
Lines 24172 24180 +8
===================================================================
- Hits 19347 18505 -842
- Misses 4825 5675 +850 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
The better fix is not not codegen these functions to begin with / or if that is not possible to drop the bodies of the function since we are going to replace them with custom rules later. HT Billy |
0691bfc to
0f798f6
Compare
wsmoses
left a comment
There was a problem hiding this comment.
I think it may still be worthwhile (and possibly necessary for sret bits) here to run the attribute detection first [and keep those attributes, minus changing readnone -> readonly, and (non sret/returnroots) writeonly -> nothing
…lia version The test's 1.13-only branch encoded that attribute inference proved the data half of the split convention readnone from the body of test_trace!, so the call worked without runtime activity there. test_trace! has a custom rule, which may read the whole argument, and since EnzymeAD/Enzyme#3352 the body of such a function no longer yields that inference on any version, so 1.13 now throws EnzymeRuntimeActivityError like the others. This is the expectation #3610 states as well. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G84mmXoaait2MBqCH5BKNb
…lia version The test's 1.13-only branch encoded that attribute inference proved the data half of the split convention readnone from the body of test_trace!, so the call worked without runtime activity there. test_trace! has a custom rule, which may read the whole argument, and since EnzymeAD/Enzyme#3352 the body of such a function no longer yields that inference on any version, so 1.13 now throws EnzymeRuntimeActivityError like the others. This is the expectation #3610 states as well. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G84mmXoaait2MBqCH5BKNb
Both failed with Enzyme_jll 0.0.299 (a segfault for #3570, a dropped store for the aggregate with inline roots) and pass with 0.0.300, which includes EnzymeAD/Enzyme#3352. Taken from #3610. Assisted-by: Claude Code (Opus 5.5)
Enzyme replaces the calls it differentiates to a function with a custom rule with the rule, so after enzyme! the function and its callees are often unused. They kept external linkage until after post_optimize!, so on non-native targets the post-AD pipeline optimized them, only for them to be dropped later. Make every function except the entry points private right after AD and run GlobalDCE, so they are deleted before any optimization. Calls Enzyme kept, e.g. from a constant callee, still reach the body. Assisted-by: Claude Code (Opus 5.5)
7d61ad2 to
042b6bd
Compare
…lia version (#3732) The test's 1.13-only branch encoded that attribute inference proved the data half of the split convention readnone from the body of test_trace!, so the call worked without runtime activity there. test_trace! has a custom rule, which may read the whole argument, and since EnzymeAD/Enzyme#3352 the body of such a function no longer yields that inference on any version, so 1.13 now throws EnzymeRuntimeActivityError like the others. This is the expectation #3610 states as well. Claude-Session: https://claude.ai/code/session_01G84mmXoaait2MBqCH5BKNb Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Both failed with Enzyme_jll 0.0.299 (a segfault for #3570, a dropped store for the aggregate with inline roots) and pass with 0.0.300, which includes EnzymeAD/Enzyme#3352. Taken from #3610. Assisted-by: Claude Code (Opus 5.5)
ccfe25a to
5923d10
Compare
Stacked on #3736 → #3732 → #3734 (Enzyme_jll 0.0.300).
Originally this PR addressed #3570 by turning functions with a custom rule into declarations during AD. EnzymeAD/Enzyme#3352 and #3353 (in Enzyme_jll 0.0.300) fix that in Enzyme itself: nothing infers
readnoneorwriteonlyon the parameters of a function with a custom rule any more. The regression tests from this PR are now in #3736, which passes without any change here. What is left is the compile-time part of the original idea, as suggested in #3610 (comment).Change
Enzyme replaces the calls it differentiates to a function with a custom rule with the rule, so after
enzyme!the function and everything it calls are often unused. They kept external linkage until the end ofcompile_unhooked_impl, so nothing could delete them before then. Right after AD, this makes every function except the entry points private and runsGlobalDCEPass. Calls Enzyme kept, e.g. from a constant callee, still reach the body.The function bodies are not dropped before AD. Enzyme keeps calling the primal for calls where everything is
Const: rules imported from ChainRules assert a non-Constreturn, and the lock rules from #3513 run only for active locks. So a body is still needed whenever such a call survives.Effect
A function with a custom rule whose body calls 400 distinct
@noinlinefunctions (the shape from #3455), Julia 1.13:post_optimize!before → afterautodiff, forward and reverse)autodiff_deferredin a kernel, 100 callees)_thunkrunspost_optimize!. The change only makes that happen earlier.post_optimize!runs insidecompile_unhooked_impl, before that cleanup, and optimized all of them. Thatpost_optimize!went from 0.029 s to 0.001 s; with trivial bodies the total compile time (~20 s) does not change measurably.Testing
advanced.jl"Copy Broadcast arg" (IllegalTypeAnalysisException). It fails the same way without this change in the same environment, and does not fail in Update Enzyme_jll to 0.0.300 #3734's CI.test/cuda.jl, Julia 1.13.1 (Quadro RTX 4000): 61/61 pass.🤖 Generated with Claude Code