File size: 7,974 Bytes
fc350c9 | 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 | # Phase 2 step 1: inventory the mix's sources with our own measurements, not their cards.
#
# Why before building. docs/02-mix-plan.md §4 records that two candidate sources disagree with
# themselves (cosmopedia-v2 card 17.8 B vs ~31.7 B implied by stored columns; finephrase's
# `token_count` describes the *source* document, not the rewrite). The mix's token arithmetic therefore
# cannot be inherited -- it needs tokens/document measured with the tokenizer we actually froze, from the
# rows we will actually read. This job establishes, per source:
# * the repo+config resolves at all (a 404 here is far cheaper now than mid-build),
# * the text column exists and under that name,
# * mean characters and mean tokens per document under the SmolLM2 tokenizer,
# * therefore how many documents each target token count costs,
# * and whether the stream is English in practice.
#
# Quota-free by rule (§3.7): this is data work, so it runs on a Kaggle CPU instance.
# Credential comes from the private store (D-006); nothing here prints a token.
import json
import os
import time
R = {}
START = time.monotonic()
BUDGET_S = 1200
ROWS_PER_SOURCE = 1500
# (repo, config, split, text column we expect, target tokens in the mix, why it is in the mix)
SOURCES = [
("HuggingFaceFW/fineweb-edu", "sample/10BT", "train", "text", 300e6, "scored general English"),
("HuggingFaceFW/finepdfs-edu", "eng_Latn", "train", "text", 150e6, "long-form educational PDFs"),
("HuggingFaceTB/cosmopedia", "stanford", "train", "text", 60e6, "textbook expository"),
("HuggingFaceTB/cosmopedia", "openstax", "train", "text", 30e6, "textbook science"),
("HuggingFaceTB/cosmopedia", "khanacademy", "train", "text", 10e6, "textbook worked steps"),
("HuggingFaceTB/cosmopedia", "auto_math_text", "train", "text", 30e6, "synthetic math text"),
("HuggingFaceTB/cosmopedia", "wikihow", "train", "text", 30e6, "procedural how-to"),
("HuggingFaceFW/finewiki", "data/enwiki", "train", "rewritten", 80e6, "rewritten wiki prose; NOTE column name is a guess"),
("omarkamali/wikipedia-monthly", "20250702.en", "train", "text", 60e6, "current encyclopedic"),
("HuggingFaceTB/finemath", "finemath4plus", "train", "text", 130e6, "math, decontaminated"),
("open-web-math/open-web-math", "default", "train", "text", 70e6, "forum/webbook math"),
("HuggingFaceFW/finephrase", "tutorial", "train", "completion", 60e6, "stepwise tutorial register; column is a guess"),
("HuggingFaceFW/finephrase", "faq", "train", "completion", 30e6, "FAQ register"),
("HuggingFaceFW/finephrase", "table", "train", "completion", 30e6, "tabular->prose"),
("HuggingFaceCode/stack-v3-train", "python", "train", "content", 60e6, "code; column and config names both guesses"),
("SimpleStories/SimpleStories", "default", "train", "text", 40e6, "long-range simple narrative"),
("common-pile/arxiv_abstracts", "default", "train", "raw_content", 20e6, "CC0 scientific abstracts"),
("common-pile/libretexts", "default", "train", "raw_content", 10e6, "OER textbooks"),
]
def guard(name, fn):
try:
R[name] = fn()
except Exception as e:
R[name] = {"error": f"{type(e).__name__}: {e}"[:260]}
def remaining():
return BUDGET_S - (time.monotonic() - START)
# ---- the tokenizer everything downstream must agree with
import ounce100m_credentials # noqa: E402
guard("credentials", lambda: ounce100m_credentials.install(verify=True))
from tokenizers import Tokenizer # noqa: E402
from huggingface_hub import hf_hub_download # noqa: E402
def load_tok():
path = hf_hub_download("HuggingFaceTB/SmolLM2-135M", "tokenizer.json")
tk = Tokenizer.from_file(path)
tk.no_truncation()
vocab = tk.get_vocab_size()
probe = ("The patient was given an intravenous dose because the oral route could not achieve "
"sufficient bioavailability, and the nurse monitored the infusion rate.")
ids = tk.encode(probe, add_special_tokens=False).ids
return {"path": path, "vocab_size": vocab, "roundtrip_ok": tk.decode(ids).split()[0] == "The",
"probe_tokens": len(ids), "probe_chars_per_token": round(len(probe) / len(ids), 2),
"target_vocab_49152": vocab == 49152}
guard("tokenizer", load_tok)
_tok_path = os.environ.get("SMOL_TOK") or (R.get("tokenizer", {}) or {}).get("path")
def tk_of():
t = _TOK[0]
return t
_TOK = [None]
if _tok_path:
_TOK[0] = Tokenizer.from_file(_tok_path)
_TOK[0].no_truncation()
def pick_text(row, expected):
"""Return (column_used, text). Cards lie; discover the real column and say which one we used."""
if expected in row and isinstance(row.get(expected), str):
return expected, row[expected]
for k, v in row.items():
if isinstance(v, str) and len(v) > 120:
return k, v
for k, v in row.items():
if isinstance(v, str) and v:
return k, v
return None, ""
def inventory(repo, config, split, expect_col, target_tokens, role):
import datasets
ds = datasets.load_dataset(repo, config, split=split, streaming=True)
n, chars, tok_n, cols, samples, lang_flags = 0, 0, 0, None, [], 0
col_used = {}
t0 = time.monotonic()
texts_for_tok = []
for row in ds:
if n >= ROWS_PER_SOURCE or time.monotonic() - t0 > 180:
break
if cols is None:
cols = sorted(row.keys())
c, txt = pick_text(row, expect_col)
col_used[c] = col_used.get(c, 0) + 1
n += 1
chars += len(txt)
if len(texts_for_tok) < 400:
texts_for_tok.append(txt[:8000])
low = txt[:400].lower()
if sum(w in low for w in (" the ", " and ", " of ", " to ", " that ")) < 2:
lang_flags += 1
el = time.monotonic() - t0
toks = sum(len(_TOK[0].encode(t, add_special_tokens=False).ids) for t in texts_for_tok)
tpd = toks / max(1, len(texts_for_tok))
cpd = chars / max(1, n)
return {
"config": config, "split": split, "role": role, "rows_sampled": n, "seconds": round(el, 1),
"columns_seen": cols, "expected_text_col": expect_col,
"col_used": sorted(col_used, key=lambda k: -col_used[k])[:3],
"mean_chars_per_doc_all": round(cpd, 0),
"mean_tokens_per_doc_sampled": round(tpd, 1),
"chars_per_token": round(sum(len(t) for t in texts_for_tok) / max(1, toks), 2),
"docs_needed_for_target": int(target_tokens / max(1.0, tpd)),
"target_tokens": int(target_tokens),
"possibly_non_english_rows": lang_flags,
"est_GB_of_target": round(target_tokens / max(1.0, tpd) * (cpd + 900) / 1024**3, 2),
"example_head": texts_for_tok[0][:120] if texts_for_tok else None,
}
inv = {}
for repo, config, split, expect_col, target, role in SOURCES:
key = f"{repo}::{config}"
if remaining() < 60:
inv[key] = {"skipped": "budget"}
continue
if _TOK[0] is None:
inv[key] = {"skipped": "no tokenizer"}
continue
try:
inv[key] = inventory(repo, config, split, expect_col, target, role)
except Exception as e:
inv[key] = {"error": f"{type(e).__name__}: {e}"[:240]}
R["inventory"] = inv
# ---- what we can conclude about the mix arithmetic
ok = {k: v for k, v in inv.items() if "mean_tokens_per_doc_sampled" in v}
R["mix_rollup"] = {
"sources_ok": len(ok), "sources_failed_or_skipped": len(inv) - len(ok),
"total_target_tokens": int(sum(v["target_tokens"] for v in ok.values())),
"total_docs_needed": int(sum(v["docs_needed_for_target"] for v in ok.values())),
"total_est_GB": round(sum(v["est_GB_of_target"] for v in ok.values()), 2),
"failed": sorted(k for k, v in inv.items() if "mean_tokens_per_doc_sampled" not in v),
}
R["_seconds_used"] = round(time.monotonic() - START, 1)
R["_budget_remaining"] = round(remaining(), 1)
print("PROBE_JSON_BEGIN")
print(json.dumps(R, indent=1, default=str))
print("PROBE_JSON_END")
|