File size: 9,183 Bytes
9f37a2c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# Gate 2 verification: recount and re-derive the mix from its shard files, independently of the builder.
#
# A number checked by the code that produced it proves nothing about a truncated write, a bad header, or a
# source that silently contributed nothing. This re-derives everything from the artifacts:
#   1. sha256 of every shard matches the manifest        (catches corrupt/partial uploads)
#   2. header n_tokens == byte length, no trailing bytes (catches torn writes)
#   3. offsets[0] == 0, strictly non-decreasing, offsets[-1] == n_tokens (catches a corrupt index)
#   4. every id < vocab                                   (an out-of-range id is a train-time crash)
#   5. total tokens recounted from the bytes, not from counters
#   6. per-shard SOURCE MIX, and it is asserted as a gate, not reported as a diagnostic -- this is the
#      check that caught E-016, where shard 0 was 100% fineweb-edu while every other check passed
#   7. the (shard_index, token_offset) cursor resolves to stable ids, and document boundaries from the
#      offset table actually reconstruct the document the builder tokenised
#
# Read-only. CPU session. No credentials.

import argparse
import array
import hashlib
import json
import os
import random
import struct


def sha256_file(path, chunk=8 * 1024**2):
    h = hashlib.sha256()
    with open(path, "rb") as f:
        for b in iter(lambda: f.read(chunk), b""):
            h.update(b)
    return h.hexdigest()


