GitLab Repo

amachine.am_transformers.am_checkpointer

  1import json
  2import shutil
  3import time
  4from dataclasses import asdict
  5from pathlib import Path
  6from typing import Any
  7import os
  8
  9import torch
 10from safetensors.torch import load_file as safetensors_load_file
 11from safetensors.torch import save_file as safetensors_save_file
 12from transformers.trainer_utils import load_sharded_checkpoint
 13
 14class _CallableSentinel:
 15    """Placeholder for unserializable Python functions found in state dicts."""
 16    pass
 17
 18class CheckpointManager:
 19
 20    def __init__(self, config, model, optimizer, trainer_state, device):
 21        self.config         = config
 22        self.model          = model
 23        self.optimizer      = optimizer
 24        self.trainer_state  = trainer_state
 25        self.device         = device
 26
 27    def _unwrap_model(self):
 28        return self.model._orig_mod if hasattr(self.model, "_orig_mod") else self.model
 29
 30    def save(self, step: int) -> None:
 31        ckpt = Path(self.config.output_dir) / f"step_{step:07d}"
 32        ckpt.mkdir(parents=True, exist_ok=True)
 33        t_save = time.time()
 34
 35        # Copy tokenizer / model config JSON files so the checkpoint is self-contained
 36        for json_file in Path(self.config.model_dir).glob("*.json"):
 37            shutil.copy2(json_file, ckpt / json_file.name)
 38
 39        self._unwrap_model().save_pretrained(str(ckpt), safe_serialization=True)
 40        self._save_optimizer(ckpt)
 41        self._save_trainer(ckpt)
 42        self._save_rng(ckpt)
 43        self.config.save(ckpt / "training_config.json")
 44
 45        print(f"Checkpoint saved: {ckpt}  ({time.time() - t_save:.1f}s)")
 46
 47    def load(self, path: str) -> None:
 48        
 49        ckpt = Path(path)
 50        print(f"Resuming from {ckpt}.")
 51
 52        result = load_sharded_checkpoint(
 53            self._unwrap_model(), str(ckpt), strict=True, prefer_safe=True
 54        )
 55        if result.missing_keys:
 56            print(f"  Warning: missing keys:    {result.missing_keys}")
 57        if result.unexpected_keys:
 58            print(f"  Warning: unexpected keys: {result.unexpected_keys}")
 59
 60        self._load_optimizer(ckpt)
 61        self._load_trainer(ckpt)
 62
 63        if not self.config.resume_reset_data:
 64            self._load_rng(ckpt)
 65
 66    # Optimizer
 67
 68    def _encode_node(self, obj: Any, tensors: dict[str, torch.Tensor]) -> dict[str, Any]:
 69        """Recursively encodes optimizer state into a strict, explicitly tagged AST schema."""
 70        if isinstance(obj, torch.Tensor):
 71            if obj.layout != torch.strided:
 72                raise TypeError(f"Unsupported tensor layout {obj.layout}. Safetensors requires strided tensors.")
 73            
 74            t_id = f"t_{len(tensors):08d}"
 75            # Detach removes autograd graph refs; contiguous prepares for Safetensors
 76            tensors[t_id] = obj.detach().cpu().contiguous()
 77            return {"t": "tensor", "v": t_id}
 78            
 79        elif isinstance(obj, dict):
 80            # Encode as list of pairs to preserve non-string keys (e.g., int parameter IDs)
 81            return {"t": "dict", "v": [[self._encode_node(k, tensors), self._encode_node(v, tensors)] for k, v in obj.items()]}
 82            
 83        elif isinstance(obj, list):
 84            return {"t": "list", "v": [self._encode_node(x, tensors) for x in obj]}
 85            
 86        elif isinstance(obj, torch.Size):
 87            return {"t": "torch_size", "v": [self._encode_node(x, tensors) for x in obj]}
 88            
 89        elif isinstance(obj, tuple):
 90            return {"t": "tuple", "v": [self._encode_node(x, tensors) for x in obj]}
 91            
 92        elif isinstance(obj, torch.dtype):
 93            # str(torch.float32) -> "torch.float32"
 94            return {"t": "torch_dtype", "v": str(obj)}
 95            
 96        elif isinstance(obj, torch.device):
 97            # str(torch.device('cuda:0')) -> "cuda:0"
 98            return {"t": "torch_device", "v": str(obj)}
 99            
