lhallee commited on
Commit
410f70b
·
verified ·
1 Parent(s): b44701d

Upload test_time_training.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. test_time_training.py +539 -0
test_time_training.py ADDED
@@ -0,0 +1,539 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import typing as T
4
+ from dataclasses import dataclass, fields
5
+
6
+ import torch
7
+ import torch.nn as nn
8
+ import torch.nn.functional as F
9
+
10
+
11
+ @dataclass
12
+ class TTTConfig:
13
+ lr: float = 4e-4
14
+ steps: int = 30
15
+ ags: int = 16
16
+ batch_size: int = 2
17
+ mask_ratio: float = 0.15
18
+ crop_size: int = 1024
19
+ bert_leave_prob: float = 0.1
20
+ bert_replace_prob: float = 0.1
21
+ optimizer: str = "sgd"
22
+ momentum: float = 0.0
23
+ weight_decay: float = 0.0
24
+ seed: int | None = 0
25
+ lora_rank: int = 8
26
+ lora_alpha: float = 32.0
27
+ lora_target_replace_module: str | None = None
28
+ lora_target_modules: tuple[str, ...] | None = None
29
+ initial_state_reset: bool = True
30
+ automatic_best_state_reset: bool = False
31
+ eval_each_step: bool = False
32
+ gradient_clip: bool = False
33
+ gradient_clip_max_norm: float = 1.0
34
+
35
+ @classmethod
36
+ def from_kwargs(cls, **kwargs: T.Any) -> "TTTConfig":
37
+ valid_names = {field.name for field in fields(cls)}
38
+ unknown_names = set(kwargs) - valid_names
39
+ assert len(unknown_names) == 0, f"Unknown TTTConfig fields: {sorted(unknown_names)}"
40
+ return cls(**kwargs)
41
+
42
+ def merged(self, overrides: T.Mapping[str, T.Any] | "TTTConfig" | None) -> "TTTConfig":
43
+ if overrides is None:
44
+ return self
45
+ if isinstance(overrides, TTTConfig):
46
+ return overrides
47
+ values = {field.name: self.__dict__[field.name] for field in fields(self)}
48
+ for name, value in overrides.items():
49
+ assert name in values, f"Unknown TTTConfig field: {name}"
50
+ values[name] = value
51
+ return TTTConfig(**values)
52
+
53
+ def verify(self) -> None:
54
+ assert self.lr > 0.0, "TTT learning rate must be positive."
55
+ assert self.steps >= 1, "TTT steps must be >= 1."
56
+ assert self.ags >= 1, "TTT gradient accumulation steps must be >= 1."
57
+ assert self.batch_size >= 1, "TTT batch_size must be >= 1."
58
+ assert 0.0 < self.mask_ratio <= 1.0, "TTT mask_ratio must be in (0, 1]."
59
+ assert self.crop_size >= 1, "TTT crop_size must be >= 1."
60
+ assert self.lora_rank >= 1, "TTT v1 is LoRA-only, so lora_rank must be >= 1."
61
+ assert self.lora_alpha > 0.0, "TTT lora_alpha must be positive."
62
+ assert self.optimizer in {"adamw", "sgd"}, "TTT optimizer must be 'adamw' or 'sgd'."
63
+ assert 0.0 <= self.bert_leave_prob <= 1.0, "bert_leave_prob must be in [0, 1]."
64
+ assert 0.0 <= self.bert_replace_prob <= 1.0, "bert_replace_prob must be in [0, 1]."
65
+ assert self.bert_leave_prob + self.bert_replace_prob <= 1.0, (
66
+ "bert_leave_prob + bert_replace_prob must be <= 1."
67
+ )
68
+ if self.gradient_clip:
69
+ assert self.gradient_clip_max_norm > 0.0, "gradient_clip_max_norm must be positive."
70
+
71
+
72
+ class LoraInjectedLinear(nn.Module):
73
+ def __init__(self, linear: nn.Module, rank: int, alpha: float) -> None:
74
+ super().__init__()
75
+ weight = linear._parameters["weight"]
76
+ assert weight.ndim == 2, "LoRA can only wrap 2D linear weights."
77
+ self.linear = linear
78
+ self.linear.requires_grad_(False)
79
+ self.rank = rank
80
+ self.scale = alpha
81
+ in_features = weight.shape[1]
82
+ out_features = weight.shape[0]
83
+ self.lora_down = nn.Linear(in_features, rank, bias=False, dtype=torch.float32)
84
+ self.lora_up = nn.Linear(rank, out_features, bias=False, dtype=torch.float32)
85
+ self.lora_down.to(device=weight.device)
86
+ self.lora_up.to(device=weight.device)
87
+ nn.init.normal_(self.lora_down.weight, std=1.0 / rank)
88
+ nn.init.zeros_(self.lora_up.weight)
89
+
90
+ @property
91
+ def weight(self) -> torch.Tensor:
92
+ return self.linear._parameters["weight"]
93
+
94
+ @property
95
+ def bias(self) -> torch.Tensor | None:
96
+ return self.linear._parameters["bias"]
97
+
98
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
99
+ base = self.linear(x)
100
+ delta = self.lora_up(self.lora_down(x.to(dtype=torch.float32))) * self.scale
101
+ return base + delta.to(dtype=base.dtype)
102
+
103
+
104
+ class FastPLMTestTimeTrainingMixin:
105
+ def init_ttt(self, ttt_config: TTTConfig | T.Mapping[str, T.Any] | None = None) -> None:
106
+ base_config = TTTConfig()
107
+ self._ttt_cfg = base_config.merged(ttt_config)
108
+ self._ttt_cfg.verify()
109
+ self._ttt_initialized = False
110
+ self._ttt_initial_state: list[dict[str, torch.Tensor]] | None = None
111
+
112
+ @property
113
+ def ttt_config(self) -> TTTConfig:
114
+ if "_ttt_cfg" not in self.__dict__:
115
+ self.init_ttt()
116
+ return self._ttt_cfg
117
+
118
+ def _ttt_get_trainable_modules(self) -> list[nn.Module]:
119
+ return [self]
120
+
121
+ def _ttt_get_frozen_modules(self) -> list[nn.Module]:
122
+ return []
123
+
124
+ def _ttt_tokenize(
125
+ self,
126
+ seq: str | list[str] | None = None,
127
+ input_ids: torch.Tensor | None = None,
128
+ **kwargs: T.Any,
129
+ ) -> torch.Tensor | dict[str, torch.Tensor]:
130
+ del kwargs
131
+ if input_ids is not None:
132
+ return input_ids
133
+ assert seq is not None, "Pass either seq or input_ids for TTT."
134
+ tokenized = self.tokenizer(seq, return_tensors="pt", padding=True)
135
+ return tokenized["input_ids"]
136
+
137
+ def _ttt_mask_token(self) -> int:
138
+ return int(self.tokenizer.mask_token_id)
139
+
140
+ def _ttt_padding_token(self) -> int:
141
+ return int(self.tokenizer.pad_token_id)
142
+
143
+ def _ttt_replacement_tokens(self, input_ids: torch.Tensor) -> torch.Tensor:
144
+ tokenizer = self.tokenizer
145
+ special_ids = set(tokenizer.all_special_ids)
146
+ vocab_size = int(self.config.vocab_size)
147
+ ids = [idx for idx in range(vocab_size) if idx not in special_ids]
148
+ assert len(ids) > 0, "TTT replacement token set is empty."
149
+ return torch.tensor(ids, device=input_ids.device, dtype=input_ids.dtype)
150
+
151
+ def _ttt_predict_logits(
152
+ self,
153
+ batch: torch.Tensor | dict[str, torch.Tensor],
154
+ **kwargs: T.Any,
155
+ ) -> torch.Tensor:
156
+ del kwargs
157
+ if isinstance(batch, dict):
158
+ output = self(**batch)
159
+ return output.logits
160
+ attention_mask = batch.ne(self._ttt_padding_token())
161
+ output = self(input_ids=batch, attention_mask=attention_mask)
162
+ return output.logits
163
+
164
+ def _ttt_eval_step(
165
+ self,
166
+ step: int,
167
+ loss: float,
168
+ seq: str | list[str] | None = None,
169
+ input_ids: torch.Tensor | None = None,
170
+ **kwargs: T.Any,
171
+ ) -> tuple[dict[str, T.Any], float | None]:
172
+ del step, loss, seq, input_ids, kwargs
173
+ return {}, None
174
+
175
+ def _ttt_is_lora_target(
176
+ self,
177
+ name: str,
178
+ full_name: str,
179
+ module: nn.Module,
180
+ active: bool,
181
+ target_modules: tuple[str, ...] | None,
182
+ ) -> bool:
183
+ if not active:
184
+ return False
185
+ if isinstance(module, LoraInjectedLinear):
186
+ return False
187
+ if (
188
+ target_modules is not None
189
+ and name not in target_modules
190
+ and full_name not in target_modules
191
+ ):
192
+ return False
193
+ if isinstance(module, nn.Linear):
194
+ return True
195
+ if "weight" not in module._parameters:
196
+ return False
197
+ weight = module._parameters["weight"]
198
+ if weight is None or weight.ndim != 2:
199
+ return False
200
+ return "Linear" in module.__class__.__name__
201
+
202
+ def _ttt_inject_lora(self) -> int:
203
+ cfg = self.ttt_config
204
+ cfg.verify()
205
+ target_class = cfg.lora_target_replace_module
206
+ target_modules = cfg.lora_target_modules
207
+ wrapped = 0
208
+
209
+ def inject(module: nn.Module, prefix: str, active: bool) -> None:
210
+ nonlocal wrapped
211
+ for name, child in list(module.named_children()):
212
+ full_name = f"{prefix}.{name}" if prefix else name
213
+ child_active = active
214
+ if target_class is not None:
215
+ child_active = active or child.__class__.__name__ == target_class
216
+ if self._ttt_is_lora_target(name, full_name, child, child_active, target_modules):
217
+ setattr(
218
+ module,
219
+ name,
220
+ LoraInjectedLinear(child, rank=cfg.lora_rank, alpha=cfg.lora_alpha),
221
+ )
222
+ wrapped += 1
223
+ continue
224
+ inject(child, full_name, child_active)
225
+
226
+ for trainable_module in self._ttt_get_trainable_modules():
227
+ inject(trainable_module, "", target_class is None)
228
+ assert wrapped > 0, "TTT LoRA injection did not find any target modules."
229
+ return wrapped
230
+
231
+ def _ttt_lora_modules(self) -> list[LoraInjectedLinear]:
232
+ return [module for module in self.modules() if isinstance(module, LoraInjectedLinear)]
233
+
234
+ def _ttt_lora_parameters(self) -> list[nn.Parameter]:
235
+ params: list[nn.Parameter] = []
236
+ for module in self._ttt_lora_modules():
237
+ params.extend(module.lora_down.parameters())
238
+ params.extend(module.lora_up.parameters())
239
+ assert len(params) > 0, "TTT has no LoRA parameters."
240
+ return params
241
+
242
+ def _ttt_snapshot_lora_state(self) -> list[dict[str, torch.Tensor]]:
243
+ snapshot = []
244
+ for module in self._ttt_lora_modules():
245
+ snapshot.append(
246
+ {
247
+ "lora_down.weight": module.lora_down.weight.detach().clone(),
248
+ "lora_up.weight": module.lora_up.weight.detach().clone(),
249
+ }
250
+ )
251
+ assert len(snapshot) > 0, "TTT has no LoRA state to snapshot."
252
+ return snapshot
253
+
254
+ def _ttt_restore_lora_state(self, state: list[dict[str, torch.Tensor]]) -> None:
255
+ modules = self._ttt_lora_modules()
256
+ assert len(modules) == len(state), "TTT LoRA state/module count mismatch."
257
+ with torch.no_grad():
258
+ for module, module_state in zip(modules, state):
259
+ module.lora_down.weight.copy_(module_state["lora_down.weight"])
260
+ module.lora_up.weight.copy_(module_state["lora_up.weight"])
261
+
262
+ def _ttt_ensure_initialized(self) -> None:
263
+ if "_ttt_cfg" not in self.__dict__:
264
+ self.init_ttt()
265
+ if self._ttt_initialized:
266
+ return
267
+ self._ttt_inject_lora()
268
+ self._ttt_initial_state = self._ttt_snapshot_lora_state()
269
+ self._ttt_initialized = True
270
+
271
+ def ttt_reset(self) -> None:
272
+ self._ttt_ensure_initialized()
273
+ assert self._ttt_initial_state is not None, "TTT initial state is not available."
274
+ self._ttt_restore_lora_state(self._ttt_initial_state)
275
+
276
+ def _ttt_make_optimizer(self) -> torch.optim.Optimizer:
277
+ cfg = self.ttt_config
278
+ params = self._ttt_lora_parameters()
279
+ if cfg.optimizer == "sgd":
280
+ return torch.optim.SGD(
281
+ params,
282
+ lr=cfg.lr,
283
+ momentum=cfg.momentum,
284
+ weight_decay=cfg.weight_decay,
285
+ )
286
+ return torch.optim.AdamW(params, lr=cfg.lr, weight_decay=cfg.weight_decay)
287
+
288
+ def _ttt_to_device(
289
+ self,
290
+ batch: torch.Tensor | dict[str, torch.Tensor],
291
+ device: torch.device,
292
+ ) -> torch.Tensor | dict[str, torch.Tensor]:
293
+ if isinstance(batch, dict):
294
+ return {name: tensor.to(device) for name, tensor in batch.items()}
295
+ return batch.to(device)
296
+
297
+ def _ttt_input_ids_from_batch(
298
+ self,
299
+ batch: torch.Tensor | dict[str, torch.Tensor],
300
+ ) -> torch.Tensor:
301
+ if isinstance(batch, dict):
302
+ return batch["input_ids"]
303
+ return batch
304
+
305
+ def _ttt_set_input_ids(
306
+ self,
307
+ batch: torch.Tensor | dict[str, torch.Tensor],
308
+ input_ids: torch.Tensor,
309
+ ) -> torch.Tensor | dict[str, torch.Tensor]:
310
+ if isinstance(batch, dict):
311
+ updated = dict(batch)
312
+ updated["input_ids"] = input_ids
313
+ return updated
314
+ return input_ids
315
+
316
+ def _ttt_non_special_mask(self, input_ids: torch.Tensor) -> torch.Tensor:
317
+ pad_token = self._ttt_padding_token()
318
+ mask = input_ids.ne(pad_token)
319
+ special_ids = set(self.tokenizer.all_special_ids)
320
+ for special_id in special_ids:
321
+ mask = mask & input_ids.ne(int(special_id))
322
+ return mask
323
+
324
+ def _ttt_sample_crop(
325
+ self,
326
+ batch: torch.Tensor | dict[str, torch.Tensor],
327
+ generator: torch.Generator,
328
+ ) -> torch.Tensor | dict[str, torch.Tensor]:
329
+ input_ids = self._ttt_input_ids_from_batch(batch)
330
+ cfg = self.ttt_config
331
+ if input_ids.shape[1] <= cfg.crop_size:
332
+ return batch
333
+ high = input_ids.shape[1] - cfg.crop_size + 1
334
+ start = int(
335
+ torch.randint(
336
+ high,
337
+ (1,),
338
+ generator=generator,
339
+ device=input_ids.device,
340
+ ).item()
341
+ )
342
+ end = start + cfg.crop_size
343
+ if isinstance(batch, dict):
344
+ cropped = {}
345
+ for name, tensor in batch.items():
346
+ if tensor.ndim >= 2 and tensor.shape[1] == input_ids.shape[1]:
347
+ cropped[name] = tensor[:, start:end]
348
+ else:
349
+ cropped[name] = tensor
350
+ return cropped
351
+ return input_ids[:, start:end]
352
+
353
+ def _ttt_sample_batch(
354
+ self,
355
+ tokenized: torch.Tensor | dict[str, torch.Tensor],
356
+ generator: torch.Generator,
357
+ ) -> tuple[torch.Tensor | dict[str, torch.Tensor], torch.Tensor]:
358
+ cfg = self.ttt_config
359
+ batch = self._ttt_sample_crop(tokenized, generator)
360
+ input_ids = self._ttt_input_ids_from_batch(batch)
361
+ rows = torch.randint(
362
+ input_ids.shape[0],
363
+ (cfg.batch_size,),
364
+ generator=generator,
365
+ device=input_ids.device,
366
+ )
367
+ if isinstance(batch, dict):
368
+ sampled: torch.Tensor | dict[str, torch.Tensor] = {}
369
+ for name, tensor in batch.items():
370
+ if tensor.ndim >= 1 and tensor.shape[0] == input_ids.shape[0]:
371
+ sampled[name] = tensor.index_select(0, rows)
372
+ else:
373
+ sampled[name] = tensor
374
+ else:
375
+ sampled = input_ids.index_select(0, rows)
376
+
377
+ sampled_ids = self._ttt_input_ids_from_batch(sampled)
378
+ labels = sampled_ids.clone()
379
+ non_special = self._ttt_non_special_mask(sampled_ids)
380
+ label_mask = torch.zeros_like(non_special)
381
+ for row_idx in range(sampled_ids.shape[0]):
382
+ candidate_positions = torch.where(non_special[row_idx])[0]
383
+ if candidate_positions.numel() == 0:
384
+ continue
385
+ num_mask = max(1, int(round(candidate_positions.numel() * cfg.mask_ratio)))
386
+ order = torch.randperm(
387
+ candidate_positions.numel(),
388
+ generator=generator,
389
+ device=sampled_ids.device,
390
+ )
391
+ chosen = candidate_positions[order[:num_mask]]
392
+ label_mask[row_idx, chosen] = True
393
+ labels = labels.masked_fill(~label_mask, -100)
394
+
395
+ masked_ids = sampled_ids.clone()
396
+ chosen_positions = torch.where(label_mask)
397
+ if chosen_positions[0].numel() > 0:
398
+ random_values = torch.rand(
399
+ chosen_positions[0].shape,
400
+ generator=generator,
401
+ device=sampled_ids.device,
402
+ )
403
+ leave = random_values < cfg.bert_leave_prob
404
+ replace = (random_values >= cfg.bert_leave_prob) & (
405
+ random_values < cfg.bert_leave_prob + cfg.bert_replace_prob
406
+ )
407
+ mask = ~(leave | replace)
408
+ if mask.any():
409
+ masked_ids[
410
+ chosen_positions[0][mask],
411
+ chosen_positions[1][mask],
412
+ ] = self._ttt_mask_token()
413
+ if replace.any():
414
+ replacement_tokens = self._ttt_replacement_tokens(sampled_ids)
415
+ replacement_idx = torch.randint(
416
+ replacement_tokens.shape[0],
417
+ (int(replace.sum().item()),),
418
+ generator=generator,
419
+ device=sampled_ids.device,
420
+ )
421
+ masked_ids[
422
+ chosen_positions[0][replace],
423
+ chosen_positions[1][replace],
424
+ ] = replacement_tokens[replacement_idx]
425
+
426
+ return self._ttt_set_input_ids(sampled, masked_ids), labels
427
+
428
+ def ttt(
429
+ self,
430
+ seq: str | list[str] | None = None,
431
+ input_ids: torch.Tensor | None = None,
432
+ ttt_config: TTTConfig | T.Mapping[str, T.Any] | None = None,
433
+ **kwargs: T.Any,
434
+ ) -> dict[str, T.Any]:
435
+ if ttt_config is not None:
436
+ if "_ttt_initialized" in self.__dict__ and self._ttt_initialized:
437
+ next_cfg = self.ttt_config.merged(ttt_config)
438
+ assert next_cfg.lora_rank == self.ttt_config.lora_rank, (
439
+ "Changing lora_rank after TTT initialization is not supported."
440
+ )
441
+ assert next_cfg.lora_alpha == self.ttt_config.lora_alpha, (
442
+ "Changing lora_alpha after TTT initialization is not supported."
443
+ )
444
+ assert (
445
+ next_cfg.lora_target_replace_module
446
+ == self.ttt_config.lora_target_replace_module
447
+ ), "Changing LoRA target class after TTT initialization is not supported."
448
+ assert next_cfg.lora_target_modules == self.ttt_config.lora_target_modules, (
449
+ "Changing LoRA target modules after TTT initialization is not supported."
450
+ )
451
+ self._ttt_cfg = next_cfg
452
+ else:
453
+ self.init_ttt(ttt_config)
454
+
455
+ self._ttt_ensure_initialized()
456
+ cfg = self.ttt_config
457
+ if cfg.initial_state_reset:
458
+ self.ttt_reset()
459
+
460
+ device = next(self.parameters()).device
461
+ tokenized = self._ttt_tokenize(seq=seq, input_ids=input_ids, **kwargs)
462
+ tokenized = self._ttt_to_device(tokenized, device)
463
+ generator_device = device if device.type == "cuda" else torch.device("cpu")
464
+ generator = torch.Generator(device=generator_device)
465
+ if cfg.seed is not None:
466
+ generator.manual_seed(cfg.seed)
467
+
468
+ module_modes = {module: module.training for module in self.modules()}
469
+ requires_grad = {param: param.requires_grad for param in self.parameters()}
470
+ losses: list[float] = []
471
+ step_metrics: list[dict[str, T.Any]] = []
472
+ best_state: list[dict[str, torch.Tensor]] | None = None
473
+ best_metric: float | None = None
474
+ best_step = 0
475
+
476
+ try:
477
+ self.train()
478
+ for param in self.parameters():
479
+ param.requires_grad_(False)
480
+ for param in self._ttt_lora_parameters():
481
+ param.requires_grad_(True)
482
+
483
+ optimizer = self._ttt_make_optimizer()
484
+ optimizer.zero_grad(set_to_none=True)
485
+ total_micro_steps = cfg.steps * cfg.ags
486
+ for micro_step in range(total_micro_steps):
487
+ batch, labels = self._ttt_sample_batch(tokenized, generator)
488
+ logits = self._ttt_predict_logits(batch, **kwargs)
489
+ labels = labels.to(device=logits.device)
490
+ loss = F.cross_entropy(
491
+ logits.reshape(-1, logits.shape[-1]),
492
+ labels.reshape(-1),
493
+ ignore_index=-100,
494
+ )
495
+ (loss / cfg.ags).backward()
496
+ if (micro_step + 1) % cfg.ags != 0:
497
+ continue
498
+
499
+ if cfg.gradient_clip:
500
+ torch.nn.utils.clip_grad_norm_(
501
+ self._ttt_lora_parameters(),
502
+ cfg.gradient_clip_max_norm,
503
+ )
504
+ optimizer.step()
505
+ optimizer.zero_grad(set_to_none=True)
506
+ step = (micro_step + 1) // cfg.ags
507
+ loss_value = float(loss.detach().item())
508
+ losses.append(loss_value)
509
+ if cfg.eval_each_step:
510
+ metrics, metric = self._ttt_eval_step(
511
+ step=step,
512
+ loss=loss_value,
513
+ seq=seq,
514
+ input_ids=input_ids,
515
+ **kwargs,
516
+ )
517
+ if len(metrics) > 0:
518
+ step_metrics.append(metrics)
519
+ if metric is not None and (
520
+ best_metric is None or metric > best_metric
521
+ ):
522
+ best_metric = metric
523
+ best_step = step
524
+ best_state = self._ttt_snapshot_lora_state()
525
+
526
+ if cfg.automatic_best_state_reset and best_state is not None:
527
+ self._ttt_restore_lora_state(best_state)
528
+ finally:
529
+ for param, value in requires_grad.items():
530
+ param.requires_grad_(value)
531
+ for module, training in module_modes.items():
532
+ module.train(training)
533
+
534
+ return {
535
+ "losses": losses,
536
+ "step_metrics": step_metrics,
537
+ "best_step": best_step,
538
+ "best_metric": best_metric,
539
+ }