angerami Claude Opus 4.7 commited on
Commit
2207aa9
·
1 Parent(s): 2dcdcdf

Pick best accelerator by default; route SVDs through MPS-safe path

Browse files

get_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
- Get torch device with automatic fallback.
8
- On macOS: defaults to cpu (mps has stability issues)
9
- Otherwise: cuda -> cpu
 
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
- else:
25
- return torch.device("cpu")
 
 
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 = torch.linalg.svdvals(W_gpu)
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 = torch.linalg.svdvals(M_align).clamp(0, 1).detach().cpu().numpy()
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 = torch.linalg.svdvals(M_wqk) # (n_heads, d_head)
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 = {