Cion-lab commited on
Commit
1f702fe
·
verified ·
1 Parent(s): 391cb47

Phase 0: mirror probe scripts + launcher pattern

Browse files
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ .DS_Store
README.md ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ounce100m-code
2
+
3
+ Canonical mirror of the scripts and configs for project `ounce100m`, so a Kaggle instance can fetch
4
+ its own source instead of having it pasted into a kernel. The constitution lives in `MASTER_PROMPT.md`
5
+ in the local workspace and is read-only.
6
+
7
+ Layout:
8
+
9
+ - `probes/` — Phase 0 platform probes and Phase 3 preflight scripts.
10
+ - `config/` — frozen architecture / mix / eval configs (Phase 1 onward).
11
+
12
+ **No credential ever appears in this repo.** Anything that needs the Hugging Face token reads it
13
+ from the environment (`HF_TOKEN`) at runtime. A launcher kernel fetches a script here and execs it:
14
+
15
+ ```python
16
+ import urllib.request
17
+ URL = "https://huggingface.co/Cion-lab/ounce100m-code/resolve/main/probes/<name>.py"
18
+ src = urllib.request.urlopen(URL, timeout=60).read().decode()
19
+ exec(compile(src, "<name>.py", "exec"), {"__name__": "__main__"})
20
+ ```
21
+
22
+ See `memory/ASSETS.md` in the local workspace for what each probe established.
probes/p0_cpu_probe.py ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Phase 0 CPU probe. Runs on a Kaggle CPU instance (zero GPU quota).
2
+ # Purpose: establish empirically what the main run will depend on --
3
+ # in-job internet, pip behavior, image contents, disk/memory limits,
4
+ # Hub file download, and the artifact-retrieval path. Writes one artifact
5
+ # to /kaggle/working so retrieval can be verified by hash.
6
+ # This file contains NO credentials and must never gain any.
7
+
8
+ import hashlib
9
+ import json
10
+ import os
11
+ import platform
12
+ import shutil
13
+ import socket
14
+ import subprocess
15
+ import sys
16
+ import time
17
+
18
+ R = {}
19
+ T_START = time.time()
20
+ MONO0 = time.monotonic()
21
+
22
+
23
+ def step(name, fn):
24
+ a, b = time.monotonic(), time.time()
25
+ try:
26
+ R[name] = fn()
27
+ except Exception as e:
28
+ R[name] = {"error": f"{type(e).__name__}: {e}"}
29
+ R.setdefault("_timing", {})[name] = round(time.monotonic() - a, 2)
30
+ R["_timing"][name + "_epoch"] = round(b, 0)
31
+
32
+
33
+ def run(cmd, timeout=90):
34
+ p = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
35
+ return {"rc": p.returncode, "out": p.stdout[-3000:], "err": p.stderr[-1500:]}
36
+
37
+
38
+ def get_http(url, timeout=25, nbytes=400):
39
+ import urllib.request
40
+
41
+ t = time.monotonic()
42
+ req = urllib.request.Request(url, headers={"User-Agent": "ounce100m-probe"})
43
+ with urllib.request.urlopen(req, timeout=timeout) as r:
44
+ body = r.read(nbytes)
45
+ return {
46
+ "status": r.status,
47
+ "ms": round((time.monotonic() - t) * 1000),
48
+ "bytes_head": len(body),
49
+ "final_url": r.url[:160],
50
+ }
51
+
52
+
53
+ def pkg_versions():
54
+ from importlib.metadata import PackageNotFoundError, version
55
+
56
+ want = [
57
+ "torch", "torchvision", "transformers", "tokenizers", "datasets", "evaluate",
58
+ "accelerate", "trl", "numpy", "pandas", "pyarrow", "sentencepiece", "tiktoken",
59
+ "protobuf", "einops", "flash-attn", "xformers", "deepspeed", "bitsandbytes",
60
+ "huggingface-hub", "safetensors", "timm", "scipy", "sentence-transformers",
61
+ "wandb", "liger-kernel", "causal-conv1d", "mamba-ssm", "unsloth", "peft",
62
+ ]
63
+ out = {}
64
+ for n in want:
65
+ try:
66
+ out[n] = version(n)
67
+ except PackageNotFoundError:
68
+ pass
69
+ return out
70
+
71
+
72
+ def cuda_info():
73
+ import torch
74
+
75
+ d = {
76
+ "torch": torch.__version__,
77
+ "cuda_built": torch.version.cuda,
78
+ "cudnn": torch.backends.cudnn.version(),
79
+ "mkl": torch.cuda.get_mkl_version() if hasattr(torch.cuda, "get_mkl_version") else None,
80
+ "allow_tf32_default": torch.backends.cuda.matmul.allow_tf32,
81
+ "arch_list": list(torch.cuda.get_arch_list()),
82
+ "device_count": torch.cuda.device_count(),
83
+ "cpu_threads": torch.get_num_threads(),
84
+ }
85
+ if torch.cuda.is_available():
86
+ props = []
87
+ for i in range(torch.cuda.device_count()):
88
+ p = torch.cuda.get_device_properties(i)
89
+ props.append({
90
+ "i": i, "name": p.name, "cc": f"{p.major}.{p.minor}",
91
+ "sm": p.multi_processor_count, "mem_GB": round(p.total_memory / 1024**3, 2),
92
+ "bf16_capable_cc": p.major >= 8,
93
+ })
94
+ d["devices"] = props
95
+ return d
96
+
97
+
98
+ def precision_probe():
99
+ # Turing T4 = cc 7.5: fp16 tensor cores yes, bf16 no. Prove it rather than recall it.
100
+ import torch
101
+
102
+ out = {}
103
+ dev = "cuda" if torch.cuda.is_available() else "cpu"
104
+ for dt in (torch.float16, torch.bfloat16, torch.float32):
105
+ try:
106
+ x = torch.randn(1024, 1024, device=dev, dtype=dt)
107
+ y = (x @ x).float()
108
+ torch.cuda.synchronize() if dev == "cuda" else None
109
+ out[str(dt)] = {"ok": True, "checksum": round(float(y.abs().mean()), 4)}
110
+ except Exception as e:
111
+ out[str(dt)] = {"ok": False, "err": f"{type(e).__name__}: {e}"[:160]}
112
+ return out
113
+
114
+
115
+ def net_checks():
116
+ out = {}
117
+ for host in ["huggingface.co", "cdn-lfs.huggingface.co", "pypi.org", "files.pythonhosted.org"]:
118
+ try:
119
+ infos = socket.getaddrinfo(host, 443)
120
+ out[host] = sorted({i[4][0] for i in infos})[:3]
121
+ except Exception as e:
122
+ out[host] = f"DNSFAIL {type(e).__name__}: {e}"
123
+ return out
124
+
125
+
126
+ def hub_download():
127
+ url = "https://huggingface.co/datasets/wikimedia/wikipedia/resolve/main/README.md"
128
+ d = get_http(url)
129
+ try:
130
+ import datasets
131
+
132
+ t = time.monotonic()
133
+ ds = datasets.load_dataset("wikimedia/wikipedia", "20231101.en",
134
+ split="train", streaming=True)
135
+ row = next(iter(ds))
136
+ d["streaming"] = {
137
+ "ok": True, "ms_to_first_row": round((time.monotonic() - t) * 1000),
138
+ "keys": list(row.keys()), "title": str(row.get("title"))[:60],
139
+ }
140
+ except Exception as e:
141
+ d["streaming"] = {"ok": False, "err": f"{type(e).__name__}: {e}"[:300]}
142
+ return d
143
+
144
+
145
+ def pip_probe():
146
+ # xxhash: small, not in the Kaggle image, useful later for shard hashing.
147
+ t = time.monotonic()
148
+ r = run("python -m pip install --quiet --disable-pip-version-check --no-input xxhash", timeout=240)
149
+ r["install_seconds"] = round(time.monotonic() - t, 1)
150
+ try:
151
+ import xxhash
152
+
153
+ r["import_ok"] = xxhash.xxh64(b"ounce100m").hexdigest()
154
+ except Exception as e:
155
+ r["import_ok"] = f"FAIL {e}"
156
+ return r
157
+
158
+
159
+ def fs_probe():
160
+ out = {}
161
+ for p in ["/kaggle/working", "/kaggle/input", "/tmp", "/", "/dev/shm"]:
162
+ try:
163
+ u = shutil.disk_usage(p)
164
+ out[p] = {"total_GB": round(u.total / 1024**3, 1), "free_GB": round(u.free / 1024**3, 1)}
165
+ except Exception as e:
166
+ out[p] = f"ERR {e}"
167
+ with open("/proc/meminfo") as f:
168
+ mm = {l.split(":")[0]: l.split()[0] for l in f if ":" in l}
169
+ out["mem_MemTotal_MB"] = int(mm.get("MemTotal", 0)) // 1024
170
+ out["mem_MemAvailable_MB"] = int(mm.get("MemAvailable", 0)) // 1024
171
+ out["cgroup_v2_max"] = None
172
+ for lp in ["/sys/fs/cgroup/memory.max", "/sys/fs/cgroup/memory/memory.limit_in_bytes"]:
173
+ if os.path.exists(lp):
174
+ out["cgroup_v2_max"] = open(lp).read().strip()
175
+ break
176
+ out["cpu_count_os"] = os.cpu_count()
177
+ try:
178
+ out["cpu_affinity"] = len(os.sched_getaffinity(0))
179
+ except Exception:
180
+ pass
181
+ out["nproc_smi"] = run("nvidia-smi -L", timeout=30)
182
+ out["df_h"] = run("df -h | head -20", timeout=30).get("out", "")
183
+ return out
184
+
185
+
186
+ def artifact():
187
+ payload = json.dumps({"probe": "p0", "t": T_START, "tokens": 12345}).encode()
188
+ path = "ounce100m_p0_artifact.json"
189
+ with open(path, "wb") as f:
190
+ f.write(payload)
191
+ return {
192
+ "path": path, "abs": os.path.abspath(path), "size": len(payload),
193
+ "sha256": hashlib.sha256(payload).hexdigest(),
194
+ "cwd": os.getcwd(),
195
+ "working_listing": sorted(os.listdir("/kaggle/working")),
196
+ }
197
+
198
+
199
+ step("env", lambda: {
200
+ "python": sys.version.split()[0], "platform": platform.platform(),
201
+ "hostname": socket.gethostname(), "user": os.environ.get("USER"),
202
+ "cwd": os.getcwd(), "start_epoch": T_START,
203
+ })
204
+ step("env_var_names", lambda: sorted(k for k in os.environ if any(
205
+ s in k.upper() for s in ["HF_", "KAGGLE_", "HUGGING", "TOKEN", "SECRET", "CUDA", "TORCH"])))
206
+ step("packages", pkg_versions)
207
+ step("net_dns", net_checks)
208
+ step("http", lambda: {
209
+ "hub_api": get_http("https://huggingface.co/api/models?limit=1"),
210
+ "pypi_simple": get_http("https://pypi.org/simple/xxhash/"),
211
+ "google": get_http("https://www.google.com/generate_204"),
212
+ })
213
+ step("hub_download", hub_download)
214
+ step("pip_install", pip_probe)
215
+ step("filesystem", fs_probe)
216
+ step("artifact", artifact)
217
+ if os.environ.get("CUDA_VISIBLE_DEVICES") != "None":
218
+ step("cuda", cuda_info)
219
+ step("precision", precision_probe)
220
+ else:
221
+ step("cuda", lambda: {"skipped": "CUDA_VISIBLE_DEVICES=None -> CPU shape"})
222
+
223
+ R["_summary"] = {
224
+ "total_seconds": round(time.monotonic() - MONO0, 1),
225
+ "end_epoch": time.time(),
226
+ "errors_seen": sorted(k for k, v in R.items() if isinstance(v, dict) and "error" in v),
227
+ }
228
+ print("PROBE_JSON_BEGIN")
229
+ print(json.dumps(R, indent=1, default=str))
230
+ print("PROBE_JSON_END")
probes/p0_gpu_probe.py ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Phase 0 GPU probe. Runs on the 2x Tesla T4 shape -- the only accelerator §2 permits.
2
+ # Purpose: prove the GPU path end to end and settle facts the main run's schedule depends on:
3
+ # (1) the real accelerator(s) handed out, (2) Turing capability envelope (fp16 yes / bf16 no),
4
+ # (3) whether NCCL all-reduce actually works across both cards, (4) torch.compile and
5
+ # flash-attention viability on cc 7.5, (5) a first tokens/sec reading, and
6
+ # (6) wall-clock, so the quota delta around this job reveals the burn rate per hour.
7
+ # No credentials here. Nothing is installed at runtime beyond what the image already has.
8
+
9
+ import hashlib
10
+ import json
11
+ import os
12
+ import platform
13
+ import socket
14
+ import subprocess
15
+ import sys
16
+ import time
17
+
18
+ R = {}
19
+ MONO0 = time.monotonic()
20
+ EPOCH0 = time.time()
21
+
22
+
23
+ def step(name, fn):
24
+ a = time.monotonic()
25
+ try:
26
+ R[name] = fn()
27
+ except Exception as e:
28
+ R[name] = {"error": f"{type(e).__name__}: {e}"}
29
+ R.setdefault("_timing", {})[name] = round(time.monotonic() - a, 2)
30
+
31
+
32
+ def run(cmd, timeout=180):
33
+ p = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
34
+ return {"rc": p.returncode, "out": p.stdout[-2500:], "err": p.stderr[-1500:]}
35
+
36
+
37
+ def gpu_identity():
38
+ import shutil
39
+
40
+ d = {"CUDA_VISIBLE_DEVICES": os.environ.get("CUDA_VISIBLE_DEVICES"),
41
+ "KAGGLE_DOCKER_IMAGE": os.environ.get("KAGGLE_DOCKER_IMAGE"),
42
+ "python": sys.version.split()[0]}
43
+ for p in ["/kaggle/working", "/tmp"]:
44
+ u = shutil.disk_usage(p)
45
+ d[f"free_GB_{p.strip('/')}"] = round(u.free / 1024**3, 1)
46
+ d["smi_L"] = run("nvidia-smi -L", 60)
47
+ d["smi_query"] = run(
48
+ 'nvidia-smi --query-gpu=index,name,driver_version,memory.total,compute_cap,'
49
+ 'multiprocessor_count --format=csv', 60)
50
+ return d
51
+
52
+
53
+ def torch_env():
54
+ import torch
55
+
56
+ out = {
57
+ "torch": torch.__version__, "cuda_built": torch.version.cuda,
58
+ "cudnn": torch.backends.cudnn.version(),
59
+ "nccl_built": torch.cuda.nccl.version() if hasattr(torch.cuda, "nccl") else None,
60
+ "arch_list": list(torch.cuda.get_arch_list()),
61
+ "device_count": torch.cuda.device_count(),
62
+ "tf32_default": torch.backends.cuda.matmul.allow_tf32,
63
+ }
64
+ devs = []
65
+ for i in range(torch.cuda.device_count()):
66
+ p = torch.cuda.get_device_properties(i)
67
+ devs.append({"i": i, "name": p.name, "cc": f"{p.major}.{p.minor}", "sm": p.multi_processor_count,
68
+ "mem_GB": round(p.total_memory / 1024**3, 2)})
69
+ out["devices"] = devs
70
+ out["all_turing_T4"] = all(d["cc"] == "7.5" and "T4" in d["name"] for d in devs)
71
+ out["exactly_two"] = len(devs) == 2
72
+ for extra in ("triton", "flash_attn", "xformers", "transformers", "datasets", "accelerate", "trl"):
73
+ try:
74
+ from importlib.metadata import version
75
+
76
+ out[f"pkg_{extra}"] = version(extra if extra != "flash_attn" else "flash-attn")
77
+ except Exception:
78
+ out[f"pkg_{extra}"] = "absent"
79
+ return out
80
+
81
+
82
+ def precision_and_gemm():
83
+ import torch
84
+
85
+ out = {}
86
+ for dt in (torch.float32, torch.float16, torch.bfloat16):
87
+ for tf32 in ([False] if dt != torch.float32 else [False, True]):
88
+ torch.backends.cuda.matmul.allow_tf32 = tf32
89
+ key = f"{str(dt).replace('torch.', '')}{'_tf32' if tf32 else ''}"
90
+ try:
91
+ t0 = time.monotonic()
92
+ a = torch.randn(2048, 2048, device="cuda", dtype=dt)
93
+ b = torch.randn(2048, 2048, device="cuda", dtype=dt)
94
+ for _ in range(10):
95
+ c = a @ b
96
+ torch.cuda.synchronize()
97
+ dt_s = (time.monotonic() - t0) / 10
98
+ out[key] = {"ok": True, "ms_per_gemm": round(dt_s * 1000, 2),
99
+ "tflops": round(2 * 2048**3 / dt_s / 1e12, 2),
100
+ "checksum": round(float(c.float().abs().mean()), 4)}
101
+ except Exception as e:
102
+ out[key] = {"ok": False, "err": f"{type(e).__name__}: {e}"[:180]}
103
+ torch.backends.cuda.matmul.allow_tf32 = False
104
+ return out
105
+
106
+
107
+ def sdpa_backends():
108
+ import torch
109
+
110
+ out = {}
111
+ for name in ("flash", "mem_efficient", "math"):
112
+ try:
113
+ fn = getattr(torch.backends.cuda, f"{name}_sdpa_enabled")
114
+ out[name + "_enabled"] = fn()
115
+ except Exception as e:
116
+ out[name + "_enabled"] = str(e)[:80]
117
+ from torch.nn.attention import SDPBackend, sdpa_kernel
118
+ q = torch.randn(2, 4, 128, 64, device="cuda", dtype=torch.float16)
119
+ for name in ("FLASH_ATTENTION", "EFFICIENT_ATTENTION", "MATH"):
120
+ try:
121
+ be = getattr(SDPBackend, name)
122
+ with sdpa_kernel(be):
123
+ o = torch.nn.functional.scaled_dot_product_attention(q, q, q)
124
+ torch.cuda.synchronize()
125
+ out[f"sdpa_{name}"] = {"ok": True, "shape": list(o.shape)}
126
+ except Exception as e:
127
+ out[f"sdpa_{name}"] = {"ok": False, "err": f"{type(e).__name__}: {e}"[:180]}
128
+ return out
129
+
130
+
131
+ def ddp_smoke():
132
+ # The main run is 2 GPUs. If NCCL across these two T4s does not work, every plan that
133
+ # assumes data-parallel throughput is wrong -- prove it here on a toy, not at launch.
134
+ worker = r'''
135
+ import os, json, time, torch, torch.distributed as dist
136
+ dist.init_process_group("nccl")
137
+ r = dist.get_rank()
138
+ torch.cuda.set_device(r)
139
+ x = torch.ones(1024, 1024, device=f"cuda:{r}") * (r + 1)
140
+ t0 = time.monotonic()
141
+ for _ in range(20):
142
+ y = x.clone()
143
+ dist.all_reduce(y)
144
+ torch.cuda.synchronize()
145
+ dt = (time.monotonic() - t0) / 20
146
+ print("WORKER_JSON " + json.dumps({"rank": r, "world": dist.get_world_size(),
147
+ "allreduce_mean": float(y.mean()), "ms_per_allreduce": round(dt*1000, 3),
148
+ "backend": dist.get_backend(), "device": torch.cuda.get_device_name(r)}), flush=True)
149
+ dist.destroy_process_group()
150
+ '''
151
+ with open("ddp_worker.py", "w") as f:
152
+ f.write(worker)
153
+ t = time.monotonic()
154
+ p = subprocess.run(
155
+ [sys.executable, "-m", "torch.distributed.run", "--nproc_per_node=2", "--master_port=29511",
156
+ "ddp_worker.py"], capture_output=True, text=True, timeout=300,
157
+ env={**os.environ, "OMP_NUM_THREADS": "1"})
158
+ lines = [l for l in (p.stdout + p.stderr).splitlines() if l.startswith("WORKER_JSON ")]
159
+ return {"rc": p.returncode, "seconds": round(time.monotonic() - t, 1),
160
+ "workers": [json.loads(l[len("WORKER_JSON "):]) for l in lines],
161
+ "err_tail": p.stderr[-1200:]}
162
+
163
+
164
+ def tiny_lm_steps():
165
+ # A few hundred steps of a real 10M-param causal LM, so tokens/sec is a measurement and not
166
+ # an extrapolation. Not a model-building exercise: config is deliberately small.
167
+ import torch
168
+ from transformers import LlamaConfig, LlamaForCausalLM
169
+
170
+ cfg = LlamaConfig(hidden_size=384, num_hidden_layers=6, num_attention_heads=6,
171
+ num_key_value_heads=6, intermediate_size=1024, vocab_size=32000,
172
+ max_position_embeddings=1024)
173
+ model = LlamaForCausalLM(cfg).cuda().to(torch.float16)
174
+ n = sum(p.numel() for p in model.parameters())
175
+ opt = torch.optim.AdamW(model.parameters(), lr=3e-4)
176
+ B, L = 8, 1024
177
+ ids = torch.randint(0, 32000, (B, L), device="cuda")
178
+ tg = torch.randint(0, 32000, (B, L), device="cuda")
179
+ model.train()
180
+ losses = []
181
+ warm = 5
182
+ t = time.monotonic()
183
+ for s in range(warm + 60):
184
+ opt.zero_grad(set_to_none=True)
185
+ with torch.autocast("cuda", dtype=torch.float16):
186
+ out = model(input_ids=ids, labels=tg)
187
+ loss = out.loss
188
+ loss.backward()
189
+ opt.step()
190
+ if s >= warm:
191
+ losses.append(round(float(loss), 4))
192
+ torch.cuda.synchronize()
193
+ el = time.monotonic() - t
194
+ tps = (60 * B * L) / el
195
+ return {"params": n, "loss_first": losses[0], "loss_last": losses[-1],
196
+ "losses_sampled": losses[::15], "seconds": round(el, 1),
197
+ "tokens_per_sec": round(tps), "peak_mem_GB": round(torch.cuda.max_memory_allocated() / 1024**3, 2),
198
+ "note": "single GPU, fp16 autocast, 8x1024 batch"}
199
+
200
+
201
+ def compile_smoke():
202
+ import torch
203
+
204
+ try:
205
+ m = torch.nn.Sequential(torch.nn.Linear(512, 512), torch.nn.GELU(), torch.nn.Linear(512, 512)).cuda()
206
+ cm = torch.compile(m)
207
+ x = torch.randn(256, 512, device="cuda")
208
+ t0 = time.monotonic()
209
+ for _ in range(20):
210
+ y = cm(x)
211
+ torch.cuda.synchronize()
212
+ compile_s = time.monotonic() - t0
213
+ return {"ok": True, "warm_plus_20_steps_s": round(compile_s, 2),
214
+ "out_shape": list(y.shape)}
215
+ except Exception as e:
216
+ return {"ok": False, "err": f"{type(e).__name__}: {e}"[:300]}
217
+
218
+
219
+ def artifact():
220
+ payload = json.dumps({"probe": "p0-gpu", "epoch0": EPOCH0, "host": socket.gethostname()}).encode()
221
+ open("ounce100m_p0_gpu_artifact.json", "wb").write(payload)
222
+ return {"size": len(payload), "sha256": hashlib.sha256(payload).hexdigest()}
223
+
224
+
225
+ step("gpu_identity", gpu_identity)
226
+ step("torch_env", torch_env)
227
+ step("precision_gemm", precision_and_gemm)
228
+ step("sdpa", sdpa_backends)
229
+ step("ddp_nccl", ddp_smoke)
230
+ step("compile", compile_smoke)
231
+ step("tiny_lm", tiny_lm_steps)
232
+ step("smi_after", lambda: run("nvidia-smi --query-gpu=index,name,utilization.gpu,memory.used "
233
+ "--format=csv", 60))
234
+ step("artifact", artifact)
235
+
236
+ R["_timing"]["total"] = round(time.monotonic() - MONO0, 1)
237
+ R["_session"] = {"epoch_start": EPOCH0, "epoch_end": time.time(),
238
+ "wall_seconds": round(time.time() - EPOCH0, 1),
239
+ "hostname": socket.gethostname(), "platform": platform.platform()}
240
+ print("PROBE_JSON_BEGIN")
241
+ print(json.dumps(R, indent=1, default=str))
242
+ print("PROBE_JSON_END")
probes/p0b_cpu_fs.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Phase 0 CPU probe B. Follows up the one gap in p0_cpu_probe.py: its /proc/meminfo parser
2
+ # crashed (E-003), so instance disk and RAM -- the numbers §3.13 depends on -- were never read.
3
+ # Also answers a question the first probe raised: the CPU image ships torch 2.10.0+cpu, so
4
+ # which image/torch a GPU session actually gets has to be observed from inside one.
5
+ # Zero GPU quota. No credentials.
6
+
7
+ import json
8
+ import os
9
+ import shutil
10
+ import subprocess
11
+ import sys
12
+ import time
13
+
14
+ t0 = time.time()
15
+
16
+
17
+ def meminfo():
18
+ out = {}
19
+ with open("/proc/meminfo") as f:
20
+ for line in f:
21
+ if ":" not in line:
22
+ continue
23
+ k, rest = line.split(":", 1)
24
+ parts = rest.split()
25
+ if parts:
26
+ out[k] = int(parts[0]) // 1024
27
+ return {k: out[k] for k in ("MemTotal", "MemAvailable", "MemFree", "SwapTotal", "Shmem") if k in out}
28
+
29
+
30
+ def disk():
31
+ out = {}
32
+ for p in ["/kaggle/working", "/kaggle/input", "/tmp", "/.cache", "/root/.cache", "/dev/shm", "/usr"]:
33
+ try:
34
+ u = shutil.disk_usage(p)
35
+ out[p] = {"total_GB": round(u.total / 1024**3, 1), "used_GB": round(u.used / 1024**3, 1),
36
+ "free_GB": round(u.free / 1024**3, 1)}
37
+ except Exception as e:
38
+ out[p] = f"missing ({type(e).__name__})"
39
+ return out
40
+
41
+
42
+ def cgroup():
43
+ out = {}
44
+ for name, paths in {
45
+ "mem_max": ["/sys/fs/cgroup/memory.max", "/sys/fs/cgroup/memory/memory.limit_in_bytes"],
46
+ "cpu_max": ["/sys/fs/cgroup/cpu.max", "/sys/fs/cgroup/cpu/cpu.cfs_quota_us"],
47
+ }.items():
48
+ for p in paths:
49
+ if os.path.exists(p):
50
+ out[name] = open(p).read().strip()
51
+ out[name + "_path"] = p
52
+ break
53
+ else:
54
+ out[name] = "not found"
55
+ return out
56
+
57
+
58
+ R = {
59
+ "shape": {"CUDA_VISIBLE_DEVICES_present": "CUDA_VISIBLE_DEVICES" in os.environ,
60
+ "CUDA_VISIBLE_DEVICES": os.environ.get("CUDA_VISIBLE_DEVICES"),
61
+ "KAGGLE_DOCKER_IMAGE": os.environ.get("KAGGLE_DOCKER_IMAGE"),
62
+ "KAGGLE_KERNEL_RUN_TYPE": os.environ.get("KAGGLE_KERNEL_RUN_TYPE"),
63
+ "KAGGLE_CONTAINER_NAME": os.environ.get("KAGGLE_CONTAINER_NAME"),
64
+ "KAGGLE_GCP_ZONE": os.environ.get("KAGGLE_GCP_ZONE"),
65
+ "KAGGLE_KERNEL_INTEGRATIONS": os.environ.get("KAGGLE_KERNEL_INTEGRATIONS")},
66
+ "cpu": {"os_cpu_count": os.cpu_count(), "affinity": len(os.sched_getaffinity(0)),
67
+ "loadavg": os.getloadavg(),
68
+ "model": [l for l in open("/proc/cpuinfo") if "model name" in l][:1]},
69
+ "mem_MB": meminfo(),
70
+ "cgroup": cgroup(),
71
+ "disk": disk(),
72
+ "df_h": subprocess.run("df -h", shell=True, capture_output=True, text=True).stdout,
73
+ "du_cache": subprocess.run("du -sh /root/.cache/* 2>/dev/null | head -10", shell=True,
74
+ capture_output=True, text=True).stdout,
75
+ }
76
+ import socket
77
+
78
+ dns = {}
79
+ for h in ["cdn-lfs.huggingface.co", "cdn-lfs-us-1.huggingface.co", "huggingface.co", "xethub.huggingface.co"]:
80
+ try:
81
+ dns[h] = sorted({i[4][0] for i in socket.getaddrinfo(h, 443)})[:2]
82
+ except Exception as e:
83
+ dns[h] = f"DNSFAIL {type(e).__name__}"
84
+ R["dns"] = dns
85
+ R["_seconds"] = round(time.time() - t0, 1)
86
+ print("PROBE_JSON_BEGIN")
87
+ print(json.dumps(R, indent=1, default=str))
88
+ print("PROBE_JSON_END")
probes/p0c_fp16_ddp_probe.py ADDED
@@ -0,0 +1,275 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Phase 0 GPU probe C -- corrects two failures in probe B and settles the schedule's biggest risk.
2
+ #
3
+ # Why this exists:
4
+ # * probe B's tiny_lm ran the weights in pure fp16 with no GradScaler and the loss came back NaN,
5
+ # so its tokens/sec figure measured a broken recipe. The main run cannot discover that at 100M.
6
+ # * probe B's DDP step never ran: torchrun's two WORKER_JSON lines broke the parser, so whether
7
+ # NCCL works across these two specific T4s is STILL unproven, and the whole stack choice in
8
+ # Phase 1 assumes it does.
9
+ # * probe B's throughput was an 11.6M-weights model (35M params once a 50k-row embedding is
10
+ # counted), not the ~95M we will actually train, so it is the wrong order of magnitude to plan on.
11
+ #
12
+ # Nothing here is a model-building exercise: one config near the likely main-run shape, a few
13
+ # hundred steps, a hard self-imposed deadline so this cannot run away.
14
+ # Readable canonical copy: HF repo Cion-lab/ounce100m-code, path probes/p0c_fp16_ddp_probe.py
15
+ # This file contains NO credentials and must never gain any.
16
+
17
+ import json
18
+ import os
19
+ import socket
20
+ import subprocess
21
+ import sys
22
+ import time
23
+
24
+ R = {}
25
+ MONO0 = time.monotonic()
26
+ EPOCH0 = time.time()
27
+ DEADLINE_S = 420 # ~7 min of work; aborts remaining measurements rather than burning quota
28
+
29
+ import torch # noqa: E402
30
+
31
+
32
+ def remaining():
33
+ return DEADLINE_S - (time.monotonic() - MONO0)
34
+
35
+
36
+ R["setup"] = {
37
+ "torch": torch.__version__, "cuda": torch.version.cuda,
38
+ "devices": [(torch.cuda.get_device_properties(i).name,
39
+ f"{torch.cuda.get_device_properties(i).major}."
40
+ f"{torch.cuda.get_device_properties(i).minor}")
41
+ for i in range(torch.cuda.device_count())],
42
+ "host": socket.gethostname(),
43
+ "KAGGLE_DOCKER_IMAGE": os.environ.get("KAGGLE_DOCKER_IMAGE"),
44
+ }
45
+
46
+ # --- the config: ~95M params with a 50k vocab and tied embeddings, i.e. main-run shaped ------
47
+ H, NL, NH, NKV, FF, VOCAB, SEQ = 768, 12, 12, 3, 2048, 50257, 1024
48
+
49
+ from transformers import LlamaConfig, LlamaForCausalLM # noqa: E402
50
+
51
+ CFG = LlamaConfig(
52
+ hidden_size=H, num_hidden_layers=NL, num_attention_heads=NH, num_key_value_heads=NKV,
53
+ intermediate_size=FF, vocab_size=VOCAB, max_position_embeddings=SEQ, tie_word_embeddings=True,
54
+ )
55
+
56
+
57
+ def make_model(attn="sdpa"):
58
+ m = LlamaForCausalLM(CFG, attn_implementation=attn)
59
+ return m.cuda()
60
+
61
+
62
+ def n_params(m):
63
+ return sum(p.numel() for p in m.parameters())
64
+
65
+
66
+ def step_run(model, mk_batch, steps, scaler=None, accum=1, deadline=None):
67
+ """Returns (tokens_per_sec, first_loss, last_loss, nan_count, peak_GB)."""
68
+ opt = torch.optim.AdamW(model.parameters(), lr=3e-4)
69
+ model.train()
70
+ losses, nans = [], 0
71
+ B = mk_batch().shape[0]
72
+ torch.cuda.synchronize()
73
+ t0 = time.monotonic()
74
+ for s in range(steps):
75
+ for micro in range(accum):
76
+ ids = mk_batch()
77
+ with torch.autocast("cuda", dtype=torch.float16):
78
+ loss = model(input_ids=ids, labels=ids).loss / accum
79
+ if scaler is not None:
80
+ scaler.scale(loss).backward()
81
+ else:
82
+ loss.backward()
83
+ if scaler is not None:
84
+ scaler.unscale_(opt)
85
+ torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
86
+ scaler.step(opt)
87
+ scaler.update()
88
+ else:
89
+ torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
90
+ opt.step()
91
+ opt.zero_grad(set_to_none=True)
92
+ lv = float(loss.detach())
93
+ losses.append(lv)
94
+ nans += int(lv != lv)
95
+ if deadline and time.monotonic() - MONO0 > deadline:
96
+ break
97
+ torch.cuda.synchronize()
98
+ el = time.monotonic() - t0
99
+ done = max(1, len(losses))
100
+ return {
101
+ "tokens_per_sec": round(done * B * SEQ / el),
102
+ "steps_done": len(losses),
103
+ "loss_first3": [round(v, 3) for v in losses[:3]],
104
+ "loss_last3": [round(v, 3) for v in losses[-3:]],
105
+ "nan_count": nans,
106
+ "peak_GB": round(torch.cuda.max_memory_allocated() / 1024**3, 2),
107
+ "seconds": round(el, 1),
108
+ }
109
+
110
+
111
+ def batch_fn(micro_b):
112
+ def f():
113
+ return torch.randint(0, VOCAB, (micro_b, SEQ), device="cuda")
114
+ return f
115
+
116
+
117
+ results = {}
118
+
119
+ # 1. the recipe probe that failed before: fp32 master weights + fp16 autocast + GradScaler
120
+ m = make_model()
121
+ results["model_shape"] = {
122
+ "params_total": n_params(m),
123
+ "params_embedding": int(CFG.vocab_size * H * (2 if not CFG.tie_word_embeddings else 1)),
124
+ "config": {"H": H, "layers": NL, "heads": NH, "kv_heads": NKV, "ff": FF,
125
+ "vocab": VOCAB, "seq": SEQ},
126
+ }
127
+ del m
128
+ torch.cuda.empty_cache()
129
+
130
+ try:
131
+ m = make_model()
132
+ opt_probe = torch.optim.AdamW(m.parameters(), lr=3e-4)
133
+ sc = torch.amp.GradScaler("cuda")
134
+ b = torch.randint(0, VOCAB, (2, SEQ), device="cuda")
135
+ ok_fp16 = None
136
+ for _ in range(4):
137
+ opt_probe.zero_grad(set_to_none=True)
138
+ with torch.autocast("cuda", dtype=torch.float16):
139
+ loss = m(input_ids=b, labels=b).loss
140
+ sc.scale(loss).backward()
141
+ sc.step(opt_probe)
142
+ sc.update()
143
+ ok_fp16 = float(loss.detach())
144
+ results["fp16_with_scaler_sanity"] = {"loss": round(ok_fp16, 3), "finite": ok_fp16 == ok_fp16}
145
+ del m, opt_probe, sc
146
+ torch.cuda.empty_cache()
147
+ except Exception as e:
148
+ results["fp16_with_scaler_sanity"] = {"error": f"{type(e).__name__}: {e}"[:200]}
149
+
150
+ # 2. bf16 on Turing: expected to be unsupported or emulated. Record, do not assume.
151
+ try:
152
+ m = make_model()
153
+ b = torch.randint(0, VOCAB, (2, SEQ), device="cuda")
154
+ t = time.monotonic()
155
+ with torch.autocast("cuda", dtype=torch.bfloat16):
156
+ loss = m(input_ids=b, labels=b).loss
157
+ torch.cuda.synchronize()
158
+ results["bf16_autocast"] = {"ok": True, "loss": round(float(loss.detach()), 3),
159
+ "first_fwd_s": round(time.monotonic() - t, 2)}
160
+ del m
161
+ torch.cuda.empty_cache()
162
+ except Exception as e:
163
+ results["bf16_autocast"] = {"ok": False, "err": f"{type(e).__name__}: {e}"[:200]}
164
+
165
+ # 3. throughput sweep, single GPU, correct recipe, decreasing-deadline order
166
+ m = make_model()
167
+ scaler = torch.amp.GradScaler("cuda")
168
+ for micro_b in (4, 8, 12):
169
+ if remaining() < 90:
170
+ results["sweep_stopped_early_at"] = micro_b
171
+ break
172
+ try:
173
+ results[f"1gpu_bs{micro_b}"] = step_run(
174
+ m, batch_fn(micro_b), steps=40, scaler=scaler, deadline=MONO0 + DEADLINE_S - 60)
175
+ except Exception as e:
176
+ results[f"1gpu_bs{micro_b}"] = {"error": f"{type(e).__name__}: {e}"[:200]}
177
+ torch.cuda.empty_cache()
178
+ del m, scaler
179
+ torch.cuda.empty_cache()
180
+ R["single_gpu"] = results
181
+
182
+ # 4. attention/compile variants at one batch size, to see what actually pays on cc 7.5
183
+ variants = {}
184
+ for name, kwargs in [("eager", {"attn": "eager"}), ("sdpa", {"attn": "sdpa"})]:
185
+ if remaining() < 90:
186
+ break
187
+ try:
188
+ m = make_model(kwargs["attn"])
189
+ sc = torch.amp.GradScaler("cuda")
190
+ variants[name] = step_run(m, batch_fn(4), steps=30, scaler=sc,
191
+ deadline=MONO0 + DEADLINE_S - 30)
192
+ del m, sc
193
+ torch.cuda.empty_cache()
194
+ except Exception as e:
195
+ variants[name] = {"error": f"{type(e).__name__}: {e}"[:200]}
196
+ R["attn_variants"] = variants
197
+
198
+
199
+ # 5. DDP across the two cards, via torchrun, parsed line-by-line so one stray line cannot
200
+ # swallow the whole measurement (the mistake that voided probe B's attempt).
201
+ DDP_SRC = r'''
202
+ import json, os, time, torch, torch.distributed as dist
203
+ from torch.nn.parallel import DistributedDataParallel as DDP
204
+ from transformers import LlamaConfig, LlamaForCausalLM
205
+
206
+ dist.init_process_group("nccl")
207
+ rank = dist.get_rank()
208
+ world = dist.get_world_size()
209
+ torch.cuda.set_device(rank)
210
+
211
+ H, NL, NH, NKV, FF, VOCAB, SEQ = 768, 12, 12, 3, 2048, 50257, 1024
212
+ cfg = LlamaConfig(hidden_size=H, num_hidden_layers=NL, num_attention_heads=NH,
213
+ num_key_value_heads=NKV, intermediate_size=FF, vocab_size=VOCAB,
214
+ max_position_embeddings=SEQ, tie_word_embeddings=True)
215
+ torch.manual_seed(1234)
216
+ model = LlamaForCausalLM(cfg, attn_implementation="sdpa").cuda().to(torch.float32)
217
+ model = DDP(model, device_ids=[rank])
218
+ opt = torch.optim.AdamW(model.parameters(), lr=3e-4)
219
+ scaler = torch.amp.GradScaler("cuda")
220
+
221
+ x = torch.randint(0, VOCAB, (4, SEQ), device="cuda")
222
+ losses = []
223
+ t0 = time.monotonic()
224
+ for s in range(30):
225
+ opt.zero_grad(set_to_none=True)
226
+ with torch.autocast("cuda", dtype=torch.float16):
227
+ loss = model(input_ids=x, labels=x).loss
228
+ scaler.scale(loss).backward()
229
+ scaler.step(opt)
230
+ scaler.update()
231
+ losses.append(float(loss.detach()))
232
+ torch.cuda.synchronize()
233
+ el = time.monotonic() - t0
234
+ out = {"rank": rank, "world": world, "backend": dist.get_backend(),
235
+ "device": torch.cuda.get_device_name(rank),
236
+ "params_per_rank": sum(p.numel() for p in model.parameters()),
237
+ "loss_first": round(losses[0], 3), "loss_last": round(losses[-1], 3),
238
+ "all_finite": all(v == v for v in losses),
239
+ "tokens_per_sec_per_rank": round(30 * 4 * SEQ / el),
240
+ "peak_GB": round(torch.cuda.max_memory_allocated() / 1024**3, 2)}
241
+ print("RESULT_JSON:" + json.dumps(out), flush=True)
242
+ dist.barrier()
243
+ dist.destroy_process_group()
244
+ '''
245
+
246
+ ddp = {}
247
+ if remaining() > 120:
248
+ with open("ddp_worker_c.py", "w") as f:
249
+ f.write(DDP_SRC)
250
+ t = time.monotonic()
251
+ p = subprocess.run(
252
+ [sys.executable, "-m", "torch.distributed.run", "--nproc_per_node=2",
253
+ "--master_port=29533", "ddp_worker_c.py"],
254
+ capture_output=True, text=True, timeout=300,
255
+ env={**os.environ, "OMP_NUM_THREADS": "2", "TORCH_CPP_LOG_LEVEL": "ERROR"})
256
+ workers = []
257
+ for line in (p.stdout + "\n" + p.stderr).splitlines():
258
+ marker = "RESULT_JSON:"
259
+ if marker in line:
260
+ try:
261
+ workers.append(json.loads(line.split(marker, 1)[1].strip()))
262
+ except Exception as e:
263
+ workers.append({"parse_error": str(e)[:120], "raw": line[:200]})
264
+ ddp = {"rc": p.returncode, "seconds": round(time.monotonic() - t, 1), "workers": workers,
265
+ "nccl_ok": (len(workers) == 2 and all(w.get("all_finite") for w in workers)
266
+ and all(w.get("world") == 2 for w in workers)),
267
+ "stderr_tail": p.stderr[-900:]}
268
+ else:
269
+ ddp = {"skipped": "deadline"}
270
+ R["ddp"] = ddp
271
+ R["_session"] = {"epoch_start": EPOCH0, "epoch_end": time.time(),
272
+ "wall_seconds": round(time.time() - EPOCH0, 1), "deadline_s": DEADLINE_S}
273
+ print("PROBE_JSON_BEGIN")
274
+ print(json.dumps(R, indent=1, default=str))
275
+ print("PROBE_JSON_END")