File size: 12,389 Bytes
e1acc15 | 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 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 | """V1 same-input replay; private dumps, aggregate parity receipts only."""
from __future__ import annotations
import argparse
import importlib
import json
import os
from pathlib import Path
import sys
import time
from typing import Any
import torch
from torch import Tensor
from .model import Drafter, load
from .ops import frozen_linear
from .data import D2Blocks, sha256
def v1_import(root: str) -> None:
if not (Path(root)/"verify/run_ref.py").is_file():
raise FileNotFoundError(f"V1 verify modules missing under {root}")
sys.path.insert(0, root)
def g1_drafts(path: Path, data: D2Blocks) -> dict[str, dict[int, list[int]]]:
"""Read D2-DATA g1.py's local seq_id/P output; require exact block coverage.
G1's JSONL has no embedded manifest hash. Its file digest and the current
manifest identity must therefore be kept together in the evaluation receipt.
Use only the coordinator's G1 output for that exact dataset, never a guess.
"""
result: dict[str, dict[int, list[int]]] = {}
for line in path.read_text().splitlines():
row = json.loads(line)
req = row["seq_id"]
if req in result:
raise ValueError("duplicate sequence in G1 K2 output")
result[req] = {int(p): [int(t) for t in ids] for p, ids in row["drafts"].items()}
expected_requests = {req for _, _, req in data.sequences}
if set(result) != expected_requests:
raise ValueError("G1 K2 request set differs from the selected dataset split")
for _, index, req in data.sequences:
length, gen_start = index[1:3]
if set(result[req]) != set(range(gen_start-1, length-2)):
raise ValueError("G1 K2 position coverage is incomplete or mismatched")
if any(len(ids) != 5 or any(t < 0 or t >= 129280 for t in ids) for ids in result[req].values()):
raise ValueError("G1 K2 draft shape or vocabulary invalid")
return result
def load_capture_set(path: str, reader: str | None = None, split: str | None = None,
k2_mapping: str | None = None) -> Any:
if reader is None:
return importlib.import_module("verify.captures").CaptureSet(path)
if split is None:
raise ValueError("D2 shard replay requires --split")
manifest = Path(path)
data = D2Blocks(manifest, split)
spec = importlib.util.spec_from_file_location("d2_data_shards", reader)
if spec is None or spec.loader is None:
raise ImportError(f"cannot load D2-DATA reader {reader}")
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
drafts = None
by_seq = None
if k2_mapping:
if Path(k2_mapping).suffix == ".jsonl":
by_seq = g1_drafts(Path(k2_mapping), data)
else:
mapping = json.loads(Path(k2_mapping).read_text())
if mapping["arm"] != "k2" or mapping["split"] != split or mapping["manifest_sha256"] != sha256(manifest):
raise ValueError("K2 draft mapping identity/split mismatch")
drafts = mapping["drafts"]
paths = [str((manifest.parent/entry["file"]).resolve()) for entry in data.manifest["splits"][split]["files"]]
cs = module.ShardCaptureSet(paths, drafts=drafts, drafts_by_seq=by_seq, verify_sha256=True)
if k2_mapping and any(s.engine_drafts_placeholder for s in cs.samples):
raise ValueError("K2 replay mapping has missing samples")
return cs
def capture_inputs(cs: Any, s: Any, device: str) -> tuple[dict[str, Tensor], dict[str, object]]:
positions = cs.window(s)
status = cs.window_status(s)
if status["missing"]:
raise ValueError(f"incomplete V1 window for {s.key}")
rows = [cs.rows[s.req][p] for p in positions]
has = [r.taps is not None for r in rows]
taps = torch.stack([r.taps.reshape(-1) if r.taps is not None else torch.zeros(15360, dtype=torch.bfloat16) for r in rows])
mx = torch.stack([r.mx.reshape(-1) if r.mx is not None else torch.zeros(5120) for r in rows])
inp = {"taps": taps[None], "engine_mx": mx[None], "has_taps": torch.tensor([has]),
"positions": torch.tensor([positions]), "mask": torch.ones(1, len(rows), dtype=torch.bool),
"anchor": torch.tensor([s.anchor]), "P": torch.tensor([s.P])}
meta = {"n": len(rows), "taps_rows": sum(has), "engine_mx_rows": len(rows)-sum(has),
"P_source": "taps" if has[-1] else "engine_mx", "first": positions[0], "last": positions[-1]}
return {k: v.to(device) for k, v in inp.items()}, meta
def read_checkpoint(path: Path) -> tuple[dict[str, Any], str]:
digest = sha256(path)
state = torch.load(path, map_location="cpu", weights_only=False)
if sha256(path) != digest:
raise ValueError("checkpoint changed while being read; use an immutable archived step")
return state, digest
def apply_trainables(model: Drafter, values: dict[str, Tensor]) -> None:
named = dict(model.named_parameters())
if set(values) != set(named):
raise ValueError("checkpoint trainable names differ from Phase 2a model")
with torch.no_grad():
for name, p in named.items():
if p.shape != values[name].shape or not torch.isfinite(values[name]).all():
raise ValueError(f"bad adapter tensor {name}")
p.copy_(values[name])
def apply_adapter(model: Drafter, path: str) -> str:
state, digest = read_checkpoint(Path(path))
if state.get("schema") != "dsv41.d2.train-checkpoint.v1" or state.get("frozen_identity") != model.frozen_identity:
raise ValueError("adapter checkpoint frozen source differs from loaded model")
apply_trainables(model, state["trainable"])
return digest
@torch.no_grad()
def dump_sample(model: Drafter, inp: dict[str, Tensor], window: dict[str, object], meta: dict[str, object]) -> dict[str, Any]:
tr: dict[str, Any] = {}
hidden, hx = model.hidden(**inp, trace=tr)
ids, conf = model.greedy(hidden, hx, inp["anchor"])
base = torch.cat([frozen_linear(hidden.float(), model.t["head.weight"][lo:lo+4096])
for lo in range(0, model.c.vocab, 4096)], -1)
provenance = {"git_sha": os.environ.get("D2_GIT_SHA", "uncommitted-snapshot"), "torch": torch.__version__,
"source_sha256": {p.name: sha256(p) for p in sorted(Path(__file__).parent.glob("*.py"))},
"frozen_identity": model.frozen_identity, "reference_math": model.reference_math,
"dtype_policy": "BF16 frozen masters; packed FP4 forward FP64 sum; FP32 activations/router/Markov; STE QDQ", **meta}
out: dict[str, Any] = {"drafts": ids[0].tolist(), "conf": conf[0].cpu(), "base_logits": base[0].cpu(),
"P": int(inp["P"][0]), "anchor": int(inp["anchor"][0]), "window": window, "meta": provenance,
"head_hidden": hidden[0].cpu(), "head_x": hx[0].cpu(),
"main_x_P": tr["main_x"][0, -1].cpu()}
kvs = []
for i in range(model.c.stages):
st = tr[f"s{i}"]
kvs.append(st["kv"][0].cpu())
out[f"s{i}"] = {n: st[n][0].cpu() for n in ("attn_out", "ffn_in", "ffn_out", "h_out")}
out[f"s{i}"]["route"] = {"idx": st["route"]["idx"][0].cpu(), "biased": st["route"]["biased"].cpu()}
out["kv_window"] = torch.stack(kvs, 1)
return out
def relative(a: Tensor, b: Tensor) -> float:
return float((a.double()-b.double()).norm()/b.double().norm().clamp_min(1e-30))
def main() -> None:
ap = argparse.ArgumentParser(__doc__)
ap.add_argument("--v1-root", required=True)
ap.add_argument("--captures", required=True)
ap.add_argument("--ckpt", required=True)
ap.add_argument("--references", required=True, help="V1 dumps root with ref and mirror directories")
ap.add_argument("--out", required=True)
ap.add_argument("--adapter")
ap.add_argument("--limit", type=int, default=50)
ap.add_argument("--device", default="cpu")
ap.add_argument("--oracle", action="store_true")
ap.add_argument("--reference-math", action="store_true", help="CPU unquantized reference math; GPU quantized GEMMs")
ap.add_argument("--data-reader", help="D2-DATA shards.py; --captures then names MANIFEST.json")
ap.add_argument("--split", choices=("dev", "heldout_legal", "heldout_general", "acap"))
ap.add_argument("--k2-mapping")
ap.add_argument("--native-manifest")
ap.add_argument("--package-root", type=Path, help="verified merged native arm; owns native manifest and adapter provenance")
ap.add_argument("--parity-gate", action="store_true", help="also require exact agreement for an adapted arm")
a = ap.parse_args()
if a.device != "cpu":
from .safety import assert_gpu_lease
assert_gpu_lease()
v1_import(a.v1_root)
cs = load_capture_set(a.captures, a.data_reader, a.split, a.k2_mapping)
if cs.plumbing:
raise ValueError(f"V1 plumbing errors: {len(cs.plumbing)}")
samples = sorted(cs.samples, key=lambda s: (s.req, s.P, s.round))
samples = [s for s in samples if (Path(a.references)/"ref"/f"{s.key}.pt").is_file()]
# Spread 50 rounds across all four requests rather than the first request.
if a.limit and len(samples) > a.limit:
samples = [samples[j*len(samples)//a.limit] for j in range(a.limit)]
if not samples:
raise ValueError("no reference-covered samples")
torch.set_num_threads(8)
package = None
if a.package_root:
from .reference import verify_package
if a.adapter or a.native_manifest or Path(a.ckpt).resolve() != a.package_root.resolve():
raise ValueError("--package-root must equal --ckpt and cannot combine with adapter/native-manifest")
package = verify_package(a.package_root)
a.native_manifest = str(a.package_root/"NATIVE_MANIFEST.json")
model = load(a.ckpt, a.device, oracle=a.oracle, reference_math=a.reference_math, native_manifest=a.native_manifest).eval()
adapter_identity = apply_adapter(model, a.adapter) if a.adapter else (package["adapter_sha256"] if package else None)
out = Path(a.out)
out.mkdir(mode=0o700, parents=True, exist_ok=True)
receipts: list[dict[str, object]] = []
begin = time.monotonic()
for s in samples:
inp, window = capture_inputs(cs, s, model.activation_device)
arm = dump_sample(model, inp, window, {"arm": "D2-TRAIN", "adapter": a.adapter, "native_root": a.ckpt,
"adapter_sha256": adapter_identity, "package_identity": package["drafter_identity"] if package else None,
"accumulation": str(model.acc), "uuid": os.environ.get("CUDA_VISIBLE_DEVICES")})
arm["sample"] = {"key": s.key, "req": s.req, "P": s.P, "realized": s.realized,
"engine_drafts": s.engine_drafts, "engine_drafts_placeholder": getattr(s, "engine_drafts_placeholder", False)}
if a.k2_mapping:
arm["meta"]["k2_mapping_sha256"] = sha256(Path(a.k2_mapping))
arm["meta"]["target_manifest_sha256"] = sha256(Path(a.captures))
torch.save(arm, out/f"{s.key}.pt")
ref = torch.load(Path(a.references)/"ref"/f"{s.key}.pt", weights_only=False, map_location="cpu")
mir = torch.load(Path(a.references)/"mirror"/f"{s.key}.pt", weights_only=False, map_location="cpu")
err = relative(arm["base_logits"], ref["base_logits"])
band = relative(mir["base_logits"], ref["base_logits"])
row = {"key": s.key, "drafts_equal": arm["drafts"] == ref["drafts"], "logit_relerr": err,
"storage_band": band, "within_v1_3x_band": err <= 3*band,
"seconds": time.monotonic()-begin}
receipts.append(row)
(out/"parity.json").write_text(json.dumps({"samples": len(receipts), "rows": receipts,
"reference_math": model.reference_math, "frozen_identity": model.frozen_identity,
"acc": str(model.acc), "pass": all(r["drafts_equal"] and r["within_v1_3x_band"] for r in receipts)}, indent=2)+"\n")
print(json.dumps(row), flush=True)
if ((not a.adapter and not package) or a.parity_gate) and not all(r["drafts_equal"] and r["within_v1_3x_band"] for r in receipts):
raise SystemExit("D2 unadapted parity gate failed; see parity.json")
if __name__ == "__main__":
main()
|