File size: 6,019 Bytes
411a8a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#!/usr/bin/env python3
"""
in2_gate.py -- the EXACT layout gate for in2_gen.py output. No GPU, no CUDA.

This is the only exact pre-flight check in the IN2 pilot. The step-0 loss check
was demoted to a diagnostic because its two hypotheses sit only ~0.22 nats apart
(no-induction 4.147 vs strong-induction 3.925, against a 3.901 plain baseline),
which cannot validate a layout. These assertions can.

Checks, per arm dir:
  1  file size is exactly examples*T*2 bytes (headerless uint16)
  2  every token in [0, VOCAB)
  3  no answer span intrudes into the last PAD_TAIL offsets of its example
  4  planted spans (needle / probe+answer / decoy) never overlap
  5  RETRIEVAL IS ACTUALLY POSSIBLE: the tokens at the probe equal the needle's
     key, and the tokens at the answer equal the needle's value. If this fails
     the task is unsolvable and the whole experiment is void.
  6  batch alignment: re-derive x/y exactly as DataLoaderLite::next_batch does
     (x=buf[i], y=buf[i+1], stride B*T*dp) and assert every example boundary
     lands on a row boundary
  7  arm-specific distance invariants (b: all == span, c: all >= d_min)

Cross-arm:
  8  arms B and C have BIT-IDENTICAL answer_start histograms -- the discipline
     the whole B-vs-C contrast rests on

Usage
  python3 in2_gate.py --dirs /tmp/in2b /tmp/in2c --B 2 --dp 2
"""
import argparse, array, json, os, sys

VOCAB = 50304
fails = []


def check(cond, msg):
    print(("  ok   " if cond else "  FAIL ") + msg)
    if not cond:
        fails.append(msg)


def load(d, split="train"):
    js = [f for f in os.listdir(d) if f.endswith(".json") and f"_{split}_" in f]
    if len(js) != 1:
        raise SystemExit(f"{d}: expected exactly one {split} manifest, found {js}")
    man = json.load(open(os.path.join(d, js[0])))
    toks = array.array("H")
    for sp in man.get("shards", [man["bin"]]):
        with open(sp, "rb") as f:
            toks.fromfile(f, os.path.getsize(sp) // 2)
    return man, toks


def gate_one(d, B, dp, split="train"):
    man, toks = load(d, split)
    T, n_ex, arm, span = man["T"], man["examples"], man["arm"], man["span"]
    kl, vl, pad = man["key_len"], man["val_len"], man["pad_tail"]
    print(f"\n=== arm {arm}  T={T}  examples={n_ex}  K={man['K']} ===")

    tot = sum(os.path.getsize(sp) for sp in man.get("shards", [man["bin"]]))
    check(tot == n_ex * T * 2,
          f"1 total size across {len(man.get('shards',[1]))} shard(s) == examples*T*2 "
          f"({n_ex*T*2} bytes, got {tot})")
    lo, hi = min(toks), max(toks)
    check(lo >= 0 and hi < VOCAB, f"2 tokens in [0,{VOCAB}) (min {lo}, max {hi})")

    bad_pad = bad_ovl = bad_ret = bad_dist = 0
    for ei, recs in enumerate(man["records"]):
        base = ei * T
        spans = []
        for r in recs:
            a0, a1 = r["answer_start"], r["answer_start"] + vl
            if a1 > T - pad:
                bad_pad += 1
            spans.append((r["needle_start"], r["needle_start"] + span))
            spans.append((r["probe_start"], r["probe_start"] + span))
            # 5: the probe must reproduce the needle's key, the answer its value
            nk = toks[base + r["needle_start"]: base + r["needle_start"] + kl]
            nv = toks[base + r["needle_start"] + kl: base + r["needle_start"] + span]
            pk = toks[base + r["probe_start"]: base + r["probe_start"] + kl]
            av = toks[base + a0: base + a1]
            if nk != pk or nv != av:
                bad_ret += 1
            if arm == "b" and r["distance"] != span:
                bad_dist += 1
            if arm == "c" and r["distance"] < man["d_min"]:
                bad_dist += 1
        spans.sort()
        for (s0, e0), (s1, _) in zip(spans, spans[1:]):
            if s1 < e0:
                bad_ovl += 1
    check(bad_pad == 0, f"3 no answer within the last {pad} offsets ({bad_pad} bad)")
    check(bad_ovl == 0, f"4 planted spans never overlap ({bad_ovl} bad)")
    check(bad_ret == 0, f"5 RETRIEVAL POSSIBLE: probe==key and answer==value ({bad_ret} bad)")
    check(bad_dist == 0, f"7 arm-{arm} distance invariant ({bad_dist} bad)")

    # 6: DataLoaderLite windowing. next_batch reads B*T+1 tokens and sets
    # y[i]=buf[i+1]; pos_ advances by B*T*world. Example boundaries must land on
    # row boundaries or an example would straddle two rows.
    stride = B * T * dp
    check(stride % T == 0, f"6a batch stride {stride} is a multiple of T={T}")
    check((n_ex * T) % stride == 0,
          f"6b total {n_ex*T} tokens is a whole number of batches (stride {stride})")
    if len(toks) >= B * T + 1:
        x0 = toks[0:B * T]
        y0 = toks[1:B * T + 1]
        rows_ok = all(x0[r * T] == toks[r * T] for r in range(B))
        check(rows_ok, f"6c row r starts at token r*T for all B={B} rows")
        check(y0[T - 1] == toks[T], "6d y[T-1] targets the next example's first token "
                                    "(why PAD_TAIL exists)")
    return man


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--dirs", nargs="+", required=True)
    ap.add_argument("--B", type=int, default=2)
    ap.add_argument("--dp", type=int, default=2)
    ap.add_argument("--split", default="train", choices=["train", "val"])
    args = ap.parse_args()

    mans = {}
    for d in args.dirs:
        m = gate_one(d, args.B, args.dp, args.split)
        mans[m["arm"]] = m

    if "b" in mans and "c" in mans:
        print("\n=== cross-arm ===")
        hb, hc = mans["b"]["answer_start_hist"], mans["c"]["answer_start_hist"]
        check(hb == hc, "8 arms B and C have BIT-IDENTICAL answer_start histograms "
                        f"({len(hb)} vs {len(hc)} distinct offsets)")
        check(mans["b"]["seed"] == mans["c"]["seed"], "8b same seed")
        check(mans["b"]["answer_tokens"] == mans["c"]["answer_tokens"],
              "8c same answer-token count")

    print(f"\n{'ALL PASS' if not fails else str(len(fails)) + ' FAILURE(S)'}")
    return 1 if fails else 0


if __name__ == "__main__":
    sys.exit(main())