polystep 0.9.0__tar.gz → 0.10.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- polystep-0.10.0/CHANGELOG.md +575 -0
- {polystep-0.9.0 → polystep-0.10.0}/CITATION.cff +2 -2
- polystep-0.10.0/CONTRIBUTING.md +93 -0
- polystep-0.10.0/LIMITATIONS.md +234 -0
- {polystep-0.9.0/src/polystep.egg-info → polystep-0.10.0}/PKG-INFO +113 -80
- polystep-0.10.0/README.md +294 -0
- {polystep-0.9.0 → polystep-0.10.0}/docs/api_overview.md +82 -44
- polystep-0.10.0/docs/performance.md +490 -0
- {polystep-0.9.0 → polystep-0.10.0}/docs/reproducibility.md +25 -33
- {polystep-0.9.0 → polystep-0.10.0}/pyproject.toml +14 -14
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/__init__.py +5 -33
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/_compiled.py +14 -31
- polystep-0.10.0/src/polystep/_serialization.py +206 -0
- polystep-0.10.0/src/polystep/_step_blockwise.py +671 -0
- polystep-0.10.0/src/polystep/_step_core.py +34 -0
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/_step_momentum.py +10 -12
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/_step_monolithic.py +586 -514
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/adaptive_subspace.py +47 -64
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/api.py +129 -132
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/ask_tell.py +13 -8
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/benchmarks/__init__.py +0 -26
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/benchmarks/baselines.py +8 -263
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/benchmarks/rl/cartpole.py +2 -4
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/benchmarks/rl/gym_evaluator.py +2 -5
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/benchmarks/rl/policies.py +46 -21
- polystep-0.10.0/src/polystep/benchmarks/threads.py +88 -0
- polystep-0.10.0/src/polystep/benchmarks/utils.py +529 -0
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/blockwise.py +61 -235
- polystep-0.10.0/src/polystep/cma.py +195 -0
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/cma_subspace.py +44 -166
- polystep-0.10.0/src/polystep/cost_nn.py +1857 -0
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/costs.py +1 -1
- polystep-0.10.0/src/polystep/dynamics.py +106 -0
- polystep-0.10.0/src/polystep/epsilon.py +162 -0
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/factored_subspace.py +7 -16
- polystep-0.10.0/src/polystep/geometry.py +277 -0
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/hybrid_subspace.py +196 -366
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/layers/attention.py +6 -11
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/layers/rnn.py +4 -13
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/objectives/base.py +1 -8
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/optimizer.py +425 -366
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/projection/sparse.py +27 -8
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/quadratic_model.py +108 -96
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/solver.py +84 -290
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/solvers/__init__.py +3 -6
- polystep-0.9.0/src/polystep/solvers/_prelude.py → polystep-0.10.0/src/polystep/solvers/_shared.py +140 -5
- polystep-0.10.0/src/polystep/solvers/base.py +42 -0
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/solvers/greedy.py +31 -34
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/solvers/kl_softmax.py +35 -33
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/solvers/sinkhorn.py +69 -86
- polystep-0.10.0/src/polystep/solvers/softmax.py +81 -0
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/solvers/tempered_softmax.py +3 -38
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/subspace.py +61 -59
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/transform.py +43 -35
- {polystep-0.9.0 → polystep-0.10.0/src/polystep.egg-info}/PKG-INFO +113 -80
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep.egg-info/SOURCES.txt +21 -10
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep.egg-info/requires.txt +4 -4
- {polystep-0.9.0 → polystep-0.10.0}/tests/conftest.py +19 -17
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_ablation_solvers.py +61 -9
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_adaptive_subspace.py +236 -173
- polystep-0.10.0/tests/test_amortization.py +253 -0
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_api.py +27 -25
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_ask_tell.py +26 -5
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_baseline_fairness.py +3 -5
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_benchmarks.py +38 -2
- polystep-0.10.0/tests/test_biased_rotation.py +100 -0
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_blockwise.py +34 -171
- polystep-0.10.0/tests/test_candidate_autocast.py +98 -0
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_checkpoint_resume.py +75 -3
- polystep-0.10.0/tests/test_cma.py +325 -0
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_cma_subspace.py +116 -66
- polystep-0.10.0/tests/test_compiled.py +261 -0
- polystep-0.10.0/tests/test_cost_nn.py +547 -0
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_costs.py +4 -22
- polystep-0.10.0/tests/test_dfo.py +507 -0
- polystep-0.10.0/tests/test_diagnostics.py +95 -0
- polystep-0.10.0/tests/test_dynamics.py +137 -0
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_end_to_end_learning.py +39 -0
- polystep-0.10.0/tests/test_epsilon.py +269 -0
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_evaluator_compile_forward.py +25 -0
- polystep-0.10.0/tests/test_fastpath_parity.py +168 -0
- polystep-0.10.0/tests/test_fused_projection.py +208 -0
- polystep-0.10.0/tests/test_geometry.py +220 -0
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_hybrid_subspace.py +293 -216
- polystep-0.9.0/tests/test_hardening.py → polystep-0.10.0/tests/test_input_validation.py +37 -9
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_integration.py +85 -116
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_kl_softmax.py +106 -2
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_layers.py +111 -119
- polystep-0.10.0/tests/test_math_guards.py +118 -0
- polystep-0.10.0/tests/test_mixed_precision.py +501 -0
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_mnist.py +95 -185
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_no_test_set_leakage.py +14 -2
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_nondiff_data.py +0 -6
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_nondiff_models.py +170 -211
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_numerical_stress.py +13 -45
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_optimizer.py +128 -171
- polystep-0.10.0/tests/test_per_sample.py +70 -0
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_quadratic_model.py +77 -21
- polystep-0.9.0/tests/test_correctness_regressions.py → polystep-0.10.0/tests/test_regressions.py +175 -136
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_rl_benchmarks.py +0 -2
- polystep-0.10.0/tests/test_sequential_fast_path.py +362 -0
- polystep-0.9.0/tests/test_silent_wrongness.py → polystep-0.10.0/tests/test_silent_failures.py +130 -7
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_sinkhorn.py +42 -184
- polystep-0.9.0/tests/test_sinkhorn_correctness.py → polystep-0.10.0/tests/test_sinkhorn_numerics.py +76 -21
- polystep-0.10.0/tests/test_site_vmap.py +174 -0
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_softmax.py +15 -7
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_softmax_integration.py +81 -109
- polystep-0.10.0/tests/test_sparse_delta_evaluator.py +472 -0
- polystep-0.10.0/tests/test_sparse_integration.py +181 -0
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_sparse_projection.py +53 -91
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_subspace.py +100 -153
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_subspace_blockwise.py +113 -64
- polystep-0.10.0/tests/test_subspace_delta.py +249 -0
- polystep-0.10.0/tests/test_subspace_gain.py +64 -0
- polystep-0.10.0/tests/test_transform.py +290 -0
- polystep-0.10.0/tests/test_trust_region.py +184 -0
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_vmap_layer_compatibility.py +16 -46
- polystep-0.9.0/CHANGELOG.md +0 -502
- polystep-0.9.0/CONTRIBUTING.md +0 -75
- polystep-0.9.0/LIMITATIONS.md +0 -233
- polystep-0.9.0/README.md +0 -261
- polystep-0.9.0/docs/performance.md +0 -174
- polystep-0.9.0/src/polystep/_step_blockwise.py +0 -993
- polystep-0.9.0/src/polystep/benchmarks/utils.py +0 -1223
- polystep-0.9.0/src/polystep/cma.py +0 -366
- polystep-0.9.0/src/polystep/cost_nn.py +0 -1098
- polystep-0.9.0/src/polystep/dynamics.py +0 -197
- polystep-0.9.0/src/polystep/epsilon.py +0 -217
- polystep-0.9.0/src/polystep/geometry.py +0 -356
- polystep-0.9.0/src/polystep/solvers/base.py +0 -79
- polystep-0.9.0/src/polystep/solvers/softmax.py +0 -159
- polystep-0.9.0/tests/test_biased_rotation.py +0 -53
- polystep-0.9.0/tests/test_cma.py +0 -296
- polystep-0.9.0/tests/test_compiled.py +0 -270
- polystep-0.9.0/tests/test_cost_nn.py +0 -545
- polystep-0.9.0/tests/test_dfo_features.py +0 -317
- polystep-0.9.0/tests/test_dynamics.py +0 -201
- polystep-0.9.0/tests/test_fullspace_chunk_bound.py +0 -49
- polystep-0.9.0/tests/test_fused_projection_rebuild.py +0 -53
- polystep-0.9.0/tests/test_geometry.py +0 -233
- polystep-0.9.0/tests/test_mixed_precision.py +0 -235
- polystep-0.9.0/tests/test_mixed_precision_step.py +0 -104
- polystep-0.9.0/tests/test_sparse_delta_evaluator.py +0 -202
- polystep-0.9.0/tests/test_sparse_integration.py +0 -291
- polystep-0.9.0/tests/test_transform.py +0 -279
- polystep-0.9.0/tests/test_trust_region.py +0 -97
- polystep-0.9.0/tests/test_turbo.py +0 -478
- {polystep-0.9.0 → polystep-0.10.0}/LICENSE +0 -0
- {polystep-0.9.0 → polystep-0.10.0}/MANIFEST.in +0 -0
- {polystep-0.9.0 → polystep-0.10.0}/setup.cfg +0 -0
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/benchmarks/rl/__init__.py +0 -0
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/benchmarks/rl/metrics.py +0 -0
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/layers/__init__.py +0 -0
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/objectives/__init__.py +0 -0
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/objectives/synthetic.py +0 -0
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/projection/__init__.py +0 -0
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep/py.typed +0 -0
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep.egg-info/dependency_links.txt +0 -0
- {polystep-0.9.0 → polystep-0.10.0}/src/polystep.egg-info/top_level.txt +0 -0
- {polystep-0.9.0 → polystep-0.10.0}/tests/__init__.py +0 -0
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_factored_subspace.py +0 -0
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_objectives.py +0 -0
- {polystep-0.9.0 → polystep-0.10.0}/tests/test_subspace_contract.py +0 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.10.0 - 2026-07-30
|
|
4
|
+
|
|
5
|
+
New default polytope, four site-aware evaluation paths, and a pass over fast-path
|
|
6
|
+
correctness. Retune `step_radius` wherever `polytope_type` was left at its default.
|
|
7
|
+
Measured numbers are in `docs/performance.md`.
|
|
8
|
+
|
|
9
|
+
### Breaking
|
|
10
|
+
|
|
11
|
+
- `polytope_type` defaults to `'simplex'` (was `'orthoplex'`): `k+1` evaluations per step
|
|
12
|
+
instead of `2k`. Features reading the antithetic ordering (`use_quadratic_model`,
|
|
13
|
+
`newton_refinement`, `trust_region`, `multifidelity_screen`) warn on another polytope.
|
|
14
|
+
- Rotations use a sign-corrected QR at or below `geometry._QR_MAX_BATCH` and Householder
|
|
15
|
+
reflections above. Both Haar on `SO(d)`, but the draw per seed differs from 0.9.0 and
|
|
16
|
+
across the threshold, so seeded runs do not reproduce step for step.
|
|
17
|
+
- `compile_forward` defaults to the in-place path; pass `False` to opt out.
|
|
18
|
+
- `adaptive_num_probe` defaults on only when `num_probe > 1`.
|
|
19
|
+
- Momentum is zeroed on a basis change instead of carried into the new basis.
|
|
20
|
+
- `projection_mode` removed: `'structured'` truncated its block-diagonal from the right,
|
|
21
|
+
leaving the trailing output rows with a zero projection.
|
|
22
|
+
- `use_csa` raises `TypeError`; use `use_adaptive_radius`. Gone with it:
|
|
23
|
+
`polystep.cma.update_step_size_csa`, `SolverState.sigma`/`.use_csa`,
|
|
24
|
+
`CMAAdaptiveSubspace.d_sigma`/`expected_norm`. A 0.9.0 checkpoint still loads.
|
|
25
|
+
- Top-level exports removed. Still importable from their modules: `PolyStep`
|
|
26
|
+
(`polystep.solver`), `VmapSafeLSTMCell` (`polystep.layers`). Deleted outright:
|
|
27
|
+
`Solver`, `SoftmaxResult`, `SinkhornResult`, `get_device`, `POLYTOPE_NUM_VERTICES_MAP`,
|
|
28
|
+
`get_sampled_polytope_vertices`, `get_probe_points`, `compute_nn_cost_matrix`,
|
|
29
|
+
`update_adaptive_radius`, `StyblinskiTang`, `Levy`, `Griewank`, `Beale`, `Branin`.
|
|
30
|
+
- `PolyStep` drops `layout`, `block_strategy` and `block_group_size`, with
|
|
31
|
+
`polystep.blockwise.compute_block_cost_matrix` and `blocks_to_layout_flat_batch`.
|
|
32
|
+
`PolyStep.compile` defaults to `False`.
|
|
33
|
+
- `update_evolution_path_sigma` takes `C_diag=None` for an already-whitened displacement;
|
|
34
|
+
`update_covariance_diagonal` takes `trace` and normalizes before clamping.
|
|
35
|
+
- Rejected at construction instead of failing later: `epsilon`/`ent_epsilon <= 0`,
|
|
36
|
+
`probe_radius <= 0`, `step_radius < 0`, `cost_batch_size=0`,
|
|
37
|
+
`AdaptiveSubspace(subspace_dim > full_dim)`, unknown `block_strategy`/`polytope_type`.
|
|
38
|
+
- `numpy` is no longer a core dependency; it moved to the `examples`, `dev` and
|
|
39
|
+
`experiments` extras.
|
|
40
|
+
- `step_radius` is not comparable across subspace classes: `LinearSubspace` amplifies by
|
|
41
|
+
`sqrt(num_params / num_coords)` where `HybridSubspace` and `FactoredSubspace` are unit
|
|
42
|
+
gain.
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
|
|
46
|
+
- `SubspaceDeltaEvaluator` and `SiteVmapEvaluator` score candidates without building a
|
|
47
|
+
candidate weight. The second assumes nothing about the module set, so it covers conv,
|
|
48
|
+
normalization, attention and custom `forward`.
|
|
49
|
+
- `polystep_elementwise` and `polystep_weight_transform` put a custom layer on the
|
|
50
|
+
batched paths, probed at build time so a false declaration falls back rather than
|
|
51
|
+
returning a wrong loss.
|
|
52
|
+
- `candidate_autocast`: BF16 candidate arithmetic with parameters at their own dtype.
|
|
53
|
+
- `use_quadratic_model` and `trust_region` run at `num_probe=1`, reading curvature from
|
|
54
|
+
`L(+s) + L(-s) - 2 L(0)` with one shared `f(X)` per particle.
|
|
55
|
+
- `solver='kl_softmax'`, interpolating softmax (`kl_softmax_lam=0`) and Sinkhorn (`inf`).
|
|
56
|
+
- `AdaptiveSubspace.rotation_interval`, `NNCostEvaluator(per_sample=True)` and
|
|
57
|
+
`get_diagnostics()` (`ess`, `rho`, `evals` per iteration).
|
|
58
|
+
- `examples/10_cnn_mnist.py` (LeNet-5) and `examples/11_transformer_selective_copy.py`,
|
|
59
|
+
both forward-pass only, with `--compare` against the materializing path.
|
|
60
|
+
|
|
61
|
+
### Fixed
|
|
62
|
+
|
|
63
|
+
- A model computing in two dtypes raised in subspace mode: every per-layer projection was
|
|
64
|
+
built at the layout's `dominant_dtype`, and `SiteVmapEvaluator` cast inputs to the
|
|
65
|
+
perturbed layer's dtype rather than the model's. `HybridSubspace`, `LinearSubspace` and
|
|
66
|
+
`LowRankSubspace` now train such a model at each parameter's own dtype; full space and
|
|
67
|
+
`AdaptiveSubspace` raise at construction naming the fix.
|
|
68
|
+
- Fast-path selection accepted models it scored wrong: the bmm plan deduplicated shared
|
|
69
|
+
modules, loss subclasses and forward hooks were skipped, tied-weight detection merged a
|
|
70
|
+
square parameter with its transpose, aliasing was keyed on the first element's address,
|
|
71
|
+
frozen parameters raised `KeyError` or dropped a bias, `ParamLayout` merged differently
|
|
72
|
+
shaped views of one buffer, and `register_evaluator` and `reset_vmap()` left stale
|
|
73
|
+
plans. All decline or rebuild now.
|
|
74
|
+
- Numerics: the Sinkhorn and KL-softmax plans overflowed to `inf` after a dual
|
|
75
|
+
divergence, giving NaN parameters; `sanitize_cost` overflowed its own penalty near
|
|
76
|
+
dtype max; the in-place path's FP32 loss buffer collapsed distinct FP64 candidates;
|
|
77
|
+
site-aware chunks ignored `candidate_autocast`; the CMA clamp ran before renormalizing.
|
|
78
|
+
- Basis changes leaked state: rotations left velocity, the Newton direction and the
|
|
79
|
+
transport EMA in the replaced basis; `HybridSubspace` kept displacement history across
|
|
80
|
+
one and rotated at step 0; a rank transition never rebuilt the fused projection, so
|
|
81
|
+
`_fused_P` kept the old rank's width; `RankSchedule` applied stage 0 late and omitted
|
|
82
|
+
`_applied_rank` from `state_dict`; `_write_params` wrote a stale buffer snapshot.
|
|
83
|
+
- Step accounting: block-wise averaged loss, ESS and rho over blocks rather than
|
|
84
|
+
candidates; the trust-region ratio compared across a rotation and a minibatch and
|
|
85
|
+
double-shrank; `adaptive_probes` could latch permanently and reused rows across a
|
|
86
|
+
covariance change; `adaptive_num_probe` left a stale Newton direction;
|
|
87
|
+
`multifidelity_screen` billed stage one at the wrong fidelity.
|
|
88
|
+
- Solvers: `MinCostGreedySolver` stepped on an all-infeasible row; `TopKMeanSolver` gave
|
|
89
|
+
mass to masked vertices; `KLSoftmaxSolver` built its plan one dual update behind and
|
|
90
|
+
never converged below the working dtype's smallest normal; `SinkhornSolver` reset
|
|
91
|
+
non-finite duals silently; the low-level `PolyStep` froze at one particle and left
|
|
92
|
+
diagnostics empty.
|
|
93
|
+
- Config that silently did nothing: `LinearEpsilon` accepted a negative `decay`,
|
|
94
|
+
`max_subspace_dim` was not a bound, `RankSchedule` rebuilt the subspace every step,
|
|
95
|
+
`ProgressiveEpsilon` never advanced outside the monolithic step, explicit CMA rates
|
|
96
|
+
were discarded, `svd_ratio=0` still forced an SVD, and `TernaryLinear`'s default
|
|
97
|
+
threshold returned a constant.
|
|
98
|
+
- `examples/05` annealed epsilon to 0.1, which concentrates the plan toward argmax so the
|
|
99
|
+
effective step grows to the full `step_radius` as `step_radius` anneals; the last epoch
|
|
100
|
+
diverged on every seed. `examples/10` built its schedules positionally, so its radii
|
|
101
|
+
were constant.
|
|
102
|
+
- Crashes and unclear failures now raise, warn or decline: empty cost matrices, tiny
|
|
103
|
+
solver temperatures, an evaluator left alive after an exception in `train()`, NaN
|
|
104
|
+
fitness in `PolyStepES.tell`, `torch.set_default_device`, vector-only models, fp16 CPU
|
|
105
|
+
and CUDA `particle_dim > 2` rotation paths, a singular biased-rotation frame, and an
|
|
106
|
+
unprojected weight site.
|
|
107
|
+
|
|
108
|
+
### Performance
|
|
109
|
+
|
|
110
|
+
- `SubspaceDeltaEvaluator` reads the projection as a strided view instead of gathering
|
|
111
|
+
and transposing a column block per chunk, and batches over columns rather than `d_out`.
|
|
112
|
+
Losses bit-identical.
|
|
113
|
+
- `HybridSubspace.init_projections` memoizes the step-0 basis, so an absorb re-anchors
|
|
114
|
+
the origin without redrawing it. The QR was 30% of the step under the default
|
|
115
|
+
`absorb_mode='stagnation'`.
|
|
116
|
+
- Haar rotations pick QR or Householder reflections by batch size, each on the side of
|
|
117
|
+
the crossover where it wins.
|
|
118
|
+
- Chunks break at parameter and coordinate boundaries, so the site-aware paths stop
|
|
119
|
+
falling back mid-sweep, and block-wise mode reaches the delta and factored evaluators.
|
|
120
|
+
- The non-differentiable model zoo takes the batched paths; rate-coded SNNs compute the
|
|
121
|
+
static input current once instead of per timestep, bit-identical.
|
|
122
|
+
- `SparseDeltaEvaluator` takes candidates grouped by particle, so each gather runs once
|
|
123
|
+
per group. `mixed_precision=True` no longer disables the subspace-delta path.
|
|
124
|
+
- The block-diagonal fuse is capped at 32 MB and skips a single dense block, past which
|
|
125
|
+
the padding read costs more than the launches saved.
|
|
126
|
+
- Every example pins a thread count below `nproc`, where the OpenMP spin-wait takes over.
|
|
127
|
+
Ten pin 1; `07` pins 8, its own objective being wide enough to pay for the pool.
|
|
128
|
+
- Smaller: cached per-chunk casts and projections, one `sanitize_cost` per step,
|
|
129
|
+
candidates written straight into layout order, `torch._foreach_copy_` weight restore,
|
|
130
|
+
bias folded into `baddbmm`, batched sparse chunk projection, and a Haar guard that no
|
|
131
|
+
longer forces `dim-1` host syncs on CUDA.
|
|
132
|
+
|
|
133
|
+
## 0.9.0 - 2026-07-27
|
|
134
|
+
|
|
135
|
+
### Added
|
|
136
|
+
|
|
137
|
+
- `SparseDeltaEvaluator`: a full-space candidate replaces `particle_dim` scalars, so its
|
|
138
|
+
output differs from the base in at most that many columns. Propagates the delta through
|
|
139
|
+
elementwise layers and goes dense one `Linear` past the perturbed one, instead of a
|
|
140
|
+
dense `bmm` over `N` weight sets. Automatic for `nn.Sequential` of `Linear` and
|
|
141
|
+
elementwise layers, with a per-chunk fallback.
|
|
142
|
+
- `FactoredSubspace`: perturbs each 2D parameter by `dW = A @ B` with `B` fixed, so
|
|
143
|
+
`x (W + A B)^T = x W^T + (x B^T) A^T` and no candidate weight is built. Not the
|
|
144
|
+
default: the fixed `B` confines perturbations to `rank` input directions.
|
|
145
|
+
- `step(..., objective_token=...)` marks which objective a step measures, so cost rows
|
|
146
|
+
cannot be reused across minibatches. `release_evaluator()`, `resync_from_model()`,
|
|
147
|
+
`MinCostGreedySolver` and `TopKMeanSolver` are public.
|
|
148
|
+
- `MANIFEST.in`, so the sdist ships a runnable test suite. CI runs it from the tarball.
|
|
149
|
+
|
|
150
|
+
### Fixed
|
|
151
|
+
|
|
152
|
+
- `api.train()` never called `register_evaluator`, the only assignment of
|
|
153
|
+
`_cost_evaluator`, `_fused_inputs`, `_factored_evaluator` and
|
|
154
|
+
`_sparse_delta_evaluator`, so every fast evaluation path was unreachable through the
|
|
155
|
+
training API.
|
|
156
|
+
- Cost-row reuse under `adaptive_probes` was per particle, but a candidate is the whole
|
|
157
|
+
configuration with one row replaced, so a stagnant particle's row was still measured
|
|
158
|
+
against every other particle's position. Reuse is now all or nothing.
|
|
159
|
+
- `multifidelity_screen` averaged direction contrast across particles, which carry
|
|
160
|
+
independent rotations, so the kept set was chosen at random. Ranked per particle now.
|
|
161
|
+
- Tied weights raised on both evaluation paths. `batch_unflatten` emits the canonical key
|
|
162
|
+
only; `unflatten` still carries aliases for `load_state_dict`.
|
|
163
|
+
- `restore_best` keyed on the OT cost when no callback was registered, restoring the step
|
|
164
|
+
with the cheapest probe cloud rather than the best weights. It also snapshots to CPU.
|
|
165
|
+
- Chunk size ignored the weight set a non-fused subspace builds per candidate and the
|
|
166
|
+
vmap activations. A 120K-param step peaked at 1661 MB in full space and 10565 MB in
|
|
167
|
+
subspace mode; now 946 MB and 1461 MB.
|
|
168
|
+
- `AdaptiveSubspace` and per-layer rotation replaced the projection while the coordinates
|
|
169
|
+
were non-zero, moving the weights with nothing evaluated behind them.
|
|
170
|
+
- `CMAAdaptiveSubspace` never rotated or absorbed, left its hyperparameters at `0.0` so
|
|
171
|
+
the covariance update was inert, normalized the evolution paths by `sigma` instead of
|
|
172
|
+
`step_radius` so `C_diag` decayed to its floor, carried state through basis rotations
|
|
173
|
+
untransformed, and accumulated at bf16 where `1 + c_1*x` rounds back to `1`.
|
|
174
|
+
- Anderson acceleration subtracted `dX` instead of `dX + dR`, so `anderson_depth > 0`
|
|
175
|
+
converged slower than no acceleration.
|
|
176
|
+
- `num_particles = 1` froze the optimizer: balanced OT with one row forces a uniform
|
|
177
|
+
plan. The default now selects `SoftmaxSolver` and warns.
|
|
178
|
+
- `scale_cost` divided before recentering, so adding a constant to every cost changed the
|
|
179
|
+
plan. `SinkhornSolver` also reported `ent_reg_cost` in the wrong frame; the plan and
|
|
180
|
+
duals were unaffected. The greedy and top-k solvers had the same reporting bug.
|
|
181
|
+
- Softmax paths recentred by the global minimum, so a row above it underflowed to `-inf`
|
|
182
|
+
and returned NaN; the fused kernel did not recenter at all.
|
|
183
|
+
- `sanitize_cost` mapped `-inf` to the worst finite value instead of the best, and used
|
|
184
|
+
an absolute penalty that entered the `scale_cost='mean'` reduction.
|
|
185
|
+
- `state_dict` captured only `SolverState`, so resume was not exact for `amortize_steps`,
|
|
186
|
+
`adaptive_probes`, `trust_region` or `HybridSubspace`. Format is 3; format 2 loads with
|
|
187
|
+
a warning.
|
|
188
|
+
- A stagnation absorb never cleared `stagnation_count`, redrawing the basis every step on
|
|
189
|
+
a plateau. `resync_from_model()` left momentum and rotation state on the old anchor.
|
|
190
|
+
- Probe losses accumulated in FP32 unconditionally, so a float64 objective whose costs
|
|
191
|
+
differ below FP32 resolution produced a constant cost matrix.
|
|
192
|
+
- Block-wise biased rotation raised on bf16 under `mixed_precision=True`. A zero descent
|
|
193
|
+
direction produced a singular rotation collapsing `+e0` and `-e0`.
|
|
194
|
+
- Smaller: the in-place evaluator dropped unknown parameter keys silently; the
|
|
195
|
+
trust-region ratio scored a Newton step that was never applied; displacement history
|
|
196
|
+
mixed bases; randomized PCA drew from the global RNG; rank transitions dropped
|
|
197
|
+
`HybridSubspace` settings; `data_dependent_init` had the wrong sign; a float64 model
|
|
198
|
+
with a subspace raised; a frozen model gave an opaque error; `pyproject.toml` declared
|
|
199
|
+
`torch>=2.4` where 0.2.0 announced `torch>=2.8`.
|
|
200
|
+
|
|
201
|
+
### Changed
|
|
202
|
+
|
|
203
|
+
- `adaptive_probes` and `adaptive_num_probe` default on under
|
|
204
|
+
`block_strategy='monolithic'`.
|
|
205
|
+
- `use_csa` is refused with a warning: the OT step is a deterministic descent direction,
|
|
206
|
+
so CSA reads agreement as the normal state and `sigma` grows without bound.
|
|
207
|
+
- `multifidelity_screen` cuts the forward budget instead of only reweighting, and runs
|
|
208
|
+
only
|
|
209
|
+
when `screen_fidelity/num_probe + screen_keep_ratio < 1`. Needs a cheap closure from
|
|
210
|
+
`screen_closure_from()`; `api.train` supplies one.
|
|
211
|
+
- Block-wise warns for options it ignores; `grouped` warns when combined with a subspace.
|
|
212
|
+
`LinearSubspace` amplifies rather than dilutes the perturbation, as its docstring said.
|
|
213
|
+
- `_step_blockwise` claimed blockwise cuts OT cost from `O(N^2)` to `O(N^2/L)`. The
|
|
214
|
+
forward count is identical; `L` solves over `P/L` rows replace one over `P`.
|
|
215
|
+
- Removed `get_sobol_rotation_matrices`, `HybridSubspace.reconstruct_base` and
|
|
216
|
+
`reconstruct_batch_delta`: no callers, no coverage.
|
|
217
|
+
|
|
218
|
+
### Performance
|
|
219
|
+
|
|
220
|
+
- `thin_qr` pins basis construction to one thread: `HybridSubspace` setup for the MNIST
|
|
221
|
+
example drops from 27.0 s to 97.8 ms. `apply_biased_rotation` uses modified
|
|
222
|
+
Gram-Schmidt instead of `torch.linalg.qr`, 7-33x faster at these sizes. Compiled
|
|
223
|
+
kernels take radius and epsilon as 0-d tensors, so a scheduled radius no longer
|
|
224
|
+
recompiles every step.
|
|
225
|
+
- Example 06 on an RTX 5090: 1024 s to 845 s, shift-recovery +9.3 pp to +10.7 pp.
|
|
226
|
+
|
|
227
|
+
### Examples
|
|
228
|
+
|
|
229
|
+
- Examples pin CPU threads, check optional imports before use instead of failing after
|
|
230
|
+
the run, and register the evaluator in hand-rolled loops. `python-sat` moved into the
|
|
231
|
+
`examples` extra, which example 04 needs.
|
|
232
|
+
|
|
233
|
+
## 0.8.0 - 2026-07-24
|
|
234
|
+
|
|
235
|
+
### Added
|
|
236
|
+
|
|
237
|
+
- `compile_forward` on `NNCostEvaluator` / `PolyStepOptimizer`: CUDA-graphs the
|
|
238
|
+
forward+loss on the in-place path and replays it per candidate. Rescues the
|
|
239
|
+
memory-forced path; does not beat compiled vmap. See `docs/performance.md`.
|
|
240
|
+
- `TrainConfig(restore_best=True)` (default): restore the lowest-loss weights before
|
|
241
|
+
`train()` returns.
|
|
242
|
+
- `PolyStepOptimizer.state_dict()` / `load_state_dict()` for checkpoint-resume (solver
|
|
243
|
+
state, `ProgressiveEpsilon` internals, RNG). Model weights save separately.
|
|
244
|
+
|
|
245
|
+
### Changed
|
|
246
|
+
|
|
247
|
+
- `compile_evaluator` / `compile_forward` propagate to a registered evaluator via
|
|
248
|
+
`register_evaluator`, so they work outside `api.train()`.
|
|
249
|
+
- `train()` skips the extra per-step full-batch forward and its host syncs when no
|
|
250
|
+
callback consumes per-step metrics.
|
|
251
|
+
|
|
252
|
+
### Fixed
|
|
253
|
+
|
|
254
|
+
- Standalone `PolyStep` accepts any epsilon scheduler (`CosineEpsilon`,
|
|
255
|
+
`ProgressiveEpsilon`), not only `LinearEpsilon` (was `TypeError`).
|
|
256
|
+
- `mixed_precision=True` builds the `ParamLayout` after the BF16 cast, so full-space
|
|
257
|
+
candidate params are BF16 (were FP32).
|
|
258
|
+
- `evaluate_subspace_inplace` casts float inputs to the param dtype, fixing a mismatch
|
|
259
|
+
under `mixed_precision` + `HybridSubspace` + FP32 inputs.
|
|
260
|
+
- `cost_batch_size` subsampling keeps the seeded RNG and compares the full device on a
|
|
261
|
+
mismatch (was non-deterministic; fixes a `cuda:0`/`cuda:1` `randperm` crash).
|
|
262
|
+
- `PolyStep.run()` accepts a 1-D initial point (normalized to one particle).
|
|
263
|
+
- `ProgressiveEpsilon` no longer inflates epsilon every step under a fixed-iteration
|
|
264
|
+
Sinkhorn solver (`threshold<=0`).
|
|
265
|
+
- Greedy solvers: `MinCostGreedySolver` sanitizes non-finite costs; `TopKMeanSolver`
|
|
266
|
+
rejects `k=0`.
|
|
267
|
+
- `PolyStep.step()` uses the shared `sanitize_cost` (was an inline copy that synced and
|
|
268
|
+
could overflow the penalty to `inf`); `state.costs` records the raw mean cost.
|
|
269
|
+
- `HybridSubspace` docstring corrected (QR-orthonormal columns; `step_radius` retune).
|
|
270
|
+
- `NNCostEvaluator` compile docstrings match the shipped modes; the vmap-fallback filter
|
|
271
|
+
matches functorch markers instead of any "batched" error.
|
|
272
|
+
|
|
273
|
+
## 0.7.0 - 2026-07-20
|
|
274
|
+
|
|
275
|
+
### Added
|
|
276
|
+
|
|
277
|
+
- On PyPI now: `pip install polystep` (or `uv add polystep`).
|
|
278
|
+
|
|
279
|
+
### Changed
|
|
280
|
+
|
|
281
|
+
- Wider install range: `torch >= 2.4` (was 2.10) and `numpy >= 1.24` (was 2.0).
|
|
282
|
+
- Blockwise and subspace-blockwise steps reuse one buffer for the per-chunk configs
|
|
283
|
+
instead of reallocating it every chunk, so a step allocates less.
|
|
284
|
+
|
|
285
|
+
### Fixed
|
|
286
|
+
|
|
287
|
+
- Solvers stay accurate when the cost matrix has a large constant offset (`|C|` far above
|
|
288
|
+
`epsilon`). The cost is recentered before the log-domain math, which leaves the
|
|
289
|
+
transport plan unchanged, so `SinkhornSolver.matrix`, `KLSoftmaxSolver(lam=0)`, and the
|
|
290
|
+
softmax solvers no longer lose precision or return NaN in that regime.
|
|
291
|
+
- `SinkhornSolver` computes `ent_reg_cost` from the actual plan mass, so it is correct
|
|
292
|
+
before convergence too, and it warns when the two marginals do not sum to the same
|
|
293
|
+
total.
|
|
294
|
+
- A 1D biased rotation returns the identity instead of flipping the axis it just aligned
|
|
295
|
+
(`SO(1)` cannot represent a reflection).
|
|
296
|
+
|
|
297
|
+
## 0.6.1 - 2026-07-15
|
|
298
|
+
|
|
299
|
+
### Added
|
|
300
|
+
|
|
301
|
+
- `examples/09_hard_decision_tree.py`: a hard oblique decision tree with strict argmax
|
|
302
|
+
routing and no relaxation, trained on an XOR checkerboard. PolyStep optimizes the hard
|
|
303
|
+
tree directly while OpenAI-ES and SPSA stall on its piecewise-constant loss; a
|
|
304
|
+
soft-tree Adam baseline is scored after hardening.
|
|
305
|
+
|
|
306
|
+
### Changed
|
|
307
|
+
|
|
308
|
+
- The HybridSubspace fused block-diagonal projection is rebuilt only when the projection
|
|
309
|
+
rotates. With the default `rotation_interval=0` the projection is static, so the
|
|
310
|
+
previous per-step `block_diag` rebuild was pure overhead.
|
|
311
|
+
|
|
312
|
+
### Fixed
|
|
313
|
+
|
|
314
|
+
- `mixed_precision=True` runs end to end. The barycentric and fused-softmax projections
|
|
315
|
+
cast the FP32 OT weights to the BF16 geometry dtype, `HybridSubspace` runs its
|
|
316
|
+
projection QR in FP32 (no BF16 CPU `geqrf`), and the cost evaluator matches float
|
|
317
|
+
inputs to the parameter dtype. Previously the first step raised a dtype mismatch.
|
|
318
|
+
- The barycentric projection normalizes by the realized transport row sum instead of the
|
|
319
|
+
target marginal, so an unconverged Sinkhorn plan gives a translation-invariant step.
|
|
320
|
+
The softmax path is unchanged.
|
|
321
|
+
- Full-space monolithic steps bound the default evaluation chunk to a fixed memory
|
|
322
|
+
budget, avoiding the `O(n_params^2)` config buffer that ran out of memory at the
|
|
323
|
+
default `chunk_size=None`.
|
|
324
|
+
- `apply_biased_rotation` realigns the first search axis with the requested bias (QR
|
|
325
|
+
fixes the column only up to sign), so the biased search points toward descent rather
|
|
326
|
+
than ascent, and runs its QR and determinant in FP32 for BF16 inputs.
|
|
327
|
+
|
|
328
|
+
## 0.6.0 - 2026-07-12
|
|
329
|
+
|
|
330
|
+
### Added
|
|
331
|
+
|
|
332
|
+
- `examples/08_direct_loss_minimization.py`: direct F1 minimization on an imbalanced
|
|
333
|
+
checkerboard where PolyStepES beats both a biased gradient (Adam+STE) and OpenAI-ES.
|
|
334
|
+
- `experiments/runners/variant_sweep.py`: a reproducible sweep of the optimizer variants
|
|
335
|
+
(solver, representation, block strategy, adaptation flags, schedules, geometry) across
|
|
336
|
+
nine tasks, ranked by forward-eval budget with a state-mutation self-check that flags
|
|
337
|
+
any variant whose state never changed.
|
|
338
|
+
|
|
339
|
+
### Changed
|
|
340
|
+
|
|
341
|
+
- CMA covariance updates use the covariance-metric offspring step `y = sqrt(C_diag) * z`,
|
|
342
|
+
so the evolution paths and rank-mu match the covariance-scaled sampling.
|
|
343
|
+
- AdaptiveSubspace runs the per-step basis QR on the GPU in fp32 for CUDA targets,
|
|
344
|
+
several times faster than the previous CPU round-trip for large models.
|
|
345
|
+
|
|
346
|
+
### Fixed
|
|
347
|
+
|
|
348
|
+
- `SinkhornSolver` rejects `max_iterations < 1` and validates user-supplied marginals
|
|
349
|
+
(finite, nonnegative, positive mass) instead of clamping to an infeasible plan.
|
|
350
|
+
- `PolyStepES` validates `dim`, `num_particles`, `epsilon`, and `step_radius` at
|
|
351
|
+
construction.
|
|
352
|
+
- CMA covariance and step-size adaptation are disabled with a warning under
|
|
353
|
+
non-monolithic block strategies, where they previously never updated.
|
|
354
|
+
- `trust_region` now records its predicted improvement independent of `biased_rotation`,
|
|
355
|
+
so the step-radius multiplier adapts whenever `use_quadratic_model` and
|
|
356
|
+
`num_probe >= 2` hold instead of staying frozen at 1.0. `trust_region` also
|
|
357
|
+
auto-enables the quadratic model.
|
|
358
|
+
- Heaviside stall detection uses the correct generation index (`generation + 1`).
|
|
359
|
+
|
|
360
|
+
### Removed
|
|
361
|
+
|
|
362
|
+
- Dead `compute_ot_weights` export; the rank-mu path uses the transport masses directly.
|
|
363
|
+
|
|
364
|
+
## 0.5.0 - 2026-07-07
|
|
365
|
+
|
|
366
|
+
### Added
|
|
367
|
+
|
|
368
|
+
- `PolyStepES` and `minimize`: an ask/tell interface so PolyStep drops into
|
|
369
|
+
evolution-strategy harnesses (evosax, NeuroEvoBench) as a gradient-free optimizer.
|
|
370
|
+
- `examples/07_binary_net_no_ste.py`: STE-free binary-net training, PolyStepES vs
|
|
371
|
+
OpenAI-ES.
|
|
372
|
+
- `experiments/bench_ask_tell.py`: ask/tell comparison against a Gaussian ES on the
|
|
373
|
+
synthetic suite.
|
|
374
|
+
|
|
375
|
+
### Changed
|
|
376
|
+
|
|
377
|
+
- CMA covariance rank-mu now estimates per-coordinate variance from the
|
|
378
|
+
transport-weighted polytope vertices, and drops the per-step scatter allocation.
|
|
379
|
+
- CSA calibrates the step-size against the running norm of the evolution path instead of
|
|
380
|
+
sqrt(n), so sigma no longer collapses on OT-scale steps.
|
|
381
|
+
- Anderson acceleration uses a Tikhonov ridge solve and only accepts steps that do not
|
|
382
|
+
regress the dual objective.
|
|
383
|
+
- Newton refinement keeps its step only when the quadratic model says it beats the plain
|
|
384
|
+
OT step.
|
|
385
|
+
- Amortized momentum coasts along the OT step, not the OT-plus-momentum step.
|
|
386
|
+
|
|
387
|
+
### Fixed
|
|
388
|
+
|
|
389
|
+
- Multi-fidelity screening no longer feeds its own dampened cost back in, which had
|
|
390
|
+
permanently starved low-contrast directions.
|
|
391
|
+
- Sinkhorn fixed-iteration mode reports convergence on a finite result, so
|
|
392
|
+
ProgressiveEpsilon stops inflating epsilon.
|
|
393
|
+
- `adaptive_omega` no longer erases the divergence back-off within the same check.
|
|
394
|
+
- Guarded the barycentric projection against a zero source marginal.
|
|
395
|
+
|
|
396
|
+
## 0.4.0 - 2026-06-21
|
|
397
|
+
|
|
398
|
+
### Added
|
|
399
|
+
|
|
400
|
+
- `solvers/_shared.py`: one set of shared solver helpers (FP32 promotion, device-side
|
|
401
|
+
non-finite cost sanitization, device/dtype-aligned marginals and warm-start duals) used
|
|
402
|
+
by the Sinkhorn, softmax, and tempered-softmax solvers.
|
|
403
|
+
- `experiments/scripts/bench_eps_rescale.py` and `tests/test_input_validation.py`.
|
|
404
|
+
|
|
405
|
+
### Changed
|
|
406
|
+
|
|
407
|
+
- Fused-softmax now honors every `scale_cost` mode (`'mean'/'max_cost'/float/None`),
|
|
408
|
+
matching `SoftmaxSolver`; the compiled kernel no longer scales internally.
|
|
409
|
+
- `TemperedSoftmaxSolver` gains FP32 promotion, non-finite handling, and an
|
|
410
|
+
autocast-disabled softmax (previously absent).
|
|
411
|
+
- Warm-start dual re-centering uses the valid gauge `f -> f+c, g -> g-c` instead of
|
|
412
|
+
independent mean subtraction, which perturbed the iterate under `omega != 1`.
|
|
413
|
+
- `BatchedLinearEvaluator` applies the real activation modules (exact
|
|
414
|
+
`LeakyReLU.negative_slope`, `GELU.approximate`) rather than hardcoded functional
|
|
415
|
+
defaults; non-default `Flatten` falls back to vmap.
|
|
416
|
+
- Low-level `PolyStep.step()` forwards `init_eps` like the optimizer path.
|
|
417
|
+
|
|
418
|
+
### Removed
|
|
419
|
+
|
|
420
|
+
- Low-rank Sinkhorn (`SinkhornSolver.{rank,gamma,auto_rank_threshold}`,
|
|
421
|
+
`_solve_low_rank`, `SinkhornResult.{_Q,_R,_g_lr}`, the `solve(seed=...)` arg, and the
|
|
422
|
+
`rank` params on `PolyStepOptimizer` / `PolyStep`).
|
|
423
|
+
|
|
424
|
+
### Fixed
|
|
425
|
+
|
|
426
|
+
- Re-validate `epsilon > 0` inside `SinkhornSolver.solve()` (schedules mutate
|
|
427
|
+
it); reject `check_every < 1`, `num_probe < 1`, and `scale_cost` of 0 / non-finite.
|
|
428
|
+
- Mezzadri rotation sign correction treats `sign(0)` as `+1` so an underflowed QR
|
|
429
|
+
diagonal can't null a column; dropped a redundant `Q.clone()`.
|
|
430
|
+
|
|
431
|
+
## 0.3.0 - 2026-05-27
|
|
432
|
+
|
|
433
|
+
Cleanup. No public API changes.
|
|
434
|
+
|
|
435
|
+
### Changed
|
|
436
|
+
|
|
437
|
+
- `solvers/sinkhorn.py`: dropped two GPU->CPU syncs per Sinkhorn solve. `cost_scale` (the
|
|
438
|
+
warm-start `clamp_` bound) stays on-device as a 0-d
|
|
439
|
+
tensor, and `ent_reg_cost = <f,a> + <g,b>` resolves both inner products
|
|
440
|
+
in one host transfer via `torch.stack([...]).sum().item()`. Same pattern applied to the
|
|
441
|
+
low-rank convergence loop and its `err_a/err_b` marginal transfers.
|
|
442
|
+
- `_step_blockwise.py`: per-block fused-softmax `ent_cost_tensor.item()` calls now defer
|
|
443
|
+
to a single `torch.stack([...]).sum().item()` after the per-block loop, mirroring
|
|
444
|
+
`block_disp_terms` and `block_model_loss_terms`. Saves `O(num_blocks)` syncs per step
|
|
445
|
+
in the fused-softmax path.
|
|
446
|
+
- `cma.py`: `update_step_size_csa` now accepts an optional pre-computed `p_sigma_norm`;
|
|
447
|
+
both call sites (`_step_common.update_cma_state`, `_step_monolithic.step`) forward the
|
|
448
|
+
norm they already paid for in `compute_heaviside_sigma`, removing one redundant
|
|
449
|
+
`torch.norm(...).item()` per CMA generation.
|
|
450
|
+
|
|
451
|
+
### Fixed
|
|
452
|
+
|
|
453
|
+
- `CMAAdaptiveSubspace.rotate()` now forwards `transport_matrix`, `X_vertices`, and
|
|
454
|
+
`X_current` to the wrapped `AdaptiveSubspace`, so the `'ot_bias'` rotation mode fires
|
|
455
|
+
when CMA is enabled (it previously fell through to random rotation).
|
|
456
|
+
- `docs/api_overview.md`: the `PolyStepOptimizer.step` snippet used a zero-argument
|
|
457
|
+
lambda; replaced with the real `closure(batched_params) -> losses` signature via
|
|
458
|
+
`NNCostEvaluator`. The `SparseRandomProjection` example used `input_dim` / `output_dim`
|
|
459
|
+
keyword args; the actual constructor takes `full_dim` / `subspace_dim`.
|
|
460
|
+
- Tightened two test tolerances that were 100-1000x looser than the solver's convergence
|
|
461
|
+
threshold (`tests/test_numerical_stress.py` row-sum checks;
|
|
462
|
+
`tests/test_ablation_solvers.py` tempered-softmax-vs-greedy match).
|
|
463
|
+
|
|
464
|
+
## 0.2.2 - 2026-05-21
|
|
465
|
+
|
|
466
|
+
Codebase cleanup, test modernization, and documentation correctness pass.
|
|
467
|
+
|
|
468
|
+
### Breaking
|
|
469
|
+
|
|
470
|
+
- Vertex generator API (`get_orthoplex_vertices`, `get_simplex_vertices`,
|
|
471
|
+
`get_cube_vertices`) signature changed from `(dim, origin, radius)` to
|
|
472
|
+
`(dim, *, radius=1.0)`. The `origin` parameter has been removed - all generators now
|
|
473
|
+
produce unit-radius templates centered at the origin; translation is handled by the
|
|
474
|
+
solver. This affects direct callers only; `PolyStep` and `PolyStepOptimizer` are
|
|
475
|
+
unaffected.
|
|
476
|
+
|
|
477
|
+
### Fixed
|
|
478
|
+
|
|
479
|
+
- `compute_ot_weights` in `cma.py`: replaced linear softmax normalization with
|
|
480
|
+
entropy-based OT weight computation matching the theoretical derivation.
|
|
481
|
+
- `@torch.inference_mode()` migration across `transform.py` (removed unused
|
|
482
|
+
`import warnings`).
|
|
483
|
+
- README and API overview: `max_iteration` -> `max_iterations` (matching the actual
|
|
484
|
+
`PolyStep` dataclass field).
|
|
485
|
+
- API overview: `AdaptiveSubspace` and `LinearSubspace` code snippets updated from stale
|
|
486
|
+
direct constructors to the `from_layout()` factory.
|
|
487
|
+
- API overview: `SolverState(X=X_init)` -> `solver.init_state(X_init)`.
|
|
488
|
+
|
|
489
|
+
## 0.2.1 - 2026-05-14
|
|
490
|
+
|
|
491
|
+
### Added
|
|
492
|
+
|
|
493
|
+
- `examples/06_loihi_snn_polystep.py`: end-to-end skeleton for a Loihi 2-style two-stage
|
|
494
|
+
workflow. Stage 1 pretrains a hard-LIF MNIST SNN with PolyStep using
|
|
495
|
+
`PSTORCH_CONFIGS["snn"]`. Stage 2 adapts only the writable subset a real Loihi 2 chip
|
|
496
|
+
exposes at runtime (`fc2`, per-population `vth`, and `beta`; about 1.3% of model
|
|
497
|
+
parameters) under an `N(0, 1)` Gaussian input shift. Stage 2 uses TENT-style
|
|
498
|
+
safeguards: mixed-batch (half clean / half shifted), rank-8 probing on the writable
|
|
499
|
+
subspace, and two probes per step. Both stages use best-test early stopping (patience
|
|
500
|
+
4) since zeroth-order test curves are noisier. Shifted-test evaluations share a fixed
|
|
501
|
+
seeded noise mask across pre / post / baseline so the reported recovery is a paired
|
|
502
|
+
comparison. On default settings the example reaches ~83% best clean accuracy and a +13
|
|
503
|
+
pp paired shift-recovery over the frozen-readout baseline with near-zero clean-accuracy
|
|
504
|
+
degradation in about 17 minutes on a single GPU. Reported numbers shift by a few
|
|
505
|
+
percentage points run-to-run on CUDA because of non-deterministic cuBLAS reductions,
|
|
506
|
+
but the qualitative recovery is robust. The host loop is backend-agnostic:
|
|
507
|
+
`LoihiSpikeEvaluator` is the single swap point against a Lava `netx` deployment path.
|
|
508
|
+
|
|
509
|
+
## 0.2.0 - 2026-05-14
|
|
510
|
+
|
|
511
|
+
Dependency floor lift, native fast-path adoption, and performance pass for PyTorch 2.12.
|
|
512
|
+
|
|
513
|
+
### Changed
|
|
514
|
+
|
|
515
|
+
- Minimum Python is now 3.11 (was 3.10); minimum PyTorch is now 2.8 (was 2.4); NumPy
|
|
516
|
+
floor is now `>=2.0`.
|
|
517
|
+
- Ruff `target-version` bumped to `py311`. Classifiers updated to advertise 3.11 / 3.12 /
|
|
518
|
+
3.13 / 3.14.
|
|
519
|
+
|
|
520
|
+
### Performance
|
|
521
|
+
|
|
522
|
+
- `SinkhornSolver` convergence loop: batched the per-check scalar measurements (err_a,
|
|
523
|
+
err_b, dual norm, Lyapunov) into a single device-to-host transfer, and switched the
|
|
524
|
+
Anderson-acceleration Lyapunov accept gate to a device-side `torch.where`. Eliminates
|
|
525
|
+
4-7 GPU-CPU syncs per `check_every` interval. Microbench (200 iter, n=m=512, fp32 on
|
|
526
|
+
RTX 5090, full Anderson + adaptive omega): **490 ms -> 48 ms (~10x)**.
|
|
527
|
+
- Block-wise step (`_step_blockwise.py`): per-block displacement and model-loss scalars
|
|
528
|
+
are accumulated as device tensors and reduced to the host once per step instead of
|
|
529
|
+
twice per block.
|
|
530
|
+
- `NNCostEvaluator.evaluate` and `BatchedLinearEvaluator.evaluate` now use
|
|
531
|
+
`@torch.inference_mode()` instead of `@torch.no_grad()` (matches the rest of
|
|
532
|
+
`cost_nn.py`).
|
|
533
|
+
|
|
534
|
+
## 0.1.0 - 2026-05-03 (Initial Release)
|
|
535
|
+
|
|
536
|
+
First public release alongside the arXiv preprint *Training Non-Differentiable Networks
|
|
537
|
+
via Optimal Transport*.
|
|
538
|
+
|
|
539
|
+
### Core
|
|
540
|
+
|
|
541
|
+
- `PolyStepOptimizer` for gradient-free training of any `nn.Module`
|
|
542
|
+
- Softmax solver (default; fast path) and log-domain Sinkhorn solver with warm-started
|
|
543
|
+
dual potentials
|
|
544
|
+
- Polytope sampling (orthoplex, simplex, cube) with random rotations
|
|
545
|
+
- Vectorised NN evaluation via `torch.func` + optional `torch.vmap`
|
|
546
|
+
- High-level `train()` API with callbacks and early stopping
|
|
547
|
+
|
|
548
|
+
### Subspace compression
|
|
549
|
+
|
|
550
|
+
- `LinearSubspace` - random projection
|
|
551
|
+
- `AdaptiveSubspace` - rotating orthogonal projection
|
|
552
|
+
- `HybridSubspace` - per-layer projections (recommended)
|
|
553
|
+
- `CMAAdaptiveSubspace` - CMA-ES covariance adaptation
|
|
554
|
+
- `SparseRandomProjection` - for models with 100M+ parameters
|
|
555
|
+
|
|
556
|
+
### Scalability
|
|
557
|
+
|
|
558
|
+
- Block-wise per-layer OT decomposition
|
|
559
|
+
- Chunked cost evaluation for memory control
|
|
560
|
+
- `torch.compile` support on GPU hot paths
|
|
561
|
+
- Mixed precision (BF16 model + FP32 solver)
|
|
562
|
+
|
|
563
|
+
### Layers
|
|
564
|
+
|
|
565
|
+
- `VmapSafeMultiHeadAttention` - attention compatible with `torch.vmap`
|
|
566
|
+
- `VmapSafeLSTM` - LSTM compatible with `torch.vmap`
|
|
567
|
+
|
|
568
|
+
### Examples and experiments
|
|
569
|
+
|
|
570
|
+
- 5 runnable demos under `examples/` (quickstart, SNN, RL, MAX-SAT, MNIST)
|
|
571
|
+
- Paper-reproduction harness under `experiments/runners/` covering SNN, INT8, argmax,
|
|
572
|
+
staircase, hard MoE, MAX-SAT (100K-1M vars), MNIST, ETTh1 timeseries, RL, and GPT-2
|
|
573
|
+
head-only fine-tuning
|
|
574
|
+
- Ablations: OT vs softmax solver, epsilon / radius / particles / subspace grid, MAX-SAT
|
|
575
|
+
scaling
|
|
@@ -6,8 +6,8 @@ authors:
|
|
|
6
6
|
- family-names: Le
|
|
7
7
|
given-names: An T.
|
|
8
8
|
license: Apache-2.0
|
|
9
|
-
version: 0.
|
|
10
|
-
date-released: "2026-07-
|
|
9
|
+
version: 0.10.0
|
|
10
|
+
date-released: "2026-07-29"
|
|
11
11
|
repository-code: "https://github.com/anindex/polystep"
|
|
12
12
|
keywords:
|
|
13
13
|
- gradient-free optimization
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Contributing to polystep
|
|
2
|
+
|
|
3
|
+
## Getting Started
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
git clone https://github.com/anindex/polystep.git
|
|
7
|
+
cd polystep
|
|
8
|
+
pip install -e ".[dev]"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Development Workflow
|
|
12
|
+
|
|
13
|
+
1. Create a branch from `main`
|
|
14
|
+
2. Make your changes
|
|
15
|
+
3. Run the checks below; they are the lint and test commands CI runs
|
|
16
|
+
4. Submit a pull request
|
|
17
|
+
|
|
18
|
+
## Running the checks
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Lint and formatting, exactly as CI runs them
|
|
22
|
+
ruff check .
|
|
23
|
+
ruff format --check .
|
|
24
|
+
|
|
25
|
+
# Fast tests (recommended during development). The pyproject addopts deselect both
|
|
26
|
+
# the slow and the gpu markers, so neither is collected.
|
|
27
|
+
pytest tests/ -v
|
|
28
|
+
|
|
29
|
+
# Exactly what CI selects, in parallel. About 9s.
|
|
30
|
+
pytest tests/ -m "not slow and not gpu" -n auto
|
|
31
|
+
|
|
32
|
+
# Every test, including the ones marked slow. Those train on MNIST; about 90 s total.
|
|
33
|
+
pytest tests/ -m ""
|
|
34
|
+
|
|
35
|
+
# With coverage
|
|
36
|
+
pytest tests/ --cov=polystep --cov-report=term-missing
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`conftest.py` pins `torch.set_num_threads(1)` for everything except the `slow` tests,
|
|
40
|
+
which train at batch 512 and get `cpu_count() - 8`. The fast tests are small-tensor
|
|
41
|
+
bound, where torch's intra-op pool costs more than it saves; unpinned the suite is both
|
|
42
|
+
slower and erratic, which was tripping the timeout on the Sinkhorn-heavy tests. Serial it
|
|
43
|
+
runs in about 18 s. `POLYSTEP_TEST_THREADS` overrides the fast count.
|
|
44
|
+
|
|
45
|
+
CI runs the fast suite on Python 3.11-3.14 per push, and the `slow` marker nightly (or on
|
|
46
|
+
`workflow_dispatch`), since those legs download MNIST.
|
|
47
|
+
|
|
48
|
+
It also builds the sdist, checks that it collects, and runs `tests/test_api.py` from the
|
|
49
|
+
unpacked tarball. To reproduce that:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
uv build --sdist
|
|
53
|
+
mkdir -p /tmp/sdist && tar xzf dist/*.tar.gz -C /tmp/sdist --strip-components=1
|
|
54
|
+
cd /tmp/sdist && uv pip install --system ".[dev]" && pytest tests/test_api.py -q
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Tests that read or import `experiments/` request the `require_experiments` fixture, which
|
|
58
|
+
skips them outside a repo checkout.
|
|
59
|
+
|
|
60
|
+
## Cutting a release
|
|
61
|
+
|
|
62
|
+
`release.yml` publishes on a GitHub Release through PyPI trusted publishing. It refuses
|
|
63
|
+
to publish unless the tag, `src/polystep/__init__.py`, and `CITATION.cff` all carry the
|
|
64
|
+
same version and `CHANGELOG.md` has a section for it, so update all four together:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# 1. bump __version__ in src/polystep/__init__.py
|
|
68
|
+
# 2. bump version and date-released in CITATION.cff
|
|
69
|
+
# 3. add a "## X.Y.Z - YYYY-MM-DD" section to CHANGELOG.md
|
|
70
|
+
# 4. tag as vX.Y.Z and publish the GitHub Release
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`pyproject.toml` reads the version from `polystep.__version__`, so it needs no edit.
|
|
74
|
+
|
|
75
|
+
## Code Style
|
|
76
|
+
|
|
77
|
+
- Follow existing code conventions
|
|
78
|
+
- Use type hints for public functions
|
|
79
|
+
- Add docstrings with Args/Returns sections for public APIs
|
|
80
|
+
- Keep comments short and specific; explain why, not what
|
|
81
|
+
|
|
82
|
+
The package ships `py.typed`, so annotations on the public surface are part of the
|
|
83
|
+
contract. `mypy src/polystep/` currently reports errors, mostly narrowing complaints on
|
|
84
|
+
the duck-typed `subspace` argument, so it is not yet a CI gate. New code should not add
|
|
85
|
+
to the count.
|
|
86
|
+
|
|
87
|
+
## Reporting Issues
|
|
88
|
+
|
|
89
|
+
Please include:
|
|
90
|
+
- Python and PyTorch versions
|
|
91
|
+
- GPU model (if relevant)
|
|
92
|
+
- Minimal reproduction script
|
|
93
|
+
- Full error traceback
|