File size: 7,522 Bytes
41dd7e0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | """Draw forecasts from a trained model: for every initial condition of a split, ``n_samples`` samples of x_T.
python -m bifurcation.sample --config CONFIG --data DIR --checkpoint FILE --out DIR [--split test] [--gids 5,16]
[--n-samples 10000] [--n-steps N] [--seed-base 777] [--chunk 2000] [--truth-bank DIR] [--device cuda]
CONFIG is a run configuration (records/<system>/runs/<model>/config.yaml, or the config_resolved.yaml of a run trained
here). The EMA weights of the checkpoint are used when the method keeps an EMA. ``--n-steps`` overrides the sampler
resolution (steps per segment for Cascaded SI, SDE steps for Direct SI, Heun steps for EDM). The noise of condition
``gid`` comes from the stream seeded with ``seed_base * 100003 + gid`` and is drawn in chunks of ``--chunk`` samples.
``--truth-bank`` supplies x_0 of test conditions that are not in the package (waves, NLKG).
Output: ``OUT/<variant>/group_<gid>.npz`` with ``gen_xT_norm`` (n_samples, D), ``x0_norm`` (D,), ``true_xT_norm``
(the package's truth members at the horizon) and ``gid``; states are in the package's normalized units.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import os
from datetime import datetime, timezone
from pathlib import Path
import numpy as np
import torch
from .data.loaders import get_split_set
from .registry import build_method, build_model
from .utils import load_yaml
def parse_args(argv=None):
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
p.add_argument("--config", required=True, help="run configuration (YAML)")
p.add_argument("--data", required=True, help="dataset package directory")
p.add_argument("--checkpoint", required=True)
p.add_argument("--out", required=True, help="output directory")
p.add_argument("--split", default="test", choices=("train", "val", "test"))
p.add_argument("--gids", default=None, help="comma-separated subset of the split's conditions")
p.add_argument("--n-samples", type=int, default=10000)
p.add_argument("--n-steps", type=int, default=None, help="sampler resolution override")
p.add_argument("--seed-base", type=int, default=777)
p.add_argument("--chunk", type=int, default=None, help="draw the samples of a condition in chunks of this size")
p.add_argument("--truth-bank", default=None, help="truth bank with the x_0 of test conditions absent from the package")
p.add_argument("--device", default="cuda" if torch.cuda.is_available() else "cpu")
return p.parse_args(argv)
def _sha256(path: Path) -> str:
h = hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(1 << 24), b""):
h.update(chunk)
return h.hexdigest()
def main(argv=None) -> int:
args = parse_args(argv)
cfg = load_yaml(args.config)
cfg["data"]["packaged_dataset_path"] = str(Path(args.data).resolve()) + "/"
device = str(args.device)
allow_tf32 = cfg.get("training", {}).get("allow_tf32")
if allow_tf32 is not None: # the arithmetic class the model was trained under
torch.backends.cuda.matmul.allow_tf32 = bool(allow_tf32)
torch.backends.cudnn.allow_tf32 = bool(allow_tf32)
seed = int(cfg.get("seed", cfg["data"].get("seed", 0)))
ckpt_path = Path(args.checkpoint).resolve()
ckpt = torch.load(ckpt_path, map_location=device)
model = build_model(cfg["model"], cfg["method"], cfg["data"])
ema_halflife = cfg.get("training", {}).get("ema_halflife_kimg", cfg["method"].get("ema_halflife_kimg"))
method_uses_ema = ema_halflife is not None and float(ema_halflife) > 0
if "ema_state_dict" in ckpt and method_uses_ema:
model.load_state_dict(ckpt["ema_state_dict"])
weights = "ema"
else:
model.load_state_dict(ckpt["model_state_dict"])
weights = "raw"
model.to(device).eval()
print(f"checkpoint: {ckpt_path.name} (step {ckpt.get('step')}, {weights} weights)")
if args.n_steps is not None:
sampling = cfg["method"]["sampling"]
for key in ("n_sde_steps", "n_sde_steps_per_seg", "n_steps"):
if key in sampling:
break
else:
raise KeyError("--n-steps: method.sampling has none of n_sde_steps / n_sde_steps_per_seg / n_steps")
print(f"sampler resolution: method.sampling.{key} {sampling[key]} -> {args.n_steps}")
sampling[key] = args.n_steps
method = build_method(cfg["method"])
split_x0, truth_clouds, gids = get_split_set(cfg, args.split, seed=seed, x0_bank=args.truth_bank)
if args.gids is not None:
wanted = sorted(int(g) for g in args.gids.split(","))
missing = [g for g in wanted if g not in gids]
if missing:
raise ValueError(f"--gids not in split '{args.split}': {missing}")
sel = [gids.index(g) for g in wanted]
split_x0, truth_clouds, gids = split_x0[sel], truth_clouds[sel], wanted
split_x0, truth_clouds = split_x0.to(device), truth_clouds.to(device)
n_gids = split_x0.shape[0]
print(f"split {args.split}: {n_gids} conditions, {args.n_samples} samples each")
out = Path(args.out).resolve()
out.mkdir(parents=True, exist_ok=True)
split_offset = {"train": 1000, "val": 2000, "test": 3000}[args.split]
variants = method.variants_for_eval()
for v_idx, (label, sample_kwargs) in enumerate(variants.items()):
bundle_dir = out / label
bundle_dir.mkdir(parents=True, exist_ok=True)
torch.manual_seed(seed + split_offset + 100 * v_idx)
with torch.no_grad():
for i in range(n_gids):
x0_i, gid = split_x0[i], int(gids[i])
torch.manual_seed(int(args.seed_base) * 100003 + gid)
if args.chunk is not None and args.n_samples > int(args.chunk):
parts, left = [], int(args.n_samples)
while left > 0:
m = min(int(args.chunk), left)
parts.append(method.sample(model, x0_i, n_samples=m, **sample_kwargs).squeeze(0))
left -= m
gen_cloud = torch.cat(parts, dim=0)
else:
gen_cloud = method.sample(model, x0_i, n_samples=int(args.n_samples), **sample_kwargs).squeeze(0)
np.savez(bundle_dir / f"group_{gid:04d}.npz", true_xT_norm=truth_clouds[i].cpu().numpy(),
gen_xT_norm=gen_cloud.cpu().numpy(), x0_norm=x0_i.cpu().numpy(), gid=np.int64(gid))
print(f" {label}: condition {gid} ({i + 1}/{n_gids})", flush=True)
record = {"config": str(Path(args.config).resolve()), "checkpoint": str(ckpt_path), "checkpoint_sha256": _sha256(ckpt_path),
"weights": weights, "split": args.split, "gids": list(gids), "n_samples": int(args.n_samples),
"sampling": {k: cfg["method"]["sampling"].get(k) for k in ("n_sde_steps", "n_sde_steps_per_seg", "n_steps",
"n_segments", "noise_scale") if k in cfg["method"]["sampling"]},
"seed_base": int(args.seed_base), "chunk": args.chunk, "variants": list(variants), "device": device,
"allow_tf32": allow_tf32, "at": datetime.now(timezone.utc).isoformat(timespec="seconds")}
(out / "SAMPLES.json").write_text(json.dumps(record, indent=1) + "\n")
return 0
if __name__ == "__main__":
raise SystemExit(main())
|