adv-optm 2.6.1.dev3__tar.gz → 2.6.2.dev2__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.
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/PKG-INFO +1 -1
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm/__init__.py +1 -1
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm/optim/AdaMuon_adv.py +1 -2
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm/optim/AdamW_adv.py +1 -2
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm/optim/Adopt_adv.py +3 -3
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm/optim/Lion_adv.py +0 -2
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm/optim/Muon_adv.py +1 -2
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm/optim/Prodigy_adv.py +1 -2
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm/optim/SignSGD_adv.py +0 -2
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm/optim/SinkSGD_adv.py +25 -10
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm/util/param_update.py +27 -33
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm/util/scaled_optm.py +1 -20
- adv_optm-2.6.2.dev2/adv_optm/util/sinkhorn.py +275 -0
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm.egg-info/PKG-INFO +1 -1
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/setup.py +1 -1
- adv_optm-2.6.1.dev3/adv_optm/util/sinkhorn.py +0 -126
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/LICENSE +0 -0
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/README.md +0 -0
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm/optim/__init__.py +0 -0
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm/util/Kourkoutas.py +0 -0
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm/util/Muon_AuxAdam.py +0 -0
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm/util/Muon_util.py +0 -0
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm/util/OrthoGrad.py +0 -0
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm/util/__init__.py +0 -0
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm/util/centered_decay.py +0 -0
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm/util/factorization_util.py +0 -0
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm/util/lion_k.py +0 -0
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm/util/signed_util.py +0 -0
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm/util/state_util.py +0 -0
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm/util/update_util.py +0 -0
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm.egg-info/SOURCES.txt +0 -0
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm.egg-info/dependency_links.txt +0 -0
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm.egg-info/requires.txt +0 -0
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/adv_optm.egg-info/top_level.txt +0 -0
- {adv_optm-2.6.1.dev3 → adv_optm-2.6.2.dev2}/setup.cfg +0 -0
|
@@ -137,7 +137,6 @@ class AdaMuon_adv(torch.optim.Optimizer):
|
|
|
137
137
|
# Decoupled/cautious weight decay
|
|
138
138
|
weight_decay: float = 0,
|
|
139
139
|
cautious_wd: bool = False,
|
|
140
|
-
scaled_wd: bool = False,
|
|
141
140
|
# Nesterov momentum
|
|
142
141
|
nesterov: bool = True,
|
|
143
142
|
nesterov_coef: float | None = None,
|
|
@@ -228,7 +227,7 @@ class AdaMuon_adv(torch.optim.Optimizer):
|
|
|
228
227
|
|
|
229
228
|
defaults = {
|
|
230
229
|
"lr": lr, "betas": betas, "weight_decay": weight_decay, "cautious_wd": cautious_wd,
|
|
231
|
-
"eps": eps, "rms_rescaling": rms_rescaling, "ns_steps": ns_steps,
|
|
230
|
+
"eps": eps, "rms_rescaling": rms_rescaling, "ns_steps": ns_steps,
|
|
232
231
|
"ns_eps": ns_eps, "ns_coeffs": ns_coeffs, "nnmf_factor": nnmf_factor,
|
|
233
232
|
"vector_reshape": vector_reshape,
|
|
234
233
|
"nesterov":nesterov, "nesterov_coef": nesterov_coef, "use_atan2":use_atan2,
|
|
@@ -98,7 +98,6 @@ class AdamW_adv(torch.optim.Optimizer):
|
|
|
98
98
|
weight_decay: float = 0.0,
|
|
99
99
|
fisher_wd: bool = False,
|
|
100
100
|
cautious_wd: bool = False,
|
|
101
|
-
scaled_wd: bool = False,
|
|
102
101
|
# Adam's Bias Correction
|
|
103
102
|
use_bias_correction: bool = True,
|
|
104
103
|
# Stochastic Rounding for BF16
|
|
@@ -157,7 +156,7 @@ class AdamW_adv(torch.optim.Optimizer):
|
|
|
157
156
|
|
|
158
157
|
defaults = {
|
|
159
158
|
"lr": lr, "betas": betas, "eps": eps, "weight_decay": weight_decay,
|
|
160
|
-
"fisher_wd": fisher_wd, "cautious_wd": cautious_wd,
|
|
159
|
+
"fisher_wd": fisher_wd, "cautious_wd": cautious_wd,
|
|
161
160
|
"use_atan2": use_atan2, "nesterov": nesterov, "nesterov_coef": nesterov_coef,
|
|
162
161
|
"normed_momentum": normed_momentum,
|
|
163
162
|
"orthogonal_gradient": orthogonal_gradient, "use_bias_correction": use_bias_correction,
|
|
@@ -101,7 +101,6 @@ class Adopt_adv(torch.optim.Optimizer):
|
|
|
101
101
|
weight_decay: float = 0.0,
|
|
102
102
|
fisher_wd: bool = False,
|
|
103
103
|
cautious_wd: bool = False,
|
|
104
|
-
scaled_wd: bool = False,
|
|
105
104
|
# ADOPT clipping
|
|
106
105
|
clip_lambda: Optional[Callable[[int], float]] = lambda step: step**0.25,
|
|
107
106
|
# Adam_atan2 (scale invariant)
|
|
@@ -158,7 +157,7 @@ class Adopt_adv(torch.optim.Optimizer):
|
|
|
158
157
|
state_precision = "factored"
|
|
159
158
|
|
|
160
159
|
defaults = {
|
|
161
|
-
"lr": lr, "betas": betas, "eps": eps, "weight_decay": weight_decay,
|
|
160
|
+
"lr": lr, "betas": betas, "eps": eps, "weight_decay": weight_decay,
|
|
162
161
|
"fisher_wd": fisher_wd, "cautious_wd": cautious_wd, "orthogonal_gradient": orthogonal_gradient,
|
|
163
162
|
"nesterov": nesterov, "nesterov_coef": nesterov_coef,
|
|
164
163
|
"kourkoutas_beta": kourkoutas_beta, "beta2_min": beta2_min, "ema_alpha": ema_alpha,
|
|
@@ -315,7 +314,8 @@ class Adopt_adv(torch.optim.Optimizer):
|
|
|
315
314
|
# The first step is for initialization only (skip when use_atan2 as it's scale invariant).
|
|
316
315
|
if state['step'] == 0 and not (self.use_atan2 or group.get('spectral_normalization', False)):
|
|
317
316
|
state['step'] += 1
|
|
318
|
-
|
|
317
|
+
if group.get('kourkoutas_beta', False):
|
|
318
|
+
self.kourkoutas_helper.accumulate_gradient_sq_norm(p, grad)
|
|
319
319
|
return
|
|
320
320
|
|
|
321
321
|
random_int_tensor = None
|
|
@@ -64,7 +64,6 @@ class Lion_adv(torch.optim.Optimizer):
|
|
|
64
64
|
# Decoupled/cautious weight decay
|
|
65
65
|
weight_decay: float = 0.0,
|
|
66
66
|
cautious_wd: bool = False,
|
|
67
|
-
scaled_wd: bool = False,
|
|
68
67
|
# Stochastic Rounding for BF16
|
|
69
68
|
stochastic_rounding: bool = True,
|
|
70
69
|
# OrthoGrad
|
|
@@ -97,7 +96,6 @@ class Lion_adv(torch.optim.Optimizer):
|
|
|
97
96
|
betas=betas,
|
|
98
97
|
weight_decay=weight_decay,
|
|
99
98
|
cautious_wd=cautious_wd,
|
|
100
|
-
scaled_wd=scaled_wd,
|
|
101
99
|
vector_reshape=vector_reshape,
|
|
102
100
|
orthogonal_gradient=orthogonal_gradient,
|
|
103
101
|
kappa_p=kappa_p,
|
|
@@ -111,7 +111,6 @@ class Muon_adv(torch.optim.Optimizer):
|
|
|
111
111
|
# Decoupled/cautious weight decay
|
|
112
112
|
weight_decay: float = 0.0,
|
|
113
113
|
cautious_wd: bool = False,
|
|
114
|
-
scaled_wd: bool = False,
|
|
115
114
|
# Nesterov momentum
|
|
116
115
|
nesterov: bool = True,
|
|
117
116
|
nesterov_coef: float | None = None,
|
|
@@ -202,7 +201,7 @@ class Muon_adv(torch.optim.Optimizer):
|
|
|
202
201
|
defaults = {
|
|
203
202
|
"lr": lr, "beta1": beta1, "weight_decay": weight_decay, "cautious_wd": cautious_wd,
|
|
204
203
|
"nesterov": nesterov, "nesterov_coef": nesterov_coef, "ns_steps": ns_steps, "ns_eps": ns_eps,
|
|
205
|
-
"ns_coeffs": ns_coeffs, "nnmf_factor": nnmf_factor,
|
|
204
|
+
"ns_coeffs": ns_coeffs, "nnmf_factor": nnmf_factor,
|
|
206
205
|
"vector_reshape": vector_reshape, "rms_rescaling": rms_rescaling,
|
|
207
206
|
"orthogonal_gradient": orthogonal_gradient,
|
|
208
207
|
'compiled_optimizer': compiled_optimizer,
|
|
@@ -115,7 +115,6 @@ class Prodigy_adv(torch.optim.Optimizer):
|
|
|
115
115
|
weight_decay: float = 0.0,
|
|
116
116
|
fisher_wd: bool = False,
|
|
117
117
|
cautious_wd: bool = False,
|
|
118
|
-
scaled_wd: bool = False,
|
|
119
118
|
# Stochastic Rounding for BF16
|
|
120
119
|
stochastic_rounding: bool = True,
|
|
121
120
|
# Adam_atan2 (scale invariant)
|
|
@@ -182,7 +181,7 @@ class Prodigy_adv(torch.optim.Optimizer):
|
|
|
182
181
|
|
|
183
182
|
defaults = {
|
|
184
183
|
"lr": lr, "betas": betas, "eps": eps, "weight_decay": weight_decay,
|
|
185
|
-
"fisher_wd": fisher_wd, "cautious_wd": cautious_wd,
|
|
184
|
+
"fisher_wd": fisher_wd, "cautious_wd": cautious_wd,
|
|
186
185
|
"use_atan2": use_atan2,
|
|
187
186
|
"orthogonal_gradient": orthogonal_gradient,
|
|
188
187
|
"compiled_optimizer": compiled_optimizer,
|
|
@@ -59,7 +59,6 @@ class SignSGD_adv(torch.optim.Optimizer):
|
|
|
59
59
|
# weight decay features
|
|
60
60
|
geometric_wd: bool = False,
|
|
61
61
|
cautious_wd: bool = False,
|
|
62
|
-
scaled_wd: bool = False,
|
|
63
62
|
# Stochastic Rounding for BF16
|
|
64
63
|
stochastic_rounding: bool = True,
|
|
65
64
|
# OrthoGrad
|
|
@@ -109,7 +108,6 @@ class SignSGD_adv(torch.optim.Optimizer):
|
|
|
109
108
|
momentum=momentum,
|
|
110
109
|
weight_decay=weight_decay,
|
|
111
110
|
cautious_wd=cautious_wd,
|
|
112
|
-
scaled_wd=scaled_wd,
|
|
113
111
|
geometric_wd=geometric_wd,
|
|
114
112
|
vector_reshape=vector_reshape,
|
|
115
113
|
orthogonal_gradient=orthogonal_gradient,
|
|
@@ -8,7 +8,7 @@ from ..util.OrthoGrad import _orthogonalize_gradient
|
|
|
8
8
|
from ..util.scaled_optm import scale_update, is_spectral, init_spectral_norm
|
|
9
9
|
from ..util.centered_decay import _init_anchor, dequantize_anchor
|
|
10
10
|
from ..util.state_util import init_state_tensor, get_state, set_state, upcast_grad_for_precision
|
|
11
|
-
from ..util.sinkhorn import apply_sr_sinkhorn, get_sinkhorn_wd_scaler
|
|
11
|
+
from ..util.sinkhorn import apply_sr_sinkhorn, get_sinkhorn_wd_scaler, apply_oft_sinkhorn, apply_spectral_oft_sinkhorn
|
|
12
12
|
from ..util.signed_util import get_signsgd_wd_target
|
|
13
13
|
|
|
14
14
|
class SinkSGD_adv(torch.optim.Optimizer):
|
|
@@ -56,6 +56,8 @@ class SinkSGD_adv(torch.optim.Optimizer):
|
|
|
56
56
|
# Sinkhorn Iterative Normalization
|
|
57
57
|
sinkhorn_iterations: int = 5,
|
|
58
58
|
orthogonal_sinkhorn: bool = False,
|
|
59
|
+
# OFT-specific Sinkhorn (symmetric)
|
|
60
|
+
skew_sinkoft: bool = False,
|
|
59
61
|
# Normalization then Momentum
|
|
60
62
|
normed_momentum: bool = False,
|
|
61
63
|
# SNR Precondition (requires normed_momentum)
|
|
@@ -66,7 +68,6 @@ class SinkSGD_adv(torch.optim.Optimizer):
|
|
|
66
68
|
# weight decay features
|
|
67
69
|
geometric_wd: bool = False,
|
|
68
70
|
cautious_wd: bool = False,
|
|
69
|
-
scaled_wd: bool = False,
|
|
70
71
|
# Stochastic Rounding for BF16
|
|
71
72
|
stochastic_rounding: bool = True,
|
|
72
73
|
# OrthoGrad
|
|
@@ -104,11 +105,11 @@ class SinkSGD_adv(torch.optim.Optimizer):
|
|
|
104
105
|
defaults = {
|
|
105
106
|
"lr": lr, "momentum": momentum,
|
|
106
107
|
"weight_decay": weight_decay, "nesterov": nesterov, "nesterov_coef": nesterov_coef, "normed_momentum": normed_momentum, "snr_cond": snr_cond,
|
|
107
|
-
"geometric_wd": geometric_wd, "cautious_wd": cautious_wd,
|
|
108
|
+
"geometric_wd": geometric_wd, "cautious_wd": cautious_wd,
|
|
108
109
|
"orthogonal_gradient": orthogonal_gradient,
|
|
109
110
|
"compiled_optimizer": compiled_optimizer,
|
|
110
111
|
"sinkhorn_iterations": sinkhorn_iterations,
|
|
111
|
-
"orthogonal_sinkhorn": orthogonal_sinkhorn,
|
|
112
|
+
"orthogonal_sinkhorn": orthogonal_sinkhorn, "skew_sinkoft": skew_sinkoft,
|
|
112
113
|
"spectral_normalization": spectral_normalization,
|
|
113
114
|
"centered_wd": centered_wd, "centered_wd_mode": centered_wd_mode,
|
|
114
115
|
"state_precision": state_precision,
|
|
@@ -237,6 +238,7 @@ class SinkSGD_adv(torch.optim.Optimizer):
|
|
|
237
238
|
nesterov = group['nesterov']
|
|
238
239
|
nesterov_coef = group.get('nesterov_coef', None)
|
|
239
240
|
snr_cond = group.get('snr_cond', False)
|
|
241
|
+
oft_sym = group.get('skew_sinkoft', False) and getattr(p, '_is_oft', False)
|
|
240
242
|
|
|
241
243
|
vt_row = None
|
|
242
244
|
vt_col = None
|
|
@@ -249,9 +251,12 @@ class SinkSGD_adv(torch.optim.Optimizer):
|
|
|
249
251
|
grad = _orthogonalize_gradient(p, grad, group["orthogonal_gradient"])
|
|
250
252
|
|
|
251
253
|
if normed_mt:
|
|
254
|
+
# Sinkhorn iterative normalization
|
|
252
255
|
if not is_vector:
|
|
253
|
-
|
|
254
|
-
|
|
256
|
+
if not oft_sym:
|
|
257
|
+
grad = apply_sr_sinkhorn(grad, iters=sinkhorn_iterations, p=p, ortho_project=orthogonal_sinkhorn)
|
|
258
|
+
else:
|
|
259
|
+
grad = apply_oft_sinkhorn(grad, iters=sinkhorn_iterations, p=p, ortho_project=orthogonal_sinkhorn)
|
|
255
260
|
else:
|
|
256
261
|
# For vectors, apply sign operation
|
|
257
262
|
grad = grad.sign_()
|
|
@@ -343,9 +348,15 @@ class SinkSGD_adv(torch.optim.Optimizer):
|
|
|
343
348
|
update.atan2_(denom)
|
|
344
349
|
|
|
345
350
|
if not group.get('normed_momentum', False):
|
|
351
|
+
# Sinkhorn iterative normalization
|
|
346
352
|
if not is_vector:
|
|
347
|
-
|
|
348
|
-
|
|
353
|
+
if not oft_sym:
|
|
354
|
+
update = apply_sr_sinkhorn(update, iters=sinkhorn_iterations, p=p, ortho_project=orthogonal_sinkhorn)
|
|
355
|
+
else:
|
|
356
|
+
if group.get('spectral_normalization', False):
|
|
357
|
+
update = apply_spectral_oft_sinkhorn(p, update, iters=sinkhorn_iterations, lr=step_size, state=state, ortho_project=orthogonal_sinkhorn)
|
|
358
|
+
else:
|
|
359
|
+
update = apply_oft_sinkhorn(update, iters=sinkhorn_iterations, p=p, ortho_project=orthogonal_sinkhorn)
|
|
349
360
|
else:
|
|
350
361
|
# For vectors, apply sign operation
|
|
351
362
|
update = update.sign_()
|
|
@@ -362,9 +373,13 @@ class SinkSGD_adv(torch.optim.Optimizer):
|
|
|
362
373
|
del anchor
|
|
363
374
|
|
|
364
375
|
update_scaling = step_size
|
|
365
|
-
|
|
376
|
+
|
|
377
|
+
# If spectral normalization was already applied natively in the OFT sinkhorn, skip the scaling block
|
|
378
|
+
spectral_sinkhorn_applied = oft_sym and not normed_mt and group.get('spectral_normalization', False)
|
|
379
|
+
|
|
380
|
+
if group.get('spectral_normalization', False) and not spectral_sinkhorn_applied:
|
|
366
381
|
update = scale_update(p, update, update_scaling, state=state)
|
|
367
|
-
|
|
382
|
+
elif not spectral_sinkhorn_applied:
|
|
368
383
|
if snr_cond:
|
|
369
384
|
update_scaling = update_scaling * (4/math.pi)
|
|
370
385
|
update.mul_(update_scaling)
|
|
@@ -6,7 +6,7 @@ import torch.nn.functional as F
|
|
|
6
6
|
|
|
7
7
|
from typing import Dict, Any
|
|
8
8
|
|
|
9
|
-
from .scaled_optm import adjust_wds
|
|
9
|
+
from .scaled_optm import adjust_wds
|
|
10
10
|
from .centered_decay import dequantize_anchor
|
|
11
11
|
|
|
12
12
|
_generators: Dict[torch.device, torch.Generator] = {}
|
|
@@ -18,8 +18,8 @@ def _apply_weight_decay(
|
|
|
18
18
|
p: Tensor,
|
|
19
19
|
state: Dict[str, Any],
|
|
20
20
|
group: Dict[str, Any],
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
scaled_wd: float | Tensor | None,
|
|
22
|
+
scaled_cwd: float | Tensor | None,
|
|
23
23
|
wd_target: Tensor | None = None,
|
|
24
24
|
cwd_target: Tensor | None = None,
|
|
25
25
|
) -> None:
|
|
@@ -29,26 +29,26 @@ def _apply_weight_decay(
|
|
|
29
29
|
cautious = group.get('cautious_wd', False)
|
|
30
30
|
|
|
31
31
|
# Standard Weight Decay (pulls toward zero)
|
|
32
|
-
if
|
|
32
|
+
if scaled_wd is not None:
|
|
33
33
|
if wd_target is None:
|
|
34
34
|
wd_target = p_calc
|
|
35
35
|
# Cautious Weight Decay: only decay if the update pushes in the same direction as the decay
|
|
36
36
|
if cautious:
|
|
37
37
|
mask = (update_calc * p_calc >= 0).to(p_calc.dtype)
|
|
38
|
-
if isinstance(
|
|
39
|
-
p_calc.addcmul_(wd_target, mask *
|
|
38
|
+
if isinstance(scaled_wd, Tensor):
|
|
39
|
+
p_calc.addcmul_(wd_target, mask * scaled_wd, value=-1.0)
|
|
40
40
|
else:
|
|
41
|
-
p_calc.addcmul_(wd_target, mask, value=-
|
|
41
|
+
p_calc.addcmul_(wd_target, mask, value=-scaled_wd)
|
|
42
42
|
del mask
|
|
43
43
|
else:
|
|
44
44
|
# Standard decoupled weight decay
|
|
45
|
-
if isinstance(
|
|
46
|
-
p_calc.addcmul_(wd_target,
|
|
45
|
+
if isinstance(scaled_wd, Tensor):
|
|
46
|
+
p_calc.addcmul_(wd_target, scaled_wd, value=-1.0)
|
|
47
47
|
else:
|
|
48
|
-
p_calc.add_(wd_target, alpha=-
|
|
48
|
+
p_calc.add_(wd_target, alpha=-scaled_wd)
|
|
49
49
|
|
|
50
50
|
# Centered Weight Decay (pulls toward anchor)
|
|
51
|
-
if
|
|
51
|
+
if scaled_cwd is not None and 'anchor_data' in state:
|
|
52
52
|
if cwd_target is not None:
|
|
53
53
|
decay_target = cwd_target
|
|
54
54
|
else:
|
|
@@ -59,17 +59,17 @@ def _apply_weight_decay(
|
|
|
59
59
|
if cautious:
|
|
60
60
|
# Cautious Weight Decay: only decay if the update pushes in the same direction as the decay
|
|
61
61
|
mask = (update_calc * decay_target >= 0).to(p_calc.dtype)
|
|
62
|
-
if isinstance(
|
|
63
|
-
p_calc.addcmul_(decay_target, mask *
|
|
62
|
+
if isinstance(scaled_cwd, Tensor):
|
|
63
|
+
p_calc.addcmul_(decay_target, mask * scaled_cwd, value=-1.0)
|
|
64
64
|
else:
|
|
65
|
-
p_calc.addcmul_(decay_target, mask, value=-
|
|
65
|
+
p_calc.addcmul_(decay_target, mask, value=-scaled_cwd)
|
|
66
66
|
del mask
|
|
67
67
|
else:
|
|
68
68
|
# Standard decoupled weight decay
|
|
69
|
-
if isinstance(
|
|
70
|
-
p_calc.addcmul_(decay_target,
|
|
69
|
+
if isinstance(scaled_cwd, Tensor):
|
|
70
|
+
p_calc.addcmul_(decay_target, scaled_cwd, value=-1.0)
|
|
71
71
|
else:
|
|
72
|
-
p_calc.add_(decay_target, alpha=-
|
|
72
|
+
p_calc.add_(decay_target, alpha=-scaled_cwd)
|
|
73
73
|
|
|
74
74
|
if cwd_target is None:
|
|
75
75
|
del decay_target
|
|
@@ -105,24 +105,18 @@ def apply_parameter_update(
|
|
|
105
105
|
wd = group["weight_decay"] if wd is None else wd
|
|
106
106
|
cwd = group.get("centered_wd", 0.0)
|
|
107
107
|
wd, cwd = adjust_wds(wd, cwd, p)
|
|
108
|
-
scaled_wd = group.get("scaled_wd", False)
|
|
109
|
-
decoupled = scaled_wd or decoupled
|
|
110
108
|
|
|
111
109
|
# Calculate global decay factor for decoupled vs standard
|
|
112
110
|
decay_factor = (lr / self._init_lr) if decoupled else lr
|
|
113
111
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if scaled_wd:
|
|
118
|
-
eff_wd = scale_wd(eff_wd, p, skip_vectors=True)
|
|
119
|
-
eff_cwd = scale_wd(eff_cwd, p, skip_vectors=False)
|
|
112
|
+
scaled_wd = (wd * decay_factor) if wd != 0 else None
|
|
113
|
+
scaled_cwd = (cwd * decay_factor) if cwd != 0 else None
|
|
120
114
|
|
|
121
115
|
if wd_scaler is not None:
|
|
122
|
-
if
|
|
123
|
-
|
|
124
|
-
if
|
|
125
|
-
|
|
116
|
+
if scaled_wd is not None:
|
|
117
|
+
scaled_wd = scaled_wd * wd_scaler
|
|
118
|
+
if scaled_cwd is not None:
|
|
119
|
+
scaled_cwd = scaled_cwd * wd_scaler
|
|
126
120
|
|
|
127
121
|
state = self.state[p]
|
|
128
122
|
|
|
@@ -135,8 +129,8 @@ def apply_parameter_update(
|
|
|
135
129
|
cwd_t = cwd_target.float() if cwd_target is not None else None
|
|
136
130
|
|
|
137
131
|
# Apply weight decay if needed
|
|
138
|
-
if
|
|
139
|
-
_apply_weight_decay(p_fp32, update_fp32, p, state, group,
|
|
132
|
+
if scaled_wd is not None or scaled_cwd is not None:
|
|
133
|
+
_apply_weight_decay(p_fp32, update_fp32, p, state, group, scaled_wd, scaled_cwd, wd_t, cwd_t)
|
|
140
134
|
|
|
141
135
|
# Apply main update
|
|
142
136
|
p_fp32.add_(-update_fp32)
|
|
@@ -153,8 +147,8 @@ def apply_parameter_update(
|
|
|
153
147
|
|
|
154
148
|
else:
|
|
155
149
|
# Standard path for non-bfloat16 or without stochastic rounding
|
|
156
|
-
if
|
|
157
|
-
_apply_weight_decay(p, update, p, state, group,
|
|
150
|
+
if scaled_wd is not None or scaled_cwd is not None:
|
|
151
|
+
_apply_weight_decay(p, update, p, state, group, scaled_wd, scaled_cwd, wd_target, cwd_target)
|
|
158
152
|
|
|
159
153
|
# Apply main update
|
|
160
154
|
p.add_(-update)
|
|
@@ -97,25 +97,6 @@ def adjust_wds(wd: float, cwd: float, p: torch.Tensor) -> tuple[float, float]:
|
|
|
97
97
|
# Centered WD safely regularizes the delta without collapsing base feature variance.
|
|
98
98
|
return wd, cwd
|
|
99
99
|
|
|
100
|
-
def scale_wd(wd: float | None, p: torch.Tensor, skip_vectors: bool = False) -> float:
|
|
101
|
-
"""
|
|
102
|
-
Scale-invariant, dimension-scaled weight decay.
|
|
103
|
-
"""
|
|
104
|
-
if wd is None:
|
|
105
|
-
return wd
|
|
106
|
-
if getattr(p, '_is_oft', False):
|
|
107
|
-
n_el = p.shape[-1]
|
|
108
|
-
b = (1.0 + math.sqrt(1.0 + 8.0 * n_el)) / 2.0
|
|
109
|
-
wd = (2 * wd) / (b - 1)
|
|
110
|
-
return wd
|
|
111
|
-
|
|
112
|
-
is_vector = p.ndim < 2 or getattr(p, '_is_dora_scale', False) or getattr(p, 'is_vector', False)
|
|
113
|
-
if is_vector:
|
|
114
|
-
return 0.0 if skip_vectors else wd
|
|
115
|
-
|
|
116
|
-
if p.ndim >= 2:
|
|
117
|
-
width = p.numel() // p.shape[0]
|
|
118
|
-
return wd / width
|
|
119
100
|
|
|
120
101
|
def is_spectral(p: torch.Tensor) -> bool:
|
|
121
102
|
"""Determines if a parameter should undergo spectral normalization updates."""
|
|
@@ -133,7 +114,7 @@ def init_spectral_norm(state: dict, p: torch.Tensor):
|
|
|
133
114
|
if getattr(p, '_is_oft', False):
|
|
134
115
|
n_el = p.shape[-1]
|
|
135
116
|
b = int((1.0 + math.sqrt(1.0 + 8.0 * n_el)) / 2.0)
|
|
136
|
-
|
|
117
|
+
get_cached_structural_tensors(b, p.device)
|
|
137
118
|
gen = param_update.get_generator(p.device)
|
|
138
119
|
batch_size = p.numel() // n_el
|
|
139
120
|
# Initialize v (Right singular vector)
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import torch
|
|
3
|
+
|
|
4
|
+
from . import scaled_optm
|
|
5
|
+
|
|
6
|
+
def apply_sr_sinkhorn(update: torch.Tensor, iters: int = 5, p: torch.Tensor | None = None, ortho_project: bool = False) -> torch.Tensor:
|
|
7
|
+
"""
|
|
8
|
+
Applies Square-Root Sinkhorn (SR-Sinkhorn) multi-normalization.
|
|
9
|
+
As described in 'Gradient Multi-Normalization for Efficient LLM Training'.
|
|
10
|
+
|
|
11
|
+
This technique normalizes a 2D matrix alternatively by its row-wise L2 norm
|
|
12
|
+
and column-wise L2 norm, driving it toward a fixed point that uniformly
|
|
13
|
+
distributes update magnitudes.
|
|
14
|
+
"""
|
|
15
|
+
original_shape = update.shape
|
|
16
|
+
original_dtype = update.dtype
|
|
17
|
+
update = update.float()
|
|
18
|
+
|
|
19
|
+
# 1D Vector Case
|
|
20
|
+
if update.dim() == 1:
|
|
21
|
+
if ortho_project:
|
|
22
|
+
p_float = p.float()
|
|
23
|
+
p_norm_sq = torch.dot(p_float, p_float).add_(1e-30)
|
|
24
|
+
proj = torch.dot(p_float, update) / p_norm_sq
|
|
25
|
+
update.sub_(p_float * proj)
|
|
26
|
+
norm = update.norm(p=2).clamp_min_(1e-12)
|
|
27
|
+
return update.mul_(math.sqrt(update.numel()) / norm).view(original_shape).to(original_dtype)
|
|
28
|
+
|
|
29
|
+
# 2D+ Matrix Case
|
|
30
|
+
update_2d = update.view(update.shape[0], -1)
|
|
31
|
+
|
|
32
|
+
m, n = update_2d.shape
|
|
33
|
+
|
|
34
|
+
# Dynamically determine the order of normalization based on aspect ratio
|
|
35
|
+
# Normalizing the longer dimension first aids stability.
|
|
36
|
+
scale_cond = update_2d.shape[0] > update_2d.shape[1]
|
|
37
|
+
dim = 0 if scale_cond else 1
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# Precompute scaling factors.
|
|
41
|
+
scale_first = math.sqrt(m if scale_cond else n)
|
|
42
|
+
scale_second = math.sqrt(n if scale_cond else m)
|
|
43
|
+
|
|
44
|
+
if ortho_project:
|
|
45
|
+
param_2d = p.float().view(p.shape[0], -1)
|
|
46
|
+
p_norm_sq_dim = torch.sum(param_2d * param_2d, dim=dim, keepdim=True).add_(1e-30)
|
|
47
|
+
p_norm_sq_adim = torch.sum(param_2d * param_2d, dim=1-dim, keepdim=True).add_(1e-30)
|
|
48
|
+
|
|
49
|
+
# In-place alternating Sinkhorn normalization steps
|
|
50
|
+
for _ in range(iters):
|
|
51
|
+
# First normalization step
|
|
52
|
+
# Stability floor: equivalent to a single-element vector norm lower bound (lb)
|
|
53
|
+
norm1_lb = 1 / math.sqrt(update_2d.shape[dim])
|
|
54
|
+
norm1 = update_2d.norm(p=2, dim=dim, keepdim=True).clamp_min_(norm1_lb)
|
|
55
|
+
update_2d.mul_(scale_first / norm1)
|
|
56
|
+
if ortho_project:
|
|
57
|
+
update_2d = ortho_normed(param_2d, update_2d, p_norm_sq_dim, dim, scale_first)
|
|
58
|
+
|
|
59
|
+
# Second normalization step
|
|
60
|
+
norm2_lb = 1 / math.sqrt(update_2d.shape[1-dim])
|
|
61
|
+
norm2 = update_2d.norm(p=2, dim=1-dim, keepdim=True).clamp_min_(norm2_lb)
|
|
62
|
+
update_2d.mul_(scale_second / norm2)
|
|
63
|
+
if ortho_project:
|
|
64
|
+
update_2d = ortho_normed(param_2d, update_2d, p_norm_sq_adim, 1-dim, scale_second)
|
|
65
|
+
|
|
66
|
+
return update_2d.view(original_shape).to(original_dtype)
|
|
67
|
+
|
|
68
|
+
def ortho_normed(p_2d, update_2d, p_norm_sq, dim, target_norm):
|
|
69
|
+
"""
|
|
70
|
+
Projects the update to be orthogonal to p along 'dim' and restores the original norm.
|
|
71
|
+
"""
|
|
72
|
+
# Project: g_orth = g - (p * <p, g> / ||p||^2)
|
|
73
|
+
dot_prod = torch.sum(p_2d * update_2d, dim=dim, keepdim=True)
|
|
74
|
+
proj = dot_prod / p_norm_sq
|
|
75
|
+
|
|
76
|
+
# In-place subtraction: update_2d = update_2d - (proj * p_2d)
|
|
77
|
+
update_2d.addcmul_(proj, p_2d, value=-1.0)
|
|
78
|
+
|
|
79
|
+
# Magnitude Preservation
|
|
80
|
+
norm_lb = 1 / math.sqrt(update_2d.shape[dim])
|
|
81
|
+
g_orth_norm = update_2d.norm(p=2, dim=dim, keepdim=True).clamp_min_(norm_lb)
|
|
82
|
+
scale_factor = target_norm / g_orth_norm
|
|
83
|
+
return update_2d.mul_(scale_factor)
|
|
84
|
+
|
|
85
|
+
def get_sinkhorn_wd_scaler(
|
|
86
|
+
p: torch.Tensor,
|
|
87
|
+
row_denom: torch.Tensor | None = None,
|
|
88
|
+
col_denom: torch.Tensor | None = None
|
|
89
|
+
):
|
|
90
|
+
"""
|
|
91
|
+
Computes a structural weight decay multiplier.
|
|
92
|
+
Penalizes parameters belonging to dominant rows/columns more heavily,
|
|
93
|
+
while protecting parameters in under-utilized/noisy rows/columns from decay.
|
|
94
|
+
"""
|
|
95
|
+
if p.ndim < 2:
|
|
96
|
+
return 1.0
|
|
97
|
+
|
|
98
|
+
p_2d = p.view(p.shape[0], -1)
|
|
99
|
+
|
|
100
|
+
# Lower bounds based on the effective 2D shapes
|
|
101
|
+
row_lb = 1 / math.sqrt(p_2d.shape[1])
|
|
102
|
+
col_lb = 1 / math.sqrt(p_2d.shape[0])
|
|
103
|
+
|
|
104
|
+
# Get the norms
|
|
105
|
+
row_norms = torch.linalg.vector_norm(p_2d, ord=2, dim=1, keepdim=True).clamp_min_(row_lb)
|
|
106
|
+
col_norms = torch.linalg.vector_norm(p_2d, ord=2, dim=0, keepdim=True).clamp_min_(col_lb)
|
|
107
|
+
|
|
108
|
+
# Compute the structural scaler
|
|
109
|
+
row_factor = row_norms.sqrt_()
|
|
110
|
+
col_factor = col_norms.sqrt_()
|
|
111
|
+
|
|
112
|
+
if row_denom is not None and col_denom is not None:
|
|
113
|
+
# Reshape denominators to ensure safe in-place broadcasting
|
|
114
|
+
row_denom = row_denom.sqrt().view(p_2d.shape[0], 1)
|
|
115
|
+
col_denom = col_denom.sqrt().view(1, p_2d.shape[1])
|
|
116
|
+
|
|
117
|
+
# High denom (noise) -> smaller angle (protects weights)
|
|
118
|
+
# Low denom (confident) -> larger angle (decays weights)
|
|
119
|
+
row_factor.atan2_(row_denom)
|
|
120
|
+
col_factor.atan2_(col_denom)
|
|
121
|
+
|
|
122
|
+
# Outer product: merges the row and column confidences into a 2D matrix
|
|
123
|
+
wd_scaler = row_factor * col_factor
|
|
124
|
+
|
|
125
|
+
# Normalize the scaler so its mean is exactly 1.0
|
|
126
|
+
wd_scaler.div_(wd_scaler.mean().clamp_min_(1e-12))
|
|
127
|
+
|
|
128
|
+
return wd_scaler.view_as(p)
|
|
129
|
+
|
|
130
|
+
def apply_oft_sinkhorn(
|
|
131
|
+
update: torch.Tensor,
|
|
132
|
+
iters: int = 5,
|
|
133
|
+
p: torch.Tensor | None = None,
|
|
134
|
+
ortho_project: bool = False
|
|
135
|
+
):
|
|
136
|
+
n_el = update.shape[-1]
|
|
137
|
+
block_size = int((1 + math.sqrt(1 + 8 * n_el)) / 2)
|
|
138
|
+
device, dtype = update.device, update.dtype
|
|
139
|
+
rows, cols = scaled_optm.get_cached_structural_tensors(block_size, device)
|
|
140
|
+
batch_size = update.shape[0]
|
|
141
|
+
|
|
142
|
+
# Initialize matrices
|
|
143
|
+
G = torch.zeros(batch_size, block_size, block_size, device=device, dtype=dtype)
|
|
144
|
+
batch_idx = torch.arange(batch_size, device=device)[:, None]
|
|
145
|
+
|
|
146
|
+
# Construct skew-symmetric gradient matrix G
|
|
147
|
+
G = G.index_put((batch_idx, rows, cols), update)
|
|
148
|
+
G = G - G.transpose(-2, -1)
|
|
149
|
+
|
|
150
|
+
# If OrthoGrad is enabled, construct the skew-symmetric parameter matrix Q
|
|
151
|
+
if ortho_project and p is not None:
|
|
152
|
+
Q = torch.zeros_like(G)
|
|
153
|
+
p_flat = p.view(batch_size, -1)
|
|
154
|
+
Q = Q.index_put((batch_idx, rows, cols), p_flat)
|
|
155
|
+
Q = Q - Q.transpose(-2, -1)
|
|
156
|
+
# Compute the squared Frobenius norm for each block (batch-wise)
|
|
157
|
+
Q_norm_sq = torch.sum(Q * Q, dim=(1, 2), keepdim=True).add_(1e-30)
|
|
158
|
+
# Theoretical norm of a Sinkhorn-normalized matrix to restore magnitude
|
|
159
|
+
target_norm = math.sqrt(block_size)
|
|
160
|
+
|
|
161
|
+
for _ in range(iters):
|
|
162
|
+
norms = torch.linalg.vector_norm(G, ord=2, dim=2, keepdim=True).clamp_min_(1e-12)
|
|
163
|
+
S = norms.sqrt_()
|
|
164
|
+
G.div_(S * S.mT)
|
|
165
|
+
|
|
166
|
+
# OrthoGrad step
|
|
167
|
+
if ortho_project and p is not None:
|
|
168
|
+
# Batch-wise Frobenius inner product
|
|
169
|
+
dot_prod = torch.sum(Q * G, dim=(1, 2), keepdim=True)
|
|
170
|
+
proj = dot_prod / Q_norm_sq
|
|
171
|
+
# Subtract projection
|
|
172
|
+
G.addcmul_(proj, Q, value=-1.0)
|
|
173
|
+
# Restore magnitude
|
|
174
|
+
g_orth_norm = torch.linalg.vector_norm(G, ord=2, dim=(1, 2), keepdim=True).clamp_min_(1e-12)
|
|
175
|
+
G.mul_(target_norm / g_orth_norm)
|
|
176
|
+
|
|
177
|
+
update.copy_(G[batch_idx, rows, cols])
|
|
178
|
+
|
|
179
|
+
# Global normalization
|
|
180
|
+
norm = torch.linalg.vector_norm(update).clamp_min_(1e-12)
|
|
181
|
+
target_norm_global = math.sqrt(update.numel())
|
|
182
|
+
update.mul_(target_norm_global / norm)
|
|
183
|
+
|
|
184
|
+
return update
|
|
185
|
+
|
|
186
|
+
@torch.no_grad()
|
|
187
|
+
def apply_spectral_oft_sinkhorn(
|
|
188
|
+
p: torch.Tensor,
|
|
189
|
+
update: torch.Tensor,
|
|
190
|
+
lr: float,
|
|
191
|
+
state: dict,
|
|
192
|
+
iters: int = 5,
|
|
193
|
+
ortho_project: bool = True
|
|
194
|
+
) -> torch.Tensor:
|
|
195
|
+
"""
|
|
196
|
+
Applies Spectral Normalization directly on the skew-symmetric gradient.
|
|
197
|
+
"""
|
|
198
|
+
n_el = p.shape[-1]
|
|
199
|
+
block_size = int((1 + math.sqrt(1 + 8 * n_el)) / 2)
|
|
200
|
+
device, dtype = p.device, p.dtype
|
|
201
|
+
rows, cols = scaled_optm.get_cached_structural_tensors(block_size, device)
|
|
202
|
+
|
|
203
|
+
orig_shape = p.shape
|
|
204
|
+
|
|
205
|
+
# Align the scale of p with the forward pass
|
|
206
|
+
scale_factor = getattr(p, '_oft_scale_factor', 1.0)
|
|
207
|
+
batch_size = update.shape[0]
|
|
208
|
+
|
|
209
|
+
# Initialize matrices
|
|
210
|
+
G = torch.zeros(batch_size, block_size, block_size, device=device, dtype=dtype)
|
|
211
|
+
batch_idx = torch.arange(batch_size, device=device)[:, None]
|
|
212
|
+
|
|
213
|
+
# Construct skew-symmetric gradient matrix G
|
|
214
|
+
G = G.index_put((batch_idx, rows, cols), update)
|
|
215
|
+
G = G - G.transpose(-2, -1)
|
|
216
|
+
|
|
217
|
+
# If OrthoGrad is enabled, construct the skew-symmetric parameter matrix Q
|
|
218
|
+
if ortho_project and p is not None:
|
|
219
|
+
Q = torch.zeros_like(G)
|
|
220
|
+
p_flat = p.view(batch_size, -1)
|
|
221
|
+
Q = Q.index_put((batch_idx, rows, cols), p_flat)
|
|
222
|
+
Q = Q - Q.transpose(-2, -1)
|
|
223
|
+
# Compute the squared Frobenius norm for each block (batch-wise)
|
|
224
|
+
Q_norm_sq = torch.sum(Q * Q, dim=(1, 2), keepdim=True).add_(1e-30)
|
|
225
|
+
# Theoretical norm of a Sinkhorn-normalized matrix to restore magnitude
|
|
226
|
+
target_norm = math.sqrt(block_size)
|
|
227
|
+
|
|
228
|
+
for _ in range(iters):
|
|
229
|
+
norms = torch.linalg.vector_norm(G, ord=2, dim=2, keepdim=True).clamp_min_(1e-12)
|
|
230
|
+
S = norms.sqrt_()
|
|
231
|
+
G.div_(S * S.mT)
|
|
232
|
+
|
|
233
|
+
# OrthoGrad step
|
|
234
|
+
if ortho_project and p is not None:
|
|
235
|
+
# Batch-wise Frobenius inner product
|
|
236
|
+
dot_prod = torch.sum(Q * G, dim=(1, 2), keepdim=True)
|
|
237
|
+
proj = dot_prod / Q_norm_sq
|
|
238
|
+
# Subtract projection
|
|
239
|
+
G.addcmul_(proj, Q, value=-1.0)
|
|
240
|
+
# Restore magnitude
|
|
241
|
+
g_orth_norm = torch.linalg.vector_norm(G, ord=2, dim=(1, 2), keepdim=True).clamp_min_(1e-12)
|
|
242
|
+
G.mul_(target_norm / g_orth_norm)
|
|
243
|
+
|
|
244
|
+
update.copy_(G[batch_idx, rows, cols])
|
|
245
|
+
|
|
246
|
+
# Spectral Normalization on G
|
|
247
|
+
u_state = state['spectral_u'].unsqueeze(-1).to(dtype)
|
|
248
|
+
v_state = state['spectral_v'].unsqueeze(-1).to(dtype)
|
|
249
|
+
# Power Iteration step to estimate the largest singular value (sigma)
|
|
250
|
+
# Update v (Right Singular Vector)
|
|
251
|
+
v_raw = torch.bmm(G.mT, u_state)
|
|
252
|
+
v_norm = torch.linalg.vector_norm(v_raw, dim=1, keepdim=True)
|
|
253
|
+
candidate_v = v_raw / v_norm.clamp_min(1e-8)
|
|
254
|
+
next_v = torch.where(v_norm >= 1e-6, candidate_v, v_state)
|
|
255
|
+
# Update u (Left Singular Vector)
|
|
256
|
+
u_raw = torch.bmm(G, next_v)
|
|
257
|
+
u_norm = torch.linalg.vector_norm(u_raw, dim=1, keepdim=True)
|
|
258
|
+
candidate_u = u_raw / u_norm.clamp_min(1e-8)
|
|
259
|
+
next_u = torch.where(u_norm >= 1e-6, candidate_u, u_state)
|
|
260
|
+
state['spectral_v'].copy_(next_v.squeeze(-1))
|
|
261
|
+
state['spectral_u'].copy_(next_u.squeeze(-1))
|
|
262
|
+
|
|
263
|
+
# Estimate sigma (The spectral norm) for each block
|
|
264
|
+
sigma = torch.sum(next_u * u_raw, dim=1, keepdim=True)
|
|
265
|
+
|
|
266
|
+
# Squeeze out the last dimension so shape becomes (batch_size, 1)
|
|
267
|
+
sigma = sigma.squeeze(-1)
|
|
268
|
+
|
|
269
|
+
target_scale = 0.5 * scale_factor
|
|
270
|
+
spectral_eps = 1.0 / (2.0 * math.sqrt(block_size))
|
|
271
|
+
|
|
272
|
+
# Apply the clamp and scaling block-wise
|
|
273
|
+
scale = lr * (target_scale / sigma.clamp_min(spectral_eps))
|
|
274
|
+
|
|
275
|
+
return update.mul_(scale).view(orig_shape)
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import math
|
|
2
|
-
import torch
|
|
3
|
-
|
|
4
|
-
def apply_sr_sinkhorn(update: torch.Tensor, iters: int = 5, p: torch.Tensor | None = None, ortho_project: bool = False) -> torch.Tensor:
|
|
5
|
-
"""
|
|
6
|
-
Applies Square-Root Sinkhorn (SR-Sinkhorn) multi-normalization.
|
|
7
|
-
As described in 'Gradient Multi-Normalization for Efficient LLM Training'.
|
|
8
|
-
|
|
9
|
-
This technique normalizes a 2D matrix alternatively by its row-wise L2 norm
|
|
10
|
-
and column-wise L2 norm, driving it toward a fixed point that uniformly
|
|
11
|
-
distributes update magnitudes.
|
|
12
|
-
"""
|
|
13
|
-
original_shape = update.shape
|
|
14
|
-
original_dtype = update.dtype
|
|
15
|
-
update = update.float()
|
|
16
|
-
|
|
17
|
-
# 1D Vector Case
|
|
18
|
-
if update.dim() == 1:
|
|
19
|
-
if ortho_project:
|
|
20
|
-
p_float = p.float()
|
|
21
|
-
p_norm_sq = torch.dot(p_float, p_float).add_(1e-30)
|
|
22
|
-
proj = torch.dot(p_float, update) / p_norm_sq
|
|
23
|
-
update.sub_(p_float * proj)
|
|
24
|
-
norm = update.norm(p=2).clamp_min_(1e-12)
|
|
25
|
-
return update.mul_(math.sqrt(update.numel()) / norm).view(original_shape).to(original_dtype)
|
|
26
|
-
|
|
27
|
-
# 2D+ Matrix Case
|
|
28
|
-
update_2d = update.view(update.shape[0], -1)
|
|
29
|
-
|
|
30
|
-
m, n = update_2d.shape
|
|
31
|
-
|
|
32
|
-
# Dynamically determine the order of normalization based on aspect ratio
|
|
33
|
-
# Normalizing the longer dimension first aids stability.
|
|
34
|
-
scale_cond = update_2d.shape[0] > update_2d.shape[1]
|
|
35
|
-
dim = 0 if scale_cond else 1
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
# Precompute scaling factors.
|
|
39
|
-
scale_first = math.sqrt(m if scale_cond else n)
|
|
40
|
-
scale_second = math.sqrt(n if scale_cond else m)
|
|
41
|
-
|
|
42
|
-
if ortho_project:
|
|
43
|
-
param_2d = p.float().view(p.shape[0], -1)
|
|
44
|
-
p_norm_sq_dim = torch.sum(param_2d * param_2d, dim=dim, keepdim=True).add_(1e-30)
|
|
45
|
-
p_norm_sq_adim = torch.sum(param_2d * param_2d, dim=1-dim, keepdim=True).add_(1e-30)
|
|
46
|
-
|
|
47
|
-
# In-place alternating Sinkhorn normalization steps
|
|
48
|
-
for _ in range(iters):
|
|
49
|
-
# First normalization step
|
|
50
|
-
# Stability floor: equivalent to a single-element vector norm lower bound (lb)
|
|
51
|
-
norm1_lb = 1 / math.sqrt(update_2d.shape[dim])
|
|
52
|
-
norm1 = update_2d.norm(p=2, dim=dim, keepdim=True).clamp_min_(norm1_lb)
|
|
53
|
-
update_2d.mul_(scale_first / norm1)
|
|
54
|
-
if ortho_project:
|
|
55
|
-
update_2d = ortho_normed(param_2d, update_2d, p_norm_sq_dim, dim, scale_first)
|
|
56
|
-
|
|
57
|
-
# Second normalization step
|
|
58
|
-
norm2_lb = 1 / math.sqrt(update_2d.shape[1-dim])
|
|
59
|
-
norm2 = update_2d.norm(p=2, dim=1-dim, keepdim=True).clamp_min_(norm2_lb)
|
|
60
|
-
update_2d.mul_(scale_second / norm2)
|
|
61
|
-
if ortho_project:
|
|
62
|
-
update_2d = ortho_normed(param_2d, update_2d, p_norm_sq_adim, 1-dim, scale_second)
|
|
63
|
-
|
|
64
|
-
return update_2d.view(original_shape).to(original_dtype)
|
|
65
|
-
|
|
66
|
-
def ortho_normed(p_2d, update_2d, p_norm_sq, dim, target_norm):
|
|
67
|
-
"""
|
|
68
|
-
Projects the update to be orthogonal to p along 'dim' and restores the original norm.
|
|
69
|
-
"""
|
|
70
|
-
# Project: g_orth = g - (p * <p, g> / ||p||^2)
|
|
71
|
-
dot_prod = torch.sum(p_2d * update_2d, dim=dim, keepdim=True)
|
|
72
|
-
proj = dot_prod / p_norm_sq
|
|
73
|
-
|
|
74
|
-
# In-place subtraction: update_2d = update_2d - (proj * p_2d)
|
|
75
|
-
update_2d.addcmul_(proj, p_2d, value=-1.0)
|
|
76
|
-
|
|
77
|
-
# Magnitude Preservation
|
|
78
|
-
norm_lb = 1 / math.sqrt(update_2d.shape[dim])
|
|
79
|
-
g_orth_norm = update_2d.norm(p=2, dim=dim, keepdim=True).clamp_min_(norm_lb)
|
|
80
|
-
scale_factor = target_norm / g_orth_norm
|
|
81
|
-
return update_2d.mul_(scale_factor)
|
|
82
|
-
|
|
83
|
-
def get_sinkhorn_wd_scaler(
|
|
84
|
-
p: torch.Tensor,
|
|
85
|
-
row_denom: torch.Tensor | None = None,
|
|
86
|
-
col_denom: torch.Tensor | None = None
|
|
87
|
-
):
|
|
88
|
-
"""
|
|
89
|
-
Computes a structural weight decay multiplier.
|
|
90
|
-
Penalizes parameters belonging to dominant rows/columns more heavily,
|
|
91
|
-
while protecting parameters in under-utilized/noisy rows/columns from decay.
|
|
92
|
-
"""
|
|
93
|
-
if p.ndim < 2:
|
|
94
|
-
return 1.0
|
|
95
|
-
|
|
96
|
-
p_2d = p.view(p.shape[0], -1)
|
|
97
|
-
|
|
98
|
-
# Lower bounds based on the effective 2D shapes
|
|
99
|
-
row_lb = 1 / math.sqrt(p_2d.shape[1])
|
|
100
|
-
col_lb = 1 / math.sqrt(p_2d.shape[0])
|
|
101
|
-
|
|
102
|
-
# Get the norms
|
|
103
|
-
row_norms = torch.linalg.vector_norm(p_2d, ord=2, dim=1, keepdim=True).clamp_min_(row_lb)
|
|
104
|
-
col_norms = torch.linalg.vector_norm(p_2d, ord=2, dim=0, keepdim=True).clamp_min_(col_lb)
|
|
105
|
-
|
|
106
|
-
# Compute the structural scaler
|
|
107
|
-
row_factor = row_norms.sqrt_()
|
|
108
|
-
col_factor = col_norms.sqrt_()
|
|
109
|
-
|
|
110
|
-
if row_denom is not None and col_denom is not None:
|
|
111
|
-
# Reshape denominators to ensure safe in-place broadcasting
|
|
112
|
-
row_denom = row_denom.sqrt().view(p_2d.shape[0], 1)
|
|
113
|
-
col_denom = col_denom.sqrt().view(1, p_2d.shape[1])
|
|
114
|
-
|
|
115
|
-
# High denom (noise) -> smaller angle (protects weights)
|
|
116
|
-
# Low denom (confident) -> larger angle (decays weights)
|
|
117
|
-
row_factor.atan2_(row_denom)
|
|
118
|
-
col_factor.atan2_(col_denom)
|
|
119
|
-
|
|
120
|
-
# Outer product: merges the row and column confidences into a 2D matrix
|
|
121
|
-
wd_scaler = row_factor * col_factor
|
|
122
|
-
|
|
123
|
-
# Normalize the scaler so its mean is exactly 1.0
|
|
124
|
-
wd_scaler.div_(wd_scaler.mean().clamp_min_(1e-12))
|
|
125
|
-
|
|
126
|
-
return wd_scaler.view_as(p)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|