|
1 | | -""" |
2 | | -Optimizers for GameNGen |
3 | | -Paper uses Adafactor for Tier 3 (Section 4.2) |
4 | | -""" |
| 1 | +"""Optimizer construction for diffusion training.""" |
5 | 2 |
|
6 | | -from typing import Iterable, Optional, Tuple |
| 3 | +from typing import Iterable |
7 | 4 |
|
8 | | -import numpy as np |
9 | 5 | import torch |
10 | 6 |
|
11 | 7 |
|
12 | | -class Adafactor(torch.optim.Optimizer): |
13 | | - """ |
14 | | - Adafactor optimizer |
15 | | - Paper Section 4.2: "with the Adafactor optimizer without weight decay" |
| 8 | +def create_optimizer(optimizer_name: str, parameters: Iterable[torch.nn.Parameter], config: dict) -> torch.optim.Optimizer: |
| 9 | + """Build the configured optimizer with explicit, reproducible settings. |
16 | 10 |
|
17 | | - Implementation based on Shazeer & Stern (2018) |
18 | | - "Adafactor: Adaptive Learning Rates with Sublinear Memory Cost" |
| 11 | + The paper profile uses the maintained Transformers Adafactor implementation. |
| 12 | + Keeping the implementation in its upstream package avoids silently training |
| 13 | + with a partial local reimplementation. |
19 | 14 | """ |
20 | 15 |
|
21 | | - def __init__( |
22 | | - self, |
23 | | - params: Iterable[torch.nn.Parameter], |
24 | | - lr: float = 1e-3, |
25 | | - eps: Tuple[float, float] = (1e-30, 1e-3), |
26 | | - clip_threshold: float = 1.0, |
27 | | - decay_rate: float = -0.8, |
28 | | - beta1: Optional[float] = None, |
29 | | - weight_decay: float = 0.0, |
30 | | - scale_parameter: bool = True, |
31 | | - relative_step: bool = True, |
32 | | - warmup_init: bool = True, |
33 | | - ): |
34 | | - """ |
35 | | - Args: |
36 | | - params: Iterable of parameters |
37 | | - lr: Learning rate (if relative_step=False) |
38 | | - eps: Regularization constants (eps0, eps1) |
39 | | - clip_threshold: Threshold for clipping update |
40 | | - decay_rate: Coefficient for computing running avg of squared gradient |
41 | | - beta1: Coefficient for running avg of gradient (momentum) |
42 | | - weight_decay: Weight decay coefficient |
43 | | - scale_parameter: Whether to scale parameters |
44 | | - relative_step: Whether to use relative step sizes |
45 | | - warmup_init: Whether to use warmup initialization |
46 | | - """ |
47 | | - if relative_step and warmup_init: |
48 | | - # Paper doesn't use warmup, but option available |
49 | | - lr = None |
50 | | - |
51 | | - defaults = dict( |
52 | | - lr=lr, |
53 | | - eps=eps, |
54 | | - clip_threshold=clip_threshold, |
55 | | - decay_rate=decay_rate, |
56 | | - beta1=beta1, |
57 | | - weight_decay=weight_decay, |
58 | | - scale_parameter=scale_parameter, |
59 | | - relative_step=relative_step, |
60 | | - warmup_init=warmup_init, |
61 | | - ) |
62 | | - super().__init__(params, defaults) |
63 | | - |
64 | | - def _get_lr(self, param_group, param_scale): |
65 | | - """Compute learning rate""" |
66 | | - if param_group["lr"] is None: |
67 | | - min_step = ( |
68 | | - 1e-6 * param_group["step"] if param_group["warmup_init"] else 1e-2 |
69 | | - ) |
70 | | - rel_step_sz = min(min_step, 1.0 / np.sqrt(param_group["step"])) |
71 | | - param_group["lr"] = rel_step_sz * param_scale |
72 | | - return param_group["lr"] |
73 | | - |
74 | | - def _get_options(self, param_group, param_shape): |
75 | | - """Get factorization options""" |
76 | | - factored = len(param_shape) >= 2 |
77 | | - use_first_moment = param_group["beta1"] |
78 | | - return factored, use_first_moment |
79 | | - |
80 | | - def _rms(self, tensor): |
81 | | - """Root mean square""" |
82 | | - return tensor.norm(2) / (tensor.numel() ** 0.5) |
83 | | - |
84 | | - def _approx_sq_grad(self, exp_avg_sq_row, exp_avg_sq_col): |
85 | | - """Approximation of exponential moving average of square of gradient""" |
86 | | - r_factor = ( |
87 | | - (exp_avg_sq_row / exp_avg_sq_row.mean(dim=-1, keepdim=True)) |
88 | | - .rsqrt_() |
89 | | - .unsqueeze(1) |
90 | | - ) |
91 | | - c_factor = exp_avg_sq_col.unsqueeze(0).rsqrt() |
92 | | - v = r_factor * c_factor |
93 | | - return v |
94 | | - |
95 | | - def step(self, closure=None): |
96 | | - """ |
97 | | - Perform a single optimization step |
98 | | -
|
99 | | - Args: |
100 | | - closure: A closure that reevaluates model and returns loss |
101 | | - """ |
102 | | - loss = None |
103 | | - if closure is not None: |
104 | | - loss = closure() |
105 | | - |
106 | | - for group in self.param_groups: |
107 | | - for p in group["params"]: |
108 | | - if p.grad is None: |
109 | | - continue |
110 | | - |
111 | | - grad = p.grad.data |
112 | | - if grad.is_sparse: |
113 | | - raise RuntimeError("Adafactor does not support sparse gradients") |
114 | | - |
115 | | - state = self.state[p] |
116 | | - grad_shape = grad.shape |
117 | | - |
118 | | - param_group = group |
119 | | - factored, use_first_moment = self._get_options(param_group, grad_shape) |
120 | | - |
121 | | - # State initialization |
122 | | - if len(state) == 0: |
123 | | - state["step"] = 0 |
124 | | - |
125 | | - if use_first_moment: |
126 | | - # Exponential moving average of gradient values |
127 | | - state["exp_avg"] = torch.zeros_like(grad) |
128 | | - |
129 | | - if factored: |
130 | | - state["exp_avg_sq_row"] = torch.zeros(grad_shape[0]) |
131 | | - state["exp_avg_sq_col"] = torch.zeros(grad_shape[1:]).flatten() |
132 | | - else: |
133 | | - state["exp_avg_sq"] = torch.zeros_like(grad) |
134 | | - |
135 | | - state["RMS"] = 0 |
136 | | - |
137 | | - state["step"] += 1 |
138 | | - lr = self._get_lr(param_group, self._rms(p.data)) |
139 | | - param_group["lr"] = lr |
140 | | - |
141 | | - # Momentum |
142 | | - if use_first_moment: |
143 | | - state["exp_avg"].mul_(param_group["beta1"]).add_( |
144 | | - grad, alpha=1 - param_group["beta1"] |
145 | | - ) |
146 | | - |
147 | | - # Second moment estimation |
148 | | - eps_0, eps_1 = param_group["eps"] |
149 | | - decay_rate = 1 - (state["step"] + 1) ** param_group["decay_rate"] |
150 | | - |
151 | | - if factored: |
152 | | - update = grad**2 + eps_0 |
153 | | - state["exp_avg_sq_row"].mul_(decay_rate).add_( |
154 | | - update.mean(dim=list(range(1, len(grad_shape)))), |
155 | | - alpha=1 - decay_rate, |
156 | | - ) |
157 | | - state["exp_avg_sq_col"].mul_(decay_rate).add_( |
158 | | - update.mean(dim=0).flatten(), alpha=1 - decay_rate |
159 | | - ) |
160 | | - update = self._approx_sq_grad( |
161 | | - state["exp_avg_sq_row"], state["exp_avg_sq_col"] |
162 | | - ) |
163 | | - update.mul_(grad) |
164 | | - else: |
165 | | - state["exp_avg_sq"].mul_(decay_rate).add_( |
166 | | - grad**2 + eps_0, alpha=1 - decay_rate |
167 | | - ) |
168 | | - update = grad / (state["exp_avg_sq"].sqrt() + eps_1) |
169 | | - |
170 | | - # Clip |
171 | | - rms = self._rms(update) |
172 | | - state["RMS"] = rms |
173 | | - |
174 | | - if param_group["clip_threshold"] > 0: |
175 | | - clip_val = param_group["clip_threshold"] / max(1.0, rms) |
176 | | - update.clamp_(-clip_val, clip_val) |
177 | | - |
178 | | - # Update |
179 | | - if use_first_moment: |
180 | | - update = state["exp_avg"] |
181 | | - |
182 | | - if param_group["weight_decay"] > 0: |
183 | | - p.data.mul_(1 - param_group["weight_decay"] * lr) |
184 | | - |
185 | | - p.data.add_(update, alpha=-lr) |
186 | | - |
187 | | - return loss |
188 | | - |
189 | | - |
190 | | -# Simple wrapper for easier usage |
191 | | -def create_optimizer(optimizer_name: str, parameters, config: dict): |
192 | | - """ |
193 | | - Create optimizer based on config |
194 | | -
|
195 | | - Args: |
196 | | - optimizer_name: 'AdamW' or 'Adafactor' |
197 | | - parameters: Model parameters |
198 | | - config: Configuration dict |
199 | | -
|
200 | | - Returns: |
201 | | - Optimizer instance |
202 | | - """ |
203 | | - if optimizer_name.lower() == "adamw": |
| 16 | + name = optimizer_name.lower() |
| 17 | + if name == "adamw": |
204 | 18 | return torch.optim.AdamW( |
205 | 19 | parameters, |
206 | 20 | lr=config["learning_rate"], |
207 | 21 | weight_decay=config.get("weight_decay", 0.0), |
208 | 22 | betas=(config.get("adam_beta1", 0.9), config.get("adam_beta2", 0.999)), |
209 | 23 | eps=config.get("adam_epsilon", 1e-8), |
210 | 24 | ) |
211 | | - |
212 | | - elif optimizer_name.lower() == "adafactor": |
213 | | - # Paper's setting: "without weight decay" |
| 25 | + if name == "adafactor": |
| 26 | + try: |
| 27 | + from transformers.optimization import Adafactor |
| 28 | + except ImportError as error: |
| 29 | + raise RuntimeError( |
| 30 | + "Adafactor requires the optional 'transformers' dependency. " |
| 31 | + "Install the diffusion extra before using the paper profile." |
| 32 | + ) from error |
214 | 33 | return Adafactor( |
215 | 34 | parameters, |
216 | 35 | lr=config["learning_rate"], |
217 | | - weight_decay=0.0, # Paper doesn't use weight decay |
| 36 | + weight_decay=0.0, |
218 | 37 | scale_parameter=False, |
219 | 38 | relative_step=False, |
| 39 | + warmup_init=False, |
220 | 40 | ) |
221 | | - |
222 | | - else: |
223 | | - raise ValueError(f"Unknown optimizer: {optimizer_name}") |
224 | | - |
225 | | - |
226 | | -if __name__ == "__main__": |
227 | | - # Test Adafactor |
228 | | - print("Testing Adafactor optimizer...") |
229 | | - |
230 | | - # Create dummy model |
231 | | - model = torch.nn.Linear(100, 10) |
232 | | - |
233 | | - # Create optimizer |
234 | | - optimizer = Adafactor(model.parameters(), lr=2e-5) |
235 | | - |
236 | | - print(f"Optimizer created: {optimizer}") |
237 | | - |
238 | | - # Test step |
239 | | - x = torch.randn(32, 100) |
240 | | - y = torch.randn(32, 10) |
241 | | - |
242 | | - for i in range(10): |
243 | | - optimizer.zero_grad() |
244 | | - output = model(x) |
245 | | - loss = torch.nn.functional.mse_loss(output, y) |
246 | | - loss.backward() |
247 | | - optimizer.step() |
248 | | - |
249 | | - print(f"Step {i}: loss = {loss.item():.4f}") |
250 | | - |
251 | | - print("\n✓ Adafactor test passed!") |
| 41 | + raise ValueError(f"unknown optimizer: {optimizer_name}") |
0 commit comments