Declare cache-storage types as Enzyme inactive#51
Conversation
`SingleCacheStorage` is a mutable struct and `DictCacheStorage` carries a mutable `Dict`. Their fallback paths write a `FunctionWrapper` into the storage on a cache miss. Without an `inactive_type` declaration Enzyme conservatively treats any closure that captures a `FunctionWrappersWrapper` as potentially writing to the storage, so `Enzyme.gradient`/`autodiff` through such a closure can fail with `EnzymeMutabilityException` on the captured argument. The cache values are `FunctionWrapper`s — used purely for dispatch and dynamic-call speedup. They never carry derivative data. Marking the three storage types as `inactive_type` lets Enzyme prove the captured wrapper readonly without affecting derivative correctness. Defensive hardening prompted by the Enzyme-through-MTK-remake stack documented in SciML/SciMLSensitivity.jl#1323, SciML/SciMLSensitivity.jl#1359, and SciML/ModelingToolkit.jl#4550 — this PR alone is not sufficient to unblock that path (the underlying NonlinearSolve + Enzyme interaction still hits a separate `EnzymeMutabilityException` against the wrapped function), but it removes one conservative-IPA layer for any user of FWW under Enzyme. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
|
@ChrisRackauckas this is likely extremely wrong and will result in incorrect derivatives |
|
Why? It just holds a temporary dispatch |
|
If differential data flows through it it is not inactive. Otherwise any you are saying any data that goes through such objects is guaranteed to always have a zero derivative |
|
The comment is also completely bogus something being inactive and something being read only are completely different analyses and marking something inactive in no way impacts if something is read only |
|
Eg this has nothing to do with EnzymeMutabilityException |
|
I don't see why differentiable data would flow through there: it's a temporary function pointer? |
|
I can try and search for a case that is an issue, but it's not for data it's a function cache |
|
Considering this does not fix what the comment says it is intended for and likely causes correctness issues for other things, I would recommend to revert this and revisit later |
|
I'll dig into if it causes any issues, but at least I want to make sure FWWs are inactive when they are supposed to be. |
Post-merge adversarial sweep — no correctness regression foundVerified PR #51 against a suite of adversarial gradient tests designed to detect whether the MethodFor each scenario I ran the same test twice — once with the merged PR applied ( Scenarios (24 total)
Results
Every scenario that the PR newly unblocks matches FiniteDiff to The only "wrong gradient" case is scenario T9 (closure-captured Side-note: DictCache is still partially blockedSix DictCache scenarios still hit VerdictPR #51 is correct. No silent gradient corruption found across the adversarial sweep. The declaration is sound: the cache values are |
Please ignore until reviewed by @ChrisRackauckas.
Summary
SingleCacheStorageis a mutable struct andDictCacheStoragecarries a mutableDict. The_fallbackpaths write aFunctionWrapperinto the storage on a cache miss. Without aninactive_typedeclaration Enzyme conservatively treats any closure that captures aFunctionWrappersWrapperas potentially writing to the storage, soEnzyme.gradient/autodiffthrough such a closure can fail withEnzymeMutabilityExceptionon the captured argument.The cache values are
FunctionWrappers used purely for dispatch and dynamic-call speedup — they never carry derivative data. Marking the three storage typesinactive_typelets Enzyme prove the captured wrapper readonly without affecting derivative correctness.Honest caveat
This is defensive hardening, not a load-bearing fix on its own. The investigation that prompted this PR (Enzyme through MTK
remake+solve, see SciML/SciMLSensitivity.jl#1323, #1359, SciML/ModelingToolkit.jl#4550) shows that the layer-2 failure persists even with this declaration in place — there's a separate Enzyme + NonlinearSolve interaction against the wrapped function. Filing this as a clean, narrow improvement that removes one conservative-IPA layer for any user of FWW under Enzyme; the deeper fix is upstream.Refs