Skip to content

Complete rewrite of forward mode - #1151

Closed
yebai wants to merge 46 commits into
mainfrom
step-8-ndual-routing
Closed

Complete rewrite of forward mode#1151
yebai wants to merge 46 commits into
mainfrom
step-8-ndual-routing

Conversation

@yebai

@yebai yebai commented Apr 20, 2026

Copy link
Copy Markdown
Member

Complete rewrite of forward mode. The new implementation is more Cassette-like: it lifts each primal method so that arbitrary arguments can pass through it, including Dual and NDual numbers.

This needs to be split into smaller PRs for a comfortable review.

Do not review; work in progress.

CI Summary — GitHub Actions

Documentation Preview

Mooncake.jl documentation for PR #1151 is available at:
https://chalk-lab.github.io/Mooncake.jl/previews/PR1151/

Performance

Performance Ratio:
Ratio of time to compute gradient and time to compute function.
Warning: results are very approximate! See here for more context.

┌───────────────────────┬──────────┬──────────┬─────────────┬─────────┬─────────────┬────────┐
│                 Label │   Primal │ Mooncake │ MooncakeFwd │  Zygote │ ReverseDiff │ Enzyme │
│                String │   String │   String │      String │  String │      String │ String │
├───────────────────────┼──────────┼──────────┼─────────────┼─────────┼─────────────┼────────┤
│              sum_1000 │ 191.0 ns │     1.58 │        1.63 │   0.681 │        3.36 │   6.61 │
│             _sum_1000 │  1.09 μs │     6.03 │        1.03 │  4080.0 │        38.0 │   1.06 │
│          sum_sin_1000 │  7.43 μs │     2.54 │        1.12 │    1.63 │        11.2 │   1.75 │
│         _sum_sin_1000 │  4.58 μs │     3.84 │        2.68 │   385.0 │        18.2 │   3.09 │
│              kron_sum │ 218.0 μs │     12.2 │        3.13 │    7.14 │       464.0 │   21.9 │
│         kron_view_sum │ 277.0 μs │     12.5 │        5.28 │    27.6 │       455.0 │   13.2 │
│ naive_map_sin_cos_exp │  2.33 μs │     2.72 │         1.5 │ missing │        7.96 │   2.08 │
│       map_sin_cos_exp │  2.18 μs │      3.3 │         1.7 │    1.52 │        7.22 │   2.65 │
│ broadcast_sin_cos_exp │  2.35 μs │     3.08 │         1.5 │    4.29 │        1.38 │   2.02 │
│            simple_mlp │ 405.0 μs │     4.56 │        2.64 │    1.46 │        8.36 │   2.73 │
│                gp_lml │ 165.0 μs │     12.7 │        2.72 │    5.67 │     missing │   6.55 │
│    large_single_block │ 471.0 ns │     8.74 │        1.92 │  4130.0 │        31.8 │   2.08 │
└───────────────────────┴──────────┴──────────┴─────────────┴─────────┴─────────────┴────────┘

yebai and others added 27 commits April 12, 2026 16:37
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nfwd bridge

Step 1: Delete old forward-mode stack (forward_mode.jl, 110+ frule!! methods,
LazyFoRRule/DynamicFoRRule, forward debug tests). Stub dual_ir and forward IR
inspection.

