Phase 0: mirror probe scripts + launcher pattern
Browse files- .gitignore +3 -0
- README.md +22 -0
- probes/p0_cpu_probe.py +230 -0
- probes/p0_gpu_probe.py +242 -0
- probes/p0b_cpu_fs.py +88 -0
- probes/p0c_fp16_ddp_probe.py +275 -0
.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")
|