Repository navigation
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/39576540264/artifacts/12341814530. |
|
CI status on 06fe8b6:
Generated by Claude Code |
|
On b69e3b9 the The new tests assume Enzyme runs the primal when every argument is The fix depends on a decision about Enzyme's semantics, which is being asked of the PR author:
Integration Flux 1.11 is the same baseline failure as on main. Generated by Claude Code |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## vc/native-inactive-callees #3783 +/- ##
==============================================================
+ Coverage 79.92% 80.02% +0.10%
==============================================================
Files 72 72
Lines 25335 25451 +116
==============================================================
+ Hits 20248 20367 +119
+ Misses 5087 5084 -3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@claude rebase and squash |
1325ee9 to
31e181c
Compare
|
Rebased onto Generated by Claude Code |
6340be4 to
4c9a829
Compare
|
On 6340be4, Downgrade - Julia 1.10 failed in "Native derivative-free callees" ( I rebased this PR onto 1762496 (now 4c9a829). The diff is unchanged apart from the base, and the guard is back in place. The Flux 1.11 failure is the same main-branch failure noted above. Generated by Claude Code |
2cc5365 to
fffac06
Compare
|
Integration Tests - Lux - Julia 1.13 fails on fffac06 in This PR doesn't touch Generated by Claude Code |
When no custom rule method applies to a call whose function, arguments, keyword tuple and return are all Const, `has_rule` now returns false and Enzyme runs the primal as it would for a function without a rule, instead of throwing a MethodError. Calls with a non-constant argument or return and no matching rule still throw. On Julia 1.12 and 1.13 such primals are called through their natively compiled entry point, like derivative-free callees: `native_entry` accepts ruled callees, and `bind_native_callees!` keeps the emitted stub as a thin forwarder to the native entry (`bind_ruled_stub!`) so the rule handlers still see the emitted signature. `materialize_native_invokes!` handles ruled callees for nested AD. Julia 1.10/1.11 keep emitting the body. The function-attribute logic of `prepare_llvm` moves into `mark_compiled!`. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Qv3fN6RpocyrsdKXh7W26
…d it
The rule handlers build a rule's annotations from the signature of the
MethodInstance a specsig call calls. For a callee with `@nospecialize`
arguments (finalizer, deepcopy) compileable_specialization swaps the
inferred MethodInstance for its compilation signature, whose arguments are
`Any`, and now that native_uses_specsig follows uses_specsig for up to three
parameters, such a callee got a ruled stub: its rule was called with
`Duplicated{Any}` or `BatchDuplicated{Any, N}`. Upgrade a ruled callee to
its native CodeInstance only when it is the MethodInstance inference
resolved the call to; otherwise the jl_invoke is handled with the run-time
types, as before.
A keyword call of a function with a rule is resolved natively as ruled on
Julia 1.12 and later, and never derivative-free: adjust the test.
Assisted-by: Claude Code (Opus 5.5)
fffac06 to
f4dc7b9
Compare
|
Buildkite Generated by Claude Code |
Before: Enzyme never ran the primal of a function with a (non-easy) custom rule. When no rule method matched,
fwd_miandaug_fwd_misubstitutedcustom_rule_method_error, sohas_rulestayed true. Every call went to the rule handlers, and a call with only constant arguments threw aMethodErrorat runtime. This happened for example with rules written for active arguments only, as ChainRules imports make them. Separately, on every version Enzyme inferred, emitted, optimized and code generated the body of such a function in every thunk.After (user-visible semantics change): when no rule method applies to a constant call, Enzyme now runs the primal as is, as it would for a function without a rule, instead of throwing. A constant call is one where the function, every argument, the keyword tuple and the return are all
Constin the activities the rule lookup uses. That primal is called through its natively compiled entry point, like a derivative-free callee, and its body is no longer emitted into the thunk. A rule that does apply to a constant call still runs. A call with a non-constant argument or return and no matching rule still throws aMethodError. A return through an sret buffer that activity analysis gives a shadow (for example a struct holding aVector{Float64}) is not constant, so such a call still needs a rule.How (semantics): in
src/rules/customrules.jl,has_rulereturns false when the lookup fell back tocustom_rule_method_errorandno_rule_for_constant_callfinds every activity fromenzyme_custom_setup_args/enzyme_custom_setup_retto beConst. The four handlers (enzyme_custom_fwd,_augfwd,_rev,_diffuse) only consulthas_rulefor a constant instruction and value. They then use Enzyme's default handling of a constant call, so no shadow, tape or reverse code is produced. This is the same path as an easy rule on a constant call.How (native calls):
native_entrynow also accepts callees that have a forward or reverse rule (including keyword calls). For these it skips the guaranteed-const and "Julia would inline" checks, sinceNoInlineCallInfoalready prevents inlining, but keeps the specsig and specptr checks.EnzymeContext.native_calleesrecords the kind.bind_native_callees!does not replace the stub Julia's codegen emits for such a callee. Instead it keeps the stub as a thin forwarder to a declaration bound to the native entry (bind_ruled_stub!). The reason is that the rule handlers index call operands by the emitted signature, which has nopgcstackparameter. The stub is what runs whenhas_ruleis false. With return roots, the native entry returns into a local buffer and roots array, and the stub splits the value into its own sret and roots, becauseenzyme-fixup-juliarejects an sret with GC roots that is passed on to a call. The stub gets the attributesprepare_llvmgives an emitted function. That logic is moved out intomark_compiled!with no behavior change.is_native_edgedoes not treat a ruled callee as inactive. Failing to bind a ruled callee throws instead of silently leaving ajl_invokestub, which would drop the rule.For nested AD,
materialize_native_invokes!(which now takes the method table) leaves the declaration as a declaration when the outer mode has a rule for it, and otherwise gives it a body, for example for forward over reverse of a function that has only a reverse rule. It also drops staleenzyme_math=enzyme_custommarks from a linked inner derivative when the outer mode has no rule for that function.Julia 1.10/1.11 keep emitting the body of a ruled callee, but the
has_rulechange applies there too. GPU, non-host and package-image compilation are unchanged, because thehost_interpanduse_native_calleesgates still apply.Stacked on #3768.
Not included: the planned 1.10/1.11 follow-up (in
handle_compiled, swap the emitted body of a ruled function for a native declaration).Also changed: on 1.12 and 1.13, inside a natively called ruled primal,
within_autodiff()returnsfalseandignore_derivativesis the identity. When the body is emitted (1.10/1.11),within_autodiff()is folded totrue.Performance
TensorKit enzyme tests (TensorKit#464,
ksh/enz_fact), Julia 1.13, wall time of each test file in its own process (CI=true). Each test ran with this PR and with #3768 (9d97ea0) at the same time, one test after another, so both see the same machine load:The full TensorKit enzyme suite (21 files) passes on 1.13 and on 1.10. The exception is
traceon 1.10, which fails identically on #3768 and main. That one comes from TensorKit: QuantumKitHub/TensorKit.jl@832ceb7 removed its Enzyme rule fortrace_permute!, and Enzyme's raw-storage adjoint differs from TensorKit's dim-weighted one bydim(c_A)/dim(c_C). 1.10 is not compared here, since this PR still emits ruled callees there.🤖 Generated with Claude Code