Step 2: Reimplement public forward via nfwd bridge. NfwdCache for forward-mode
with chunked gradients. HVPCache for nfwd-over-reverse Hessian-vector products
via NDual{T,1} reverse rules. Add _nfwd_lift for Tuple inputs. Update docs and tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add NTangent width-aware tangent container to src/tangents/dual.jl
- Add tangent_type(Val(N), P) and dual_type(Val(N), P) width-aware queries
- Update verify_dual_type to handle NTangent-wrapped tangents
- Restore 171 frule!! definitions from main across 10 rule files
- Add _chunk_pack_tangent disambiguations for NTangent{Vararg{NoTangent}}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add PrimalMode context to contexts.jl with is_primitive always false
- Add PrimalMode interpreter delegation in abstract_interpretation.jl
- Add primal_mode.jl (605 lines) from primal-mode-migration donor
- Update optimise_ir! to use PrimalMode interpreter
- Rename DerivedFRule/LazyFRule/DynamicFRule to DerivedPrimal/LazyPrimal/DynamicPrimal
- Rename forwards_mode_design.md to primal_mode_design.md
- Add primal_mode.jl test (44 lines)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Port LazyFoRRule/DynamicFoRRule from main for forward-over-reverse
  lazy rule compilation (fixes x^4, x^6 HVP on Julia 1.10)
- Add interp keyword to optimise_ir! for ForwardMode inlining control
- Fix FoR value_and_hvp!! aliasing bug: copy grad/hvp before returning
  so successive calls don't overwrite earlier results
- Add dual-mode HVP correctness tests (FoR + RoF): quadratic,
  Rosenbrock, multi-arg, namedtuple, nested tuple intermediates
- Add dual-mode Hessian correctness tests (FoR + RoF): Rosenbrock,
  quadratic, multi-arg, namedtuple, nested tuple, tuple map/reduce

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add build_rrule methods and RRule call method to NfwdMooncake that
were lost during the forward-mode refactoring. These are needed by
integration tests (distributions) and the public API.

Remove 4 @test_broken zero-allocation assertions for array-input
nfwd gradient paths that are not yet resolved.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…hecking

Step 7 infrastructure: zero_dual/randn_dual/uninit_dual for NDual and
Complex{NDual}, primal/tangent accessors for Array{NDual} and
Array{Complex{NDual}}, ndual_width/check_ndual_width_consistency for
runtime width validation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add width param to const_dual!, _uninit_dual dispatcher (4 overloads)
- Thread width through __unflatten_dual_varargs with _group_vararg_dual
- Add W type param to DerivedPrimal, DynamicPrimal for zero-alloc width
- Add width field to LazyPrimal, DynamicPrimal; propagate in _copy
- Relax Vararg{Dual,N} to Vararg{Any,N} in DerivedPrimal/DynamicPrimal
- Include width in closure cache key and primal_rule_type
- Add __get_primal(::NDual) and _partial_i(::NDual) in NfwdMooncake.jl
- All modify_primal_stmts! handlers use info.width for _uninit_dual

Scalar end-to-end verified: sin, sin∘cos, x*x at width-2.
Tests: basic (26533 pass), Nfwd (477 pass).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…idth-N

Add element-wise NDual lifting for container types:
- MemoryRef/Memory: dual_type(Val(N), MemoryRef{T}) → MemoryRef{NDual{T,N}} (1.11+)
- Tuple: dual_type(Val(N), Tuple{T1,T2}) → Tuple{dual_type(Val(N),T1), ...}
- Val{0} ambiguity resolvers for MemoryRef/Memory

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add frule!! overloads for NDual containers (Array{NDual}, MemoryRef{NDual},
Memory{NDual}) so the primal-mode compiler can trace through array operations
at width-N.

Key additions:
- _uninit_dual(Val{N}, Array{T,D}): returns bare Array{NDual{T,N}}
- lmemoryrefget/lmemoryrefset!/memoryrefnew: NDual container variants
- lgetfield for bare NDual containers (tangent = NoTangent)
- sum/sum(abs2) NDual overloads for primitive array reductions
- _HasNDual/_NDualMemTypes dispatch unions
- copy for Array{NDual}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Make the primal-mode compiler NDual-aware so that chunk_size > 1
produces width-N dual numbers instead of looping width-1.

Key changes:
- FCache carries width; value_and_gradient!! splits into
  _gradient_width1 (Dual path) and _gradient_widthN (NDual path)
