Spaces:
Running
Running
Commit ·
e5ce61e
1
Parent(s): 074aaf8
perf(sub_zero): kill AtP per-pair thrash (autograd.grad + GPU reduction)
Browse filesRoot cause of 51s/pair: the backward hook did g.detach().float().cpu() on
every scored weight, forcing up to ~96 CUDA syncs + ~10-22GB PCIe transfer
per pair and serializing the backward.
Fix:
- torch.autograd.grad over exactly the scored weights (no hooks, one pass)
- grad->SV reduction on GPU; only a tiny [rank] vector crosses to CPU
- drop gradient_checkpointing_enable() (scoped grad fixed the OOM; checkpointing
was doubling backward compute for no memory benefit on 48GB/80GB)
Expected: ~0.2-2s/pair on A100 vs 51s/pair. Resume/OOM-skip/fingerprint tests pass.
Co-Authored-By: Claude <noreply@anthropic.com>
- deploy_to_hf_space.py +2 -1
- qwip_atlas/sub_zero_surgery.py +45 -58
deploy_to_hf_space.py
CHANGED
|
@@ -41,7 +41,8 @@ def main() -> None:
|
|
| 41 |
repo_type="space",
|
| 42 |
folder_path=str(args.local_dir),
|
| 43 |
path_in_repo="",
|
| 44 |
-
ignore_patterns=["outputs/*", "atlas/*", ".venv/*", "__pycache__/*", "*.pyc", ".DS_Store"
|
|
|
|
| 45 |
)
|
| 46 |
print("[hf] upload complete")
|
| 47 |
|
|
|
|
| 41 |
repo_type="space",
|
| 42 |
folder_path=str(args.local_dir),
|
| 43 |
path_in_repo="",
|
| 44 |
+
ignore_patterns=["outputs/*", "atlas/*", ".venv/*", "__pycache__/*", "*.pyc", ".DS_Store",
|
| 45 |
+
"*.tmp", "*.npz", "*.npy", "*.sqlite", "sub_zero_ckpt/*", "l*_census_raw*"],
|
| 46 |
)
|
| 47 |
print("[hf] upload complete")
|
| 48 |
|
qwip_atlas/sub_zero_surgery.py
CHANGED
|
@@ -589,40 +589,44 @@ def _capture_atp_gradients(
|
|
| 589 |
"data": atp_accum}, tmp)
|
| 590 |
tmp.replace(ckpt_path) # atomic; a kill mid-write can't corrupt it
|
| 591 |
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 596 |
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
def _mk(li=li, pname=pname, w=pmod.weight):
|
| 604 |
-
def _hook(g):
|
| 605 |
-
param_grads[(li, pname)] = g.detach().float().cpu()
|
| 606 |
-
hooks.append(w.register_hook(_hook))
|
| 607 |
-
_mk()
|
| 608 |
|
|
|
|
|
|
|
| 609 |
enc = tokenizer(corp_prompts[idx], return_tensors="pt", truncation=True, max_length=max_length)
|
| 610 |
-
enc = {k: v.to(device) for k, v in enc.items()}
|
| 611 |
-
model.zero_grad()
|
| 612 |
try:
|
| 613 |
out = model(**enc, use_cache=False)
|
| 614 |
logits = out.logits[0, :-1, :]
|
| 615 |
targets = enc["input_ids"][0, 1:]
|
| 616 |
loss = F.cross_entropy(logits, targets)
|
| 617 |
-
|
| 618 |
except torch.cuda.OutOfMemoryError:
|
| 619 |
# A single pathological pair must not wall the whole stage. Free
|
| 620 |
# what we can, drop this pair's partial state, advance, and flush
|
| 621 |
# so a re-run does not retry the same OOMing pair forever.
|
| 622 |
-
|
| 623 |
-
h.remove()
|
| 624 |
-
param_grads.clear()
|
| 625 |
-
enc.clear()
|
| 626 |
if torch.cuda.is_available():
|
| 627 |
torch.cuda.empty_cache()
|
| 628 |
gc.collect()
|
|
@@ -630,33 +634,25 @@ def _capture_atp_gradients(
|
|
| 630 |
_flush(idx + 1)
|
| 631 |
continue
|
| 632 |
|
| 633 |
-
for
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
|
|
|
|
|
|
| 638 |
continue
|
| 639 |
-
for pname in proj_svd[li]:
|
| 640 |
-
g_w = param_grads.get((li, pname))
|
| 641 |
-
if g_w is None:
|
| 642 |
-
continue
|
| 643 |
-
|
| 644 |
-
u, s, vh = proj_svd[li][pname]
|
| 645 |
-
c_act = corp_proj_acts[li].get(pname)
|
| 646 |
-
a_act = auth_proj_acts[li].get(pname)
|
| 647 |
-
|
| 648 |
-
if c_act is None or a_act is None or c_act.shape[-1] != vh.shape[1]:
|
| 649 |
-
continue
|
| 650 |
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
|
| 654 |
|
| 655 |
-
|
| 656 |
-
|
|
|
|
| 657 |
|
| 658 |
# Explicit cleanup to prevent CUDA memory fragmentation across pairs.
|
| 659 |
-
del loss, out, logits, targets, enc,
|
| 660 |
if torch.cuda.is_available():
|
| 661 |
torch.cuda.empty_cache()
|
| 662 |
if idx % 50 == 0:
|
|
@@ -1467,8 +1463,11 @@ def build_brain_atlas(
|
|
| 1467 |
# requires_grad on all ~8B params allocates a .grad buffer for every
|
| 1468 |
# parameter during backward, which OOMs a 48GB card already holding a 16GB
|
| 1469 |
# model; scoped grad keeps peak memory to the scored MLP projections.
|
| 1470 |
-
#
|
| 1471 |
-
#
|
|
|
|
|
|
|
|
|
|
| 1472 |
scored_weights = []
|
| 1473 |
for li, layer in enumerate(layers):
|
| 1474 |
if li not in proj_svd:
|
|
@@ -1478,13 +1477,6 @@ def build_brain_atlas(
|
|
| 1478 |
pmod.weight.requires_grad_(True)
|
| 1479 |
scored_weights.append(pmod.weight)
|
| 1480 |
|
| 1481 |
-
model.train()
|
| 1482 |
-
try:
|
| 1483 |
-
model.gradient_checkpointing_enable()
|
| 1484 |
-
except Exception as _e:
|
| 1485 |
-
print(f"[sub_zero_surgery] gradient checkpointing unavailable "
|
| 1486 |
-
f"({_e.__class__.__name__}); proceeding without")
|
| 1487 |
-
|
| 1488 |
try:
|
| 1489 |
atp_scores = _capture_atp_gradients(
|
| 1490 |
model, tokenizer, corp[:32], auth[:32], layers,
|
|
@@ -1492,13 +1484,8 @@ def build_brain_atlas(
|
|
| 1492 |
ckpt_dir=ckpt_dir, fingerprint=fp_atp, ckpt_name="stage3_atp",
|
| 1493 |
)
|
| 1494 |
finally:
|
| 1495 |
-
try:
|
| 1496 |
-
model.gradient_checkpointing_disable()
|
| 1497 |
-
except Exception:
|
| 1498 |
-
pass
|
| 1499 |
for w in scored_weights:
|
| 1500 |
w.requires_grad_(False)
|
| 1501 |
-
model.eval()
|
| 1502 |
for p in model.parameters():
|
| 1503 |
p.requires_grad_(False)
|
| 1504 |
print(" AtP complete")
|
|
|
|
| 589 |
"data": atp_accum}, tmp)
|
| 590 |
tmp.replace(ckpt_path) # atomic; a kill mid-write can't corrupt it
|
| 591 |
|
| 592 |
+
# Build the flat list of scored (li, pname, weight) ONCE. Using
|
| 593 |
+
# torch.autograd.grad over exactly these leaves (instead of a full
|
| 594 |
+
# loss.backward() + a register_hook per weight) is the key perf fix: the
|
| 595 |
+
# old hook did g.detach().float().cpu() on every scored weight, which
|
| 596 |
+
# forced up to ~96 individual GPU->CPU syncs per pair and serialised the
|
| 597 |
+
# whole backward (~51 s/pair on a 6000 Ada). Now the grads stay on GPU and
|
| 598 |
+
# only one tiny [rank]-sized vector crosses to CPU per projection.
|
| 599 |
+
scored_params: List[Tuple[int, str, torch.nn.Parameter]] = []
|
| 600 |
+
for li, layer in enumerate(layers):
|
| 601 |
+
if li not in proj_svd:
|
| 602 |
+
continue
|
| 603 |
+
for pname, pmod in _get_projection_map(layer).items():
|
| 604 |
+
if pname in proj_svd[li] and hasattr(pmod, "weight") and pmod.weight.requires_grad:
|
| 605 |
+
scored_params.append((li, pname, pmod.weight))
|
| 606 |
+
scored_weight_list = [w for _, _, w in scored_params]
|
| 607 |
|
| 608 |
+
# Pre-stage the SVD right singular vectors on the model device once, so the
|
| 609 |
+
# per-pair grad->SV reduction is a single small GPU matmul (not a CPU one).
|
| 610 |
+
vh_gpu: Dict[Tuple[int, str], torch.Tensor] = {}
|
| 611 |
+
for li, pname, _ in scored_params:
|
| 612 |
+
_, _, vh = proj_svd[li][pname]
|
| 613 |
+
vh_gpu[(li, pname)] = vh.to(device=device, dtype=torch.float32, non_blocking=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
|
| 615 |
+
for idx in tqdm(range(start_idx, n_pairs), desc="AtP gradient", unit="pair",
|
| 616 |
+
initial=start_idx, total=n_pairs):
|
| 617 |
enc = tokenizer(corp_prompts[idx], return_tensors="pt", truncation=True, max_length=max_length)
|
| 618 |
+
enc = {k: v.to(device, non_blocking=True) for k, v in enc.items()}
|
|
|
|
| 619 |
try:
|
| 620 |
out = model(**enc, use_cache=False)
|
| 621 |
logits = out.logits[0, :-1, :]
|
| 622 |
targets = enc["input_ids"][0, 1:]
|
| 623 |
loss = F.cross_entropy(logits, targets)
|
| 624 |
+
grads = torch.autograd.grad(loss, scored_weight_list, allow_unused=True)
|
| 625 |
except torch.cuda.OutOfMemoryError:
|
| 626 |
# A single pathological pair must not wall the whole stage. Free
|
| 627 |
# what we can, drop this pair's partial state, advance, and flush
|
| 628 |
# so a re-run does not retry the same OOMing pair forever.
|
| 629 |
+
del enc
|
|
|
|
|
|
|
|
|
|
| 630 |
if torch.cuda.is_available():
|
| 631 |
torch.cuda.empty_cache()
|
| 632 |
gc.collect()
|
|
|
|
| 634 |
_flush(idx + 1)
|
| 635 |
continue
|
| 636 |
|
| 637 |
+
for (li, pname, _w), g_w in zip(scored_params, grads):
|
| 638 |
+
if g_w is None:
|
| 639 |
+
continue
|
| 640 |
+
_, _, vh = proj_svd[li][pname]
|
| 641 |
+
c_act = corp_proj_acts[li].get(pname)
|
| 642 |
+
a_act = auth_proj_acts[li].get(pname)
|
| 643 |
+
if c_act is None or a_act is None or c_act.shape[-1] != vh.shape[1]:
|
| 644 |
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 645 |
|
| 646 |
+
c_sv = (c_act @ vh.T).mean(0) # CPU, small ([rank])
|
| 647 |
+
a_sv = (a_act @ vh.T).mean(0) # CPU, small
|
| 648 |
+
diff = c_sv - a_sv # CPU, small
|
| 649 |
|
| 650 |
+
# grad->SV reduction on GPU; one tiny CPU transfer at the end.
|
| 651 |
+
g_sv = (g_w.float() @ vh_gpu[(li, pname)].T).norm(dim=0).cpu()
|
| 652 |
+
atp_accum[li][pname].append(diff * g_sv)
|
| 653 |
|
| 654 |
# Explicit cleanup to prevent CUDA memory fragmentation across pairs.
|
| 655 |
+
del loss, out, logits, targets, enc, grads
|
| 656 |
if torch.cuda.is_available():
|
| 657 |
torch.cuda.empty_cache()
|
| 658 |
if idx % 50 == 0:
|
|
|
|
| 1463 |
# requires_grad on all ~8B params allocates a .grad buffer for every
|
| 1464 |
# parameter during backward, which OOMs a 48GB card already holding a 16GB
|
| 1465 |
# model; scoped grad keeps peak memory to the scored MLP projections.
|
| 1466 |
+
# No gradient checkpointing: with scoped grad the model + ~11GB of grad
|
| 1467 |
+
# buffers + batch-1 activations fit in 48GB (and trivially in 80GB), so
|
| 1468 |
+
# checkpointing would just double the backward compute by recomputing the
|
| 1469 |
+
# forward. The per-pair OOM-skip guard inside _capture_atp_gradients is the
|
| 1470 |
+
# safety net if a pathological pair ever exceeds headroom.
|
| 1471 |
scored_weights = []
|
| 1472 |
for li, layer in enumerate(layers):
|
| 1473 |
if li not in proj_svd:
|
|
|
|
| 1477 |
pmod.weight.requires_grad_(True)
|
| 1478 |
scored_weights.append(pmod.weight)
|
| 1479 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1480 |
try:
|
| 1481 |
atp_scores = _capture_atp_gradients(
|
| 1482 |
model, tokenizer, corp[:32], auth[:32], layers,
|
|
|
|
| 1484 |
ckpt_dir=ckpt_dir, fingerprint=fp_atp, ckpt_name="stage3_atp",
|
| 1485 |
)
|
| 1486 |
finally:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1487 |
for w in scored_weights:
|
| 1488 |
w.requires_grad_(False)
|
|
|
|
| 1489 |
for p in model.parameters():
|
| 1490 |
p.requires_grad_(False)
|
| 1491 |
print(" AtP complete")
|