polystep 1.0.0__tar.gz → 1.1.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-1.0.0 → polystep-1.1.0}/CHANGELOG.md +53 -2
- {polystep-1.0.0 → polystep-1.1.0}/CITATION.cff +2 -2
- polystep-1.1.0/CONTRIBUTING.md +65 -0
- {polystep-1.0.0 → polystep-1.1.0}/LIMITATIONS.md +70 -24
- {polystep-1.0.0 → polystep-1.1.0}/PKG-INFO +47 -24
- {polystep-1.0.0 → polystep-1.1.0}/README.md +46 -22
- polystep-1.1.0/docs/api_overview.md +527 -0
- {polystep-1.0.0 → polystep-1.1.0}/docs/determinism.md +13 -3
- polystep-1.1.0/docs/performance.md +439 -0
- {polystep-1.0.0 → polystep-1.1.0}/docs/reproducibility.md +34 -11
- {polystep-1.0.0 → polystep-1.1.0}/pyproject.toml +0 -6
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/__init__.py +1 -19
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/_serialization.py +2 -1
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/_step_blockwise.py +8 -6
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/_step_core.py +27 -8
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/_step_momentum.py +0 -2
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/_step_monolithic.py +93 -60
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/adaptive_subspace.py +7 -18
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/api.py +77 -52
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/ask_tell.py +11 -10
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/baselines/core.py +7 -1
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/baselines/methods.py +5 -5
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/benchmarks/rl/gym_evaluator.py +1 -2
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/benchmarks/rl/policies.py +1 -2
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/benchmarks/threads.py +1 -2
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/benchmarks/utils.py +29 -25
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/cost_nn.py +861 -242
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/costs.py +5 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/dynamics.py +1 -1
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/epsilon.py +17 -12
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/factored_subspace.py +7 -7
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/geometry.py +12 -17
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/hybrid_subspace.py +57 -38
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/layers/attention.py +34 -8
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/optimizer.py +186 -68
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/quadratic_model.py +29 -21
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/solver.py +19 -8
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/solvers/_shared.py +34 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/solvers/kl_softmax.py +44 -27
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/solvers/sinkhorn.py +32 -11
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/subspace.py +37 -6
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/transform.py +117 -53
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep.egg-info/PKG-INFO +47 -24
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep.egg-info/requires.txt +0 -1
- {polystep-1.0.0 → polystep-1.1.0}/tests/conftest.py +2 -1
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_ablation_solvers.py +6 -1
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_adaptive_subspace.py +189 -12
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_amortization.py +54 -0
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_api.py +172 -10
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_ask_tell.py +80 -1
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_baseline_fairness.py +3 -27
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_baselines.py +31 -19
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_benchmarks.py +83 -1
- polystep-1.1.0/tests/test_biased_rotation.py +109 -0
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_blockwise.py +10 -3
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_checkpoint_resume.py +193 -8
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_compiled.py +1 -2
- polystep-1.1.0/tests/test_cost_nn.py +1432 -0
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_dfo.py +166 -10
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_epsilon.py +46 -4
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_evaluator_compile_forward.py +3 -2
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_factored_subspace.py +54 -4
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_fastpath_parity.py +31 -19
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_fused_projection.py +32 -0
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_hybrid_subspace.py +100 -7
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_input_validation.py +39 -20
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_integration.py +0 -4
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_kl_softmax.py +117 -0
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_layers.py +161 -12
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_math_guards.py +21 -1
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_mixed_precision.py +72 -7
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_mnist.py +5 -6
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_no_test_set_leakage.py +0 -6
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_nondiff_data.py +20 -13
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_nondiff_models.py +4 -7
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_optimizer.py +158 -14
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_per_sample.py +3 -2
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_probe_jitter.py +1 -11
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_quadratic_model.py +60 -13
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_rl_benchmarks.py +0 -1
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_search_experiments.py +2 -2
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_sequential_fast_path.py +254 -61
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_sinkhorn.py +108 -12
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_sinkhorn_numerics.py +2 -2
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_site_vmap.py +962 -66
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_softmax.py +2 -2
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_softmax_integration.py +0 -13
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_sparse_delta_evaluator.py +182 -4
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_sparse_projection.py +1 -2
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_subspace_blockwise.py +1 -5
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_subspace_contract.py +5 -0
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_subspace_delta.py +34 -0
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_transform.py +241 -8
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_trust_region.py +31 -18
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_tuning_sweep.py +0 -16
- polystep-1.0.0/CONTRIBUTING.md +0 -65
- polystep-1.0.0/docs/api_overview.md +0 -390
- polystep-1.0.0/docs/performance.md +0 -237
- polystep-1.0.0/tests/test_biased_rotation.py +0 -145
- polystep-1.0.0/tests/test_cost_nn.py +0 -609
- {polystep-1.0.0 → polystep-1.1.0}/LICENSE +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/MANIFEST.in +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/setup.cfg +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/_compiled.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/baselines/__init__.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/benchmarks/__init__.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/benchmarks/rl/__init__.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/benchmarks/rl/cartpole.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/benchmarks/rl/metrics.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/blockwise.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/layers/__init__.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/layers/rnn.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/objectives/__init__.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/objectives/base.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/objectives/synthetic.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/projection/__init__.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/projection/sparse.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/py.typed +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/solvers/__init__.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/solvers/base.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/solvers/greedy.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep/solvers/softmax.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep.egg-info/SOURCES.txt +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep.egg-info/dependency_links.txt +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/src/polystep.egg-info/top_level.txt +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/tests/__init__.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_candidate_autocast.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_controlled.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_costs.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_determinism.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_diagnostics.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_dynamics.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_end_to_end_learning.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_geometry.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_numerical_stress.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_objectives.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_practical.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_sparse_integration.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_subspace_gain.py +0 -0
- {polystep-1.0.0 → polystep-1.1.0}/tests/test_timeseries_val_selection.py +0 -0
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.0 - 2026-10-24
|
|
4
|
+
|
|
5
|
+
### Features and performance
|
|
6
|
+
|
|
7
|
+
- Add `step_batch(inputs, targets, loss_fn=...)` for supervised training. It shares
|
|
8
|
+
the fast evaluation paths with `train()`; both accept tuple and dict inputs.
|
|
9
|
+
`register_evaluator` now returns a closure for the registered batch.
|
|
10
|
+
- Add `is_causal` support to `VmapSafeMultiHeadAttention`.
|
|
11
|
+
- Extend fast MLP evaluation to functional losses and traceable custom forwards.
|
|
12
|
+
Reuse unchanged forward work, reduce CPU cross-entropy overhead, and replay
|
|
13
|
+
site-local CUDA forwards with CUDA graphs.
|
|
14
|
+
- Measured full-space MLP steps are 1.9–3.3x faster on one CPU thread and 2.6x on a
|
|
15
|
+
shared RTX 5090 than 1.0.0. Tiny tied-weight models can be slower on CUDA. See
|
|
16
|
+
[performance](docs/performance.md) for the evaluation paths and tradeoffs.
|
|
17
|
+
- Refresh the examples and their metrics, clarify simulated Loihi adaptation, and
|
|
18
|
+
add SNN training and CartPole GIFs. The MAX-SAT example reaches about 98% SAT with
|
|
19
|
+
600 default steps instead of 1500.
|
|
20
|
+
|
|
21
|
+
### Fixes
|
|
22
|
+
|
|
23
|
+
- Restore forward-written buffers between scoring calls and vmap chunks, even on
|
|
24
|
+
failure, and preserve scores that share those buffers. Protect frozen embedding
|
|
25
|
+
weights changed by `max_norm`, reject evaluators for another model, and follow
|
|
26
|
+
model dtype and device changes.
|
|
27
|
+
- Preserve finite-difference gradients and curvature at small probe radii. Correct
|
|
28
|
+
KL zero-support handling and marginal diagnostics, including tiny target masses.
|
|
29
|
+
- Fix CUDA rotation for large adaptive subspaces, carry FP16/BF16 absorb rounding
|
|
30
|
+
error forward, and correct biased rotation, screening, amortized schedules,
|
|
31
|
+
epsilon restarts, half-precision attention, and loss precision in ES and baselines.
|
|
32
|
+
- Fall back to sequential scoring when vmap cannot batch a model, including stock
|
|
33
|
+
`nn.LSTM`.
|
|
34
|
+
|
|
35
|
+
### Upgrade notes
|
|
36
|
+
|
|
37
|
+
- Pass the model to `PolyStepOptimizer`, not `model.parameters()`. Rebuild the
|
|
38
|
+
optimizer if its trainable parameter set changes. Separate parameters or buffers
|
|
39
|
+
that share trainable storage now raise `ValueError`; tie weights by assigning
|
|
40
|
+
the same Parameter, or register a clone.
|
|
41
|
+
- `FactoredSubspace` redraws `B` every 5 OT steps by default; set
|
|
42
|
+
`rotation_interval=0` for the old behavior. Hybrid and factored seeded bases change.
|
|
43
|
+
- `SinkhornSolver` and `KLSoftmaxSolver` default to `threshold=3e-5`, using relative
|
|
44
|
+
marginal error and the dual step divided by epsilon, respectively. Stalled solves
|
|
45
|
+
above the threshold report `converged=False`.
|
|
46
|
+
- `train()` uses `step_batch`; `restore_best` compares weights on the first training
|
|
47
|
+
batch. Seeded results can differ from 1.0.
|
|
48
|
+
- For compiled hybrid/factored runs, rebuild the optimizer from saved model weights:
|
|
49
|
+
checkpoint parameter names no longer contain the `_orig_mod.` prefix. The
|
|
50
|
+
[upgrade guide](docs/api_overview.md#upgrading-from-10) covers all compatibility
|
|
51
|
+
changes; [reproducibility](docs/reproducibility.md#changes-since-the-published-tables)
|
|
52
|
+
lists the affected published results.
|
|
53
|
+
|
|
3
54
|
## 1.0.0 - 2026-10-13
|
|
4
55
|
|
|
5
56
|
### Upgrade notes
|
|
@@ -10,8 +61,8 @@
|
|
|
10
61
|
- Checkpoints use format 6. Rebuild the optimizer from saved model weights when
|
|
11
62
|
upgrading from 0.13; older optimizer checkpoints cannot be loaded.
|
|
12
63
|
- Some low-level exports and experiment helpers moved or were removed. The
|
|
13
|
-
[migration guide](docs/api_overview.md#upgrading-from-013)
|
|
14
|
-
|
|
64
|
+
[migration guide](docs/api_overview.md#upgrading-from-013) covers the main API
|
|
65
|
+
replacements and checkpoint requirements.
|
|
15
66
|
|
|
16
67
|
### Fixes
|
|
17
68
|
|
|
@@ -6,8 +6,8 @@ authors:
|
|
|
6
6
|
- family-names: Le
|
|
7
7
|
given-names: An T.
|
|
8
8
|
license: Apache-2.0
|
|
9
|
-
version: 1.
|
|
10
|
-
date-released: "2026-10-
|
|
9
|
+
version: 1.1.0
|
|
10
|
+
date-released: "2026-10-24"
|
|
11
11
|
repository-code: "https://github.com/anindex/polystep"
|
|
12
12
|
keywords:
|
|
13
13
|
- gradient-free optimization
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
From the repository root, install the development dependencies and run the CI checks:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install -e ".[dev]"
|
|
7
|
+
ruff check .
|
|
8
|
+
ruff format --check .
|
|
9
|
+
pytest tests/ -q -n auto --maxprocesses=4
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
A bug fix should have a small regression test of observable behavior. Extend an
|
|
13
|
+
existing case when it already covers the same path; avoid separate tests for trivial
|
|
14
|
+
defaults, getters, or repeated shapes. Keep public APIs typed. Comments should
|
|
15
|
+
explain constraints, numerical choices, or non-obvious behavior.
|
|
16
|
+
|
|
17
|
+
## Tests
|
|
18
|
+
|
|
19
|
+
The default suite excludes `slow` and `gpu` tests. To select them:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pytest tests/ -m slow
|
|
23
|
+
pytest tests/ -m gpu
|
|
24
|
+
pytest tests/ -m ""
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Capping parallel test workers at four limits startup and memory costs. The fast
|
|
28
|
+
suite uses one CPU thread per worker; `POLYSTEP_TEST_THREADS` overrides it. Slow
|
|
29
|
+
tests use `ncpu - 8` threads, clamped to 2-16 and split across xdist workers, and may
|
|
30
|
+
download datasets. Keep downloads and training-quality checks in the slow suite;
|
|
31
|
+
use synthetic inputs for unit tests.
|
|
32
|
+
|
|
33
|
+
Tests importing `experiments/` must skip when that directory is absent, since it is
|
|
34
|
+
excluded from distributions. Install `pip install -e ".[dev,experiments]"` to run
|
|
35
|
+
tests that need optional experiment dependencies. Missing optional dependencies and
|
|
36
|
+
unavailable hardware are valid reasons to skip a test; unsupported parameter pairs
|
|
37
|
+
should not be collected. Tests run without automatic retries.
|
|
38
|
+
|
|
39
|
+
Pushes and pull requests test Python 3.11 with PyTorch 2.8 and Python 3.14 with
|
|
40
|
+
current PyTorch. Scheduled and manual runs add Python 3.12/3.13, coverage, optional
|
|
41
|
+
experiment dependencies, real-data learning checks, and the shipped tests against
|
|
42
|
+
the installed wheel. Every run builds both distributions and checks the wheel's
|
|
43
|
+
imports and type marker.
|
|
44
|
+
|
|
45
|
+
## Releases
|
|
46
|
+
|
|
47
|
+
Update `polystep.__version__`. Set the same version and the release date in
|
|
48
|
+
`CITATION.cff`. Add a `CHANGELOG.md` entry with the heading `## X.Y.Z - YYYY-MM-DD`.
|
|
49
|
+
`pyproject.toml` reads the package version automatically.
|
|
50
|
+
If the project's maturity changes, update its `Development Status` classifier.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
uv build
|
|
54
|
+
uvx twine check --strict dist/*
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
After the full manual CI workflow passes on the release commit, publish a GitHub
|
|
58
|
+
Release tagged `vX.Y.Z`. The release workflow checks versions, lint, metadata, and
|
|
59
|
+
the installed wheel before publishing. Only the publish job has OIDC permission;
|
|
60
|
+
it uses the `pypi` environment and PyPI Trusted Publishing.
|
|
61
|
+
|
|
62
|
+
## Issues
|
|
63
|
+
|
|
64
|
+
Include a minimal reproduction, the traceback, Python and PyTorch versions, and
|
|
65
|
+
hardware details relevant to the failure.
|
|
@@ -10,41 +10,66 @@ directions; very small projection ratios can fail on large models.
|
|
|
10
10
|
use sparse signed columns, which are not orthonormal. Claims requiring an orthogonal
|
|
11
11
|
projection do not apply to those columns.
|
|
12
12
|
|
|
13
|
-
`FactoredSubspace` restricts
|
|
14
|
-
|
|
13
|
+
`FactoredSubspace` restricts each step to the input directions spanned by its factor
|
|
14
|
+
`B`. It redraws `B` every 5 OT steps by default; with `rotation_interval=0` the whole
|
|
15
|
+
run stays in one set of directions. Retune radii when switching subspaces.
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
direction
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
`
|
|
17
|
+
A basis change clears momentum, the `amortize_steps` coasting direction and the
|
|
18
|
+
`biased_rotation` direction, so these options need a rotation interval longer than one
|
|
19
|
+
step. `AdaptiveSubspace` rotates every step by default, and the optimizer warns when
|
|
20
|
+
any subspace combines these options with `rotation_interval=1`. `FactoredSubspace`
|
|
21
|
+
clears them every 5 OT steps without a warning. Under `amortize_steps`,
|
|
22
|
+
`rotation_interval` and `absorb_interval` count OT steps only.
|
|
23
|
+
|
|
24
|
+
Heavy-ball momentum can amplify the barycentric displacement; `step_radius` bounds the
|
|
25
|
+
displacement before momentum.
|
|
22
26
|
|
|
23
27
|
## Evaluation
|
|
24
28
|
|
|
25
29
|
- `step()` expects `closure(batched_params) -> losses` with shape `(N,)`.
|
|
26
|
-
The candidate tensors may share storage that the next chunk
|
|
27
|
-
any you keep after the closure
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
The candidate tensors may be expanded views or share storage that the next chunk
|
|
31
|
+
overwrites, so treat them as read-only and clone any you keep after the closure
|
|
32
|
+
returns.
|
|
33
|
+
- `step_batch()` and `train()` register each batch. A manual `step(closure)` loop must
|
|
34
|
+
register the evaluator and current batch to use the fast paths. The evaluator's
|
|
35
|
+
loss and the closure must compute the same objective.
|
|
36
|
+
- Multi-input batches use vmap and the site-local path; the batched-matmul, delta and
|
|
37
|
+
factored paths and multifidelity screening need one input tensor.
|
|
38
|
+
- Tied weights must be the same Parameter object, and buffers may not share memory
|
|
39
|
+
with trainable parameters. Register a clone or compute the tensor in forward instead.
|
|
40
|
+
- `NNCostEvaluator`, `step_batch()` and `train()` score candidates in eval mode:
|
|
41
|
+
standard dropout is off and batch-norm layers do not update their running statistics.
|
|
42
|
+
A custom closure that runs the model in train mode compares candidates under
|
|
43
|
+
different dropout masks.
|
|
44
|
+
- The evaluator restores buffers written by the forward, such as a call counter,
|
|
45
|
+
after each batched chunk and each candidate in the sequential and in-place loops.
|
|
46
|
+
Embedding weights changed by `max_norm` are restored even when frozen. Update
|
|
47
|
+
persistent state outside the closure.
|
|
48
|
+
- Writes that leave the tensor version counter unchanged, such as
|
|
49
|
+
`F.batch_norm(training=True)` running statistics or writes through `.data`, are found
|
|
50
|
+
by comparing values after the first forward and the first candidate of each
|
|
51
|
+
sequential, in-place or chunked vmap sweep. A write first triggered after those
|
|
52
|
+
checks can go undetected and persist into later evaluations.
|
|
53
|
+
- Float inputs are cast to the dtype of the model's first floating weight only when
|
|
54
|
+
the forward raises on them as given. The evaluator checks this with one extra
|
|
55
|
+
forward per input dtype and puts back the buffers it changed. The delta and
|
|
56
|
+
factored paths, which accept plain Linear stacks only, always cast.
|
|
33
57
|
- Mixed-dtype models require `HybridSubspace` or `FactoredSubspace`. Full-space and
|
|
34
58
|
global-subspace coordinates use one dtype and reject mixed parameter dtypes.
|
|
35
59
|
- `mixed_precision=True` uses BF16 geometry and forwards; solver costs use FP32.
|
|
36
60
|
Models needing autocast internally must provide it. Half-precision candidate outputs
|
|
37
|
-
reduce in FP32 only under the builtin cross-entropy, MSE and L1 losses
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
61
|
+
reduce in FP32 only under the builtin cross-entropy, MSE and L1 losses; other losses
|
|
62
|
+
may round nearby candidate scores to the same value. With FP16 or BF16 parameters,
|
|
63
|
+
`FactoredSubspace` materializes every candidate, and the fast paths can differ from
|
|
64
|
+
the model's own Linear output by about one unit in the last place (ulp), enough to
|
|
65
|
+
flip a hard threshold. `use_inplace=True` scores with the model's forward. See
|
|
41
66
|
[precision](docs/performance.md#compilation-and-precision).
|
|
42
|
-
- `train()`
|
|
67
|
+
- `step_batch()` and `train()` reject `trust_region`. See
|
|
43
68
|
[radius control](docs/api_overview.md#quadratic-model-and-radius-control) for its
|
|
44
69
|
manual-step use.
|
|
45
70
|
- `trust_region` and `use_adaptive_radius` both scale the step. Choose one; the
|
|
46
71
|
optimizer warns when both are on.
|
|
47
|
-
-
|
|
72
|
+
- Multifidelity screening imputes dropped costs using an additive offset. This can
|
|
48
73
|
bias the full-fidelity cost estimate.
|
|
49
74
|
- Full-space blockwise mode does not support every monolithic adaptation option;
|
|
50
75
|
unsupported options warn at construction.
|
|
@@ -53,14 +78,21 @@ Heavy-ball momentum can amplify the barycentric displacement.
|
|
|
53
78
|
forward neither draws random numbers nor reads the perturbed parameter before
|
|
54
79
|
calling its layer. Attention scores and other temporary tensors inside a module's
|
|
55
80
|
forward are not counted. Set `chunk_size` by hand if such a model runs out of memory.
|
|
81
|
+
Under vmap, CUDA attention materializes the scores of every candidate in the chunk.
|
|
82
|
+
`PolyStepOptimizer(chunk_size=...)` caps the candidates scored per step chunk, and
|
|
83
|
+
`NNCostEvaluator(chunk_size=...)` caps how many of them each vmap call batches.
|
|
84
|
+
- On CUDA the site-local path replays CUDA graphs, which freeze Python-side state the
|
|
85
|
+
forward reads, such as a Python counter or a flag. Pass `compile_forward=False` for
|
|
86
|
+
such a forward.
|
|
56
87
|
- The library does not distribute optimizer state across multiple GPUs.
|
|
57
88
|
|
|
58
89
|
## Attention and recurrent layers
|
|
59
90
|
|
|
60
91
|
[`VmapSafeMultiHeadAttention`](src/polystep/layers/attention.py) requires batch-first
|
|
61
92
|
inputs and `kdim == vdim == embed_dim`. It does not support `add_bias_kv`,
|
|
62
|
-
`add_zero_attn`, returned attention weights
|
|
63
|
-
|
|
93
|
+
`add_zero_attn`, or returned attention weights. `is_causal` is a hint that needs
|
|
94
|
+
`attn_mask`, as in `nn.MultiheadAttention`. It returns an output tensor, not an
|
|
95
|
+
`(output, weights)` tuple.
|
|
64
96
|
|
|
65
97
|
Boolean masks use `True` for masked positions; float masks are additive. Attention
|
|
66
98
|
masks accept `(query, key)`, `(batch * heads, query, key)`, and broadcastable 4D shapes.
|
|
@@ -68,6 +100,19 @@ The wrapper also accepts `(batch, query, key)` masks.
|
|
|
68
100
|
|
|
69
101
|
CPU vmap may emit a batching-fallback warning.
|
|
70
102
|
|
|
103
|
+
Stock `nn.MultiheadAttention` and `nn.TransformerEncoderLayer` work, but vmap has no
|
|
104
|
+
batching rule for their fused kernels, so it runs them once per candidate and PyTorch
|
|
105
|
+
prints a performance-drop warning. On CUDA this makes a step several times slower, and
|
|
106
|
+
the evaluator warns and points to `VmapSafeMultiHeadAttention`. On CPU stock
|
|
107
|
+
`nn.MultiheadAttention` measured faster than `VmapSafeMultiHeadAttention`, so the
|
|
108
|
+
evaluator stays silent there.
|
|
109
|
+
|
|
110
|
+
Stock `nn.LSTM`, `nn.GRU` and `nn.RNN` have no vmap batching rule, so the evaluator
|
|
111
|
+
scores them one candidate at a time in a sequential loop, with a warning. On a small
|
|
112
|
+
CPU model, `NNCostEvaluator(..., use_inplace=True)` ran 1.1 to 1.3 times faster than
|
|
113
|
+
the loop, and `VmapSafeLSTM` 3 to 5 times faster. `step_batch()` and `train()` build
|
|
114
|
+
their own evaluator, so they take the loop for these layers.
|
|
115
|
+
|
|
71
116
|
[`VmapSafeLSTM`](src/polystep/layers/rnn.py) requires batch-first dense inputs. It does
|
|
72
117
|
not support bidirectional recurrence, projection, or `PackedSequence`. Its explicit
|
|
73
118
|
cell computations can be slower than fused cuDNN recurrence.
|
|
@@ -80,7 +125,7 @@ Independent particle rotations mean Sinkhorn's shared column index does not iden
|
|
|
80
125
|
a common direction across particles.
|
|
81
126
|
|
|
82
127
|
Sinkhorn requires `omega` in `[0.5, 1.95]`. Anderson acceleration and adaptive omega
|
|
83
|
-
are inactive in fixed-iteration mode (`threshold <= 0`).
|
|
128
|
+
are inactive in fixed-iteration mode (`threshold <= 0`). Sinkhorn and KLSoftmax promote
|
|
84
129
|
FP16/BF16 costs to FP32. Cost differences and reductions must also fit the solver's
|
|
85
130
|
dtype: finite FP32 inputs near its limits can still overflow when recentered or
|
|
86
131
|
averaged. Rescale the objective before evaluation or return FP64 costs in that case.
|
|
@@ -92,6 +137,7 @@ finite magnitude plus one, clamped to the dtype limit. An infeasible candidate c
|
|
|
92
137
|
therefore retain weight, depending on epsilon. With `scale_cost='mean'` or
|
|
93
138
|
`'max_cost'`, the solvers compute the penalty after scaling. Otherwise, that weight
|
|
94
139
|
also depends on the cost offset.
|
|
140
|
+
|
|
95
141
|
Balanced Sinkhorn sends every column its prescribed mass, even when all of that
|
|
96
142
|
column's candidates are infeasible. The monolithic step records a vertex's cost as
|
|
97
143
|
the mean of its probes with each nonfinite probe already penalized, so with several
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: polystep
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: PyTorch PolyStep Optimizer - gradient-free neural network training via entropic optimal transport
|
|
5
5
|
Author: An T. Le
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -35,7 +35,6 @@ Requires-Dist: numpy>=2.0; extra == "dev"
|
|
|
35
35
|
Requires-Dist: setuptools>=77; extra == "dev"
|
|
36
36
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
37
37
|
Requires-Dist: pytest-timeout>=2.2; extra == "dev"
|
|
38
|
-
Requires-Dist: pytest-rerunfailures>=14.0; extra == "dev"
|
|
39
38
|
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
40
39
|
Requires-Dist: pytest-xdist>=3.5; extra == "dev"
|
|
41
40
|
Requires-Dist: ruff==0.15.13; extra == "dev"
|
|
@@ -75,10 +74,11 @@ backpropagation is usually faster.
|
|
|
75
74
|
[API reference](https://github.com/anindex/polystep/blob/main/docs/api_overview.md) ·
|
|
76
75
|
[Julia package](https://github.com/anindex/PolyStep.jl)
|
|
77
76
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
| One particle | Four particles, two minima |
|
|
78
|
+
|:---:|:---:|
|
|
79
|
+
| [<img src="docs/figures/polystep_particle.gif" width="440" alt="One particle probes an objective and moves toward its minimum through weighted barycentric steps.">](https://vietngth.github.io/polystep-visualization/) | [<img src="docs/figures/polystep_particles.gif" width="440" alt="Four particles probe a two-basin objective and move toward its minima through weighted barycentric steps.">](https://vietngth.github.io/polystep-visualization/) |
|
|
80
|
+
|
|
81
|
+
Animations from [Viet T. Nguyen's interactive visualization](https://vietngth.github.io/polystep-visualization/).
|
|
82
82
|
|
|
83
83
|
## Installation
|
|
84
84
|
|
|
@@ -89,7 +89,7 @@ pip install "polystep[examples]" # dependencies for the repository's examples
|
|
|
89
89
|
|
|
90
90
|
Python 3.11+ and PyTorch 2.8+ are required. Choose a CUDA build compatible with your
|
|
91
91
|
driver from the [PyTorch installation guide](https://pytorch.org/get-started/locally/).
|
|
92
|
-
For development,
|
|
92
|
+
For development, run `pip install -e ".[dev]"` from a checkout.
|
|
93
93
|
|
|
94
94
|
## Quickstart
|
|
95
95
|
|
|
@@ -99,6 +99,7 @@ from torch import nn
|
|
|
99
99
|
from torch.utils.data import DataLoader, TensorDataset
|
|
100
100
|
from polystep import HybridSubspace, ParamLayout, PolyStepOptimizer, TrainConfig, train
|
|
101
101
|
|
|
102
|
+
torch.set_num_threads(1)
|
|
102
103
|
model = nn.Sequential(nn.Linear(16, 32), nn.ReLU(), nn.Linear(32, 3))
|
|
103
104
|
loader = DataLoader(
|
|
104
105
|
TensorDataset(torch.randn(256, 16), torch.randint(0, 3, (256,))),
|
|
@@ -111,9 +112,24 @@ optimizer = PolyStepOptimizer(model, subspace=subspace, solver="softmax")
|
|
|
111
112
|
train(model, loader, nn.CrossEntropyLoss(), optimizer, TrainConfig(epochs=5))
|
|
112
113
|
```
|
|
113
114
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
`max_subspace_dim=128` caps the search at 128 coordinates, so each step scores
|
|
116
|
+
144 candidates. Biases and normalization weights keep their full width. On a larger
|
|
117
|
+
model, the cap must cover those parameters plus at least one coordinate per weight
|
|
118
|
+
matrix. If the cap is too small, PolyStep
|
|
119
|
+
warns and reports the minimum dimension it can use.
|
|
120
|
+
|
|
121
|
+
`step_batch` uses the same candidate evaluation paths as `train()`:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
loss_fn = nn.CrossEntropyLoss()
|
|
125
|
+
for x, y in loader:
|
|
126
|
+
optimizer.step_batch(x, y, loss_fn=loss_fn)
|
|
127
|
+
optimizer.release_evaluator()
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`torch.set_num_threads(1)` avoids thread overhead for a model this small. Larger
|
|
131
|
+
models may benefit from more threads; `python -m polystep.benchmarks.threads` compares
|
|
132
|
+
several counts on an MLP workload. See
|
|
117
133
|
[performance](https://github.com/anindex/polystep/blob/main/docs/performance.md)
|
|
118
134
|
for candidate paths, thread counts, compilation, and memory settings.
|
|
119
135
|
|
|
@@ -125,8 +141,8 @@ from polystep import PolyStepES
|
|
|
125
141
|
es = PolyStepES(dim=20, epsilon=0.1, step_radius=0.3)
|
|
126
142
|
for _ in range(300):
|
|
127
143
|
candidates = es.ask()
|
|
128
|
-
es.tell(candidates.square().sum(dim=1))
|
|
129
|
-
print(es.best_fitness, es.
|
|
144
|
+
es.tell((candidates - 1).square().sum(dim=1))
|
|
145
|
+
print(es.best_fitness, (es.mean - 1).square().sum().item())
|
|
130
146
|
```
|
|
131
147
|
|
|
132
148
|
### Synthetic objectives
|
|
@@ -152,6 +168,17 @@ depends on the radius, representation, and evaluation budget. PolyStep's optiona
|
|
|
152
168
|
quadratic models and radius controllers are implementation heuristics; they do not
|
|
153
169
|
inherit classical direct-search or trust-region guarantees.
|
|
154
170
|
|
|
171
|
+
| SNN training | CartPole policy |
|
|
172
|
+
|:---:|:---:|
|
|
173
|
+
| <img src="docs/figures/polystep_snn_progress.gif" width="440" alt="Training loss and batch accuracy over 60 PolyStep updates of a spiking network with hard LIF thresholds."> | <img src="docs/figures/rl_cartpole_policy.gif" width="440" alt="A trained CartPole policy uses hard left/right actions to keep the pole upright."> |
|
|
174
|
+
|
|
175
|
+
The [SNN example](examples/02_snn_starter.py) trains a network with hard LIF spikes
|
|
176
|
+
on four synthetic classes, without surrogate gradients.
|
|
177
|
+
|
|
178
|
+
The [CartPole example](examples/03_rl_cartpole.py) uses real-valued weights and a
|
|
179
|
+
hard left/right `argmax`. PolyStep optimizes episode return without gradients
|
|
180
|
+
through the actions or simulator.
|
|
181
|
+
|
|
155
182
|
## Baselines
|
|
156
183
|
|
|
157
184
|
`polystep.baselines` provides `openai_es`, `spsa`, `mezo`, `random_search`, `eggroll`,
|
|
@@ -161,8 +188,8 @@ scored, even when candidates are batched:
|
|
|
161
188
|
```python
|
|
162
189
|
from polystep.baselines import Objective, openai_es
|
|
163
190
|
|
|
164
|
-
objective = Objective(lambda x: x.square().sum(1), dim=64, budget=10_000)
|
|
165
|
-
result = openai_es(objective, sigma=0.05,
|
|
191
|
+
objective = Objective(lambda x: (x - 1).square().sum(1), dim=64, budget=10_000)
|
|
192
|
+
result = openai_es(objective, sigma=0.05, popsize=32)
|
|
166
193
|
```
|
|
167
194
|
|
|
168
195
|
The paper runners match search spaces, budgets and tuning as the
|
|
@@ -171,17 +198,12 @@ The paper runners match search spaces, budgets and tuning as the
|
|
|
171
198
|
## Benchmarks
|
|
172
199
|
|
|
173
200
|
Five-seed mean ± sample standard deviation, with configurations and checkpoints
|
|
174
|
-
selected on validation data; the probSAT and RC2 columns are single runs.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
spent at their own population size, and Adam trains for PolyStep's 30 epochs, except
|
|
179
|
-
50 on ETTh1. On MAX-SAT, CMA-ES and OpenAI-ES get 50 evaluations per PolyStep step.
|
|
180
|
-
OpenAI-ES scores 50 candidates at each of PolyStep's steps, while CMA-ES doubles its
|
|
181
|
-
population at each restart and takes fewer generations. The tables were recorded
|
|
182
|
-
before 1.0; the
|
|
201
|
+
selected on validation data; the probSAT and RC2 columns are single runs. Each
|
|
202
|
+
baseline gets a matched evaluation or step budget, as the
|
|
203
|
+
[protocol](https://github.com/anindex/polystep/blob/main/docs/reproducibility.md#protocol)
|
|
204
|
+
describes. The tables were recorded before 1.0; the
|
|
183
205
|
[reproducibility guide](https://github.com/anindex/polystep/blob/main/docs/reproducibility.md#changes-since-the-published-tables)
|
|
184
|
-
lists the rows that
|
|
206
|
+
lists the rows that later releases change, and the
|
|
185
207
|
[experiment index](https://github.com/anindex/polystep/blob/main/experiments/EXPERIMENT_INDEX.md)
|
|
186
208
|
has the full tables.
|
|
187
209
|
|
|
@@ -225,6 +247,7 @@ for unsupported configurations and comparison limits.
|
|
|
225
247
|
- [Performance](https://github.com/anindex/polystep/blob/main/docs/performance.md)
|
|
226
248
|
- [Examples](https://github.com/anindex/polystep/blob/main/examples/README.md)
|
|
227
249
|
- [Experiments](https://github.com/anindex/polystep/blob/main/experiments/README.md) and [reproduction](https://github.com/anindex/polystep/blob/main/docs/reproducibility.md)
|
|
250
|
+
- [Limitations](https://github.com/anindex/polystep/blob/main/LIMITATIONS.md)
|
|
228
251
|
- [Determinism](https://github.com/anindex/polystep/blob/main/docs/determinism.md)
|
|
229
252
|
- [Contributing](https://github.com/anindex/polystep/blob/main/CONTRIBUTING.md) and [changelog](https://github.com/anindex/polystep/blob/main/CHANGELOG.md)
|
|
230
253
|
|
|
@@ -20,10 +20,11 @@ backpropagation is usually faster.
|
|
|
20
20
|
[API reference](https://github.com/anindex/polystep/blob/main/docs/api_overview.md) ·
|
|
21
21
|
[Julia package](https://github.com/anindex/PolyStep.jl)
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
| One particle | Four particles, two minima |
|
|
24
|
+
|:---:|:---:|
|
|
25
|
+
| [<img src="docs/figures/polystep_particle.gif" width="440" alt="One particle probes an objective and moves toward its minimum through weighted barycentric steps.">](https://vietngth.github.io/polystep-visualization/) | [<img src="docs/figures/polystep_particles.gif" width="440" alt="Four particles probe a two-basin objective and move toward its minima through weighted barycentric steps.">](https://vietngth.github.io/polystep-visualization/) |
|
|
26
|
+
|
|
27
|
+
Animations from [Viet T. Nguyen's interactive visualization](https://vietngth.github.io/polystep-visualization/).
|
|
27
28
|
|
|
28
29
|
## Installation
|
|
29
30
|
|
|
@@ -34,7 +35,7 @@ pip install "polystep[examples]" # dependencies for the repository's examples
|
|
|
34
35
|
|
|
35
36
|
Python 3.11+ and PyTorch 2.8+ are required. Choose a CUDA build compatible with your
|
|
36
37
|
driver from the [PyTorch installation guide](https://pytorch.org/get-started/locally/).
|
|
37
|
-
For development,
|
|
38
|
+
For development, run `pip install -e ".[dev]"` from a checkout.
|
|
38
39
|
|
|
39
40
|
## Quickstart
|
|
40
41
|
|
|
@@ -44,6 +45,7 @@ from torch import nn
|
|
|
44
45
|
from torch.utils.data import DataLoader, TensorDataset
|
|
45
46
|
from polystep import HybridSubspace, ParamLayout, PolyStepOptimizer, TrainConfig, train
|
|
46
47
|
|
|
48
|
+
torch.set_num_threads(1)
|
|
47
49
|
model = nn.Sequential(nn.Linear(16, 32), nn.ReLU(), nn.Linear(32, 3))
|
|
48
50
|
loader = DataLoader(
|
|
49
51
|
TensorDataset(torch.randn(256, 16), torch.randint(0, 3, (256,))),
|
|
@@ -56,9 +58,24 @@ optimizer = PolyStepOptimizer(model, subspace=subspace, solver="softmax")
|
|
|
56
58
|
train(model, loader, nn.CrossEntropyLoss(), optimizer, TrainConfig(epochs=5))
|
|
57
59
|
```
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
`max_subspace_dim=128` caps the search at 128 coordinates, so each step scores
|
|
62
|
+
144 candidates. Biases and normalization weights keep their full width. On a larger
|
|
63
|
+
model, the cap must cover those parameters plus at least one coordinate per weight
|
|
64
|
+
matrix. If the cap is too small, PolyStep
|
|
65
|
+
warns and reports the minimum dimension it can use.
|
|
66
|
+
|
|
67
|
+
`step_batch` uses the same candidate evaluation paths as `train()`:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
loss_fn = nn.CrossEntropyLoss()
|
|
71
|
+
for x, y in loader:
|
|
72
|
+
optimizer.step_batch(x, y, loss_fn=loss_fn)
|
|
73
|
+
optimizer.release_evaluator()
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`torch.set_num_threads(1)` avoids thread overhead for a model this small. Larger
|
|
77
|
+
models may benefit from more threads; `python -m polystep.benchmarks.threads` compares
|
|
78
|
+
several counts on an MLP workload. See
|
|
62
79
|
[performance](https://github.com/anindex/polystep/blob/main/docs/performance.md)
|
|
63
80
|
for candidate paths, thread counts, compilation, and memory settings.
|
|
64
81
|
|
|
@@ -70,8 +87,8 @@ from polystep import PolyStepES
|
|
|
70
87
|
es = PolyStepES(dim=20, epsilon=0.1, step_radius=0.3)
|
|
71
88
|
for _ in range(300):
|
|
72
89
|
candidates = es.ask()
|
|
73
|
-
es.tell(candidates.square().sum(dim=1))
|
|
74
|
-
print(es.best_fitness, es.
|
|
90
|
+
es.tell((candidates - 1).square().sum(dim=1))
|
|
91
|
+
print(es.best_fitness, (es.mean - 1).square().sum().item())
|
|
75
92
|
```
|
|
76
93
|
|
|
77
94
|
### Synthetic objectives
|
|
@@ -97,6 +114,17 @@ depends on the radius, representation, and evaluation budget. PolyStep's optiona
|
|
|
97
114
|
quadratic models and radius controllers are implementation heuristics; they do not
|
|
98
115
|
inherit classical direct-search or trust-region guarantees.
|
|
99
116
|
|
|
117
|
+
| SNN training | CartPole policy |
|
|
118
|
+
|:---:|:---:|
|
|
119
|
+
| <img src="docs/figures/polystep_snn_progress.gif" width="440" alt="Training loss and batch accuracy over 60 PolyStep updates of a spiking network with hard LIF thresholds."> | <img src="docs/figures/rl_cartpole_policy.gif" width="440" alt="A trained CartPole policy uses hard left/right actions to keep the pole upright."> |
|
|
120
|
+
|
|
121
|
+
The [SNN example](examples/02_snn_starter.py) trains a network with hard LIF spikes
|
|
122
|
+
on four synthetic classes, without surrogate gradients.
|
|
123
|
+
|
|
124
|
+
The [CartPole example](examples/03_rl_cartpole.py) uses real-valued weights and a
|
|
125
|
+
hard left/right `argmax`. PolyStep optimizes episode return without gradients
|
|
126
|
+
through the actions or simulator.
|
|
127
|
+
|
|
100
128
|
## Baselines
|
|
101
129
|
|
|
102
130
|
`polystep.baselines` provides `openai_es`, `spsa`, `mezo`, `random_search`, `eggroll`,
|
|
@@ -106,8 +134,8 @@ scored, even when candidates are batched:
|
|
|
106
134
|
```python
|
|
107
135
|
from polystep.baselines import Objective, openai_es
|
|
108
136
|
|
|
109
|
-
objective = Objective(lambda x: x.square().sum(1), dim=64, budget=10_000)
|
|
110
|
-
result = openai_es(objective, sigma=0.05,
|
|
137
|
+
objective = Objective(lambda x: (x - 1).square().sum(1), dim=64, budget=10_000)
|
|
138
|
+
result = openai_es(objective, sigma=0.05, popsize=32)
|
|
111
139
|
```
|
|
112
140
|
|
|
113
141
|
The paper runners match search spaces, budgets and tuning as the
|
|
@@ -116,17 +144,12 @@ The paper runners match search spaces, budgets and tuning as the
|
|
|
116
144
|
## Benchmarks
|
|
117
145
|
|
|
118
146
|
Five-seed mean ± sample standard deviation, with configurations and checkpoints
|
|
119
|
-
selected on validation data; the probSAT and RC2 columns are single runs.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
spent at their own population size, and Adam trains for PolyStep's 30 epochs, except
|
|
124
|
-
50 on ETTh1. On MAX-SAT, CMA-ES and OpenAI-ES get 50 evaluations per PolyStep step.
|
|
125
|
-
OpenAI-ES scores 50 candidates at each of PolyStep's steps, while CMA-ES doubles its
|
|
126
|
-
population at each restart and takes fewer generations. The tables were recorded
|
|
127
|
-
before 1.0; the
|
|
147
|
+
selected on validation data; the probSAT and RC2 columns are single runs. Each
|
|
148
|
+
baseline gets a matched evaluation or step budget, as the
|
|
149
|
+
[protocol](https://github.com/anindex/polystep/blob/main/docs/reproducibility.md#protocol)
|
|
150
|
+
describes. The tables were recorded before 1.0; the
|
|
128
151
|
[reproducibility guide](https://github.com/anindex/polystep/blob/main/docs/reproducibility.md#changes-since-the-published-tables)
|
|
129
|
-
lists the rows that
|
|
152
|
+
lists the rows that later releases change, and the
|
|
130
153
|
[experiment index](https://github.com/anindex/polystep/blob/main/experiments/EXPERIMENT_INDEX.md)
|
|
131
154
|
has the full tables.
|
|
132
155
|
|
|
@@ -170,6 +193,7 @@ for unsupported configurations and comparison limits.
|
|
|
170
193
|
- [Performance](https://github.com/anindex/polystep/blob/main/docs/performance.md)
|
|
171
194
|
- [Examples](https://github.com/anindex/polystep/blob/main/examples/README.md)
|
|
172
195
|
- [Experiments](https://github.com/anindex/polystep/blob/main/experiments/README.md) and [reproduction](https://github.com/anindex/polystep/blob/main/docs/reproducibility.md)
|
|
196
|
+
- [Limitations](https://github.com/anindex/polystep/blob/main/LIMITATIONS.md)
|
|
173
197
|
- [Determinism](https://github.com/anindex/polystep/blob/main/docs/determinism.md)
|
|
174
198
|
- [Contributing](https://github.com/anindex/polystep/blob/main/CONTRIBUTING.md) and [changelog](https://github.com/anindex/polystep/blob/main/CHANGELOG.md)
|
|
175
199
|
|