exp013 shipped: CONDITIONING HYPOTHESIS CONFIRMED - blob coupling ~200x more effective on flow than eps (prereg 5x bar); blob work migrates to flow/v-pred substrates
e8f08ec verified | """dexp013_blob_flow.py — exp013: BLOB SUPERVISION ON THE FLOW SUBSTRATE | |
| (Phil's hypothesis 2026-07-18: "Blob likely will be less effective for eps | |
| than it will be for flow matching"). | |
| Mechanism under test: eps-prediction recovers structure through | |
| x0_hat = (x_t - sqrt(1-abar)*eps_hat)/sqrt(abar) — ILL-CONDITIONED at high | |
| noise (divide by vanishing sqrt(abar)) exactly where blob supervision | |
| applies; exp012 measured the resulting inertia (+0.03%). Rectified flow | |
| recovers x0_hat = x_t - sigma*v_hat — EXACT AND LINEAR at every sigma. | |
| Prediction: the same blob coupling moves the blob gauge substantially more | |
| on the flow substrate. | |
| Substrate: base_lune UNet (flow-trained; its strongest judged arm was nl77) | |
| + the dexp011 FUSED cache (same SD15 VAE, latents + nl77 ehs + blob targets). | |
| Objective: trainer-verbatim flow (sigma~U -> SHIFT-2.5 warp, x_t = | |
| noise*s + lat*(1-s), v = noise - lat), blob term = foreground-weighted | |
| LP-x0 with x0_hat = x_t - s*v_hat, HIGH band only, lambda 0.5 (exp012-dosed | |
| for direct comparison). | |
| Arms (shared cache): frozen | uniform-mb3-flow | blob-mb3-flow. Gauges: | |
| common flow-MSE per band + the role-aligned foreground-LP-x0 gauge (all | |
| arms, in-bed). | |
| Prereg: P1 blob-vs-uniform blob-gauge movement in the HIGH band >= 5x | |
| exp012's relative +0.03% (i.e. >= +0.15%); P2 common gauge undegraded | |
| (<0.5%); P3 toggle bit-exact; P5 (mechanistic) the blob-gauge SCALE is | |
| better-conditioned: report gauge magnitudes per band vs exp012's. | |
| Pod: bash pod2/run_exp013.sh [DEXP13_SEED=0] | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import os | |
| import sys | |
| import time | |
| sys.path[:0] = ["pod2", "."] | |
| import torch | |
| import torch.nn.functional as F | |
| from pod_ledger import ledger_run, note, burn_down | |
| from d1_substrate import MEM_FRACTION | |
| from dexp008_multiband import (MultibandDelta, band_weights, band_of, attach, | |
| N_BANDS) | |
| from dexp009_bandroles import lp | |
| LUNE_REPO = "AbstractPhil/sd15-flow-lune-flux" | |
| LUNE_SUB = "flux_t2_6_pose_t4_6_port_t1_4/checkpoint-00018765/unet" | |
| SHIFT = 2.5 | |
| N_TRAIN, N_VAL = 4096, 256 | |
| BATCH = int(os.environ.get("DEXP13_BATCH", "16")) | |
| STEPS = int(os.environ.get("DEXP13_STEPS", "3000")) | |
| SEED = int(os.environ.get("DEXP13_SEED", "0")) | |
| LAM = float(os.environ.get("DEXP13_LAMBDA", "0.5")) | |
| LR, CFG_DROPOUT = 1e-3, 0.1 | |
| D11 = ("/workspace/data/dexp011" if os.path.isdir("/workspace") | |
| else "./data/dexp011") | |
| DATA_DIR = ("/workspace/data/dexp013" if os.path.isdir("/workspace") | |
| else os.path.join(os.environ.get("GEOLIP_DATA", "./data"), | |
| "dexp013")) | |
| CKPT_DIR = ("/workspace/ckpts2/dexp013" if os.path.isdir("/workspace") | |
| else DATA_DIR) | |
| def load_lune(device): | |
| from diffusers import UNet2DConditionModel | |
| unet = UNet2DConditionModel.from_pretrained( | |
| LUNE_REPO, subfolder=LUNE_SUB, torch_dtype=torch.float32).to(device) | |
| unet.requires_grad_(False) | |
| unet.eval() | |
| unet.enable_gradient_checkpointing() | |
| return unet | |
| def blob_lp_err(x0_hat, x0, blob): | |
| d2 = (lp(x0_hat) - lp(x0)) ** 2 | |
| m = blob[:, None] | |
| denom = m.sum(dim=(1, 2, 3)).clamp_min(1.0) * d2.shape[1] | |
| return (d2 * m).sum(dim=(1, 2, 3)) / denom | |
| def run(device="cuda"): | |
| torch.cuda.set_per_process_memory_fraction(MEM_FRACTION, 0) | |
| os.makedirs(CKPT_DIR, exist_ok=True) | |
| os.makedirs(DATA_DIR, exist_ok=True) | |
| cache = torch.load(os.path.join(D11, "cache.pt"), map_location="cpu", | |
| weights_only=True) | |
| assert int(cache["blob_empty_count"]) == 0, "need the rebuilt blob targets" | |
| # fixed val sigmas for the FLOW substrate (warped uniform grid, paired) | |
| gv = torch.Generator().manual_seed(SEED + 11) | |
| u = torch.linspace(0.02, 0.98, N_VAL) | |
| val_sigma = (SHIFT * u) / (1 + (SHIFT - 1) * u) | |
| def set_w(wraps, s01): | |
| w = band_weights(s01) | |
| for wr in wraps: | |
| wr.w_bands = w | |
| return w | |
| def flow_pieces(lat, s, noise): | |
| s4 = s[:, None, None, None] | |
| return noise * s4 + lat * (1 - s4), noise - lat # x_t, v | |
| def loss_of(unet, wraps, lat, ehs, blob, gen, use_blob): | |
| bsz = lat.shape[0] | |
| drop = torch.rand(bsz, generator=gen, device=device) < CFG_DROPOUT | |
| ehs = ehs.clone() | |
| ehs[drop] = 0 | |
| s = torch.rand(bsz, generator=gen, device=device) | |
| s = (SHIFT * s) / (1 + (SHIFT - 1) * s) | |
| w = set_w(wraps, s) | |
| noise = torch.randn(lat.shape, generator=gen, device=device) | |
| x_t, v = flow_pieces(lat, s, noise) | |
| pred = unet(x_t, s * 1000, ehs, return_dict=False)[0] | |
| base = ((pred - v) ** 2).mean(dim=(1, 2, 3)) | |
| if not use_blob: | |
| return base.mean() | |
| x0_hat = x_t - s[:, None, None, None] * pred # EXACT linear | |
| blob_term = blob_lp_err(x0_hat, lat, blob) | |
| return (base + LAM * w[:, 2] * blob_term).mean() | |
| def val(unet, wraps): | |
| tot = [] | |
| per_band = {0: [], 1: [], 2: []} | |
| blob_band = {0: [], 1: [], 2: []} | |
| for i in range(0, N_VAL, 32): | |
| lat = cache["val_lat"][i:i + 32].to(device) | |
| ehs = cache["val_ehs"][i:i + 32].to(device) | |
| noise = cache["val_noise"][i:i + 32].to(device) | |
| blob = cache["val_blob"][i:i + 32].float().to(device) | |
| s = val_sigma[i:i + 32].to(device) | |
| w = set_w(wraps, s) | |
| x_t, v = flow_pieces(lat, s, noise) | |
| pred = unet(x_t, s * 1000, ehs, return_dict=False)[0] | |
| mse = ((pred - v) ** 2).mean(dim=(1, 2, 3)) | |
| x0_hat = x_t - s[:, None, None, None] * pred | |
| bg = blob_lp_err(x0_hat, lat, blob) | |
| tot += mse.tolist() | |
| for j, sv in enumerate(s.tolist()): | |
| b = band_of(sv) | |
| per_band[b].append(mse[j].item()) | |
| blob_band[b].append(bg[j].item()) | |
| return (sum(tot) / len(tot), | |
| {f"band{b}": round(sum(v_) / max(len(v_), 1), 6) | |
| for b, v_ in per_band.items()}, | |
| {f"band{b}": round(sum(v_) / max(len(v_), 1), 6) | |
| for b, v_ in blob_band.items()}) | |
| def train(unet, wraps, params, label, use_blob): | |
| opt = torch.optim.Adam(params, lr=LR, weight_decay=0.0) | |
| gen = torch.Generator(device=device).manual_seed(SEED + 42) | |
| idx = torch.Generator().manual_seed(SEED + 7) | |
| t0 = time.time() | |
| for step in range(1, STEPS + 1): | |
| sel = torch.randint(0, N_TRAIN, (BATCH,), generator=idx) | |
| loss = loss_of(unet, wraps, cache["lat"][sel].to(device), | |
| cache["ehs"][sel].to(device), | |
| cache["blob"][sel].float().to(device), gen, | |
| use_blob) | |
| loss.backward() | |
| opt.step() | |
| opt.zero_grad(set_to_none=True) | |
| if step == 50 or step % 500 == 0: | |
| print(f"[{label}] step {step}: loss {loss.item():.4f} | " | |
| f"{(time.time() - t0) / step:.2f}s/step", flush=True) | |
| results = {"config": {"lambda": LAM, "steps": STEPS, "batch": BATCH, | |
| "seed": SEED, "trunk": "base_lune 18765 (flow)", | |
| "x0_recovery": "x0_hat = x_t - sigma*v (exact)"}} | |
| with ledger_run(f"dexp013 frozen s{SEED}", budget_h=0.2) as h: | |
| unet = load_lune(device) | |
| v, pb, bb = val(unet, []) | |
| results["frozen"] = {"val": v, "per_band": pb, "blob_gauge": bb} | |
| del unet | |
| torch.cuda.empty_cache() | |
| h["verdict"] = f"val {v:.5f}" | |
| for label, use_blob in (("uniform_mb3", False), ("blob_mb3", True)): | |
| with ledger_run(f"dexp013 {label} s{SEED}", budget_h=2.5) as h: | |
| unet = load_lune(device) | |
| mods, wraps = attach(unet, lambda d: MultibandDelta(d)) | |
| for m in mods: | |
| m.assert_zero_init() | |
| train(unet, wraps, mods.parameters(), label, use_blob) | |
| v, pb, bb = val(unet, wraps) | |
| for m in mods: | |
| m.enabled = False | |
| v_off, _, _ = val(unet, wraps) | |
| d = abs(v_off - results["frozen"]["val"]) | |
| assert d < 1e-9, f"toggle parity broken: {d}" | |
| for m in mods: | |
| m.enabled = True | |
| torch.save({"mods": [m.state_dict() for m in mods]}, | |
| os.path.join(CKPT_DIR, f"{label}_s{SEED}.pt")) | |
| results[label] = {"val": v, "per_band": pb, "blob_gauge": bb, | |
| "val_toggled_off": v_off} | |
| del unet, mods | |
| torch.cuda.empty_cache() | |
| h["verdict"] = f"val {v:.5f} blobH {bb['band2']}" | |
| bm, um = results["blob_mb3"], results["uniform_mb3"] | |
| rel_move = (um["blob_gauge"]["band2"] - bm["blob_gauge"]["band2"]) \ | |
| / max(um["blob_gauge"]["band2"], 1e-12) | |
| results["verdict"] = { | |
| "P1_blob_gauge_high_band": { | |
| "blob": bm["blob_gauge"]["band2"], | |
| "uniform": um["blob_gauge"]["band2"], | |
| "relative_move": round(rel_move, 5), | |
| "exp012_reference_move": 0.0003, | |
| "hit_5x": rel_move >= 0.0015}, | |
| "P2_common_undegraded": | |
| bm["val"] <= um["val"] * 1.005, | |
| "common_val": {"blob": bm["val"], "uniform": um["val"], | |
| "frozen": results["frozen"]["val"]}, | |
| "note": "flow-substrate blob test (Phil's conditioning hypothesis); " | |
| "1-seed CANDIDATE", | |
| } | |
| with open(os.path.join(DATA_DIR, "results.json" if SEED == 0 | |
| else f"results_s{SEED}.json"), "w") as f: | |
| json.dump(results, f, indent=2) | |
| note(f"dexp013: {json.dumps(results['verdict']['P1_blob_gauge_high_band'])}") | |
| print(json.dumps(results["verdict"], indent=2)) | |
| burn_down() | |
| return results | |
| def smoke(): | |
| lat = torch.randn(2, 4, 64, 64) | |
| noise = torch.randn_like(lat) | |
| s = torch.tensor([0.9, 0.2]) | |
| s4 = s[:, None, None, None] | |
| x_t = noise * s4 + lat * (1 - s4) | |
| v = noise - lat | |
| x0 = x_t - s4 * v | |
| assert (x0 - lat).abs().max() < 1e-5, "flow x0 recovery must be exact" | |
| print("dexp013 smoke PASSED (exact linear x0 recovery on the flow path)") | |
| if __name__ == "__main__": | |
| if "--run" in sys.argv: | |
| run() | |
| else: | |
| smoke() | |