Skip to content

Commit 6bf55d8

Browse files
committed
faster psgd
1 parent 284de59 commit 6bf55d8

2 files changed

Lines changed: 35 additions & 50 deletions

File tree

examples/autoencoder.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,7 @@ def main(epochs: int, batch: int, log_interval: int = 16):
6666

6767
model = torch.compile(Autoencoder().cuda(), mode="default")
6868
optimizer = heavyball.PSGDPRO(
69-
model.parameters(),
70-
lr=1e-3,
71-
precond_update_power_iterations=6,
72-
store_triu_as_line=False, cached=True
69+
model.parameters(), lr=1e-3, precond_update_power_iterations=6, store_triu_as_line=False, cached=True
7370
)
7471

7572
transform = v2.Compose([v2.ToImage(), v2.ToDtype(torch.float32)])

heavyball/utils.py

Lines changed: 34 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2829,24 +2829,21 @@ def max_singular_value_power_iter(A_outer: Tensor, max_abs: Optional[Tensor] = N
28292829
Rayleigh quotient of row with the largest norm + optional power iterations
28302830
"""
28312831
x_norm, max_idx = A_outer.norm(dim=1).max(dim=0)
2832-
x_norm = promote(x_norm)
2832+
x_norm = promote(x_norm).clamp(min=torch.finfo(torch.float32).tiny)
28332833

2834-
def _inner():
2835-
A = A_outer
2836-
x = A.index_select(0, max_idx).flatten().contiguous()
2837-
A = stochastic_round_(A / x_norm)
2838-
x = x / x_norm
2834+
A = A_outer
2835+
x = A.index_select(0, max_idx).flatten().contiguous()
2836+
A = stochastic_round_(A / x_norm)
2837+
x = x / x_norm
28392838

2840-
def _mv(x):
2841-
return promote(A.T.mv(A.mv(x.to(A.dtype))))
2839+
def _mv(x):
2840+
return promote(A.T.mv(A.mv(x.to(A.dtype))))
28422841

2843-
for _ in range(iterations):
2844-
# A @ A.T @ x, but explicitly telling torch.compile not to compute the full matrix
2845-
x = F.normalize(_mv(x), dim=0)
2846-
out = (promote(x) @ _mv(x)).to(x_norm.dtype).sqrt() * x_norm
2847-
return out.squeeze().clone()
2848-
2849-
return cond(x_norm > 0, _inner, lambda: x_norm.squeeze().clone())
2842+
for _ in range(iterations):
2843+
# A @ A.T @ x, but explicitly telling torch.compile not to compute the full matrix
2844+
x = F.normalize(_mv(x), dim=0)
2845+
out = (promote(x) @ _mv(x)).to(x_norm.dtype).sqrt() * x_norm
2846+
return out.squeeze()
28502847

28512848

28522849
@decorator_knowngood
@@ -2904,30 +2901,25 @@ def max_eigenvalue_spd(A_outer: Tensor, power_iter: int = 4) -> Tensor:
29042901
if A_outer.ndim < 2:
29052902
return A_outer.max()
29062903
x_norm, max_idx = A_outer.norm(dim=1).max(dim=0)
2907-
x_norm = promote(x_norm)
2908-
2909-
def _inner():
2910-
x = A_outer.index_select(0, max_idx).flatten().contiguous()
2911-
A = promote(A_outer) / x_norm
2912-
x = x / x_norm
2904+
x_norm = promote(x_norm).clamp(min=torch.finfo(torch.float32).tiny)
29132905

2914-
def _mv(x):
2915-
return promote((x @ A.mT) @ A.mT)
2906+
x = A_outer.index_select(0, max_idx).flatten().contiguous()
2907+
A = promote(A_outer) / x_norm
2908+
x = x / x_norm
29162909

2917-
for _ in range(power_iter):
2918-
x = F.normalize(_mv(x), dim=0)
2919-
return (x @ _mv(x)).sqrt() * x_norm
2910+
def _mv(x):
2911+
return promote((x @ A.mT) @ A.mT)
29202912

2921-
return cond(x_norm > 0, _inner, lambda: x_norm.squeeze().clone()).squeeze()
2913+
for _ in range(power_iter):
2914+
x = F.normalize(_mv(x), dim=0)
2915+
return ((x @ _mv(x)).sqrt() * x_norm).squeeze()
29222916

29232917

29242918
@decorator_knowngood
29252919
def clamped_max_singular_value(
29262920
A: Tensor, min: float, max_svd: int = 0, use_cholesky: bool = False, power_iter: int = 16
29272921
) -> Tensor:
2928-
norm = A.norm() # L2 norm is an upper bound for the spectral norm. If the upper bound is below the minimum, the real value will be too.
2929-
out = cond(norm > min, lambda: max_singular_value(A, max_svd, use_cholesky, power_iter), lambda: norm.clone())
2930-
return out.clamp(min=min)
2922+
return max_singular_value(A, max_svd, use_cholesky, power_iter).clamp(min=min)
29312923

29322924

29332925
@decorator_knowngood
@@ -2953,24 +2945,22 @@ def min_singular_value(
29532945

29542946
row_norms = A.norm(dim=1)
29552947
norm, idx = row_norms.min(dim=0)
2956-
v = cond(norm > 0, lambda: A.index_select(0, idx).flatten(), lambda: torch.rand_like(A[0]))
2948+
v = A.index_select(0, idx).flatten()
2949+
v = v + torch.randn_like(v) * torch.finfo(v.dtype).tiny # break degeneracy if zero row
29572950

2958-
v = v / promote(v.norm())
2951+
v = v / promote(v.norm().clamp(min=torch.finfo(torch.float32).tiny))
29592952
for _ in range(power_iter):
29602953
v = lambda_upper * v - promote(A.mv(v.to(A.dtype)))
2961-
v = v / promote(v.norm())
2954+
v = v / promote(v.norm().clamp(min=torch.finfo(torch.float32).tiny))
29622955
mu_hat = promote(v) @ (lambda_upper * promote(v) - promote(A.mv(v.to(A.dtype))))
29632956

29642957
lambda_min_hat = lambda_upper - mu_hat
29652958

2966-
def _approx():
2967-
mu = A.trace() / n
2968-
sigma_square = A.square().sum() / n - mu**2
2969-
return mu - (sigma_square / (n - 1)).sqrt()
2959+
mu = A.trace() / n
2960+
sigma_square = A.square().sum() / n - mu**2
2961+
approx = mu - (sigma_square / (n - 1)).sqrt()
29702962

2971-
return cond(
2972-
(~torch.isfinite(lambda_min_hat)) | (lambda_min_hat <= 0), _approx, lambda: lambda_min_hat.clone()
2973-
).squeeze()
2963+
return torch.where((~torch.isfinite(lambda_min_hat)) | (lambda_min_hat <= 0), approx, lambda_min_hat).squeeze()
29742964

29752965

29762966
@decorator_knowngood
@@ -3162,14 +3152,12 @@ def psgd_pro_update_precond(
31623152

31633153
# procrustes_step
31643154
R = (q_.T - q_).contiguous()
3165-
R_norm = max_singular_value(R, power_iter=power_iter) + torch.finfo(R.dtype).smallest_normal
3166-
R = R / R_norm
3155+
R = R / (max_singular_value(R, power_iter=power_iter) + torch.finfo(R.dtype).smallest_normal)
31673156
RQ = R @ q_
31683157
RRQ = R @ RQ
3169-
tr_RQ = RQ.diagonal().sum()
3170-
tr_RRQ = RRQ.diagonal().sum()
3171-
a = torch.where(tr_RRQ < 0, (-tr_RQ / tr_RRQ).clamp(max=max_step_size), max_step_size)
3172-
copy_stochastic_(q, q_ + a * RQ + 0.5 * a * a * RRQ)
3158+
c1, c2 = RQ.diagonal().sum(), RRQ.diagonal().sum()
3159+
a = torch.where(c2 < 0, (-c1 / c2).clamp(min=0, max=0.5), 0.5)
3160+
copy_stochastic_(q, q_ + a * RQ + (0.5 * a * a) * RRQ)
31733161

31743162

31753163
@decorator_knowngood

0 commit comments

Comments
 (0)