Download train/average_checkpoints.py from VantoraLabs/Vocetta2-1m: direct link, hf CLI and curl.
- Browser
- Download file 2.86 kB
-
https://huggingface.co/VantoraLabs/Vocetta2-1m/resolve/main/train/average_checkpoints.py
- Command line
-
hf download hf://VantoraLabs/Vocetta2-1m/train/average_checkpoints.py
-
curl -L -o average_checkpoints.py https://huggingface.co/VantoraLabs/Vocetta2-1m/resolve/main/train/average_checkpoints.py
2.86 kB
| """Average checkpoints into a soup (the lever that carried this release). | |
| Same-basin checkpoints from the same recipe, saved at different points of | |
| short continuation runs, average into a better model than any member: on this | |
| line the final soups scored +0.09 SCOREQ above their best single member, for | |
| zero training. | |
| # uniform average | |
| python average_checkpoints.py --out decoder_soup.pt a.pt b.pt c.pt d.pt | |
| # weighted average (the acoustic blend used weights 2,2,1,1,1) | |
| python average_checkpoints.py --out acoustic_G4.pt \ | |
| --weights 2,2,1,1,1 bs015.pt ft2.pt t1.pt t2.pt t3.pt | |
| Rules (measured on this line): | |
| - Only average checkpoints with IDENTICAL key sets; the script refuses to mix | |
| different architectures. | |
| - Float tensors are averaged; non-float tensors are copied from the first | |
| member. | |
| - Members must share a basin (same recipe, diverged only by training noise | |
| and step count). Mixing distant runs loses. | |
| - 2 to 6 members is the proven range; more members dilute. The shipped | |
| decoder is the average of its two best soups, which itself beat larger | |
| blends, and the shipped acoustic is a pair-heavy blend of five | |
| checkpoints. | |
| - Averaging can be applied to soups again (nested), e.g. the shipped decoder | |
| is avg(e5, ss2) where e5 and ss2 are themselves averages. | |
| """ | |
| import argparse | |
| import torch | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--out", required=True) | |
| ap.add_argument("--weights", default=None, | |
| help="comma-separated per-input weights (default: uniform)") | |
| ap.add_argument("inputs", nargs="+", help="checkpoint files to average") | |
| args = ap.parse_args() | |
| weights = None | |
| if args.weights: | |
| weights = [float(x) for x in args.weights.split(",")] | |
| if len(weights) != len(args.inputs): | |
| raise SystemExit(f"{len(weights)} weights for {len(args.inputs)} inputs") | |
| else: | |
| weights = [1.0] * len(args.inputs) | |
| tot = sum(weights) | |
| sds = [torch.load(p, map_location="cpu", weights_only=False) for p in args.inputs] | |
| ks0 = set(sds[0].keys()) | |
| for p, sd in zip(args.inputs[1:], sds[1:]): | |
| if set(sd.keys()) != ks0: | |
| raise SystemExit(f"key mismatch: {p} does not match {args.inputs[0]}") | |
| out = {} | |
| for k in sds[0].keys(): | |
| ref = sds[0][k] | |
| if torch.is_tensor(ref) and ref.is_floating_point(): | |
| acc = None | |
| for w, sd in zip(weights, sds): | |
| v = sd[k].float() * (w / tot) | |
| acc = v if acc is None else acc + v | |
| out[k] = acc | |
| else: | |
| out[k] = ref | |
| torch.save(out, args.out) | |
| n = sum(v.numel() for v in out.values() if torch.is_tensor(v)) | |
| print(f"averaged {len(sds)} checkpoints -> {args.out} ({n:,} parameters)") | |
| if __name__ == "__main__": | |
| main() | |