Add analysis scripts and experiment configurations for bridge attention and sensitivity studies

- Introduced `bridge_attention_ceiling_check.py` for variance decomposition analysis on bridge attention configurations.
- Added `bridge_attention_readout.py` to perform per-tower gate and contribution readouts, including AUC sanity checks.
- Created multiple JSON configuration files for backbone replication experiments, including anonymous CV variants and basic backbones.
- Implemented sensitivity experiments to evaluate the impact of axial length inclusion and EfficientNetV2-M performance at higher resolutions.
- Added a memory probe script to assess GPU memory usage during training with EfficientNetV2-M.
This commit is contained in:
rpotter6298
2026-07-03 08:51:44 +02:00
parent 3d954a4606
commit 708fbc70ce
52 changed files with 2223 additions and 218 deletions
@@ -0,0 +1,16 @@
[
{
"_note": "Sensitivity: refugelike ensemble (img + cd, bilateral) — apples-to-apples replication of tri_v1/baseline_ensemble (mean hb_test_auc ≈ 0.896) but with Axial_Length INCLUDED in the clinical feature set instead of excluded. Tests whether the pilot-era finding that Axial_Length negatively contributed to fused AUC survives the v4 architecture. Same seed/fold_seed start (1234/100) as baseline_ensemble so (rep, fold) pairs are matched for paired statistics. save_checkpoints + save_predictions enabled so the run can drive a downstream permutation-importance feature ablation if needed.",
"run_name": "experiments/sensitivity/refugelike_ensemble_with_axial_length",
"reps": 10,
"overrides": {
"save_checkpoints": true,
"save_predictions": true,
"data": {
"args": {
"exclude_cols": []
}
}
}
}
]
@@ -0,0 +1,107 @@
"""Memory probe: EfficientNetV2-M at 480x480, bilateral forward+backward, AMP bf16.
Goal: confirm bs=8 fits in 16 GB on the available GPU before committing to the
full sensitivity experiment.
Mimics the bilateral training step (image tower run twice on OD + OS with shared
weights, plus a small downstream head + CE loss + Adam step). The clinical tower
and L1 bridge are omitted; their memory footprint is negligible against V2-M
activations. Synthetic inputs of the correct shape — no v4 dataset needed.
Run: python v4/scripts/experiments/sensitivity/probe_v2m_480_amp.py
Expected output: GPU name, peak memory at each phase, fit/OOM verdict.
"""
from __future__ import annotations
import sys
import torch
from torch import nn
from torchvision.models import efficientnet_v2_m
BATCH = 8
RES = 480
DTYPE = torch.bfloat16
def fmt_gb(bytes_):
return f"{bytes_ / 1024**3:.2f} GB"
def main():
if not torch.cuda.is_available():
print("No CUDA/ROCm device available; probe requires a GPU.", file=sys.stderr)
sys.exit(2)
device = torch.device("cuda")
gpu_name = torch.cuda.get_device_name(0)
gpu_total = torch.cuda.get_device_properties(0).total_memory
print(f"GPU: {gpu_name} total VRAM: {fmt_gb(gpu_total)}")
print(f"Config: V2-M, bs={BATCH}, res={RES}, bilateral 2x forward, AMP={DTYPE}")
print("-" * 70)
torch.cuda.empty_cache()
torch.cuda.reset_peak_memory_stats()
backbone = efficientnet_v2_m(weights=None).to(device)
feat_dim = backbone.classifier[1].in_features
backbone.classifier = nn.Identity()
head = nn.Sequential(
nn.LayerNorm(feat_dim),
nn.Linear(feat_dim, 256),
nn.GELU(),
nn.Linear(256, 2),
).to(device)
opt = torch.optim.Adam(
list(backbone.parameters()) + list(head.parameters()),
lr=1e-4,
)
print(f"After model + optimizer load: allocated={fmt_gb(torch.cuda.memory_allocated())} "
f"peak={fmt_gb(torch.cuda.max_memory_allocated())}")
x_od = torch.randn(BATCH, 3, RES, RES, device=device)
x_os = torch.randn(BATCH, 3, RES, RES, device=device)
y = torch.randint(0, 2, (BATCH,), device=device)
print(f"After synthetic inputs: allocated={fmt_gb(torch.cuda.memory_allocated())} "
f"peak={fmt_gb(torch.cuda.max_memory_allocated())}")
try:
opt.zero_grad(set_to_none=True)
with torch.autocast(device_type="cuda", dtype=DTYPE):
z_od = backbone(x_od)
z_os = backbone(x_os)
z = z_od + z_os
logits = head(z)
loss = nn.functional.cross_entropy(logits, y)
print(f"After forward: allocated={fmt_gb(torch.cuda.memory_allocated())} "
f"peak={fmt_gb(torch.cuda.max_memory_allocated())}")
loss.backward()
print(f"After backward: allocated={fmt_gb(torch.cuda.memory_allocated())} "
f"peak={fmt_gb(torch.cuda.max_memory_allocated())}")
opt.step()
print(f"After optimizer step: allocated={fmt_gb(torch.cuda.memory_allocated())} "
f"peak={fmt_gb(torch.cuda.max_memory_allocated())}")
torch.cuda.synchronize()
peak = torch.cuda.max_memory_allocated()
headroom = gpu_total - peak
print("-" * 70)
print(f"VERDICT: FIT | peak={fmt_gb(peak)} of {fmt_gb(gpu_total)} "
f"headroom={fmt_gb(headroom)} ({100 * headroom / gpu_total:.1f}%)")
print(f"Loss value: {loss.item():.4f}")
except torch.cuda.OutOfMemoryError as e:
peak = torch.cuda.max_memory_allocated()
print("-" * 70)
print(f"VERDICT: OOM | peak before OOM={fmt_gb(peak)} of {fmt_gb(gpu_total)}")
print(f"OOM details: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
@@ -0,0 +1,24 @@
[
{
"_note": "Sensitivity: refuge V2-M ensemble (img + cd, bilateral, ortho-w0.1 inner-Hadamard not applied here — matches the plain V2-M ensemble baseline at 224 from efficientnet/refuge_efficientnetv2_m which gave 0.9132 ± 0.019). This run feeds EfficientNetV2-M at its NATIVE 480x480 input resolution instead of the pipeline default 224. Activation memory roughly 4.6x; bf16 autocast keeps bs=8 fit in 16 GB (probe peak 9.07 GB on 7800 XT). Matched seed/fold_seed (1234/100) inherited from ensemble_fused.json so reps 1-10 here pair with reps 1-10 of the 224 baseline for paired statistics. 10 reps queued — kill early if wall-clock proves prohibitive. save_checkpoints + save_predictions enabled for downstream analysis if the result is promising.",
"run_name": "experiments/sensitivity/v2m_at_480_amp",
"reps": 10,
"overrides": {
"save_checkpoints": true,
"save_predictions": true,
"training": {
"amp": true,
"amp_dtype": "bfloat16"
}
},
"tower_overrides": {
"img": {
"args": {
"backbone": "refuge_efficientnet_v2_m",
"freeze_ratio": 0.0,
"crop_size": 480
}
}
}
}
]