Spaces:
Sleeping
Sleeping
Pick best accelerator by default; route SVDs through MPS-safe path
Browse filesget_device now uses CUDA -> MPS -> CPU priority so Apple Silicon picks
MPS by default. The previous macOS->CPU branch made forward passes
10-50x slower than they need to be. CLI override (--device cuda|mps|cpu)
still wins. Drop the stale "MPS has stability issues" comment;
forward-only inference on MPS is solid in torch >= 2.
torch.linalg.svdvals is NotImplemented on MPS in current torch, but
torch.linalg.svd works. Add a _svdvals() helper in head_analyzer that
delegates to the existing _svd() (and thus inherits its numpy fallback
for ill-conditioned matrices). Swap the three svdvals call sites.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
src/transformer_analysis/device_utils.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
| 1 |
import torch
|
| 2 |
-
import platform
|
| 3 |
|
| 4 |
|
| 5 |
def get_device(device=None):
|
| 6 |
"""
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
|
|
|
| 10 |
|
| 11 |
Args:
|
| 12 |
device: Manual override ('cuda', 'mps', 'cpu', or None for auto)
|
|
@@ -16,10 +16,8 @@ def get_device(device=None):
|
|
| 16 |
"""
|
| 17 |
if device is not None:
|
| 18 |
return torch.device(device)
|
| 19 |
-
|
| 20 |
-
if platform.system() == "Darwin":
|
| 21 |
-
return torch.device("cpu")
|
| 22 |
-
elif torch.cuda.is_available():
|
| 23 |
return torch.device("cuda")
|
| 24 |
-
|
| 25 |
-
return torch.device("
|
|
|
|
|
|
| 1 |
import torch
|
|
|
|
| 2 |
|
| 3 |
|
| 4 |
def get_device(device=None):
|
| 5 |
"""
|
| 6 |
+
Pick the best available device.
|
| 7 |
+
|
| 8 |
+
Priority: explicit override -> CUDA (Colab / lab box) -> MPS (Apple Silicon)
|
| 9 |
+
-> CPU. Forward-only eval on MPS is solid in torch >= 2.
|
| 10 |
|
| 11 |
Args:
|
| 12 |
device: Manual override ('cuda', 'mps', 'cpu', or None for auto)
|
|
|
|
| 16 |
"""
|
| 17 |
if device is not None:
|
| 18 |
return torch.device(device)
|
| 19 |
+
if torch.cuda.is_available():
|
|
|
|
|
|
|
|
|
|
| 20 |
return torch.device("cuda")
|
| 21 |
+
if torch.backends.mps.is_available():
|
| 22 |
+
return torch.device("mps")
|
| 23 |
+
return torch.device("cpu")
|
src/transformer_analysis/head_analyzer.py
CHANGED
|
@@ -21,6 +21,12 @@ def _svd(W):
|
|
| 21 |
torch.from_numpy(Vh).to(dev, dt))
|
| 22 |
|
| 23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
class HeadAnalyzer:
|
| 25 |
def __init__(self, config, low_rank_svd_approximation=False, top_k_svd=-1, device="cpu"):
|
| 26 |
self.config = config
|
|
@@ -90,7 +96,7 @@ class HeadAnalyzer:
|
|
| 90 |
try:
|
| 91 |
W_gpu = W_gram_tensor.to(self.device)
|
| 92 |
# gram eigenvalues = σᵢ(W)²; take sqrt to store σᵢ(W)
|
| 93 |
-
sv2 =
|
| 94 |
svd = torch.sqrt(sv2.clamp(min=0)).detach().cpu().numpy()
|
| 95 |
self.data[weight_name].update({"SVD": svd})
|
| 96 |
P_sv, _ = np.histogram(svd, bins=self.sv_bins, density=self.use_density)
|
|
@@ -206,12 +212,12 @@ class LayerHeadContainer:
|
|
| 206 |
|
| 207 |
if compute_alignment:
|
| 208 |
M_align = torch.bmm(Vh_Q, Vh_K.transpose(1, 2)) # (n_heads, d_head, d_head)
|
| 209 |
-
cosines_all =
|
| 210 |
|
| 211 |
if compute_factored_wqk:
|
| 212 |
# M = diag(S_Q) @ U_Q^T @ U_K @ diag(S_K)
|
| 213 |
M_wqk = S_Q.unsqueeze(2) * torch.bmm(U_Q.transpose(1, 2), U_K) * S_K.unsqueeze(1)
|
| 214 |
-
S_WQK_all =
|
| 215 |
|
| 216 |
for head_idx in tqdm(range(self.n_heads), desc=f" Layer {self.layer_idx} heads", leave=False):
|
| 217 |
head_data = {
|
|
|
|
| 21 |
torch.from_numpy(Vh).to(dev, dt))
|
| 22 |
|
| 23 |
|
| 24 |
+
def _svdvals(W):
|
| 25 |
+
"""Singular values only. Delegates to _svd so we inherit the numpy fallback
|
| 26 |
+
and so we avoid torch.linalg.svdvals, which is not implemented on MPS."""
|
| 27 |
+
return _svd(W)[1]
|
| 28 |
+
|
| 29 |
+
|
| 30 |
class HeadAnalyzer:
|
| 31 |
def __init__(self, config, low_rank_svd_approximation=False, top_k_svd=-1, device="cpu"):
|
| 32 |
self.config = config
|
|
|
|
| 96 |
try:
|
| 97 |
W_gpu = W_gram_tensor.to(self.device)
|
| 98 |
# gram eigenvalues = σᵢ(W)²; take sqrt to store σᵢ(W)
|
| 99 |
+
sv2 = _svdvals(W_gpu)
|
| 100 |
svd = torch.sqrt(sv2.clamp(min=0)).detach().cpu().numpy()
|
| 101 |
self.data[weight_name].update({"SVD": svd})
|
| 102 |
P_sv, _ = np.histogram(svd, bins=self.sv_bins, density=self.use_density)
|
|
|
|
| 212 |
|
| 213 |
if compute_alignment:
|
| 214 |
M_align = torch.bmm(Vh_Q, Vh_K.transpose(1, 2)) # (n_heads, d_head, d_head)
|
| 215 |
+
cosines_all = _svdvals(M_align).clamp(0, 1).detach().cpu().numpy()
|
| 216 |
|
| 217 |
if compute_factored_wqk:
|
| 218 |
# M = diag(S_Q) @ U_Q^T @ U_K @ diag(S_K)
|
| 219 |
M_wqk = S_Q.unsqueeze(2) * torch.bmm(U_Q.transpose(1, 2), U_K) * S_K.unsqueeze(1)
|
| 220 |
+
S_WQK_all = _svdvals(M_wqk) # (n_heads, d_head)
|
| 221 |
|
| 222 |
for head_idx in tqdm(range(self.n_heads), desc=f" Layer {self.layer_idx} heads", leave=False):
|
| 223 |
head_data = {
|