100        elif isinstance(obj, (int, float, str, bool, type(None))):
101            return {"t": "primitive", "v": obj}
102            
103        elif callable(obj):
104            # PSGD/Kron put functions in param_groups. We flag them to merge back on load.
105            return {"t": "callable", "v": getattr(obj, "__name__", "anonymous_function")}
106            
107        else:
108            raise TypeError(f"Unsupported python type {type(obj).__name__} in optimizer state: {obj}")
109
110    def _decode_node(self, node: dict[str, Any], tensors: dict[str, torch.Tensor]) -> Any:
111        """Reconstructs the exact Python objects from the tagged AST schema."""
112        t = node["t"]
113        v = node["v"]
114        
115        if t == "tensor":       return tensors[v]
116        if t == "dict":         return {self._decode_node(k, tensors): self._decode_node(val, tensors) for k, val in v}
117        if t == "list":         return [self._decode_node(x, tensors) for x in v]
118        if t == "tuple":        return tuple(self._decode_node(x, tensors) for x in v)
119        if t == "torch_size":   return torch.Size(self._decode_node(x, tensors) for x in v)
120        if t == "torch_dtype":  return getattr(torch, v.split(".")[-1]) # "torch.float32" -> torch.float32
121        if t == "torch_device": return torch.device(v)
122        if t == "primitive":    return v
123        if t == "callable":     return _CallableSentinel()
124        
125        raise ValueError(f"Corrupt metadata: Unknown node type '{t}'")
126
127    def _restore_unserializables(self, rebuilt: Any, current: Any) -> Any:
128        """Walks the rebuilt state dict to inject functions from the live instantiated optimizer."""
129        if isinstance(rebuilt, dict) and isinstance(current, dict):
130            return {k: self._restore_unserializables(v, current.get(k)) for k, v in rebuilt.items()}
131        elif isinstance(rebuilt, list) and isinstance(current, list):
132            return [self._restore_unserializables(r, c) for r, c in zip(rebuilt, current)]
133        elif isinstance(rebuilt, tuple) and isinstance(current, tuple):
134            return tuple(self._restore_unserializables(r, c) for r, c in zip(rebuilt, current))
135        elif isinstance(rebuilt, _CallableSentinel):
136            return current  # Inject the live function back in
137        else:
138            return rebuilt
139
140    def _save_optimizer(self, ckpt: Path) -> None:
141        # Rank guard: Ensure only the main process writes to disk in normal DDP
142        if getattr(self, "global_rank", 0) != 0:
143            return
144
145        ckpt.mkdir(parents=True, exist_ok=True)
146        tensors: dict[str, torch.Tensor] = {}
147        state_dict = self.optimizer.state_dict()
148
149        meta = {
150            "__format_version__": 2,
151            "optimizer_state_dict": self._encode_node(state_dict, tensors)
152        }
153
154        sf_path = ckpt / "optimizer.safetensors"
155        meta_path = ckpt / "optimizer_meta.json"
156
157        tmp_sf = sf_path.with_suffix(".safetensors.tmp")
158        tmp_meta = meta_path.with_suffix(".json.tmp")
159
160        if tensors:
161            safetensors_save_file(tensors, tmp_sf)
162
163        with open(tmp_meta, "w") as f:
164            json.dump(meta, f, indent=2)
165            f.flush()
166            os.fsync(f.fileno())  # Guarantee disk persistence
167
168        # Atomic writes prevent corruption if the job is preempted mid-save
169        os.replace(tmp_meta, meta_path)
170        if tensors:
171            os.replace(tmp_sf, sf_path)
172        elif sf_path.exists():
173            # Clean up old safetensors if the optimizer structure changed to have no tensors
174            os.remove(sf_path)
175
176    def _load_optimizer(self, ckpt: Path) -> None:
177        meta_path = ckpt / "optimizer_meta.json"
178        sf_path   = ckpt / "optimizer.safetensors"
179
180        if not meta_path.exists():
181            print(f"  No optimizer metadata found at {meta_path} — starting fresh.")
182            return
183
184        with open(meta_path) as f:
185            meta = json.load(f)
186
187        if meta.get("__format_version__") != 2:
188            raise RuntimeError(f"Unsupported optimizer metadata format version {meta.get('__format_version__')} in {meta_path}")
189
190        # If sf_path doesn't exist, assume a tensor-less optimizer state
191        tensors = {}
192        if sf_path.exists():
193            tensors = safetensors_load_file(str(sf_path), device="cpu")
194
195        # 1. Decode the structural tree
196        rebuilt_state_dict = self._decode_node(meta["optimizer_state_dict"], tensors)
197        
198        # 2. Merge back live functions (needed for PSGD/Kron)
199        live_state_dict = self.optimizer.state_dict()
200        final_state_dict = self._restore_unserializables(rebuilt_state_dict, live_state_dict)
201
202        try:
203            self.optimizer.load_state_dict(final_state_dict)
204        except Exception as e:
205            raise RuntimeError(f"Failed to load optimizer state from {ckpt}: {e}") from e
206
207    # Trainer state
208
209    def _save_trainer(self, ckpt: Path) -> None:
210        state = asdict(self.trainer_state)
211        # Snapshot two extra fields for forward-compatibility checks on load
212        state["_data_path"]    = self.config.data
213        state["_was_compiled"] = self.config.compile
214        with open(ckpt / "trainer_state.json", "w") as f:
215            json.dump(state, f, indent=2)
216
217    def _load_trainer(self, ckpt: Path) -> None:
218        json_path = ckpt / "trainer_state.json"
219
220        if not json_path.exists():
221            print("  No trainer state found — all counters reset to defaults.")
222            return
223
224        with open(json_path) as f:
225            state = json.load(f)
226
227        # Restore all fields that exist on the current dataclass (forward-compatible)
228        for k, v in state.items():
229            if hasattr(self.trainer_state, k):
230                setattr(self.trainer_state, k, v)
231
232        # Warn if resuming onto a different dataset
233        saved_path = state.get("_data_path")
234        if saved_path and saved_path != self.config.data:
235            print(
236                f"  Warning: checkpoint trained on '{saved_path}', "
237                f"current data is '{self.config.data}'. "
238                f"Set resume_reset_data=True if this is intentional."
239            )
240
241        # Hard error on compile-mode mismatch (compiled vs eager weights are incompatible)
242        was_compiled = state.get("_was_compiled", False)
243        if was_compiled != self.config.compile:
244            raise ValueError(
245                f"Compile mode mismatch: checkpoint _was_compiled={was_compiled}, "
246                f"current compile={self.config.compile}."
247            )
248
249        print(
250            f"  Trainer state restored: step={self.trainer_state.global_step:,}, "
251            f"tokens={self.trainer_state.global_tokens:,}, "
252            f"loss_ema={self.trainer_state.spike_loss_ema}, "
253            f"batches_consumed={self.trainer_state.batches_consumed:,}"
254        )
255
256    # RNG state
257
258    def _save_rng(self, ckpt: Path) -> None:
259        tensors: dict[str, torch.Tensor] = {"torch_cpu": torch.get_rng_state()}
260
261        cuda_states = torch.cuda.get_rng_state_all() if torch.cuda.is_available() else []
262        for i, s in enumerate(cuda_states):
263            tensors[f"torch_cuda_{i}"] = s
264
265        safetensors_save_file(tensors, ckpt / "rng_state.safetensors")
266        with open(ckpt / "rng_meta.json", "w") as f:
267            json.dump({"num_cuda_devices": len(cuda_states)}, f)
268
269    def _load_rng(self, ckpt: Path) -> None:
270        sf_path   = ckpt / "rng_state.safetensors"
271        meta_path = ckpt / "rng_meta.json"
272
273        if not sf_path.exists() or not meta_path.exists():
274            print("  No RNG state found — skipping (data order may differ slightly on resume).")
275            return
276
277        with open(meta_path) as f:
278            meta = json.load(f)
279
280        tensors = safetensors_load_file(str(sf_path), device="cpu")
281        torch.set_rng_state(tensors["torch_cpu"])
282
283        if torch.cuda.is_available():
284            n_saved = meta["num_cuda_devices"]
285            n_avail = torch.cuda.device_count()
286            if n_saved != n_avail:
287                print(
288                    f"  Warning: checkpoint has {n_saved} CUDA device(s), "
289                    f"current run has {n_avail} — CUDA RNG state not restored."
290                )
291            else:
292                torch.cuda.set_rng_state_all(
293                    [tensors[f"torch_cuda_{i}"] for i in range(n_saved)]
294                )
295
296        print("  RNG state restored.")
class CheckpointManager:
 19class CheckpointManager:
 20
 21    def __init__(self, config, model, optimizer, trainer_state, device):
 22        self.config         = config
 23        self.model          = model
 24        self.optimizer      = optimizer
 25        self.trainer_state  = trainer_state
 26        self.device         = device
 27
 28    def _unwrap_model(self):
 29        return self.model._orig_mod if hasattr(self.model, "_orig_mod") else self.model
 30
 31    def save(self, step: int) -> None:
 32        ckpt = Path(self.config.output_dir) / f"step_{step:07d}"
 33        ckpt.mkdir(parents=True, exist_ok=True)
 34        t_save = time.time()
 35
 36        # Copy tokenizer / model config JSON files so the checkpoint is self-contained
 37        for json_file in Path(self.config.model_dir).glob("*.json"):
 38            shutil.copy2(json_file, ckpt / json_file.name)
 39
 40        self._unwrap_model().save_pretrained(str(ckpt), safe_serialization=True)
 41        self._save_optimizer(ckpt)
 42        self._save_trainer(ckpt)
 43        self._save_rng(ckpt)
 44        self.config.save(ckpt / "training_config.json")
 45
 46        print(f"Checkpoint saved: {ckpt}  ({time.time() - t_save:.1f}s)")
 47
 48    def load(self, path: str) -> None:
 49        
 50        ckpt = Path(path)
 51        print(f"Resuming from {ckpt}.")
 52
 53        result = load_sharded_checkpoint(
 54            self._unwrap_model(), str(ckpt), strict=True, prefer_safe=True
 55        )
 56        if result.missing_keys:
 57            print(f"  Warning: missing keys:    {result.missing_keys}")
 58        if result.unexpected_keys:
 59            print(f"  Warning: unexpected keys: {result.unexpected_keys}")
 60
 61        self._load_optimizer(ckpt)
 62        self._load_trainer(ckpt)
 63
 64        if not self.config.resume_reset_data:
 65            self._load_rng(ckpt)
 66
 67    # Optimizer
 68
 69    def _encode_node(self, obj: Any, tensors: dict[str, torch.Tensor]) -> dict[str, Any]:
 70        """Recursively encodes optimizer state into a strict, explicitly tagged AST schema."""
 71        if isinstance(obj, torch.Tensor):
 72            if obj.layout != torch.strided:
 73                raise TypeError(f"Unsupported tensor layout {obj.layout}. Safetensors requires strided tensors.")
 74            
 75            t_id = f"t_{len(tensors):08d}"
 76            # Detach removes autograd graph refs; contiguous prepares for Safetensors
 77            tensors[t_id] = obj.detach().cpu().contiguous()
 78            return {"t": "tensor", "v": t_id}
 79            
 80        elif isinstance(obj, dict):
 81            # Encode as list of pairs to preserve non-string keys (e.g., int parameter IDs)
 82            return {"t": "dict", "v": [[self._encode_node(k, tensors), self._encode_node(v, tensors)] for k, v in obj.items()]}
 83            
 84        elif isinstance(obj, list):
 85            return {"t": "list", "v": [self._encode_node(x, tensors) for x in obj]}
 86            
 87        elif isinstance(obj, torch.Size):
 88            return {"t": "torch_size", "v": [self._encode_node(x, tensors) for x in obj]}
 89            
 90        elif isinstance(obj, tuple):
 91            return {"t": "tuple", "v": [self._encode_node(x, tensors) for x in obj]}
 92            
 93        elif isinstance(obj, torch.dtype):
 94            # str(torch.float32) -> "torch.float32"
 95            return {"t": "torch_dtype", "v": str(obj)}
 96            
 97        elif isinstance(obj, torch.device):
 98            # str(torch.device('cuda:0')) -> "cuda:0"
 99            return {"t": "torch_device", "v": str(obj)}
