train: --benchmark N runs the same step with no Trainer, to attribute the P2 throughput gap
Browse files- train/train_ounce100m.py +72 -0
train/train_ounce100m.py
CHANGED
|
@@ -160,6 +160,68 @@ class HubPush(TrainerCallback):
|
|
| 160 |
return control
|
| 161 |
|
| 162 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
def main():
|
| 164 |
ap = argparse.ArgumentParser()
|
| 165 |
ap.add_argument("--root", default="/kaggle/working/mixroot")
|
|
@@ -187,6 +249,8 @@ def main():
|
|
| 187 |
ap.add_argument("--push-every-steps", type=int, default=0, help="0 = only at 10% boundaries")
|
| 188 |
ap.add_argument("--prune", action="store_true", help="delete local checkpoint after hub verify")
|
| 189 |
ap.add_argument("--dry-run", action="store_true", help="build everything, train 0 steps")
|
|
|
|
|
|
|
| 190 |
args = ap.parse_args()
|
| 191 |
|
| 192 |
rank = int(os.environ.get("RANK", "0"))
|
|
@@ -284,6 +348,14 @@ def main():
|
|
| 284 |
if not (90_000_000 <= pc["sum_numel"] <= 110_000_000) and not args.layers and not args.hidden:
|
| 285 |
raise SystemExit(f"parameter count {pc['sum_numel']:,} is outside §2's 90-110M budget")
|
| 286 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
# 10 checkpoints per run is §2's cadence; --push-every-steps overrides for the preflight.
|
| 288 |
ckpt_every = args.push_every_steps or max(1, steps_planned // 10)
|
| 289 |
|
|
|
|
| 160 |
return control
|
| 161 |
|
| 162 |
|
| 163 |
+
def run_benchmark(args, model, dataset, steps, world, seqs_per_step):
|
| 164 |
+
"""The same data path, optimizer and fp16 recipe as the real run, with no Trainer in the loop.
|
| 165 |
+
|
| 166 |
+
Why this exists: P2 measured 5,105 tok/s for 22L/seq-1024 where probe p0c measured 11,062 tok/s for a
|
| 167 |
+
near-identical shape with a hand-rolled loop. The gap is either (a) the attention kernel, (b) per-step
|
| 168 |
+
framework overhead, or (c) a measurement artefact, and which one it is decides whether the main run
|
| 169 |
+
needs 27 h or 68 h. Guessing wrong in either direction is expensive, so measure both paths.
|
| 170 |
+
"""
|
| 171 |
+
import torch.distributed as dist
|
| 172 |
+
from torch.nn.parallel import DistributedDataParallel as DDP
|
| 173 |
+
from torch.utils.data import DataLoader
|
| 174 |
+
|
| 175 |
+
local_rank = int(os.environ.get("LOCAL_RANK", "0"))
|
| 176 |
+
device = torch.device("cuda", local_rank)
|
| 177 |
+
torch.cuda.set_device(device)
|
| 178 |
+
if world > 1 and not dist.is_initialized():
|
| 179 |
+
dist.init_process_group("nccl")
|
| 180 |
+
model = model.to(device)
|
| 181 |
+
net = DDP(model, device_ids=[local_rank]) if world > 1 else model
|
| 182 |
+
|
| 183 |
+
opt = torch.optim.AdamW(net.parameters(), lr=args.lr, betas=(0.9, 0.95),
|
| 184 |
+
weight_decay=0.1, eps=1e-8)
|
| 185 |
+
try:
|
| 186 |
+
scaler = torch.amp.GradScaler("cuda", enabled=True) # torch >= 2.4
|
| 187 |
+
except TypeError:
|
| 188 |
+
scaler = torch.cuda.amp.GradScaler(enabled=True) # older signature
|
| 189 |
+
# Same reader, same shuffle, no accumulation: the comparison against the Trainer cells is then
|
| 190 |
+
# per-step cost with the framework removed, and nothing else differs.
|
| 191 |
+
dl = DataLoader(dataset, batch_size=args.micro_batch, shuffle=False, num_workers=2,
|
| 192 |
+
pin_memory=False, drop_last=True)
|
| 193 |
+
t0 = time.time()
|
| 194 |
+
toks = 0
|
| 195 |
+
for i, batch in enumerate(dl):
|
| 196 |
+
if i >= steps:
|
| 197 |
+
break
|
| 198 |
+
batch = {k: v.to(device, non_blocking=True) for k, v in batch.items()}
|
| 199 |
+
opt.zero_grad(set_to_none=True)
|
| 200 |
+
with torch.autocast("cuda", dtype=torch.float16):
|
| 201 |
+
out = net(**batch)
|
| 202 |
+
scaler.scale(out.loss).backward()
|
| 203 |
+
scaler.unscale_(opt)
|
| 204 |
+
torch.nn.utils.clip_grad_norm_(net.parameters(), 1.0)
|
| 205 |
+
scaler.step(opt)
|
| 206 |
+
scaler.update()
|
| 207 |
+
toks += batch["input_ids"].numel()
|
| 208 |
+
if i == 0:
|
| 209 |
+
torch.cuda.synchronize()
|
| 210 |
+
t0 = time.time() # exclude CUDA context + first-trace cost from the rate
|
| 211 |
+
toks = 0
|
| 212 |
+
sec = time.time() - t0
|
| 213 |
+
res = {"benchmark_steps": i + 1 if steps else 0, "tok_per_s": round(toks / max(1e-6, sec)),
|
| 214 |
+
"seconds": round(sec, 1), "tokens": toks,
|
| 215 |
+
"peak_gpu_gb": round(torch.cuda.max_memory_allocated() / 1e9, 2),
|
| 216 |
+
"world": world, "seq_len": args.seq_len, "micro_batch": args.micro_batch,
|
| 217 |
+
"attn": args.attn, "params": sum(p.numel() for p in model.parameters())}
|
| 218 |
+
print("BENCH_JSON " + json.dumps(res, sort_keys=True), flush=True)
|
| 219 |
+
if world > 1:
|
| 220 |
+
dist.barrier()
|
| 221 |
+
dist.destroy_process_group()
|
| 222 |
+
return res
|
| 223 |
+
|
| 224 |
+
|
| 225 |
def main():
|
| 226 |
ap = argparse.ArgumentParser()
|
| 227 |
ap.add_argument("--root", default="/kaggle/working/mixroot")
|
|
|
|
| 249 |
ap.add_argument("--push-every-steps", type=int, default=0, help="0 = only at 10% boundaries")
|
| 250 |
ap.add_argument("--prune", action="store_true", help="delete local checkpoint after hub verify")
|
| 251 |
ap.add_argument("--dry-run", action="store_true", help="build everything, train 0 steps")
|
| 252 |
+
ap.add_argument("--benchmark", type=int, default=0,
|
| 253 |
+
help="N steps with no Trainer in the loop (isolates framework per-step cost)")
|
| 254 |
args = ap.parse_args()
|
| 255 |
|
| 256 |
rank = int(os.environ.get("RANK", "0"))
|
|
|
|
| 348 |
if not (90_000_000 <= pc["sum_numel"] <= 110_000_000) and not args.layers and not args.hidden:
|
| 349 |
raise SystemExit(f"parameter count {pc['sum_numel']:,} is outside §2's 90-110M budget")
|
| 350 |
|
| 351 |
+
if args.benchmark:
|
| 352 |
+
# Both ranks iterate the same samples here (no DistributedSampler): this path measures step cost,
|
| 353 |
+
# not a training run, and it must never be used to produce a checkpoint.
|
| 354 |
+
say(f"benchmark mode: {args.benchmark} steps, no Trainer, micro={args.micro_batch} "
|
| 355 |
+
f"seq={args.seq_len} attn={args.attn} world={world}")
|
| 356 |
+
run_benchmark(args, model, train_ds, args.benchmark, world, seqs_per_step)
|
| 357 |
+
return
|
| 358 |
+
|
| 359 |
# 10 checkpoints per run is §2's cadence; --push-every-steps overrides for the preflight.
|
| 360 |
ckpt_every = args.push_every_steps or max(1, steps_planned // 10)
|
| 361 |
|