From 92ad2271330d1a64f5bced7cba716df1e3b4a615 Mon Sep 17 00:00:00 2001 From: Michael Kraus Date: Mon, 12 Oct 2026 18:00:49 +0200 Subject: [PATCH 01/11] Train on GeometricOptimizers' TrainingOptimizer: NetworkOptimizer and train! (part M1a) NetworkOptimizer wraps GO 0.9's TrainingOptimizer over the whole parameter set; train!(nn, opt, dl, batch, n_epochs; loss, show_progress) replaces the Optimizer functors and optimize_for_one_epoch!. The aliases, the per-layer caches and the hand-built step (about 400 lines) go. Compat: GO 0.9, ANN 0.9.1, NNP 0.5, SNN 0.9, GeometricBase 0.15, GeometricEquations 0.21.5, GeometricSolutions 0.6.6. The methods moved to ANN (3-tensor Dense/Linear functors, HDF5 save/load and the extension) go; the call sites in tests, docs, README and scripts move, and the CHANGELOG and KNOWN_ISSUES entries follow. GO 0.9's triangular types keep their old GML names here, through an aliased import; the next commit renames them. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 16 +- KNOWN_ISSUES.md | 179 ++++---- Project.toml | 17 +- README.md | 6 +- docs/check_references.jl | 4 +- docs/make.jl | 3 +- .../architectures/abstract_neural_networks.md | 9 +- docs/src/introduction.md | 4 +- docs/src/optimizers/optimizer.md | 31 +- .../tutorials/adjusting_the_loss_function.md | 7 +- docs/src/tutorials/grassmann_layer.md | 12 +- .../tutorials/hamiltonian_neural_network.md | 8 +- .../linear_symplectic_transformer.md | 14 +- docs/src/tutorials/matrix_softmax.md | 8 +- docs/src/tutorials/optimizer_comparison.md | 6 +- docs/src/tutorials/softmax_comparison.md | 8 +- docs/src/tutorials/symplectic_autoencoder.md | 27 +- docs/src/tutorials/symplectic_transformer.md | 14 +- docs/src/tutorials/sympnet_tutorial.md | 16 +- .../tutorials/volume_preserving_attention.md | 12 +- ...olume_preserving_transformer_rigid_body.md | 18 +- ext/HDF5Ext.jl | 87 ---- scripts/reproduction/harmonic_oscillator.jl | 6 +- scripts/reproduction/hnn_pendulum.jl | 9 +- .../reproduction/hnn_pendulum_simulation.jl | 5 +- .../linear_symplectic_transformer_gpu.jl | 14 +- scripts/reproduction/lnn_pendulum.jl | 11 +- .../symplectic_autoencoders/online_sympnet.jl | 10 +- .../online_transformer_for_sae.jl | 6 +- .../symplectic_autoencoders/training.jl | 21 +- .../symplectic_transformer/double_pendulum.jl | 18 +- .../double_pendulum_short_integration.jl | 18 +- .../reproduction/sympnets/sympnet_pendulum.jl | 10 +- .../sympnets/sympnet_toda_lattice.jl | 6 +- .../softmax_comparison.jl | 10 +- .../symplectic_transformer.jl | 14 +- .../symplectic_transformer_vector_softmax.jl | 14 +- .../volume_preserving_feedforward/abc_flow.jl | 6 +- .../double_pendulum.jl | 9 +- .../rigid_body.jl | 6 +- .../rigid_body.jl | 6 +- scripts/verification/all_zero_leaf.jl | 78 ++++ src/GeometricMachineLearning.jl | 43 +- src/arrays/poisson_tensor.jl | 4 + src/data_loader/optimize.jl | 117 ----- src/data_loader/train.jl | 56 +++ src/layers/resnet.jl | 12 - src/optimizers/optimizer.jl | 430 +----------------- test/Project.toml | 3 +- .../lagrangian_neural_network_tests.jl | 4 +- .../sae_error_lower_than_psd_error.jl | 4 +- .../symplectic_autoencoder_tests.jl | 8 +- test/data_loader/batch_data_loader_qp_test.jl | 8 +- .../data_loader_for_input_and_output.jl | 4 +- .../data_loader_optimization_step.jl | 8 +- .../optimizer_functor_with_adam.jl | 5 +- test/data_loader/train.jl | 114 +++++ test/data_loader/training_history_eltype.jl | 13 +- test/helpers/eltypes.jl | 3 + .../network_parameters_gradient_structure.jl | 41 +- test/integration/losses_and_optimization.jl | 4 +- test/integration/reduced_loss_optimization.jl | 10 +- .../integration/train_manual_architectures.jl | 69 +++ ...tiplication_network_parameters_gradient.jl | 8 +- test/layers/gradient_layer_tests.jl | 14 +- test/layers/manifold_layers.jl | 19 +- ...ulti_head_attention_stiefel_optim_cache.jl | 26 +- ...multi_head_attention_stiefel_retraction.jl | 12 +- .../multi_head_attention_stiefel_setup.jl | 12 +- ...c_attention_network_parameters_gradient.jl | 22 +- test/layers/transformer_optimizer.jl | 18 +- test/loss/training_method_losses.jl | 10 +- test/map_to_cpu.jl | 3 +- .../adam_with_learning_rate_decay.jl | 71 ++- test/optimizers/gml_gradient_dispatch.jl | 116 +---- test/optimizers/gradient_optimizer.jl | 11 +- test/optimizers/momentum_optimizer.jl | 11 +- test/optimizers/optimization_step.jl | 90 ++-- test/optimizers/optimizer.jl | 51 +++ test/optimizers/psd_optim.jl | 16 +- test/optimizers/step_size_element_type.jl | 57 +-- .../optimizers/structured_array_parameters.jl | 14 +- test/optimizers/svd_optim.jl | 16 +- test/quality/aqua.jl | 131 ++---- test/quality/exports.jl | 23 +- test/runtests.jl | 11 +- 86 files changed, 1065 insertions(+), 1440 deletions(-) delete mode 100644 ext/HDF5Ext.jl create mode 100644 scripts/verification/all_zero_leaf.jl delete mode 100644 src/data_loader/optimize.jl create mode 100644 src/data_loader/train.jl create mode 100644 test/data_loader/train.jl create mode 100644 test/helpers/eltypes.jl create mode 100644 test/integration/train_manual_architectures.jl create mode 100644 test/optimizers/optimizer.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b2b4f730..14ac6061f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,22 @@ breaking release). ## [Unreleased] +### Removed (breaking) + +- **`Optimizer`, the callable optimizers and their aliases are gone; training moves to `NetworkOptimizer` and `train!`.** Training now uses GeometricOptimizers 0.9's `TrainingOptimizer`. `NetworkOptimizer(nn; algorithm = Adam(), linesearch = default_step_size(algorithm), retraction = Cayley())`, also accepted from a `NetworkParameters`, replaces `Optimizer`. Migrate `Optimizer(method, nn; step_size = η, retraction = cayley)` to `NetworkOptimizer(nn; algorithm = method, linesearch = η, retraction = Cayley())`, and `Optimizer(nn; AdamOptimizerWithDecay(n, T)...)` to `NetworkOptimizer(nn; AdamOptimizerWithDecay(n)...)`. The `n` of `AdamOptimizerWithDecay` counts optimization steps, one per minibatch, as in GeometricOptimizers and GML 0.8. One cache and one state now cover the whole parameter set; 0.8 had one per layer. `using GeometricOptimizers, GeometricMachineLearning` leaves `Optimizer` unqualified as GeometricOptimizers' solver, and no name is shadowed. `train!(nn, opt, dl, batch, n_epochs; loss, show_progress = true)` replaces the four callable methods `(o::Optimizer)(nn, dl, batch, n_epochs[, loss, pullback])` and `optimize_for_one_epoch!`, which is removed from the exports. Migrate `o(nn, dl, batch, n, loss; kw...)` to `train!(nn, o, dl, batch, n; loss, kw...)`. Without `loss` the defaults are those of 0.8: `TransformerLoss(batch)` for a `TransformerIntegrator` with a `Batch{:Transformer}`, `FeedForwardLoss()` for a `NeuralNetworkIntegrator`, and `AutoEncoderLoss()` for an `AutoEncoder`, each with a `Batch{:FeedForward}`. `loss` is a `NetworkLoss`; passing an `AbstractPullback` as the sixth positional argument, which 0.8 allowed, is gone. The `λY` argument and the per-call `GlobalSection` are gone: the sections, moments and iteration number live in `opt`, so a second `train!` call with the same `opt` resumes the first, and `train!` draws no random numbers beyond the minibatch shuffle. `n_epochs < 1` raises an `ArgumentError` before any step. The loss history has the element type of the loss value, so a `Float64` network on `Float32` data gives `Vector{Float64}`, where 0.8 gave `float(eltype(dl))`; the epoch accumulator keeps that type. A hand-written loop steps with `GeometricOptimizers.optimization_step!(params(nn), opt, dp)`, which GML adds for `NetworkOptimizer`; `optimization_step!(opt, λY, ps, dp)` is removed. A gradient leaf that is all zero is stepped, not skipped: NeuralNetworkParameters 0.5 gives a layer the loss does not read as a zero leaf, not `nothing`. Under `GradientMethod`, `MomentumMethod` and `Adam` such a leaf stays where it is; under `AdamWithEuclideanDecay` a Euclidean leaf that is never read decays, and a leaf read for some steps and then not keeps moving on its momentum. `scripts/verification/all_zero_leaf.jl` prints the figures. + +- **The aliases `GradientOptimizer`, `MomentumOptimizer` and `AdamOptimizer` are removed**; use `GradientMethod`, `MomentumMethod` and `Adam`. These are no longer exported: `OptimizerMethod`, `GradientState`, `MomentumState`, `AdamState`, `optimization_step!`, `optimize_for_one_epoch!` and `Optimizer`. `NetworkOptimizer`, `train!` and `AdamWithEuclideanDecay` are newly exported, and the package re-exports `GradientMethod`, `MomentumMethod`, `Adam`, `AdamOptimizerWithDecay`, `DecayingStatic`, `Cayley` and `Geodesic` from GeometricOptimizers. Methods carry no element type in GeometricOptimizers 0.9: `Adam(T)` becomes `Adam()`, and `MomentumMethod(α)` becomes `MomentumMethod(; α)`. `check(::Optimizer)` is removed. + +- **The three 3-tensor functors of `Dense` and `Linear` and the HDF5 `save` and `load` methods on `NeuralNetwork` are removed**, because AbstractNeuralNetworks 0.9 provides them. The HDF5 extension and the HDF5 weak dependency are gone; with HDF5 loaded, AbstractNeuralNetworks' extension provides `save` and `load`. Its `load` has no `backend` keyword: load on the CPU, then call `changebackend(backend, nn)`. Aqua's `piracies` check runs again with 0 piracies (was 3), and the ambiguity count is 0 (was 1). This closes issue #145 and known issue B7. + +### Changed (breaking) + +- **The `[compat]` bounds move to the versions the training API needs**: GeometricOptimizers `"0.9"`, AbstractNeuralNetworks `"0.9.1"`, NeuralNetworkParameters `"0.5"`, SymbolicNeuralNetworks `"0.9"`, GeometricBase `"0.15"`, GeometricEquations `"0.21.5"` and GeometricSolutions `"0.6.6"`. HDF5 leaves `[compat]` and `[weakdeps]`. + ### Added +- **`KernelAbstractions.get_backend` is defined for `PoissonTensor`.** GeometricOptimizers 0.9 asks every operand of a checked product for its backend, so `𝕁 * Y` with a `StiefelManifold` raised `ArgumentError: Implement KernelAbstractions.get_backend(::PoissonTensor...)` without it. + - **The test suite runs on an Apple GPU in its `metal` group.** `test/devices/metal.jl` checks the two device paths that were measured by hand and not tested before: the products of a `PoissonTensor` with a wrapped device array, `𝕁 * view(A, [1, 2, 3, 4], :)` and `𝕁 * B'`, which `ext/GPUArraysCoreExt.jl` carries; and the `tensor_mat_mul!` kernel on `MetalBackend()`. Each result must stay on the device and match the host in `Float32`, with scalar indexing off. `Pkg.test(test_args = ["metal"])` runs them without the subject tests, and a missing device records a skip, which happens inside a sandbox. Metal (1.10 or later) is a test dependency on every platform; it installs and precompiles on Linux and Windows, and nothing there loads it. On an Apple M4 Max with Metal 1.11.1 all five assertions pass. - **A `Metal` workflow runs the Metal tests on GitHub's `macos-15` runner**, for the `min` and `1` Julia versions. The runner's GPU is Apple's paravirtual device, which Metal.jl supports from 1.10 and on macOS 15 or later. The workflow includes a step "Metal is functional" before the tests that fails the job where the device is unavailable, so it cannot pass without having run the tests. The job is not a required check. However, the Metal tests also run in two required checks—CI.yml's `Julia min - macOS-latest - default` and `Julia 1 - macOS-latest - default`—which execute `test/runtests.jl` with no test arguments on Apple silicon, selecting the `metal` group. - **`test/quality/jet.jl` checks the kernel launchers with JET.** It runs in `core`, directly after `quality/aqua.jl`, and takes about 14 s. Each function that launches a kernel of `src/` on the `CPU()` backend, the pullbacks among them, and the `Batch` functor that `test/data_loader/batch_index_set.jl` measures with `@allocated` get one `JET.report_opt` line per element type that the tests use, with `target_modules = (GeometricMachineLearning,)`. Each `@kernel` method also gets a line that analyses its generated `cpu_` function at the launcher's context, because JET drops the reports of a kernel body when it analyses the launcher. 142 lines pass on Julia 1.11.9 (JET 0.9.20) and 1.13.1 (JET 0.12.2). Three lines report a runtime dispatch on both versions and are `@test_broken` with issue #325: `PoissonTensor(::CPU, ::Int, ::DataType)`, and the input-and-output method of `convert_input_and_batch_indices_to_array` at `Float32` and `Float64`. Where JET does not work, the file records one `@test_skip`. JET joins `test/Project.toml` with no `[compat]` bound. `KNOWN_ISSUES.md` K2 records the Revise `EMFILE` messages that JET brings into the test log, and K3 the dispatches that the lines do not see: an unstable argument of a kernel launch, and two barriers in the Poisson-tensor kernel. @@ -27,7 +41,7 @@ breaking release). - `Pkg.test()` no longer runs the doctests. `test/quality/doctests.jl` is now the group `doctests`, which an empty `ARGS` does not run; `Pkg.test(test_args = ["doctests"])` runs it. In CI the Doctests job stays their runner, so the test matrix no longer runs them a second time. - **CI uploads coverage from the `Julia 1 - ubuntu-latest` job instead of `Julia min`, and a test job saves the Julia cache only when it succeeds.** - **The package needs Julia 1.12 or later** (`[compat] julia = "1.12"`, was `"1.11"`). On Julia 1.11.9, the gradient of an `LNNLoss` with respect to the network parameters stops with `StackOverflowError` in type inference ("Internal error: during type inference of … Encountered stack overflow") on macOS and Windows; the `Julia min` job on Linux passes. The overflow comes from SymbolicUtils 4.49.0, whose `rrule` for `SymbolicUtils.Code.create_array` returns an exactly typed tuple of tangents, one per array element; Zygote's reverse pass through the generated Hessian of `lagrangian_acceleration` is then inferred far deeper. With the 4.48.0 rule restored, or in a task with a 64 MB stack, the same gradient passes on 1.11.9; Julia 1.12.7 and 1.13.1 pass it on the root task. This closes #327: the `Julia min` jobs on macOS and Windows had failed in the *Lagrangian Neural Network* testset since 2026-10-01, and every testset after it was never run there. -- **`test/Project.toml` has no `[compat]` entry for a dependency of the package.** The entries for AbstractNeuralNetworks, GeometricOptimizers, HDF5, KernelAbstractions, LinearAlgebra, NeuralNetworkParameters, Random and Zygote are gone: the test environment contains the package, so the bounds of `Project.toml` apply there, and an entry in `test/Project.toml` can only repeat or narrow them. `NeuralNetworkParameters = "0.3"` was narrower than the package's `"0.3, 0.4"`. +- **`test/Project.toml` has no `[compat]` entry for a dependency of the package.** The entries for AbstractNeuralNetworks, GeometricOptimizers, KernelAbstractions, LinearAlgebra, NeuralNetworkParameters, Random and Zygote are gone: the test environment contains the package, so the bounds of `Project.toml` apply there, and an entry in `test/Project.toml` can only repeat or narrow them. `NeuralNetworkParameters = "0.3"` was narrower than the package's `"0.3, 0.4"`. - **The test suite follows one flat layout.** `test/runtests.jl` is one list of `@safetestset "