|
| 1 | +"""V7-C03: ckpt.inspect RPC round-trip. |
| 2 | +
|
| 3 | +Writes a tiny safetensors checkpoint with the cppmega metadata schema |
| 4 | +produced by stages.write_ckpt_metadata, hits POST /rpc with ckpt.inspect, |
| 5 | +and asserts the parsed sub-objects come back wired so the UI can render |
| 6 | +arch_hash / opt_kind / version on Load. |
| 7 | +""" |
| 8 | + |
| 9 | +from __future__ import annotations |
| 10 | + |
| 11 | +import json |
| 12 | +from pathlib import Path |
| 13 | + |
| 14 | +import mlx.core as mx |
| 15 | +import pytest |
| 16 | +from fastapi.testclient import TestClient |
| 17 | +from safetensors.numpy import save_file as save_safetensors |
| 18 | + |
| 19 | +from cppmega_v4.jsonrpc import create_app |
| 20 | + |
| 21 | + |
| 22 | +@pytest.fixture |
| 23 | +def client(): |
| 24 | + return TestClient(create_app(cache_capacity=2)) |
| 25 | + |
| 26 | + |
| 27 | +def _write_ckpt(path: Path, *, version: str = "v1", |
| 28 | + arch_hash: str = "deadbeef" * 8, |
| 29 | + opt_kind: str = "adamw", lr: float = 3e-4, |
| 30 | + step: int = 17) -> None: |
| 31 | + metadata = { |
| 32 | + "cppmega_version": version, |
| 33 | + "arch": json.dumps( |
| 34 | + {"config_hash": arch_hash, "config_json": {}}, sort_keys=True), |
| 35 | + "train": json.dumps({"global_step": step}, sort_keys=True), |
| 36 | + "opt": json.dumps({"kind": opt_kind, "lr": lr}, sort_keys=True), |
| 37 | + } |
| 38 | + # Smallest possible payload — one scalar weight. |
| 39 | + import numpy as np |
| 40 | + save_safetensors({"w": np.zeros((1,), dtype=np.float32)}, |
| 41 | + str(path), metadata=metadata) |
| 42 | + |
| 43 | + |
| 44 | +def test_ckpt_inspect_missing_file(client, tmp_path): |
| 45 | + payload = { |
| 46 | + "jsonrpc": "2.0", "id": "m1", "method": "ckpt.inspect", |
| 47 | + "params": {"path": str(tmp_path / "nope.safetensors")}, |
| 48 | + } |
| 49 | + r = client.post("/rpc", json=payload) |
| 50 | + assert r.status_code == 200 |
| 51 | + body = r.json() |
| 52 | + assert body["result"]["exists"] is False |
| 53 | + |
| 54 | + |
| 55 | +def test_ckpt_inspect_no_metadata(client, tmp_path): |
| 56 | + import numpy as np |
| 57 | + p = tmp_path / "bare.safetensors" |
| 58 | + save_safetensors({"w": np.zeros((1,), dtype=np.float32)}, str(p)) |
| 59 | + payload = { |
| 60 | + "jsonrpc": "2.0", "id": "m2", "method": "ckpt.inspect", |
| 61 | + "params": {"path": str(p)}, |
| 62 | + } |
| 63 | + r = client.post("/rpc", json=payload) |
| 64 | + assert r.status_code == 200 |
| 65 | + body = r.json() |
| 66 | + assert body["result"]["exists"] is True |
| 67 | + assert body["result"]["has_metadata"] is False |
| 68 | + |
| 69 | + |
| 70 | +def test_ckpt_inspect_round_trip(client, tmp_path): |
| 71 | + p = tmp_path / "ckpt.safetensors" |
| 72 | + _write_ckpt(p, opt_kind="muon_adamw_hybrid", lr=1e-3, step=42) |
| 73 | + |
| 74 | + payload = { |
| 75 | + "jsonrpc": "2.0", "id": "m3", "method": "ckpt.inspect", |
| 76 | + "params": {"path": str(p)}, |
| 77 | + } |
| 78 | + r = client.post("/rpc", json=payload) |
| 79 | + assert r.status_code == 200 |
| 80 | + res = r.json()["result"] |
| 81 | + assert res["exists"] is True |
| 82 | + assert res["has_metadata"] is True |
| 83 | + assert res["opt_kind"] == "muon_adamw_hybrid" |
| 84 | + assert res["opt_lr"] == pytest.approx(1e-3) |
| 85 | + assert res["global_step"] == 42 |
| 86 | + # arch_hash must be the 64-char sha256 we wrote. |
| 87 | + assert isinstance(res["arch_hash"], str) |
| 88 | + assert len(res["arch_hash"]) == 64 |
| 89 | + |
| 90 | + |
| 91 | +# Silence the unused mlx warning — keep the import so future tests on |
| 92 | +# the same module can use mlx-backed safetensors without re-importing. |
| 93 | +_ = mx |
0 commit comments