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()