Opsiom / scripts /slim_checkpoint.py
JuloEco's picture
Upload slim_checkpoint.py
89555fa verified
Raw History Blame Contribute Delete
2.7 kB
#!/usr/bin/env python3
"""Allège un checkpoint d'entraînement pour le déploiement CPU.
Un checkpoint d'entraînement contient souvent l'état de l'optimiseur (Adam =
2 copies des poids en plus) : un modèle de 500M de paramètres peut peser
plusieurs Go alors que l'inférence n'a besoin que des poids. Ce script ne garde
que `args` + `model_state_dict` (sans le préfixe `_orig_mod.`), en fp16 par
défaut : ~2 octets/paramètre, soit ~1 Go pour 500M. Au chargement, PyTorch
reconvertit en fp32 (le calcul CPU reste en fp32).
Le fichier produit garde le même format que l'original : inference.py le
charge sans modification.
Usage :
python scripts/slim_checkpoint.py chat_model_500M.pt chat_model_500M_slim.pt
python scripts/slim_checkpoint.py in.pt out.pt --dtype bf16
"""
import argparse
import dataclasses
import os
import sys
from pathlib import Path
import torch
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import inference # noqa: E402 (pour lire les `args` picklés depuis le script d'entraînement)
DTYPES = {"fp16": torch.float16, "bf16": torch.bfloat16, "fp32": torch.float32}
def _args_to_dict(args):
if isinstance(args, dict):
return dict(args)
if dataclasses.is_dataclass(args):
return dataclasses.asdict(args)
return {k: v for k, v in vars(args).items() if not k.startswith("_")}
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("src")
ap.add_argument("dst")
ap.add_argument("--dtype", choices=DTYPES, default="fp16")
opts = ap.parse_args()
dtype = DTYPES[opts.dtype]
ckpt = inference._torch_load(opts.src)
state = {k.replace("_orig_mod.", ""): v for k, v in ckpt["model_state_dict"].items()}
slim, bad = {}, []
for name, tensor in state.items():
if tensor.is_floating_point():
converted = tensor.detach().to(dtype).contiguous()
if dtype == torch.float16 and not torch.isfinite(converted).all():
bad.append(name)
slim[name] = converted
else:
slim[name] = tensor.detach().contiguous()
if bad:
sys.exit(f"❌ Débordement fp16 sur {len(bad)} tenseur(s) (ex. {bad[0]}). Relance avec --dtype bf16.")
out = {"args": _args_to_dict(ckpt["args"]), "model_state_dict": slim, "val_loss": ckpt.get("val_loss")}
torch.save(out, opts.dst)
n_params = sum(t.numel() for t in slim.values())
print(f"✅ {n_params / 1e6:.0f}M paramètres | {os.path.getsize(opts.src) / 1e9:.2f} Go -> "
f"{os.path.getsize(opts.dst) / 1e9:.2f} Go ({opts.dtype})")
if __name__ == "__main__":
main()