forked from gabmoreira/spectralguidance
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathspectral.py
More file actions
76 lines (63 loc) · 2.62 KB
/
Copy pathspectral.py
File metadata and controls
76 lines (63 loc) · 2.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import torch
import torch.distributed as dist
import logging
from torch import Tensor
from typing import Optional, Tuple, Union
logger = logging.getLogger(__name__)
def whitening(
x: torch.Tensor,
ridge: float,
return_eig: bool = False,
) -> Union[Tuple[Tensor, Tensor], Tuple[Tensor, Tensor, Tensor]]:
"""
Computes centering mean and whitening matrix.
Single-GPU: SVD on the centered data matrix (better condition number).
Distributed: all-reduce scatter matrix then eigh on the global covariance
(SVD requires the full data matrix, which doesn't exist globally).
Args:
x: (N, K) feature matrix (local shard in distributed mode)
ridge: ridge regularization (acts like eigenvalue floor)
Returns:
mu: (1, K) mean
cov_inv_sqrt: (K, K) whitening matrix
"""
dtype = x.dtype
if dist.is_initialized():
n_local = torch.tensor(len(x), device=x.device, dtype=dtype)
sum_local = x.sum(0) # (K,)
dist.all_reduce(n_local, op=dist.ReduceOp.SUM)
dist.all_reduce(sum_local, op=dist.ReduceOp.SUM)
n_global = n_local
mu = (sum_local / n_global).unsqueeze(0) # (1, K)
x_c = x - mu
S = x_c.T @ x_c # (K, K) local scatter
dist.all_reduce(S, op=dist.ReduceOp.SUM) # global scatter
C = S / (n_global - 1) # (K, K) global covariance
I = torch.eye(C.shape[0], device=C.device)
try:
L, V = torch.linalg.eigh(C + ridge * I)
except RuntimeError:
logger.info("torch.linalg.eigh failed to converge")
U, S, _ = torch.linalg.svd((C + ridge * I).to(torch.float64))
L, V = S.flip(-1).to(dtype), U.flip(-1).to(dtype)
scale = 1.0 / torch.sqrt(L.clamp(min=0.0))
W = (V @ torch.diag(scale)).to(dtype)
eigvals = (L - ridge).clamp(min=0.0) # already ascending from eigh
else:
n_samples = len(x)
mu = x.mean(dim=0, keepdim=True)
x_c = x - mu
try:
_, S, Vh = torch.linalg.svd(x_c, full_matrices=False)
except RuntimeError:
x64 = x_c.to(torch.float64)
_, S, Vh = torch.linalg.svd(x64, full_matrices=False)
S, Vh = S.to(dtype), Vh.to(dtype)
V = Vh.transpose(-2, -1)
scale = 1.0 / torch.sqrt(S**2 / (n_samples - 1) + ridge)
W = (V @ torch.diag(scale)).to(dtype)
eigvals = (S**2 / (n_samples - 1)).flip(0).clamp(min=0.0)
if return_eig:
return mu, W, eigvals
else:
return mu, W