Cion-lab commited on
Commit
d0e35c4
·
verified ·
1 Parent(s): 9119e1e

review round 3 (probe/model-card/eval): rank-visible eval failure + cross-rank peak memory + complete run_summary; card derived-and-asserted from config; harness install --no-deps and task ids confirmed; torchrun redirects 3 + tee 3

Browse files
Files changed (1) hide show
  1. build/publish_model.py +46 -16
build/publish_model.py CHANGED
@@ -13,6 +13,7 @@ import argparse
13
  import hashlib
14
  import json
15
  import os
 
16
  import struct
17
  import sys
18
  import urllib.request
@@ -60,6 +61,16 @@ def get_json(url, token=None):
60
 
61
  def card(cfg, man, aud, run, repo, files, n_params):
62
  p = cfg.get("num_hidden_layers"), cfg.get("hidden_size"), cfg.get("vocab_size")
 
 
 
 
 
 
 
 
 
 
63
  cf = man.get('contamination_filter') or {}
64
  tok_consumed = int(run.get("tokens_consumed") or 0)
65
  loss = " -> ".join(str(x) for x in (run.get("prior_loss"), run.get("final_loss")) if x is not None)
@@ -75,7 +86,7 @@ tags: [ounce100m, from-scratch, base, decoder-only, {man['tokenizer']['id'].spli
75
 
76
  # {repo.split('/')[-1]}
77
 
78
- A **{run.get('params', p[0] and '?'):,}-parameter decoder-only transformer trained from scratch on
79
  {tok_consumed:,} tokens** of `{MIX}`, in {run.get('steps_planned', '?')} optimiser steps on
80
  **2×Nvidia Tesla T4** inside Kaggle notebook sessions. It is the deliverable of a project whose whole point
81
  was to find out what a ~100 M-parameter model reaches from ~1 B tokens of a curated public mix, and to
@@ -85,8 +96,8 @@ report it honestly, including where that is near chance.
85
 
86
  | | |
87
  |---|---|
88
- | Parameters | **{n_params:,}** (verified from `model.safetensors`'s header, not asserted) |
89
- | Architecture | {p[0]} layers, hidden {p[1]}, GQA 9 Q / 3 KV heads, SwiGLU 1536, RMSNorm 1e-5, RoPE θ=10000, **tied** embeddings, vocab {p[2]:,} |
90
  | Sequence length | {run.get('seq_len')} — which is why ARC's 25-shot prompts are truncated from the left by the eval harness |
91
  | Tokens consumed | **{tok_consumed:,}** ({100.0 * tok_consumed / 1e9:.2f} % of the 1.0 B target) |
92
  | Batch | micro {run.get('micro_batch')} × accum {run.get('accum')} × 2 ranks = {run.get('micro_batch', 0) * run.get('accum', 0) * 2 * run.get('seq_len', 1024):,} tokens/step |
@@ -95,9 +106,9 @@ report it honestly, including where that is near chance.
95
  | Throughput | {run.get('tok_per_s'):,} tok/s sustained |
96
  | Attention | `{run.get('effective_attn', run.get('attn'))}` — measured faster than SDPA on Turing |
97
  | Gradient checkpointing | {run.get('grad_ckpt')} |
98
- | Wall clock | {run.get('seconds', 0) / 3600.0:.2f} h of training, split across sessions at checkpoint boundaries |
99
  | Loss | {loss} |
100
- | Validation PPL | {run.get('val_ppl')} on {MIX}'s held-out `val/` shards (22,934,043 tokens, never trained on) |
101
 
102
  ## Data
103
 
@@ -156,9 +167,15 @@ def main():
156
  ap.add_argument("--dry-run", action="store_true")
157
  a = ap.parse_args()
158
 
 
159
  import ounce100m_credentials
160
- info = ounce100m_credentials.install(verify=True)
161
  tok = os.environ["HF_TOKEN"]
 
 
 
 
 
162
  from huggingface_hub import HfApi, create_repo, snapshot_download
163
 
164
  man = get_json(f"https://huggingface.co/datasets/{MIX}/resolve/main/manifest.json")
@@ -166,7 +183,8 @@ def main():
166
 
167
  if a.verify_only:
168
  dest = "/tmp/cleanroom_" + os.path.basename(a.repo)
169
- os.environ["HF_HUB_CACHE"] = "/tmp/cleanroom_cache"
 
170
  got = snapshot_download(a.repo, local_dir=dest)
171
  print("clean-room download ->", got, flush=True)
172
  import json as _j
@@ -176,7 +194,10 @@ def main():
176
  import torch
177
  from transformers import AutoModelForCausalLM, AutoTokenizer
178
  tkn = AutoTokenizer.from_pretrained(dest)
179
- m = AutoModelForCausalLM.from_pretrained(dest, torch_dtype=torch.float16)
 
 
 
180
  assert m.num_parameters() == n_p, (m.num_parameters(), n_p)
181
  assert cfg["tie_word_embeddings"] and m.get_input_embeddings().weight.data_ptr() == \
182
  m.get_output_embeddings().weight.data_ptr(), "tied embeddings did not survive the round trip"
@@ -212,19 +233,24 @@ def main():
212
  if os.path.exists(os.path.join(src, "run_summary.json")):
213
  run = json.load(open(os.path.join(src, "run_summary.json")))
214
  n_t, n_p, dt = safetensors_counts(os.path.join(src, "model.safetensors"))
215
- if run.get("params") and run["params"] != n_p:
216
- raise SystemExit(f"run_summary claims {run['params']} params, the file has {n_p}")
 
 
 
 
 
217
  files = {f: {"sha256": sha(os.path.join(src, f)), "bytes": os.path.getsize(os.path.join(src, f))}
218
  for f in sorted(have) if f in CARD_FILES or f.endswith(".json")}
219
  print("tensors", n_t, "params", f"{n_p:,}", "dtypes", dt, flush=True)
220
  if not a.dry_run and not run:
221
  raise SystemExit("no run_summary.json next to the export: the card would be full of blanks. "
222
  "Pass --from-dir at the trainer's final/ export, or --dry-run to preview.")
223
- readme = card(cfg, man, aud, dict(run, params=n_p), a.repo, files, n_p)
224
- open(os.path.join(src, "README.md"), "w").write(readme)
225
  print("card:", f"{n_p:,} params from the safetensors header; {len(files)} files hashed", flush=True)
226
  if a.dry_run:
227
- print(readme)
228
  return
229
 
230
  api = HfApi(token=tok)
@@ -252,9 +278,13 @@ def main():
252
  R = {"repo": a.repo, "params": n_p, "tensors": n_t, "files": len(back), "missing": missing}
253
  for f in ("model.safetensors", "config.json"):
254
  try:
255
- d = urllib.request.urlopen(f"https://huggingface.co/{a.repo}/resolve/main/{f}",
256
- timeout=600).read()
257
- R[f"anon_{f}"] = {"bytes": len(d), "sha_ok": hashlib.sha256(d).hexdigest() == sha(os.path.join(src, f))}
 
 
 
 
258
  except Exception as e:
259
  R[f"anon_{f}"] = {"error": f"{type(e).__name__}: {str(e)[:120]}"}
260
  R["published_verified"] = bool(not missing and all(R.get(f"anon_{x}", {}).get("sha_ok")
 
13
  import hashlib
14
  import json
15
  import os
16
+ import shutil
17
  import struct
18
  import sys
19
  import urllib.request
 
61
 
62
  def card(cfg, man, aud, run, repo, files, n_params):
63
  p = cfg.get("num_hidden_layers"), cfg.get("hidden_size"), cfg.get("vocab_size")
64
+ # The card states the architecture; half of it was typed rather than read, so a variant export would
65
+ # have published a description of a model it is not. Derive it and refuse on drift.
66
+ frozen = {"num_hidden_layers": 22, "hidden_size": 576, "intermediate_size": 1536,
67
+ "num_attention_heads": 9, "num_key_value_heads": 3, "vocab_size": 49152,
68
+ "tie_word_embeddings": True, "rms_norm_eps": 1e-5}
69
+ drift = {k: (cfg.get(k), v) for k, v in frozen.items() if cfg.get(k) != v}
70
+ if drift:
71
+ raise SystemExit("config.json does not match the frozen architecture, so the card would be a false "
72
+ "statement about this model: " + json.dumps(drift, default=str)[:600])
73
+ rope = cfg.get("rope_theta") or (cfg.get("rope_parameters") or {}).get("rope_theta")
74
  cf = man.get('contamination_filter') or {}
75
  tok_consumed = int(run.get("tokens_consumed") or 0)
76
  loss = " -> ".join(str(x) for x in (run.get("prior_loss"), run.get("final_loss")) if x is not None)
 
86
 
87
  # {repo.split('/')[-1]}
88
 
89
+ A **{n_params:,}-parameter decoder-only transformer trained from scratch on
90
  {tok_consumed:,} tokens** of `{MIX}`, in {run.get('steps_planned', '?')} optimiser steps on
91
  **2×Nvidia Tesla T4** inside Kaggle notebook sessions. It is the deliverable of a project whose whole point
92
  was to find out what a ~100 M-parameter model reaches from ~1 B tokens of a curated public mix, and to
 
96
 
97
  | | |
98
  |---|---|
99
+ | Parameters | **{n_params:,}** (recomputed from `model.safetensors`'s own header, not asserted) |
100
+ | Architecture | {p[0]} layers, hidden {p[1]}, GQA {cfg["num_attention_heads"]} Q / {cfg["num_key_value_heads"]} KV heads, SwiGLU {cfg["intermediate_size"]}, RMSNorm {cfg["rms_norm_eps"]}, RoPE θ={rope}, **tied** embeddings, vocab {p[2]:,} |
101
  | Sequence length | {run.get('seq_len')} — which is why ARC's 25-shot prompts are truncated from the left by the eval harness |
102
  | Tokens consumed | **{tok_consumed:,}** ({100.0 * tok_consumed / 1e9:.2f} % of the 1.0 B target) |
103
  | Batch | micro {run.get('micro_batch')} × accum {run.get('accum')} × 2 ranks = {run.get('micro_batch', 0) * run.get('accum', 0) * 2 * run.get('seq_len', 1024):,} tokens/step |
 
106
  | Throughput | {run.get('tok_per_s'):,} tok/s sustained |
107
  | Attention | `{run.get('effective_attn', run.get('attn'))}` — measured faster than SDPA on Turing |
108
  | Gradient checkpointing | {run.get('grad_ckpt')} |
109
+ | Wall clock | {run.get('session_seconds', run.get('seconds', 0)) / 3600.0:.2f} h in the session that produced this export. The run is split across sessions at checkpoint boundaries, so this is **not** the total training time |
110
  | Loss | {loss} |
111
+ | Validation PPL | {run.get('val_ppl') if run.get('val_ppl') is not None else 'NOT MEASURED (' + str(run.get('val_loss_error')) + ')'} on {MIX}'s held-out `val/` shards ({man['val_tokens']:,} tokens, never trained on) |
112
 
113
  ## Data
114
 
 
167
  ap.add_argument("--dry-run", action="store_true")
168
  a = ap.parse_args()
169
 
170
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # E-028
171
  import ounce100m_credentials
172
+ ounce100m_credentials.install(verify=True)
173
  tok = os.environ["HF_TOKEN"]
174
+ # Order matters: huggingface_hub resolves its cache locations at import time, so a "clean room" that
175
+ # sets HF_HOME afterwards is testing Kaggle's warm disk instead of the Hub (review finding A6).
176
+ if a.verify_only:
177
+ os.environ["HF_HOME"] = "/tmp/cleanroom_hf"
178
+ os.environ["HF_HUB_CACHE"] = "/tmp/cleanroom_hf/hub"
179
  from huggingface_hub import HfApi, create_repo, snapshot_download
180
 
181
  man = get_json(f"https://huggingface.co/datasets/{MIX}/resolve/main/manifest.json")
 
183
 
184
  if a.verify_only:
185
  dest = "/tmp/cleanroom_" + os.path.basename(a.repo)
186
+ shutil.rmtree(dest, ignore_errors=True)
187
+ shutil.rmtree("/tmp/cleanroom_hf", ignore_errors=True)
188
  got = snapshot_download(a.repo, local_dir=dest)
189
  print("clean-room download ->", got, flush=True)
190
  import json as _j
 
194
  import torch
195
  from transformers import AutoModelForCausalLM, AutoTokenizer
196
  tkn = AutoTokenizer.from_pretrained(dest)
197
+ try: # transformers 5 renamed torch_dtype; Gate 5 is this very call
198
+ m = AutoModelForCausalLM.from_pretrained(dest, dtype=torch.float16)
199
+ except TypeError:
200
+ m = AutoModelForCausalLM.from_pretrained(dest, torch_dtype=torch.float16)
201
  assert m.num_parameters() == n_p, (m.num_parameters(), n_p)
202
  assert cfg["tie_word_embeddings"] and m.get_input_embeddings().weight.data_ptr() == \
203
  m.get_output_embeddings().weight.data_ptr(), "tied embeddings did not survive the round trip"
 
233
  if os.path.exists(os.path.join(src, "run_summary.json")):
234
  run = json.load(open(os.path.join(src, "run_summary.json")))
235
  n_t, n_p, dt = safetensors_counts(os.path.join(src, "model.safetensors"))
236
+ # `params` in run_summary.json is a dict {sum_numel, unique, trainable}; comparing that to an integer
237
+ # is always unequal and would have failed every real export (review finding A1).
238
+ claim = run.get("params_total") or run.get("params")
239
+ if isinstance(claim, dict):
240
+ claim = claim.get("sum_numel")
241
+ if claim and int(claim) != n_p:
242
+ raise SystemExit(f"run_summary claims {claim} params, the published safetensors file has {n_p}")
243
  files = {f: {"sha256": sha(os.path.join(src, f)), "bytes": os.path.getsize(os.path.join(src, f))}
244
  for f in sorted(have) if f in CARD_FILES or f.endswith(".json")}
245
  print("tensors", n_t, "params", f"{n_p:,}", "dtypes", dt, flush=True)
246
  if not a.dry_run and not run:
247
  raise SystemExit("no run_summary.json next to the export: the card would be full of blanks. "
248
  "Pass --from-dir at the trainer's final/ export, or --dry-run to preview.")
249
+ readme = card(cfg, man, aud, dict(run, params_total=n_p), a.repo, files, n_p)
250
+ open(os.path.join(src, "README.md"), "w", encoding="utf-8").write(readme)
251
  print("card:", f"{n_p:,} params from the safetensors header; {len(files)} files hashed", flush=True)
252
  if a.dry_run:
253
+ print(readme.encode("utf-8", "replace").decode("utf-8", "replace"))
254
  return
255
 
256
  api = HfApi(token=tok)
 
278
  R = {"repo": a.repo, "params": n_p, "tensors": n_t, "files": len(back), "missing": missing}
279
  for f in ("model.safetensors", "config.json"):
280
  try:
281
+ h, nbytes = hashlib.sha256(), 0 # stream it: 1.7 GB does not belong in RAM
282
+ with urllib.request.urlopen(f"https://huggingface.co/{a.repo}/resolve/main/{f}",
283
+ timeout=900) as r:
284
+ for chunk in iter(lambda: r.read(1 << 20), b""):
285
+ nbytes += len(chunk)
286
+ h.update(chunk)
287
+ R[f"anon_{f}"] = {"bytes": nbytes, "sha_ok": h.hexdigest() == sha(os.path.join(src, f))}
288
  except Exception as e:
289
  R[f"anon_{f}"] = {"error": f"{type(e).__name__}: {str(e)[:120]}"}
290
  R["published_verified"] = bool(not missing and all(R.get(f"anon_{x}", {}).get("sha_ok")