Skip to content

feat(DI): add Piste backend - #1089

Draft
EvoArt wants to merge 1 commit into
JuliaDiff:mainfrom
EvoArt:add-piste
Draft

EvoArt wants to merge 1 commit into
JuliaDiff:mainfrom
EvoArt:add-piste

Conversation

@EvoArt

@EvoArt EvoArt commented Nov 9, 2026

Copy link
Copy Markdown

Add Piste.jl as a backend, in forward and reverse mode, following @gdalle's "PR welcome" on Slack.

Piste is small and written so that its derivatives compile under juliac --trim=safe. That still holds through DI. I compiled small apps that call DI with AutoPiste using JuliaC (Julia 1.12.7, --trim=safe). Every one built with zero verifier errors, ran with codegen disabled (JULIA_LOAD_CODEGEN_LIB=0), and printed the same output as the JIT:

Through DI, prepared Forward Reverse
gradient ✅ (explicit chunksize) ✅
derivative, pushforward ✅
pullback, jacobian ✅
gradient with Constant / Cache contexts ✅ ✅
jacobian ❌ see #1088

The binary for a prepared DI gradient is 2.5 MB, against 2.4 MB for the same program calling Piste directly. One app exercising all the ✅ operators above comes to 2.8 MB.

The one ❌ is not specific to Piste. BatchSizeSettings(B::Int, N::Int) computes its singlebatch/aligned type parameters from the runtime N, so any forward backend's jacobian prep has an unknown type under --trim. I've opened #1088 with a proposed fix and kept it out of this PR.

Usage

using DifferentiationInterface, Piste
import ADTypes

f(x) = sum(abs2, x)
prep = prepare_gradient(f, AutoPiste(; chunksize = 4), x)  # forward, dual numbers
prep = prepare_gradient(f, AutoPiste(; mode = ADTypes.ReverseMode()), x)  # reverse, tape

AutoPiste itself is added in SciML/ADTypes.jl#191.

What the extension implements

  • Forward (Piste.Dual{B,T}, untagged): one- and two-argument pushforward with batches of tangents. gradient for Vector{<:AbstractFloat} inputs goes straight to Piste's chunked driver with a reusable GradientWorkspace. Other inputs fall back to DI's batched pushforwards. pick_batchsize and threshold_batchsize mirror the ForwardDiff extension. With chunksize = nothing the width is picked from length(x) at prep time, which works fine in the JIT, but trimmed code should pass a fixed chunksize.
  • Reverse (Piste.TapedReal on a tape): one- and two-argument pullback, recording once per cotangent. gradient is a single pullback reusing a ReverseWorkspace. Two-argument functions are recorded through a tracked copy of y, initialized to constant zeros. This relies on a new public rev_value_and_pullback(!) in Piste 0.4.
  • Contexts: Constant and Cache in both modes. Caches are rebuilt with the active number type through recursive_similar.
  • inplace_support is InPlaceSupported().

Not supported: second order. Piste's Duals are untagged, and nesting Dual over TapedReal isn't implemented, so SECOND_ORDER scenarios are excluded from the tests. The docs table shows ❌ for those operators. DI's own fallbacks still work for first-order operators the extension doesn't define, such as forward jacobian and reverse derivative.

Tests

test/Back/Piste runs default_scenarios (including the constantified, cachified, constant-or-cachified, tuple and smaller variants) and sparse_scenarios on AutoPiste(), AutoPiste(; chunksize = 2) and AutoPiste(; mode = ReverseMode()), plus test_counterparts. Locally (Julia 1.12.7), 76,419 tests pass and none fail or error. I've added the Piste group to the CI matrix.

Running the suite turned up one Piste bug: eltype(::Type{Dual}) returned the float type, which broke broadcasting with a scalar dual. It's fixed in Piste 0.4.

Order of merging

  1. Piste 0.4.0 (adds the pullback API and the eltype fix). Registration is pending.
  2. Add AutoPiste SciML/ADTypes.jl#191, then an ADTypes release.
  3. This PR. Its compat bounds (Piste = "0.4", ADTypes = "1.25.0") assume both releases, so CI for the Piste group will fail until then. Happy to leave this as a draft until they land, and to adjust the ADTypes bound if the release gets a different number.

The docs (backends table, a short "Piste" section), the README list and the CHANGELOG are updated.

🤖 Generated with Claude Code

Forward mode builds on `Piste.Dual` (pushforward, plus Piste's chunked driver
for `gradient`); reverse mode builds on Piste's tape (pullback and gradient via
`rev_value_and_pullback`). Constant and Cache contexts are supported in both
modes. Second order is not.

Prepared first-order operators compile under `juliac --trim=safe`, except
forward-mode `jacobian` (BatchSizeSettings is type-unstable, see issue).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant