Download harness/scripts/interpolate.py from agentic-ptb/opus-max-record: direct link, hf CLI and curl.
- Browser
- Download file 3.73 kB
-
https://huggingface.co/agentic-ptb/opus-max-record/resolve/main/harness/scripts/interpolate.py
- Command line
-
hf download hf://agentic-ptb/opus-max-record/harness/scripts/interpolate.py
-
curl -L -o interpolate.py https://huggingface.co/agentic-ptb/opus-max-record/resolve/main/harness/scripts/interpolate.py
3.73 kB
| #!/usr/bin/env python3 | |
| """Interpolate two checkpoints in weight space: out = alpha*A + (1-alpha)*B. | |
| Usage: interpolate.py OUT CKPT_A CKPT_B [alpha=0.5] | |
| Why this exists alongside `soup.py`: `soup.py` averages checkpoints *from the same training run* | |
| and assumes they share an index and shard filenames. That assumption does not hold here — the base | |
| model ships shards named `model.safetensors-0000N-of-00004.safetensors` while prime-rl's trainer | |
| writes `model-0000N-of-00004.safetensors`, so `soup.py` dies with `FileNotFoundError` on the base. | |
| This script reads each checkpoint's *own* `model.safetensors.index.json` and matches by tensor | |
| name, so it does not care how either side lays its shards out. | |
| It also does a real weighted interpolation rather than an equal average, and it verifies that both | |
| checkpoints expose exactly the same tensor names and shapes before writing anything — silently | |
| averaging a mismatched pair would produce a file that loads and generates garbage. | |
| Output carries A's shard layout and A's config/tokenizer/processor files. | |
| """ | |
| import json | |
| import os | |
| import shutil | |
| import sys | |
| import torch | |
| from safetensors.torch import load_file, save_file | |
| AUX = ( | |
| "config.json", "generation_config.json", "tokenizer.json", "tokenizer_config.json", | |
| "chat_template.jinja", "merges.txt", "vocab.json", | |
| "preprocessor_config.json", "video_preprocessor_config.json", | |
| ) | |
| def index_of(ckpt): | |
| """-> {tensor_name: shard_filename}""" | |
| with open(os.path.join(ckpt, "model.safetensors.index.json")) as f: | |
| return json.load(f)["weight_map"] | |
| def main(): | |
| if len(sys.argv) < 4: | |
| sys.exit(__doc__) | |
| out, a, b = sys.argv[1], sys.argv[2], sys.argv[3] | |
| alpha = float(sys.argv[4]) if len(sys.argv) > 4 else 0.5 | |
| ia, ib = index_of(a), index_of(b) | |
| missing = set(ia) - set(ib) | |
| if missing: | |
| sys.exit(f"{len(missing)} tensors in A are absent from B, e.g. {sorted(missing)[:3]}") | |
| extra = set(ib) - set(ia) | |
| if extra: | |
| # Expected here: the base ships a 15-tensor MTP (multi-token-prediction) head that | |
| # prime-rl's SFT trainer does not train and does not save. The served artifact is A's | |
| # structure, so B's extras are ignored rather than grafted on — the output must be | |
| # loadable by exactly the command in SUBMISSION.md. | |
| print(f"note: {len(extra)} tensors exist only in B and are ignored " | |
| f"(e.g. {sorted(extra)[:2]})", flush=True) | |
| print(f"{len(ia)} tensors; out = {alpha}*A + {1 - alpha}*B", flush=True) | |
| os.makedirs(out, exist_ok=True) | |
| # Load B once, keyed by tensor name — B's shard layout is irrelevant from here on. | |
| bt = {} | |
| for shard in sorted(set(ib.values())): | |
| bt.update(load_file(os.path.join(b, shard))) | |
| print(f"loaded B ({len(bt)} tensors)", flush=True) | |
| for shard in sorted(set(ia.values())): | |
| sd = load_file(os.path.join(a, shard)) | |
| merged = {} | |
| for name, ta in sd.items(): | |
| tb = bt[name] | |
| if ta.shape != tb.shape: | |
| sys.exit(f"shape mismatch for {name}: {tuple(ta.shape)} vs {tuple(tb.shape)}") | |
| merged[name] = (alpha * ta.to(torch.float32) | |
| + (1 - alpha) * tb.to(torch.float32)).to(ta.dtype) | |
| save_file(merged, os.path.join(out, shard), metadata={"format": "pt"}) | |
| print(f"wrote {shard} ({len(merged)} tensors)", flush=True) | |
| del sd, merged | |
| shutil.copy(os.path.join(a, "model.safetensors.index.json"), out) | |
| for f in AUX: | |
| src = os.path.join(a, f) | |
| if os.path.exists(src): | |
| shutil.copy(src, out) | |
| print("done:", out, "->", sorted(os.listdir(out)), flush=True) | |
| if __name__ == "__main__": | |
| main() | |