Repository navigation
Fuse adaptive GPUTsit5 stage arithmetic - #545
Draft
ChrisRackauckas-Claude wants to merge 10 commits into
Draft
ChrisRackauckas-Claude wants to merge 10 commits into
ChrisRackauckas-Claude wants to merge 10 commits into
Conversation
The accept branch estimated the remaining interval as `tf - t - dt`, which is evaluated as `(tf - t) - dt` and can differ from the gap left after the rounded update `t + dt`. In Float32 this can leave `t` one ulp short of `tf` with a proposed step of half an ulp; `t + dt` then rounds back to `t`, the absolute `1e-14` snap never fires, and the kernel loops forever. Computing the gap as `tf - (t + dt)` makes the next step land exactly on `tf`. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Agent-Harness: Claude Code 2.1.280 Agent-Model: claude-opus-5-5[1m] Agent-Session: https://claude.ai/code/session_01SJfy3ez7u4CqvAerKrT5np
Apply `@muladd` to the adaptive Tsit5 stage combinations and embedded error estimate, matching the fixed-step stepper. The macro is scoped to that block so the step-size controller and endpoint logic keep their existing rounding, and the function keeps its `@inline` annotation: without it the mutable integrator is heap-allocated in device code (OpenCL `gpu_malloc` fails) and Enzyme on Julia 1.12 returns a wrong adaptive Float64 gradient. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Agent-Harness: Claude Code 2.1.280 Agent-Model: claude-opus-5-5[1m] Agent-Session: https://claude.ai/code/session_01SJfy3ez7u4CqvAerKrT5np
Bring branch up to date with SciML/DiffEqGPU.jl master. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Agent-Harness: Cursor CLI Agent-Model: Composer Agent-Session: /home/crackauc/.cursor/projects/home-crackauc-sandbox-fleet-master-jobs-nw-diffeqgpu-544/agent-transcripts/25081a8c-1ae5-4f93-b431-7c031e14aebf
The stall fix only needs the `dtnew` clamp `tf - (t + dt)`. Rewriting the snap test the same way made a step whose length already covers the interval skip the snap whenever `t + dt` rounds one ulp short of `tf`, so the solver took an extra ulp-sized step and shifted the final state by `eps(tf)`. Restore `(tf - t - dt) < 1e-14` in all eight steppers and add a state check for `u' = 1` over a span where `t0 + (tf - t0)` rounds below `tf`. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Agent-Harness: Claude Code 2.1.280 Agent-Model: claude-opus-5-5[1m] Agent-Session: https://claude.ai/code/session_01SJfy3ez7u4CqvAerKrT5np
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Agent-Harness: Claude Code 2.1.280 Agent-Model: claude-opus-5-5[1m] Agent-Session: https://claude.ai/code/session_01SJfy3ez7u4CqvAerKrT5np
Clamping `dtnew` against `tf - (t + dt)` before the tstops adjustment gave `dtnew = 0` whenever `t + dt` rounded to `tf` but the step then stopped at an earlier tstop, so the next step threw `dt<dtmin`. Keep the controller's proposal and clamp it against `tf - integ.t` once the accepted time, whether `tf`, a tstop, or `t + dt`, is known. This still removes the Float32 stall and keeps the length-based endpoint snap. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Agent-Harness: Claude Code 2.1.280 Agent-Model: claude-opus-5-5[1m] Agent-Session: https://claude.ai/code/session_01SJfy3ez7u4CqvAerKrT5np
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Agent-Harness: Claude Code 2.1.280 Agent-Model: claude-opus-5-5[1m] Agent-Session: https://claude.ai/code/session_01SJfy3ez7u4CqvAerKrT5np
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Agent-Harness: Claude Code 2.1.280 Agent-Model: claude-opus-5-5[1m] Agent-Session: https://claude.ai/code/session_01SJfy3ez7u4CqvAerKrT5np
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Agent-Harness: Claude Code 2.1.280 Agent-Model: claude-opus-5-5[1m] Agent-Session: https://claude.ai/code/session_01SJfy3ez7u4CqvAerKrT5np
Member
Author
|
Taking this — Devin CLI 3000.11.3, model swe-2-high (local job 545-devin on amdci2.julia.csail.mit.edu). |
…-fma Brings in SciML#563 (docs missing_docs and CUDA scalar-indexing test fix) and SciML#564, which should clear the Documentation failure and the ensemblegpuarray_scalar_batch.jl failures seen on CUDA/AMDGPU CI. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Devin <noreply@cognition.ai> Agent-Harness: Devin CLI 3000.11.3 Agent-Model: swe-2-high Agent-Session: local session, transcript at /home/crackauc/sandbox/agent-jobs/DiffEqGPU.jl/jobs/545-devin/log.txt on amdci2.julia.csail.mit.edu
Member
|
We should just |
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.
Ignore this draft until reviewed by @ChrisRackauckas.
#544 (the endpoint-stall fix this PR used to be stacked on) is merged, and this branch now merges master
bacef856(which includes #563, fixing the:missing_docsDocumentation failure and theensemblegpuarray_scalar_batch.jlfailures that were red on the previous head). The diff against master is only commit97c0e86(Fuse adaptive GPUTsit5 stage arithmetic): the adaptive Tsit5 stage block andAGENTS.md.What changed and why
This applies
@muladdto the adaptiveGPUTsit5/GPUTsit5IControllerstage combinations and embedded error estimate, which the fixed-step stepper already does (#529). The macro is scoped to that block, so the controller and endpoint logic keep their current rounding, and the function keeps@inline. It also adds anAGENTS.mdnote explaining why@inlinemust stay on kernel steppers.Root causes of the three failures that made #529 drop adaptive FMA
I reproduced each one on current master before changing anything.
8d23077033) on Julia 1.10.12.SIGUSR1on the stuck process showed it spinning insidestep!, and tracing the steps showedt = 0.099999994f0stuck withdt = dtnew = 3.7252903f-9forever. Thedtnewclamptf - t - dtdoes not match the roundedt + dt, so a sub-half-ulp step never advancest. FMA only moved that test case onto a stall; master stalls on other inputs without FMA (31–53 of 1800 Float32 configurations per adaptive algorithm). Fix Float32 endpoint stall in adaptive GPU kernel steppers #544 (merged) fixes this for all steppers by clampingdtnewfrom the accepted time while keeping the length-based snap. Follow-ups Respect pending tstops in the adaptive next-step clamp #556 (merged) and Interpolate to tstops by step length and handle sub-dtmin stop spacing #561 refine that clamp around tstops; they are independent of this PR.@inline, not by FMA:function step!without@muladdreproduces it too. Whenstep!is not inlined, the mutableGPUATsit5Integratorescapes into a call, so Julia heap-allocates it. The pocl device IR then containscall i64 @gpu_malloc(...)→gpu_report_oom→gpu_signal_exception. The OpenCL runtime's malloc returns null, so the kernel exits before writing anything. With@inlinekept, the device IR has 0malloc/gc_pool_alloccalls.@inline, not by FMA; it reproduces without@muladd. It happens on Julia 1.12 only (LTS passes). The uninlined build givesdp[1] = 1.2214058645…(max rel. error 3.8e-6 > 1e-7), while the primal loss is identical. Central finite differences of the same primal give1.22140275843(h = 1e-5), which matches the inlined Enzyme result1.22140275841(rel. error 4.4e-10). So the uninlined gradient is wrong, which points to Enzyme mishandling the heap-allocated mutable integrator passed to a call. The bound itself is appropriate. With@inlinekept and FMA applied, the Float64 adaptive result is4.43e-10max relative error, the same as master.Why not other forms
@inline: causes failures 2 and 3 above.@muladdon the whole function: gives the same contract count, since the controller has only onea*b+cpattern (the tstops check). Scoping the macro keeps controller and endpoint arithmetic byte-for-byte unchanged, which rules out any interaction with step acceptance attf.FMA / contract counts:
code_llvm/code_nativeofstep!, Lorenz, CPU backendcontract/ x86vfmaddPTX from
GPUCompiler.code_nativeforstep!(sm_80, PTX ISA 7.8; this is compiled PTX, nothing was run on a GPU). Cells showfma.rn/ unfusedadd|sub|mul:fma_contraction=true)So on the current CUDA stack (CUDACore ≥ 6.4 requires GPUCompiler ≥ 2.7, whose NVPTX back-end contracts by default, matching nvcc
--fmad=true), master already gets most of the FMA in PTX, and this PR adds 12 more in Float32 and none in Float64. The larger effect is on GPUCompiler 1.x, and on backends that honour only explicitcontractflags (OpenCL/SPIR-V, x86 CPU). Therefore FMA does not explain the reported GRADSOLVE Float64 gap on a current CUDA.jl stack.CPU timing (CPU only, not GPU)
julia +1.12 benchmark/tsit5.jl, KernelAbstractionsCPU(), 4096 Lorenz trajectories, reltol 1e-6, abstol 1e-9. Minimum of 20 samples, two alternating rounds, identical manifests. Shared host with load average about 31–35.No meaningful CPU change: the adaptive Lorenz step is not bound by stage FLOPs on x86. Final states agree to rounding (Float64 differs in the 15th digit; Float32 in the 6th–7th).
Verification
On head
2dd2fd4(the FMA commit merged with masterbacef856), Julia 1.12.7,GROUP=<name> julia +1.12 --project -e 'using Pkg; Pkg.test()':Earlier, on head
baf4560(the FMA commit merged with master5e8faee), from separate clean worktrees, with a freshPkg.testper group:Also: an OpenCL adaptive Lorenz probe (8 trajectories, Float32) completes on LTS and 1.12, with max |OpenCL − CPU| = 2.3e-5 / 1.8e-5 at t = 1. Runic
--check,typos, andgit diff --checkexit 0. No assertion or tolerance was changed.CI on head
2dd2fd4— every red check is pre-existing or upstream, none PR-causedCUDA Tests (Julia 1)fails attest/enzyme/cuda_records.jl:9(Allocate recordsEnzyme error), identical to master'sbacef856run — CUDA CI: test/enzyme/cuda_records.jl fails on released CUDACore (Enzyme undef-shadow fill! for non-numeric eltypes) #546 (upstream CUDACore Enzyme-rule bug). The previous head's CUDA failure (ensemblegpuarray_scalar_batch.jl:102, "Scalar indexing is disallowed") is gone — Fix #555 fallout: docs missing_docs and CUDA scalar-indexing test #563's fix is in, that testset now runs55 55on CUDA.Tsit5 accuracyruns144 144on CUDA.Tests - Enzyme (Julia 1)andDocumentationboth fail on Enzyme v0.13.209 (released 2026-10-01, after master's last green run resolved v0.13.205):MethodError: no method matching augmented_primal(::RevConfigWidth{..., (false, true, true), ...}, ::Const{typeof(DiffEqGPU._kernel_transfer)}, ...)— Enzyme now passes the backend asDuplicated{CPU}where the custom rule inext/EnzymeExt.jldeclaresbackend::Const. Verified locally on this head: the fulltest/enzymesuite (imports, transfers, gradients incl. Float64 adaptive, Lorenz, tspan, u0) passes end-to-end on Enzyme v0.13.208 and v0.13.205, and errors on v0.13.209. The failing call is in_kernel_transferon the fixed-step Float32 path —ext/EnzymeExt.jland the test files are identical on master, so any branch resolving Enzyme 0.13.209 hits this. Upstream regression, not caused by this diff.gpu_kernel_de/conversions.jl:82). Metal v1 and v1.10 pass — the adaptive FMA path runs clean on real Metal hardware.ensemblegpuarray_scalar_batch.jl:102, the same Fix EnsembleGPUArray scalar parameter packing for batch_size=1 #555 regression fixed by the merged Fix #555 fallout: docs missing_docs and CUDA scalar-indexing test #563. Buildkite logs are not readable from the verification host, so AMDGPU on this head is unverified either way.Not verified / findings
prejobs and downstream packages.Investigation of the earlier attempt: #529 and https://github.com/ChrisRackauckas/InternalJunk/pull/99.
🤖 Generated with Claude Code (model: claude-opus-5-5[1m])
https://claude.ai/code/session_01SJfy3ez7u4CqvAerKrT5np
Body updated for head
2dd2fd4by Devin CLI 3000.11.3, model swe-2-high; local session transcript at /home/crackauc/sandbox/agent-jobs/DiffEqGPU.jl/jobs/545-devin/log.txt on amdci2.julia.csail.mit.eduRisk assessment
GPUTsit5kernel stepper (GPUAT5I, which covers the PI and I controllers) on every backend (CUDA, AMDGPU, Metal, oneAPI, OpenCL, CPU/KA). Only the stage and error-estimate arithmetic now uses@muladd. Results and accepted step sequences can change at the reltol level. No public API or signature changes,@inlinestays, and it doesn't use internals of any dependency (@muladdis already used on the fixed-stepstep!in the same file). The diff doesn't touch any test and doesn't loosen any tolerance.baf4560has 6 failing checks. All 6 fail the same way on the merge base5e8faee, and the ones that ran on the latest master46c4a76fail the same way there too. None is a regression:test/ensemblegpuarray_scalar_batch.jl:102("Scalar indexing is disallowed", a test file added by Fix EnsembleGPUArray scalar parameter packing for batch_size=1 #555). Master46c4a76fails with the identical error (54 passed / 1 errored). Pre-existing.:missing_docsforDiffEqGPU.ensemble_paramandDiffEqGPU.pack_ordinary_parameters, the same failure as master46c4a76and5e8faee. Pre-existing.ensemblegpuarray_scalar_batch.jl:102as master build 1734 (5e8faee). They passed ond6be47f(before Fix EnsembleGPUArray scalar parameter packing for batch_size=1 #555), so Fix EnsembleGPUArray scalar parameter packing for batch_size=1 #555 introduced the failure, not this PR. Pre-existing.gpu_kernel_de/conversions.jl:82("Initialization preprocessing runs once"), the same as master builds 1734 and 1732. Pre-existing.Tsit5 accuracytestset runs before those failures and passed on head for CUDA (144/144), AMDGPU (144/144), oneAPI (84/84) and Metal (84/84). CPU, OpenCL, Enzyme, JLArrays, QA, Downgrade, Runic and typos all pass.@muladdwrap of stage arithmetic that keeps@inlineand controller rounding unchanged, and every red check fails identically on master.test/enzyme/cuda_records.jl:9failure and a downgrade-lane failure. On this head the downgrade lane passes and the CUDA failure isensemblegpuarray_scalar_batch.jl:102. It doesn't mention the Documentation, AMDGPU or oneAPI failures.AGENTS.mdpolicy note.🤖 Risk assessment posted by an AI agent (fleet master) — harness: Devin CLI (Mac) / fusion-claude-opus-5-5-high-sidekick-swe-2-medium; dispatched by Claude Code head, model claude-opus-5-5[1m]
Conversation: local Claude Code session 3cd6500a-1f81-46b5-ac0b-c466e15b6a53 on Chris's Mac (session ID, no URL)