| |
| """Validate an HF weight export and its terminal Prime-RL metrics.""" |
|
|
| from __future__ import annotations |
|
|
| import argparse |
| import json |
| import math |
| from collections import defaultdict |
| from pathlib import Path |
|
|
|
|
| def merged_metrics(path: Path) -> dict[int, dict]: |
| by_step: dict[int, dict] = defaultdict(dict) |
| for line in path.read_text().splitlines(): |
| if line.strip(): |
| row = json.loads(line) |
| by_step[int(row["step"])].update(row) |
| return dict(by_step) |
|
|
|
|
| def main() -> None: |
| parser = argparse.ArgumentParser() |
| parser.add_argument("model", type=Path) |
| parser.add_argument("metrics", type=Path) |
| parser.add_argument("--expected-step", type=int, required=True) |
| parser.add_argument( |
| "--scan-finite", |
| action="store_true", |
| help="load every floating tensor and reject NaN/Inf (slower)", |
| ) |
| args = parser.parse_args() |
|
|
| model = args.model.resolve() |
| problems: list[str] = [] |
| if not (model / "STABLE").is_file(): |
| problems.append("missing STABLE marker") |
| index_path = model / "model.safetensors.index.json" |
| config_path = model / "config.json" |
| if not index_path.is_file(): |
| problems.append("missing model.safetensors.index.json") |
| if not config_path.is_file(): |
| problems.append("missing config.json") |
| if problems: |
| print(json.dumps({"healthy": False, "problems": problems}, indent=2)) |
| raise SystemExit(1) |
|
|
| index = json.loads(index_path.read_text()) |
| weight_map = index.get("weight_map") or {} |
| if not weight_map: |
| problems.append("empty weight_map") |
| expected_by_shard: dict[str, set[str]] = defaultdict(set) |
| for key, shard in weight_map.items(): |
| expected_by_shard[str(shard)].add(str(key)) |
|
|
| from safetensors import safe_open |
|
|
| tensor_count = 0 |
| parameter_elements = 0 |
| scanned_elements = 0 |
| dtypes: dict[str, int] = defaultdict(int) |
| for shard_name, expected_keys in sorted(expected_by_shard.items()): |
| shard_path = model / shard_name |
| if not shard_path.is_file() or shard_path.stat().st_size == 0: |
| problems.append(f"missing or empty shard: {shard_name}") |
| continue |
| try: |
| with safe_open(shard_path, framework="pt", device="cpu") as handle: |
| actual_keys = set(handle.keys()) |
| if actual_keys != expected_keys: |
| missing = len(expected_keys - actual_keys) |
| extra = len(actual_keys - expected_keys) |
| problems.append( |
| f"index/header mismatch in {shard_name}: missing={missing}, extra={extra}" |
| ) |
| for key in sorted(actual_keys): |
| view = handle.get_slice(key) |
| shape = tuple(view.get_shape()) |
| elements = math.prod(shape) |
| tensor_count += 1 |
| parameter_elements += elements |
| dtypes[str(view.get_dtype())] += 1 |
| if args.scan_finite: |
| tensor = handle.get_tensor(key) |
| if tensor.is_floating_point() or tensor.is_complex(): |
| scanned_elements += tensor.numel() |
| if not tensor.isfinite().all().item(): |
| problems.append(f"non-finite tensor: {key}") |
| except Exception as error: |
| problems.append(f"cannot load {shard_name}: {type(error).__name__}: {error}") |
|
|
| by_step = merged_metrics(args.metrics) |
| if args.expected_step not in by_step: |
| problems.append(f"metrics do not contain expected step {args.expected_step}") |
| terminal = {} |
| else: |
| terminal = by_step[args.expected_step] |
| for field in ("loss/mean", "optim/grad_norm", "optim/lr"): |
| value = terminal.get(field) |
| if not isinstance(value, (int, float)) or not math.isfinite(value): |
| problems.append(f"terminal metric {field} is not finite: {value!r}") |
| if terminal.get("loss/nan_count", 0) != 0: |
| problems.append( |
| f"terminal loss/nan_count is {terminal.get('loss/nan_count')!r}" |
| ) |
|
|
| result = { |
| "healthy": not problems, |
| "model": str(model), |
| "expected_step": args.expected_step, |
| "stable_marker": (model / "STABLE").is_file(), |
| "shards": len(expected_by_shard), |
| "tensors": tensor_count, |
| "tensor_elements": parameter_elements, |
| "dtype_tensor_counts": dict(sorted(dtypes.items())), |
| "finite_scan_enabled": args.scan_finite, |
| "finite_scanned_elements": scanned_elements, |
| "terminal_metrics": { |
| key: terminal.get(key) |
| for key in ( |
| "loss/mean", |
| "loss/nan_count", |
| "optim/grad_norm", |
| "optim/lr", |
| "perf/throughput", |
| "perf/peak_memory", |
| "progress/num_tokens", |
| "progress/num_samples", |
| ) |
| }, |
| "problems": problems, |
| } |
| print(json.dumps(result, indent=2)) |
| if problems: |
| raise SystemExit(1) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|