Skip to content

Commit 82a8fbf

Browse files
authored
FSDP opt checkpointing (#27)
1 parent 123e6a2 commit 82a8fbf

1 file changed

Lines changed: 40 additions & 27 deletions

File tree

fastgen/utils/checkpointer.py

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
StateDictOptions,
1717
)
1818
from torch.distributed.checkpoint.stateful import Stateful
19+
from torch.distributed.checkpoint.default_planner import DefaultLoadPlanner
1920

2021
from fastgen.configs.config import BaseCheckpointerConfig
2122
from fastgen.utils.distributed.s3_filesystem import S3StorageWriter, S3StorageReader
@@ -397,39 +398,51 @@ def load(
397398
for k, v in optimizer_dict.items():
398399
logger.info(f"Loading the FSDP optimizer dict for key {k}...")
399400
optim_wrapper = OptimizerWrapper(model=model_dict[k], optimizer=v)
400-
# For fresh optimizers with no state, we need to initialize with fake gradients
401-
# that are DTensors (not regular Tensors) to avoid the mixed Tensor/DTensor error
402-
if len(v.state) == 0:
403-
# Set fake DTensor gradients to initialize optimizer state
404-
for param in model_dict[k].parameters():
405-
if param.requires_grad and param.grad is None:
406-
param.grad = torch.zeros_like(param)
407401
optim_state_dict = optim_wrapper.state_dict()
408402
assert os.path.exists(f"{path}.{k}_model"), f"Key {k} does not exist in FSDP model dict"
409403
storage_reader = self.get_storage_reader(checkpoint_path=f"{path}.{k}_optim")
410404

411405
try:
412-
dcp.load(
413-
state_dict=optim_state_dict,
414-
storage_reader=storage_reader,
415-
)
416-
optim_wrapper.load_state_dict(optim_state_dict)
417-
logger.success(f"Successfully loaded optimizer state for {k}")
406+
metadata = storage_reader.read_metadata()
407+
# DCP metadata keys are flattened ("state.<param>.<buffer>"),
408+
# while optim_state_dict keys are nested ("state", "param_groups").
409+
# Compare at the parameter level instead.
410+
ckpt_param_names = set()
411+
for mkey in metadata.state_dict_metadata:
412+
if mkey.startswith("state."):
413+
rest = mkey[len("state.") :]
414+
last_dot = rest.rfind(".")
415+
if last_dot > 0:
416+
ckpt_param_names.add(rest[:last_dot])
417+
418+
current_param_names = set(optim_state_dict.get("state", {}).keys())
419+
420+
missing_from_ckpt = current_param_names - ckpt_param_names
421+
extra_in_ckpt = ckpt_param_names - current_param_names
422+
if missing_from_ckpt:
423+
logger.warning(
424+
f"Optimizer {k}: {len(missing_from_ckpt)} params in current model "
425+
f"but missing from checkpoint (will keep initialized values)"
426+
)
427+
logger.debug(f"Missing params: {sorted(missing_from_ckpt)}")
428+
if extra_in_ckpt:
429+
logger.warning(
430+
f"Optimizer {k}: {len(extra_in_ckpt)} params in checkpoint "
431+
f"but not in current model (will be ignored)"
432+
)
433+
logger.debug(f"Extra params: {sorted(extra_in_ckpt)}")
434+
if not missing_from_ckpt and not extra_in_ckpt:
435+
logger.info(f"Optimizer {k}: all {len(current_param_names)} params match checkpoint")
418436
except Exception as e:
419-
error_msg = str(e)
420-
if (
421-
"Missing key" in error_msg
422-
or "Unexpected key" in error_msg
423-
or "CheckpointException" in error_msg
424-
):
425-
logger.warning(f"Optimizer checkpoint compatibility issue for {k}: {type(e).__name__}")
426-
logger.warning(f"Initializing fresh optimizer state for {k} - training will continue")
427-
# Reset to fresh optimizer state
428-
v.__setstate__({"state": {}, "param_groups": v.param_groups})
429-
logger.info(f"Reset optimizer state for {k} due to parameter mismatch")
430-
else:
431-
logger.error(f"Unexpected optimizer loading error for {k}: {e}")
432-
raise e
437+
logger.debug(f"Could not read checkpoint metadata for param comparison: {e}")
438+
439+
dcp.load(
440+
state_dict=optim_state_dict,
441+
storage_reader=storage_reader,
442+
planner=DefaultLoadPlanner(allow_partial_load=True),
443+
)
444+
optim_wrapper.load_state_dict(optim_state_dict)
445+
logger.success(f"Successfully loaded optimizer state for {k}")
433446

434447
state = self._load_checkpoint(f"{path}.pth", device=device)
435448

0 commit comments

Comments
 (0)