musabc's picture
upload muon.py
073320f verified
Raw
History Blame Contribute Delete
4.08 kB
"""
Muon Optimizer — Keller Jordan, NanoGPT speedrun.
Newton-Schulz iterasyonu ile orthogonalize edilmiş momentum.
2D ağırlıklara (Linear weight) uygulanır. 1D parametreler (norm weight,
bias, embedding) AdamW'ye verilir.
Referans:
https://github.com/KellerJordan/modded-nanogpt
https://kellerjordan.github.io/posts/muon/
Kullanim:
# Param ayri:
muon_params = [p for p in model.parameters() if p.dim() >= 2 and p.requires_grad]
other_params = [p for p in model.parameters() if p.dim() < 2 and p.requires_grad]
# Embedding ve lm_head'i muon'dan ayir (yaygin best practice)
embed_params = [model.wte.weight] # tied ise lm_head dahil
muon_params = [p for p in muon_params if not any(p is e for e in embed_params)]
other_params = other_params + embed_params
optimizer_muon = Muon(muon_params, lr=2e-2, momentum=0.95)
optimizer_adam = torch.optim.AdamW(other_params, lr=3e-4, ...)
"""
import torch
@torch.no_grad()
def newton_schulz(G: torch.Tensor, steps: int = 5) -> torch.Tensor:
"""G matrisini orthogonalize et (yaklasik USV^T -> UV^T).
Newton-Schulz quintic iteration. bf16'da kararli, hizli.
"""
assert G.ndim == 2
a, b, c = (3.4445, -4.7750, 2.0315)
X = G.to(torch.bfloat16)
# Boyut yonune gore transpose (her iki yonde de calissin)
if X.size(0) > X.size(1):
X = X.T
# Spektral normu yaklasik 1'e cek
X = X / (X.norm() + 1e-7)
for _ in range(steps):
A = X @ X.T
B = b * A + c * (A @ A)
X = a * X + B @ X
if G.size(0) > G.size(1):
X = X.T
return X.to(G.dtype)
class Muon(torch.optim.Optimizer):
"""Muon: Momentum + orthogonalize edilmiş update.
Sadece 2D parametreler için. 1D'leri AdamW ile ayrı eğit.
Args:
params: 2D parametreler iterable
lr: 0.02 (AdamW'nin ~50x'i, çünkü update'ler ortonormal)
momentum: 0.95
nesterov: True (genelde daha iyi)
ns_steps: Newton-Schulz iter sayisi (5 default)
"""
def __init__(self, params, lr=0.02, momentum=0.95, nesterov=True, ns_steps=5):
defaults = dict(lr=lr, momentum=momentum, nesterov=nesterov, ns_steps=ns_steps)
super().__init__(params, defaults)
@torch.no_grad()
def step(self, closure=None):
loss = None
if closure is not None:
with torch.enable_grad():
loss = closure()
for group in self.param_groups:
lr = group["lr"]
momentum = group["momentum"]
nesterov = group["nesterov"]
ns_steps = group["ns_steps"]
for p in group["params"]:
if p.grad is None:
continue
if p.ndim < 2:
raise ValueError(
f"Muon sadece >=2D param destekler, {p.ndim}D bulundu. "
"1D paramları AdamW'ye ver.")
g = p.grad
state = self.state[p]
if "momentum_buffer" not in state:
state["momentum_buffer"] = torch.zeros_like(g)
buf = state["momentum_buffer"]
buf.mul_(momentum).add_(g)
# Nesterov momentum
if nesterov:
g = g.add(buf, alpha=momentum)
else:
g = buf
# Reshape if needed (e.g., conv weight)
original_shape = g.shape
if g.ndim > 2:
g = g.view(g.size(0), -1)
# Newton-Schulz orthogonalization
g_orth = newton_schulz(g, steps=ns_steps)
# Scale: sqrt(max(out, in) / min(out, in)) — ~spectral norm
# Ya da basitce sqrt(d_out / d_in) gibi.
# Modded-nanogpt: scale = max(1, p.shape[0]/p.shape[1]) ** 0.5
scale = max(1.0, g_orth.size(0) / g_orth.size(1)) ** 0.5
# Geri reshape
g_orth = g_orth.view(original_shape)
p.add_(g_orth, alpha=-lr * scale)
return loss