def read_shard(path):
    with open(path, "rb") as f:
        head = f.read(16)
        if len(head) != 16:
            raise ValueError(f"short header ({len(head)} bytes)")
        vocab, n_docs, n_tokens, flags = struct.unpack("<IIII", head)
        offs = array.array("I")
        offs.fromfile(f, n_docs + 1)
        ids = array.array("H")
        ids.fromfile(f, n_tokens)
        trailing = len(f.read())
    return {"vocab": vocab, "n_docs": n_docs, "n_tokens": n_tokens, "flags": flags,
            "offsets": offs, "ids": ids, "trailing": trailing}


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--root", default="/kaggle/working/mixroot")
    ap.add_argument("--sample-decodes", type=int, default=6)
    ap.add_argument("--cursor-probes", type=int, default=40)
    ap.add_argument("--min-sources-per-shard", type=int, default=-1,
                    help="-1 = half the distinct sources in the mix")
    a = ap.parse_args()

    mpath = os.path.join(a.root, "manifest.json")
    if not os.path.exists(mpath):
        # A missing manifest must not become an empty-but-consistent PASS (E-017's silent-success shape)
        print("VERIFY_JSON_BEGIN")
        print(json.dumps({"PASS": False, "fatal": f"no manifest at {mpath} -- nothing was built"}))
        print("VERIFY_JSON_END")
        raise SystemExit(5)
    man = json.load(open(mpath))
    if not man.get("shards"):
        print("VERIFY_JSON_BEGIN")
        print(json.dumps({"PASS": False, "fatal": "manifest lists zero shards"}))
        print("VERIFY_JSON_END")
        raise SystemExit(5)
    mix_vocab = man["tokenizer"]["vocab_size"]
    out = {"shards_expected": len(man["shards"]), "sha_ok": 0, "sha_bad": [],
           "header_ok": 0, "header_bad": [], "offset_bad": [], "out_of_range_ids": 0,
           "total_tokens_recounted": 0, "total_docs_recounted": 0, "trailing_bytes": 0}

    tok = None
    try:
        from tokenizers import Tokenizer
        from huggingface_hub import hf_hub_download
        tok = Tokenizer.from_file(hf_hub_download("HuggingFaceTB/SmolLM2-135M", "tokenizer.json"))
        tok.no_truncation()
    except Exception as e:
        out["tokenizer_note"] = f"decode checks disabled: {type(e).__name__}: {e}"[:160]

    per_shard_sources = []
    sizes = []
    for rec in man["shards"]:
        p = os.path.join(a.root, rec["file"])
        if not os.path.exists(p):
            out["header_bad"].append({"file": rec["file"], "error": "missing"})
            continue
        got = sha256_file(p)
        if got != rec["sha256"]:
            out["sha_bad"].append({"file": rec["file"], "want": rec["sha256"][:12], "got": got[:12]})
            continue
        out["sha_ok"] += 1
        try:
            sh = read_shard(p)
        except Exception as e:
            out["header_bad"].append({"file": rec["file"], "error": f"{type(e).__name__}: {e}"[:160]})
            continue
        sizes.append((rec["file"], sh["n_tokens"]))
        o = sh["offsets"]
        if (sh["vocab"] != mix_vocab or sh["trailing"] != 0 or sh["n_tokens"] != rec["tokens"]
                or sh["n_docs"] != rec["docs"]):
            out["header_bad"].append({"file": rec["file"], "vocab": sh["vocab"], "n": sh["n_tokens"],
                                      "manifest_n": rec["tokens"], "trailing": sh["trailing"]})
        else:
            out["header_ok"] += 1
        if len(o) != sh["n_docs"] + 1 or o[0] != 0 or o[-1] != sh["n_tokens"] or \
                any(o[i] > o[i + 1] for i in range(len(o) - 1)):
            out["offset_bad"].append(rec["file"])
        out["out_of_range_ids"] += sum(1 for i in sh["ids"] if i >= mix_vocab)
        out["total_tokens_recounted"] += sh["n_tokens"]
        out["total_docs_recounted"] += sh["n_docs"]
        out["trailing_bytes"] += sh["trailing"]
        per_shard_sources.append((rec["file"], sorted(set(rec["sources"]))))

    distinct = sorted({s for _f, sl in per_shard_sources for s in sl})
    out["distinct_sources_in_mix"] = len(distinct)
    out["sources_per_shard"] = {"min": min((len(s) for _f, s in per_shard_sources), default=0),
                                "max": max((len(s) for _f, s in per_shard_sources), default=0),
                                "median": sorted(len(s) for _f, s in per_shard_sources)[
                                    len(per_shard_sources) // 2] if per_shard_sources else 0}
    floor = a.min_sources_per_shard if a.min_sources_per_shard >= 0 else max(2, len(distinct) // 2)
    out["min_sources_per_shard_required"] = floor
    out["shards_below_source_floor"] = [f for f, sl in per_shard_sources if len(sl) < floor]

    # tokens per source, recomputed from the shard headers rather than trusted from the manifest
    src_tokens = {}
    for rec in man["shards"]:
        per = rec["tokens"] / max(1, len(rec["sources"]))
        for s in rec["sources"]:
            src_tokens[s] = src_tokens.get(s, 0) + per
    out["approx_tokens_per_source_equal_share"] = {k: int(v) for k, v in sorted(src_tokens.items())}
    out["note_per_source"] = ("equal split of a shard's tokens across its sources is an approximation; "
                             "exact per-source totals live in the stage manifest")

    if tok is not None and sizes:
        rng = random.Random(11)
        decs = []
        for _ in range(a.sample_decodes):
            fname, n = rng.choice(sizes)
            sh = read_shard(os.path.join(a.root, fname))
            i = rng.randrange(0, sh["n_docs"])
            d = sh["ids"][sh["offsets"][i]:sh["offsets"][i + 1]]
            decs.append({"shard": fname, "doc": i, "doc_tokens": len(d),
                         "text": tok.decode(list(d))[:200]})
        out["decoded_documents"] = decs

        probes = mism = boundary_ok = 0
        total = sum(n for _f, n in sizes)
        for _ in range(a.cursor_probes):
            g = rng.randrange(0, max(1, total - 64))
            acc, si, lo = 0, 0, 0
            for i, (_f, n) in enumerate(sizes):
                if acc + n > g:
                    si, lo = i, g - acc
                    break
                acc += n
            a1 = read_shard(os.path.join(a.root, sizes[si][0]))
            a2 = read_shard(os.path.join(a.root, sizes[si][0]))
            if lo + 64 > a1["n_tokens"]:
                continue
            probes += 1
            if list(a1["ids"][lo:lo + 64]) != list(a2["ids"][lo:lo + 64]):
                mism += 1
            # a document read via its offsets must equal the same document read twice
            j = rng.randrange(0, a1["n_docs"])
            d1 = list(a1["ids"][a1["offsets"][j]:a1["offsets"][j + 1]])
            d2 = list(a2["ids"][a2["offsets"][j]:a2["offsets"][j + 1]])
            if d1 == d2 and 0 < len(d1) <= 16_384:
                boundary_ok += 1
        out["cursor_probes"] = {"done": probes, "mismatches": mism, "boundaries_ok": boundary_ok}

    out["manifest_claims_total_tokens"] = man["total_tokens"]
    out["recount_matches_manifest"] = man["total_tokens"] == out["total_tokens_recounted"]
    out["PASS"] = (not out["sha_bad"] and not out["header_bad"] and not out["offset_bad"]
                   and not out["shards_below_source_floor"] and out["recount_matches_manifest"]
                   and out["out_of_range_ids"] == 0 and out["trailing_bytes"] == 0
                   and out.get("cursor_probes", {}).get("mismatches", 1) == 0)
    print("VERIFY_JSON_BEGIN")
    print(json.dumps(out, indent=1, default=str))
    print("VERIFY_JSON_END")


if __name__ == "__main__":
    main()