Repository navigation
Conversation
A custom augmented forward pass (enzyme_augment) that is called with a constant pointer argument is massaged in CreateAugmentedPrimal: the fixaugment_ wrapper takes the constant argument and passes it as its own shadow, and the AugmentedReturn records the activity of the custom rule (DUP_ARG), not the activity of the call (CONSTANT). When the caller is differentiated in split mode, its reverse pass compares the activities of the call with the activities recorded in the augmented forward pass. It only expects DUP_NONEED -> DUP_ARG, so the massaged argument hit Assertion `argsInverted[i] == DIFFE_TYPE::DUP_NONEED' failed. CreatePrimalAndGradient massages the custom reverse pass (enzyme_gradient) from the CONSTANT activity of the call in the same way, and the fixgradient_ wrapper has the signature of the call. Keep the argument constant in this case. Assisted-by: Claude Code (Opus 5.5)
A constant floating-point argument of a function with a custom rule is massaged to OUT_DIFF, so the custom reverse pass also returns the adjoint of that argument. The fixgradient_ wrapper returned that struct as is, but the caller expects no adjoint for a constant argument: - in combined mode, the struct had one element too many (`getNumElements() == structidx` assertion, or an invalid return in the fixgradient_ wrapper without assertions); - in split mode, the augmented call records the argument as OUT_DIFF, which hit `subdata->constant_args[i] == DIFFE_TYPE::DUP_ARG`. Return only the adjoints of the arguments that are OUT_DIFF in the call, and keep a massaged OUT_DIFF argument constant in the split reverse pass, as for a massaged DUP_ARG pointer. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld2d8Gu8C8e7rT5maHr2UZ
Member
Author
|
Generated by Claude Code |
LLVM 16 folds the extractvalue/insertvalue pair after the fixgradient_ call, newer LLVM keeps it. Only check the call and the return. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ld2d8Gu8C8e7rT5maHr2UZ
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Valentin · project thread
What
A function with a custom rule (
enzyme_augment+enzyme_gradient) is called with a constant pointer argument, from a caller that Enzyme differentiates in split mode (augmented forward pass + reverse pass). This hits an assertion inrecursivelyHandleSubfunction:Why
CreateAugmentedPrimalmassages the custom augmented forward pass to take the constant argument. Thefixaugment_wrapper has the signature of the call (no shadow for the constant argument) and passes the primal as the shadow. But theAugmentedReturnrecords the activity that it gives to the custom rule (DUP_ARG), not the activity of the call (CONSTANT).In the reverse pass of the caller (
ReverseModeGradient), the activities of the call are compared withsubdata->constant_args. The loop only expects the differenceDUP_NONEED(reverse) vsDUP_ARG(augmented), soCONSTANTvsDUP_ARGfails the assertion.CreatePrimalAndGradientmassages the custom reverse pass from theCONSTANTactivity of the call in the same way. Itsfixgradient_wrapper has the signature of the call, and it uses the augmented data with the massaged activities internally. So the correct activity for the reverse call isCONSTANT. The fix keeps the argument constant when the call activity isCONSTANT, the augmented activity isDUP_ARG, and the callee hasenzyme_gradient. The fix does not only remove the assertion: changing the activity toDUP_ARG(as the code does forDUP_NONEED) would request a non-massaged reverse pass with a shadow argument that the caller does not have.In combined mode there is no comparison, so the bug only shows when the caller is in split mode, for example when its result is used later in a differentiated function.
Found while differentiating MITgcm
global_ocean.cs32x15with LLVM flang + full LTO.Constant floating-point arguments (second commit)
A constant floating-point argument of a custom rule is massaged to
OUT_DIFF, so the custom reverse pass also returns the adjoint of that argument. Thefixgradient_wrapper returned that struct as is, so the caller got one adjoint too many:getNumElements() == structidxfails (without assertions, the verifier reportsFunction return type does not match operand type of return inst);OUT_DIFF, sosubdata->constant_args[i] == DIFFE_TYPE::DUP_ARGfails.The
fixgradient_wrapper now returns only the adjoints of the arguments that areOUT_DIFFin the call, and the split reverse pass keeps a massagedOUT_DIFFargument constant, like a massagedDUP_ARGpointer.Test
ReverseMode/custom-split-const.ll:@scalehas a custom augmented forward pass and reverse pass with a shadow for a pointer argument.@callerpasses a constant pointer to it.@topuses the result of@caller, so@calleris differentiated in split mode. The test checks that@diffecallercalls@fixgradient_scalewith only the primal pointer. LLVM 16 build with assertions: fails with the assertion above without the fix, passes with it.ReverseMode/custom-const-float.ll(combined) andReverseMode/custom-split-const-float.ll(split): a custom rule with a constantdoubleargument; thefixgradient_wrapper must return only the adjoint of the active argument. Opaque pointers, LLVM >= 15. LLVM 18 with Enzyme assertions: both assert without the second commit and pass with it. ReverseMode, ForwardMode, TypeAnalysis and ActivityAnalysis lit results are otherwise unchanged against the base.Related
Constarguments needed by a custom derivative rule #3224 (avoid pruningConstarguments needed by a custom derivative rule) is a different problem: it removesreadnone/readonly/writeonlyand the no-write shortcut for constant arguments of resolved callees. It does not touch the activity comparison in the split reverse pass, and does not fix this assertion.Not fixed here
tapeIdx == -1) and the tape type is empty ({}), thetape->getType()->isEmptyTy()branch inrecursivelyHandleSubfunctionerases the augmented call itself, so the primal side effects of the callee are lost. The tests avoid that path. This is handled separately.🤖 Generated with Claude Code
https://claude.ai/code/session_01Ld2d8Gu8C8e7rT5maHr2UZ