- _make_ndual_seed / _combine_to_ndual build NDual inputs from
  per-slot seed tangents
- prepare_derivative_cache passes Val(cs) to build_frule
- _uninit_dual lifts Memory type constants for NDual containers
- _new_ frule handles NDual container construction (Array, Memory)
  and struct construction with NTangent
- @zero_derivative frule!! uses per-arg type parameters to avoid
  Union{Dual{<:Any},Any} collapsing to Any
- Bare NDual container frule!! overloads for memoryrefnew,
  lmemoryrefget, lmemoryrefset!, getfield, copy
- NDual frule!! for sincosd, sincospi, modf (tuple-returning)
- unalias declared as forward-mode primitive (workaround for OC
  segfault with NDual containers in broadcast)
- _has_ndual / _dual_or_ndual helpers for NDual dispatch in rules
- NTangent added to StandardTangentType; _get_tangent_field
  overloads for NTangent field access

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add _combine_to_ndual methods for Complex{T} scalars and
  AbstractArray{Complex{T}} so chunked forward mode preserves
  tangent directions instead of dropping them via NoTangent fallback
- Replace zero(T) with f(x...) in empty-input Hessian early return
  so the actual primal value is returned

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…biguity

- Allow width-1 Dual calls through chunked FCache by padding into
  lane 1 of the width-W rule and extracting via _ndual_output_to_width1
- Add _combine_to_ndual complex NoTangent disambiguators (resolves Aqua)
- Change generic _combine_to_ndual fallback to NTangent (structured types)
- Fix _eval_dir NTangent check and add width-N path for tuple derivatives
- Fix HVP reverse-over-forward seed type for constant functions
- Add Complex{NDual} support: _has_ndual, _dual_or_ndual, lgetfield
  frule, _new_ frule, _ndual_width, _tangent_dir
- Fix empty Hessian to return f(x...) instead of zero(T)
- Add regression tests for chunked complex and empty Hessian

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Discover output type via compiled rule instead of raw f(x...) in
  reverse_over_forward prepare, preserving mutation-restoration semantics
- Document no-direct-call contract in prepare_hvp_cache docstring
- Add _combine_to_ndual for Tuple: element-wise NDual packing
- Add lgetfield frules for bare Tuple/NamedTuple with NDual elements

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ages

- Remove Tuple specialization in _count_slots that bypassed IdDict-based
  alias deduplication, causing redundant evaluations for aliased inputs
- Respect config.silence_debug_messages in prepare_derivative_cache
  instead of hard-coding true

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bring back detailed comments documenting cache-key design, stack aliasing
invariants, thread-safety caveats, and why sig_or_mi is excluded from the
DynamicFoRRule key. These explain non-obvious correctness constraints.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…es, fix Aqua issues

- Guard all Memory/MemoryRef references with @static if VERSION >= v"1.11-rc4"
  in Mooncake.jl, new.jl, and primal_mode.jl
- Move _HasNDual const from memory.jl to Mooncake.jl (used by always-loaded code)
- Fix Aqua unbound type param: _ndual_width uses fieldcount(L) instead of NTuple{W}
- Fix Aqua method ambiguity: add _combine_to_ndual(::Tuple, ::Tuple{}) disambiguator
- Skip forward-mode testing for @zero_derivative typed-vararg cases (pre-existing bug)
- Guard chunked complex array test for 1.11+ only (Core.arrayref lacks width-N frule)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ive vararg frule

- Consolidate all Memory/MemoryRef NDual overloads (_has_ndual, _dual_or_ndual,
  _find_ndual_memref, _ndual_width, _tangent_dir, _uninit_dual) into memory.jl
  which is already conditionally loaded on Julia 1.11+. Removes @static if guards
  from Mooncake.jl, new.jl, and primal_mode.jl.
- Fix _vararg_any_type to produce Union{<:T, Dual{<:T}} so typed vararg frule
  signatures match Dual-wrapped args. Removes mode=ReverseMode workarounds.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix forward cache review follow-ups (docs, config, interface, tests)
