SeaWolf-AI commited on
Commit
8c021ac
·
verified ·
1 Parent(s): f59f9a9

Add decision core: autojev/optim.py

Browse files
Files changed (1) hide show
  1. autojev/optim.py +102 -0
autojev/optim.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """AdamW with exact FP32 master weights and moments kept in host memory."""
2
+
3
+ from collections.abc import Iterable
4
+ from typing import TypedDict, cast
5
+
6
+ import torch
7
+
8
+
9
+ class ParameterGroup(TypedDict):
10
+ params: list[torch.Tensor]
11
+ lr: float
12
+
13
+
14
+ class OffloadState(TypedDict):
15
+ format_version: int
16
+ parameter_names: list[str]
17
+ masters: list[torch.Tensor]
18
+ optimizer: dict[str, object]
19
+
20
+
21
+ class CPUOffloadAdamW:
22
+ def __init__(
23
+ self, named_parameters: Iterable[tuple[str, torch.nn.Parameter]], lr: float,
24
+ weight_decay: float = 0.01,
25
+ ) -> None:
26
+ parameters = [(name, parameter) for name, parameter in named_parameters if parameter.requires_grad]
27
+ if not parameters:
28
+ raise ValueError("The optimizer needs trainable parameters.")
29
+ self.names = [name for name, _ in parameters]
30
+ if len(set(self.names)) != len(self.names):
31
+ raise ValueError("Parameter names must be unique.")
32
+ self.parameters = [parameter for _, parameter in parameters]
33
+ self.masters = [torch.nn.Parameter(parameter.detach().to(device="cpu", dtype=torch.float32, copy=True))
34
+ for parameter in self.parameters]
35
+ self.gradients = [torch.empty_like(master) for master in self.masters]
36
+ self.buffers = {
37
+ dtype: torch.empty(max(parameter.numel() for parameter in self.parameters if parameter.dtype == dtype),
38
+ dtype=dtype, device="cpu", pin_memory=any(parameter.is_cuda for parameter in self.parameters))
39
+ for dtype in {parameter.dtype for parameter in self.parameters}
40
+ }
41
+ self.optimizer = torch.optim.AdamW(self.masters, lr=lr, weight_decay=weight_decay, fused=True)
42
+
43
+ @property
44
+ def param_groups(self) -> list[ParameterGroup]:
45
+ return cast(list[ParameterGroup], self.optimizer.param_groups)
46
+
47
+ def zero_grad(self, set_to_none: bool = True) -> None:
48
+ self.optimizer.zero_grad(set_to_none=set_to_none)
49
+ for parameter in self.parameters:
50
+ if set_to_none:
51
+ parameter.grad = None
52
+ elif parameter.grad is not None:
53
+ parameter.grad.zero_()
54
+
55
+ @torch.no_grad()
56
+ def step(self) -> None:
57
+ for parameter, master, gradient in zip(self.parameters, self.masters, self.gradients):
58
+ if parameter.grad is None:
59
+ master.grad = None
60
+ continue
61
+ buffer = self.buffers[parameter.dtype][:parameter.numel()].view_as(parameter)
62
+ buffer.copy_(parameter.grad, non_blocking=True)
63
+ if parameter.is_cuda:
64
+ torch.cuda.synchronize(parameter.device)
65
+ gradient.copy_(buffer)
66
+ master.grad = gradient
67
+ parameter.grad = None
68
+ self.optimizer.step()
69
+ for parameter, master in zip(self.parameters, self.masters):
70
+ if master.grad is not None:
71
+ buffer = self.buffers[parameter.dtype][:parameter.numel()].view_as(parameter)
72
+ buffer.copy_(master)
73
+ parameter.copy_(buffer, non_blocking=True)
74
+ if parameter.is_cuda:
75
+ torch.cuda.synchronize(parameter.device)
76
+ master.grad = None
77
+
78
+ def state_dict(self) -> OffloadState:
79
+ """Return CPU tensor references for torch.save; never clone the large states."""
80
+ return {"format_version": 1, "parameter_names": list(self.names),
81
+ "masters": [master.detach() for master in self.masters],
82
+ "optimizer": cast(dict[str, object], self.optimizer.state_dict())}
83
+
84
+ @torch.no_grad()
85
+ def load_state_dict(self, state: OffloadState) -> None:
86
+ if state["format_version"] != 1 or state["parameter_names"] != self.names:
87
+ raise ValueError("Optimizer checkpoint does not match model parameter names and order.")
88
+ saved_masters = state["masters"]
89
+ if len(saved_masters) != len(self.masters):
90
+ raise ValueError("Optimizer checkpoint has the wrong number of master tensors.")
91
+ for master, saved in zip(self.masters, saved_masters):
92
+ if saved.device.type != "cpu" or saved.dtype != torch.float32 or saved.shape != master.shape:
93
+ raise ValueError("Master tensors must be CPU FP32 with matching parameter shapes.")
94
+ self.optimizer.load_state_dict(state["optimizer"])
95
+ for parameter, master, saved in zip(self.parameters, self.masters, saved_masters):
96
+ master.copy_(saved)
97
+ parameter.copy_(master)
98
+ for parameter in self.parameters:
99
+ if parameter.is_cuda:
100
+ torch.cuda.synchronize(parameter.device)
101
+ break
102
+ self.zero_grad()