100            
101        elif isinstance(obj, (int, float, str, bool, type(None))):
102            return {"t": "primitive", "v": obj}
103            
104        elif callable(obj):
105            # PSGD/Kron put functions in param_groups. We flag them to merge back on load.
106            return {"t": "callable", "v": getattr(obj, "__name__", "anonymous_function")}
107            
108        else:
109            raise TypeError(f"Unsupported python type {type(obj).__name__} in optimizer state: {obj}")
110
111    def _decode_node(self, node: dict[str, Any], tensors: dict[str, torch.Tensor]) -> Any:
112        """Reconstructs the exact Python objects from the tagged AST schema."""
113        t = node["t"]
114        v = node["v"]
115        
116        if t == "tensor":       return tensors[v]
117        if t == "dict":         return {self._decode_node(k, tensors): self._decode_node(val, tensors) for k, val in v}
118        if t == "list":         return [self._decode_node(x, tensors) for x in v]
119        if t == "tuple":        return tuple(self._decode_node(x, tensors) for x in v)
120        if t == "torch_size":   return torch.Size(self._decode_node(x, tensors) for x in v)
121        if t == "torch_dtype":  return getattr(torch, v.split(".")[-1]) # "torch.float32" -> torch.float32
122        if t == "torch_device": return torch.device(v)
123        if t == "primitive":    return v
124        if t == "callable":     return _CallableSentinel()
125        
126        raise ValueError(f"Corrupt metadata: Unknown node type '{t}'")
127
128    def _restore_unserializables(self, rebuilt: Any, current: Any) -> Any:
129        """Walks the rebuilt state dict to inject functions from the live instantiated optimizer."""
130        if isinstance(rebuilt, dict) and isinstance(current, dict):
131            return {k: self._restore_unserializables(v, current.get(k)) for k, v in rebuilt.items()}
132        elif isinstance(rebuilt, list) and isinstance(current, list):
133            return [self._restore_unserializables(r, c) for r, c in zip(rebuilt, current)]
134        elif isinstance(rebuilt, tuple) and isinstance(current, tuple):
135            return tuple(self._restore_unserializables(r, c) for r, c in zip(rebuilt, current))
136        elif isinstance(rebuilt, _CallableSentinel):
137            return current  # Inject the live function back in
138        else:
139            return rebuilt
140
141    def _save_optimizer(self, ckpt: Path) -> None:
142        # Rank guard: Ensure only the main process writes to disk in normal DDP
143        if getattr(self, "global_rank", 0) != 0:
144            return
145
146        ckpt.mkdir(parents=True, exist_ok=True)
147        tensors: dict[str, torch.Tensor] = {}
148        state_dict = self.optimizer.state_dict()
149
150        meta = {
151            "__format_version__": 2,
152            "optimizer_state_dict": self._encode_node(state_dict, tensors)
153        }
154
155        sf_path = ckpt / "optimizer.safetensors"
156        meta_path = ckpt / "optimizer_meta.json"
157
158        tmp_sf = sf_path.with_suffix(".safetensors.tmp")
159        tmp_meta = meta_path.with_suffix(".json.tmp")
160
161        if tensors:
162            safetensors_save_file(tensors, tmp_sf)
163
164        with open(tmp_meta, "w") as f:
165            json.dump(meta, f, indent=2)
166            f.flush()
167            os.fsync(f.fileno())  # Guarantee disk persistence
168
169        # Atomic writes prevent corruption if the job is preempted mid-save
170        os.replace(tmp_meta, meta_path)
171        if tensors:
172            os.replace(tmp_sf, sf_path)
173        elif sf_path.exists():
174            # Clean up old safetensors if the optimizer structure changed to have no tensors
175            os.remove(sf_path)
176
177    def _load_optimizer(self, ckpt: Path) -> None:
178        meta_path = ckpt / "optimizer_meta.json"
179        sf_path   = ckpt / "optimizer.safetensors"
180
181        if not meta_path.exists():
182            print(f"  No optimizer metadata found at {meta_path} — starting fresh.")
183            return
184
185        with open(meta_path) as f:
186            meta = json.load(f)
187
188        if meta.get("__format_version__") != 2:
189            raise RuntimeError(f"Unsupported optimizer metadata format version {meta.get('__format_version__')} in {meta_path}")
190
191        # If sf_path doesn't exist, assume a tensor-less optimizer state
192        tensors = {}
193        if sf_path.exists():
194            tensors = safetensors_load_file(str(sf_path), device="cpu")
195
196        # 1. Decode the structural tree
197        rebuilt_state_dict = self._decode_node(meta["optimizer_state_dict"], tensors)
198        
199        # 2. Merge back live functions (needed for PSGD/Kron)
200        live_state_dict = self.optimizer.state_dict()
201        final_state_dict = self._restore_unserializables(rebuilt_state_dict, live_state_dict)
202
203        try:
204            self.optimizer.load_state_dict(final_state_dict)
205        except Exception as e:
206            raise RuntimeError(f"Failed to load optimizer state from {ckpt}: {e}") from e
207
208    # Trainer state
209
210    def _save_trainer(self, ckpt: Path) -> None:
211        state = asdict(self.trainer_state)
212        # Snapshot two extra fields for forward-compatibility checks on load
213        state["_data_path"]    = self.config.data
214        state["_was_compiled"] = self.config.compile
215        with open(ckpt / "trainer_state.json", "w") as f:
216            json.dump(state, f, indent=2)
217
218    def _load_trainer(self, ckpt: Path) -> None:
219        json_path = ckpt / "trainer_state.json"
220
221        if not json_path.exists():
222            print("  No trainer state found — all counters reset to defaults.")
223            return
224
225        with open(json_path) as f:
226            state = json.load(f)
227
228        # Restore all fields that exist on the current dataclass (forward-compatible)
229        for k, v in state.items():
230            if hasattr(self.trainer_state, k):
231                setattr(self.trainer_state, k, v)
232
233        # Warn if resuming onto a different dataset
234        saved_path = state.get("_data_path")
235        if saved_path and saved_path != self.config.data:
236            print(
237                f"  Warning: checkpoint trained on '{saved_path}', "
238                f"current data is '{self.config.data}'. "
239                f"Set resume_reset_data=True if this is intentional."
240            )
241
242        # Hard error on compile-mode mismatch (compiled vs eager weights are incompatible)
243        was_compiled = state.get("_was_compiled", False)
244        if was_compiled != self.config.compile:
245            raise ValueError(
246                f"Compile mode mismatch: checkpoint _was_compiled={was_compiled}, "
247                f"current compile={self.config.compile}."
248            )
249
250        print(
251            f"  Trainer state restored: step={self.trainer_state.global_step:,}, "
252            f"tokens={self.trainer_state.global_tokens:,}, "
253            f"loss_ema={self.trainer_state.spike_loss_ema}, "
254            f"batches_consumed={self.trainer_state.batches_consumed:,}"
255        )
256
257    # RNG state
258
259    def _save_rng(self, ckpt: Path) -> None:
260        tensors: dict[str, torch.Tensor] = {"torch_cpu": torch.get_rng_state()}
261
262        cuda_states = torch.cuda.get_rng_state_all() if torch.cuda.is_available() else []
263        for i, s in enumerate(cuda_states):
264            tensors[f"torch_cuda_{i}"] = s
265
266        safetensors_save_file(tensors, ckpt / "rng_state.safetensors")
267        with open(ckpt / "rng_meta.json", "w") as f:
268            json.dump({"num_cuda_devices": len(cuda_states)}, f)
269
270    def _load_rng(self, ckpt: Path) -> None:
271        sf_path   = ckpt / "rng_state.safetensors"
272        meta_path = ckpt / "rng_meta.json"
273
274        if not sf_path.exists() or not meta_path.exists():
275            print("  No RNG state found — skipping (data order may differ slightly on resume).")
276            return
277
278        with open(meta_path) as f:
279            meta = json.load(f)
280
281        tensors = safetensors_load_file(str(sf_path), device="cpu")
282        torch.set_rng_state(tensors["torch_cpu"])
283
284        if torch.cuda.is_available():
285            n_saved = meta["num_cuda_devices"]
286            n_avail = torch.cuda.device_count()
287            if n_saved != n_avail:
288                print(
289                    f"  Warning: checkpoint has {n_saved} CUDA device(s), "
290                    f"current run has {n_avail} — CUDA RNG state not restored."
291                )
292            else:
293                torch.cuda.set_rng_state_all(
294                    [tensors[f"torch_cuda_{i}"] for i in range(n_saved)]
295                )
296
297        print("  RNG state restored.")
CheckpointManager(config, model, optimizer, trainer_state, device)
21    def __init__(self, config, model, optimizer, trainer_state, device):
22        self.config         = config
23        self.model          = model
24        self.optimizer      = optimizer
25        self.trainer_state  = trainer_state
26        self.device         = device
config
model
optimizer
trainer_state
device
def save(self, step: int) -> None:
31    def save(self, step: int) -> None:
32        ckpt = Path(self.config.output_dir) / f"step_{step:07d}"
33        ckpt.mkdir(parents=True, exist_ok=True)
34        t_save = time.time()
35
36        # Copy tokenizer / model config JSON files so the checkpoint is self-contained
37        for json_file in Path(self.config.model_dir).glob("*.json"):
38            shutil.copy2(json_file, ckpt / json_file.name)
39
40        self._unwrap_model().save_pretrained(str(ckpt), safe_serialization=True)
41        self._save_optimizer(ckpt)
42        self._save_trainer(ckpt)
43        self._save_rng(ckpt)
44        self.config.save(ckpt / "training_config.json")
45
46        print(f"Checkpoint saved: {ckpt}  ({time.time() - t_save:.1f}s)")
def load(self, path: str) -> None:
48    def load(self, path: str) -> None:
49        
50        ckpt = Path(path)
51        print(f"Resuming from {ckpt}.")
52
53        result = load_sharded_checkpoint(
54            self._unwrap_model(), str(ckpt), strict=True, prefer_safe=True
55        )
56        if result.missing_keys:
57            print(f"  Warning: missing keys:    {result.missing_keys}")
58        if result.unexpected_keys:
59            print(f"  Warning: unexpected keys: {result.unexpected_keys}")
60
61        self._load_optimizer(ckpt)
62        self._load_trainer(ckpt)
63
64        if not self.config.resume_reset_data:
65            self._load_rng(ckpt)