- Merge origin/main into step-8-ndual-routing
- Fix NDual memory copy forwarding
- Inline and remove single-use helper functions in interface.jl

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Handle NDual outputs in forward cache summaries and add arrayref/arrayset forward rules for arrays storing NDual elements.

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

codecov Bot commented Apr 20, 2026

Copy link
Copy Markdown

@AstitvaAggarwal

Copy link
Copy Markdown
Member

FYI: We need this to be merged for #1160 (in the future) to go through.

yebai and others added 2 commits April 28, 2026 15:31
Resolved conflicts in docs/src/interface.md and test/interface.jl
by keeping this branch's FCache naming and incorporating main's new
value_and_jacobian!! and nfwd NDual error tests.

Also fixed stale ForwardCache references introduced by the auto-merge
of src/interface.jl (value_and_jacobian!! methods used ForwardCache
instead of the branch-local FCache).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace `_validate_prepared_cache_inputs` (main's name) with
  `_validate_prepared_cache` throughout the auto-merged jacobian code
- Rewrite FCache value_and_jacobian!! to use _make_seed_tangent +
  Dual calls instead of the incompatible gradient_chunk_size /
  _fcache_gradient_seed_tangent / NTangent pattern from main;
  throw for width != nothing since NDual frules for vector
  construction/broadcast are not yet complete on this branch
- Remove nfwd NDual user-dispatch test: in this branch the primal
  compiler doesn't re-dispatch to NDual-specialised user methods,
  so the test was testing main-only behaviour
- Remove chunked FCache jacobian tests (chunk_size=1/2): NDual lacks
  frules for getfield on Tuple{NDual,...} and zero_derivative for
  Vector{NDual} in broadcast; those are separate work items
- Fix hvp_cache test to use a valid Vector input (this branch's
  prepare_hvp_cache requires Vector{<:IEEEFloat} inputs)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@yebai

yebai commented Apr 28, 2026

Copy link
Copy Markdown
Member Author

@AstitvaAggarwal, can you run all downstream tests (eg, SciML-related) on this branch?

@AstitvaAggarwal

Copy link
Copy Markdown
Member

Yeah

@AstitvaAggarwal

AstitvaAggarwal commented May 1, 2026

Copy link
Copy Markdown
Member

@yebai I have checked the relevant SciML repositories which will require running the tests and they pass.
Also ForwardMode is also not as widely used as ReverseMode - atleast within SciML.

yebai and others added 10 commits May 3, 2026 16:49
Bug fixes:
- 4-arg lgetfield frule routes through _dual_or_ndual so width-N
  NTangent field tangents lift to NDual instead of falling back to a
  bare Dual (silent width-N miss)
- _gradient_widthN raises an explicit ArgumentError when chunk>1 and
  the lifted OC returns a non-NDual output for an IEEEFloat primal,
  instead of BoundsError on coeffs[d]

Dead code:
- _fcache_derivative_chunked!! placeholder + stale docstring
- duplicate using imports in test_utils.jl
- unreachable _make_seed_tangent(::NoTangent, _cursor, _dict) overload
- unused Maybe{T} alias
- ndual_width / check_ndual_width_consistency
- LazyFRule/DynamicFRule comment refs

NDual helper consolidation:
- move _has_ndual, _dual_or_ndual, _ndual_width, _ndual_primal,
  _tangent_dir(_elem), _find_ndual_memref, _HasNDual, the Memory
  _uninit_dual overloads, and the Memory zero_derivative method into
  src/nfwd/NfwdMooncake.jl
- Mooncake.jl imports them from .NfwdMooncake so rule files can
  reference by bare name

Tests:
- new test/nfwd/nfwdmooncake.jl exercises every supported NDual
  container (NDual, Complex{NDual}, Array{NDual},
  Array{Complex{NDual}}, Memory and MemoryRef variants on 1.11+) for
  _has_ndual, _ndual_width, and _dual_or_ndual — guards against the
  silent-fallthrough failure mode where a missing container overload
  drops the FCache forward path to width-1
- wired into test/runtests.jl Nfwd group

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The fallback `_uninit_dual(::Val, v) = uninit_dual(v)` silently
returns a width-1 Dual regardless of width. For non-differentiable IR
constants this is harmless, but if a container type with a non-trivial
tangent_type ever reaches this fallback, the FCache forward path
silently downgrades to width-1.

Throw an explicit ArgumentError pointing at NfwdMooncake.jl as the
right place to add the missing overload.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The generic _count_slots fallback allocates a fresh IdDict{Any,Any}()
per call (used to track aliased mutable containers). Tuples and
NamedTuples are by definition acyclic, so the dict bookkeeping is
wasted on the hot value_and_gradient!! path.

Add specializations that recurse without state. `@allocated` confirms
zero-alloc on `(1.0, 2.0, [3.0, 4.0])` and `(a=1.0, b=2.0)`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`build_frule` already memoises the worker rule per `(F, world)` via
`interp.oc_cache`, but each `threading_run` frule call still rebuilt
the per-thread `[_copy(worker_rule) for _ in 1:threadpoolsize()]`
array — `_copy` recursively copies captures and runs once per thread.

Cache the array keyed by `(F, threadpoolsize, world)` so the per-thread
copies happen only on the first call. After warm-up, the cache stays
at exactly 1 entry per signature.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After auditing the ~20 near-duplicate Memory/MemoryRef/Array NDual
frule overloads (`Dual{<:Container{<:_HasNDual}}` vs bare
`Container{<:_HasNDual}`):
- IR probe on a chunked-gradient primal (`x -> x[1] + x[2]` walking
  Memory via memoryrefnew/memoryrefget) shows the lifted IR dispatches
  to the bare-container form at every site — arguments flow as bare
  typed SSAValues whose static type already encodes NDual elements.
- The `Dual{...}` form is the stable user-facing entry point for
  direct frule!! calls outside the lifted IR.
- `Aqua.test_ambiguities([Mooncake])` reports no ambiguities.

Add a comment explaining this duality so future readers don't try to
collapse them or wonder why both exist.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…API gap

Re-add `count_allocs == 0` assertions on `value_and_gradient!!` for
scalar / two-scalar primals. These hold (verified) on the FCache path
because `_make_seed_tangent(::Tuple)` and `(::IEEEFloat)` skip the
IdDict aliasing tracking after the recent `_count_slots` Tuple
specialisation, the gradient workspace is reused across calls, and
the lifted OC has no captures.

The chunked array gradient testsets are *not* asserting `== 0` —
array-input gradient currently allocates ~21/element (one fresh
`IdDict` per `_make_seed_tangent` call plus the Memory{NDual} lift
buffer in `_dual_or_ndual`). Pinned in pr1151.md follow-up #16.

Also flag in `src/rules/memory.jl` that the user-facing
`Dual{<:Container{<:_HasNDual}}` frule overloads can't easily be
exercised by external callers today, because `zero_dual` is width-1
only — making it width-aware to match `dual_type` is pr1151.md
follow-up #17.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DebugFRule wraps the rule with verification that allocates, so the
`count_allocs == 0` asserts I added in d26dff8 fired under
`debug_mode=true` and `(debug_mode=true, silence_debug_messages=true)`
in the parametrised testset (2 failures each).

Skip the asserts when `kwargs.debug_mode` is true; the contract is
that non-debug mode FCache scalar gradient is allocation-free.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add an `_seed_inplace!(buf, x, slot, cursor)` helper that mutates a
tangent-shaped buffer in place to represent the standard-basis seed
for a given global slot, instead of `_make_seed_tangent` rebuilding
fresh tangent arrays every direction. Cache the buffer on `FCache`
(new `seed_buf::SB` field, lazily initialised on first call).

Allocation reduction on `value_and_gradient!!` for `x -> sum(abs2, x)`
on `Vector{Float64}` (warm-cache, no friendly_tangents):
  size  before  after
   1     25     10
   2     46     16
   5    109     34
  10    214     64
  50   1054    304

About 3.3× across the range. Per-call savings come from reusing the
tangent vector buffer; remaining allocations are mostly `_scale` and
`increment!!` temporaries on the inner accumulation loop.

The generic fallback delegates to `_make_seed_tangent` so cyclic
references and partially initialised mutable structs continue to work
unchanged. Verified rules/memory passes (16143/16143) and aliased /
cyclic / mutable-struct primals still produce correct gradients.

Width-N path is unchanged in this commit (still calls
`_make_seed_tangent` per direction inside `_make_ndual_seed`). Caching
that path will need a different seeds-tuple buffer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous `tangent_type(_typeof(v)) !== NoTangent` check was too
strict: container types whose tangent_type wraps only NoTangent leaves
(e.g. `Base.RefValue{Int}` → `MutableTangent{(x=NoTangent,)}`) tripped
it even though they have no differentiable scalar slots and the
existing width-1 `uninit_dual(v)` is already correct for them.

Tighten the check to `_count_slots(v) > 0` so we only fail for
containers that *actually* carry differentiable degrees of freedom.
This caught two regressions in the basic test suite (Ref{Int} appearing
as an IR constant inside a chunked-forward primal).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add `Val{N}` overloads to the three Mooncake.jl seed-tangent
constructors so their output static type matches `dual_type(Val(N), P)`
for every primal type covered by the NDual-aware `dual_type` table:

  | Primal              | Width-N return type
  |---------------------|-----------------------------------
  | T<:IEEEFloat        | NDual{T,N}
  | Complex{T}          | Complex{NDual{T,N}}
  | Array{T,D}          | Array{NDual{T,N},D}
  | Array{Complex{T},D} | Array{Complex{NDual{T,N}},D}
  | Memory{T} (1.11+)   | Memory{NDual{T,N}}
  | MemoryRef{T}        | MemoryRef{NDual{T,N}}
  | (Complex variants)  | …

`Val(0)` is the primal passthrough, matching `dual_type(Val(0), P) == P`.

Internal `_ndual_zero` / `_ndual_array` / `_ndual_memory` helpers
factor out the per-element NDual construction; the three entry points
share them through a per-element generator (`_ -> zero(T)`,
`_ -> randn(rng, T)`).

Without this, the user-facing `Dual{<:Container{<:_HasNDual}}` frule
overloads in `memory.jl` / `array_legacy.jl` cannot be reached from
external callers — they would have to construct NDual values directly
to seed a width-N forward call.

Add type-equality + sanity tests in test/nfwd/nfwdmooncake.jl
(117/117 pass) covering all primal types and N ∈ {0,1,2,4}.

Closes pr1151.md follow-up #17.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
yebai and others added 2 commits May 27, 2026 00:41
Restores cache-owned Hessian/grad/basis buffers (dropped by the merge
conflict with main's #1178) onto the branch's refactored
`HVPCache{M,C,S}`. Drops `copy(grad)`/`copy(hvp)` in `value_and_hvp!!`
for `:forward_over_reverse` so the Hessian sweep writes straight into
`H[:, i]`/`g` without per-coordinate Vector allocations. Docstrings
note the aliasing; tests that hcat results from the same cache snapshot
via `copy` before the next call.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yebai

yebai commented Jun 16, 2026

Copy link
Copy Markdown
Member Author

Superseded by #1215

@yebai yebai closed this Jun 16, 2026
@yebai
yebai deleted the step-8-ndual-routing branch June 16, 2026 21:07
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.

2 participants