polystep 0.7.0__py3-none-any.whl
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/__init__.py +176 -0
- polystep/_compiled.py +334 -0
- polystep/_step_blockwise.py +994 -0
- polystep/_step_momentum.py +128 -0
- polystep/_step_monolithic.py +1070 -0
- polystep/adaptive_subspace.py +762 -0
- polystep/api.py +307 -0
- polystep/ask_tell.py +164 -0
- polystep/benchmarks/__init__.py +62 -0
- polystep/benchmarks/baselines.py +604 -0
- polystep/benchmarks/rl/__init__.py +13 -0
- polystep/benchmarks/rl/cartpole.py +260 -0
- polystep/benchmarks/rl/gym_evaluator.py +304 -0
- polystep/benchmarks/rl/metrics.py +53 -0
- polystep/benchmarks/rl/policies.py +133 -0
- polystep/benchmarks/utils.py +1262 -0
- polystep/blockwise.py +549 -0
- polystep/cma.py +400 -0
- polystep/cma_subspace.py +404 -0
- polystep/cost_nn.py +633 -0
- polystep/costs.py +86 -0
- polystep/dynamics.py +138 -0
- polystep/epsilon.py +217 -0
- polystep/geometry.py +410 -0
- polystep/hybrid_subspace.py +1335 -0
- polystep/layers/__init__.py +16 -0
- polystep/layers/attention.py +243 -0
- polystep/layers/rnn.py +276 -0
- polystep/objectives/__init__.py +25 -0
- polystep/objectives/base.py +77 -0
- polystep/objectives/synthetic.py +232 -0
- polystep/optimizer.py +1405 -0
- polystep/projection/__init__.py +5 -0
- polystep/projection/sparse.py +291 -0
- polystep/py.typed +0 -0
- polystep/quadratic_model.py +285 -0
- polystep/solver.py +654 -0
- polystep/solvers/__init__.py +34 -0
- polystep/solvers/_prelude.py +129 -0
- polystep/solvers/base.py +77 -0
- polystep/solvers/greedy.py +159 -0
- polystep/solvers/kl_softmax.py +200 -0
- polystep/solvers/sinkhorn.py +522 -0
- polystep/solvers/softmax.py +151 -0
- polystep/solvers/tempered_softmax.py +97 -0
- polystep/subspace.py +694 -0
- polystep/transform.py +365 -0
- polystep-0.7.0.dist-info/METADATA +285 -0
- polystep-0.7.0.dist-info/RECORD +52 -0
- polystep-0.7.0.dist-info/WHEEL +5 -0
- polystep-0.7.0.dist-info/licenses/LICENSE +191 -0
- polystep-0.7.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Amortized momentum step and related helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from typing import Callable
|
|
7
|
+
|
|
8
|
+
import torch
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def step_momentum(opt, closure: Callable) -> float:
|
|
12
|
+
"""Cheap momentum step: apply EMA transport direction with decay.
|
|
13
|
+
|
|
14
|
+
Applies the EMA-smoothed transport direction without a validation
|
|
15
|
+
forward pass. NaN safety is guaranteed by reverting to the
|
|
16
|
+
pre-step position if non-finite values appear.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
closure: ``closure(batched_params) -> losses`` (unused - kept
|
|
20
|
+
for API compatibility with other ``_step_*`` methods).
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Reused cost from the last OT step.
|
|
24
|
+
"""
|
|
25
|
+
state = opt._state
|
|
26
|
+
opt._update_sampling_projection()
|
|
27
|
+
if opt._transport_direction_ema is None:
|
|
28
|
+
return state.costs[-1] if state.costs else float("inf")
|
|
29
|
+
|
|
30
|
+
# Linear decay: first momentum step gets full strength, last gets minimal
|
|
31
|
+
phase = (opt._amortize_counter % opt.amortize_steps) / opt.amortize_steps
|
|
32
|
+
decay = 1.0 - phase
|
|
33
|
+
|
|
34
|
+
# Store pre-step X for NaN safety
|
|
35
|
+
X_old = state.X.clone()
|
|
36
|
+
|
|
37
|
+
# Choose momentum direction: Newton (if available) or EMA transport
|
|
38
|
+
if opt.use_quadratic_model and opt._newton_direction is not None:
|
|
39
|
+
direction = opt._newton_direction
|
|
40
|
+
else:
|
|
41
|
+
direction = opt._transport_direction_ema
|
|
42
|
+
|
|
43
|
+
# Apply direction with decay.
|
|
44
|
+
state.X = state.X + decay * direction
|
|
45
|
+
|
|
46
|
+
# NaN check - revert to pre-step state and use previous cost
|
|
47
|
+
if not torch.isfinite(state.X).all():
|
|
48
|
+
state.X = X_old
|
|
49
|
+
opt._transport_direction = None
|
|
50
|
+
opt._transport_direction_ema = None
|
|
51
|
+
opt._newton_direction = None
|
|
52
|
+
opt._sync_model()
|
|
53
|
+
prev_cost = state.costs[-1] if state.costs else float("inf")
|
|
54
|
+
state.costs.append(prev_cost)
|
|
55
|
+
state.linear_convergence.append(True)
|
|
56
|
+
state.displacement_sqnorms.append(0.0)
|
|
57
|
+
state.iteration_count += 1
|
|
58
|
+
return prev_cost
|
|
59
|
+
|
|
60
|
+
# Sync model parameters from updated particles
|
|
61
|
+
opt._sync_model()
|
|
62
|
+
|
|
63
|
+
# Reuse last cost (no validation forward pass)
|
|
64
|
+
cost = state.costs[-1] if state.costs else float("inf")
|
|
65
|
+
|
|
66
|
+
# Update diagnostics
|
|
67
|
+
disp_sqnorm = torch.mean(torch.sum((state.X - X_old) ** 2, dim=-1)).item()
|
|
68
|
+
state.costs.append(cost)
|
|
69
|
+
state.linear_convergence.append(True)
|
|
70
|
+
state.displacement_sqnorms.append(disp_sqnorm)
|
|
71
|
+
state.iteration_count += 1
|
|
72
|
+
|
|
73
|
+
return cost
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def evaluate_current_loss(opt, closure: Callable) -> float:
|
|
77
|
+
"""Evaluate model loss at current particle position via single forward pass.
|
|
78
|
+
|
|
79
|
+
Not called by the core optimization loop (momentum validation was
|
|
80
|
+
removed), but retained as a public diagnostic utility.
|
|
81
|
+
|
|
82
|
+
Builds a batched param config (batch=1) from current state.X and
|
|
83
|
+
calls the closure. Handles both subspace and full-space modes.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Scalar loss value (float).
|
|
87
|
+
"""
|
|
88
|
+
state = opt._state
|
|
89
|
+
with torch.no_grad():
|
|
90
|
+
if opt.subspace is not None:
|
|
91
|
+
flat_sub = state.X.reshape(-1)[: state.subspace.subspace_dim].unsqueeze(0)
|
|
92
|
+
if opt._mixed_precision and state.projection is not None:
|
|
93
|
+
flat_sub = flat_sub.to(dtype=state.projection.dtype)
|
|
94
|
+
if opt._adaptive or opt._cma_subspace:
|
|
95
|
+
val_params = state.subspace.reconstruct_batch(
|
|
96
|
+
opt._sampling_projection,
|
|
97
|
+
state.base_params,
|
|
98
|
+
flat_sub,
|
|
99
|
+
)
|
|
100
|
+
elif opt._hybrid:
|
|
101
|
+
val_params = state.subspace.reconstruct_batch(
|
|
102
|
+
state.hybrid_projections,
|
|
103
|
+
state.base_params,
|
|
104
|
+
flat_sub,
|
|
105
|
+
)
|
|
106
|
+
else:
|
|
107
|
+
val_params = state.subspace.reconstruct_batch(
|
|
108
|
+
state.base_params,
|
|
109
|
+
flat_sub,
|
|
110
|
+
)
|
|
111
|
+
else:
|
|
112
|
+
flat_config = state.X.reshape(1, -1)
|
|
113
|
+
layout_flat = opt.layout.padded_size
|
|
114
|
+
if flat_config.shape[1] >= layout_flat:
|
|
115
|
+
flat_for_layout = flat_config[:, :layout_flat]
|
|
116
|
+
else:
|
|
117
|
+
flat_for_layout = torch.nn.functional.pad(
|
|
118
|
+
flat_config,
|
|
119
|
+
(0, layout_flat - flat_config.shape[1]),
|
|
120
|
+
)
|
|
121
|
+
val_params = opt.layout.batch_unflatten(flat_for_layout)
|
|
122
|
+
|
|
123
|
+
val_loss_tensor = closure(val_params)
|
|
124
|
+
val_loss = val_loss_tensor.mean().item()
|
|
125
|
+
# Guard against NaN propagation - treat as infinite loss
|
|
126
|
+
if math.isnan(val_loss):
|
|
127
|
+
return float("inf")
|
|
128
|
+
return val_loss
|