mae-cross-objective / scripts /apply_mae_patch.py
quinnlue's picture
MAE cross-objective mixture-ranking campaign
ffdcfe7 verified
Raw
History Blame Contribute Delete
4.63 kB
"""Idempotently graft the MAE objective into the eatmap tree on the VM.
Kept here rather than applied by hand because /workspace is not a volume on this
instance -- workspace_is_volume is False, so a recycle wipes the container
filesystem. This script plus mae.py is the whole change; re-running it on a
fresh box reproduces the tree.
Edits are surgical and leave every EAT code path byte-identical: the objective
dispatch defaults to "ufo", and the two structural fixes (teacher-optional
export, teacher-aware parameter count) are no-ops when a teacher is present.
"""
import sys
from pathlib import Path
ROOT = Path(sys.argv[1] if len(sys.argv) > 1 else "/workspace/code/eat-map-regmix/eatmap")
def patch(path: Path, old: str, new: str, label: str) -> None:
text = path.read_text()
if new in text:
print(f" [skip] {label} (already applied)")
return
if old not in text:
raise SystemExit(f" [FAIL] {label}: anchor not found in {path}")
path.write_text(text.replace(old, new, 1))
print(f" [ok] {label}")
print(f"patching {ROOT}")
# ---- 1. ObjectiveConfig: objective selector + MAE decoder geometry ----------
patch(
ROOT / "config.py",
""" # null/0 means "anneal over total_steps" -- never leave this at a value
# larger than the budget or the EMA never reaches its end decay.
ema_anneal_steps: int | None = None""",
""" # null/0 means "anneal over total_steps" -- never leave this at a value
# larger than the budget or the EMA never reaches its end decay.
ema_anneal_steps: int | None = None
# "ufo" is EAT's EMA-teacher objective and the default, so every existing
# config keeps its meaning. "mae" swaps in masked log-mel reconstruction
# over the same encoder -- see eatmap/mae.py for why that sharing matters.
type: str = "ufo"
# Decoder geometry, MAE only. Asymmetric by design: the decoder is thrown
# away after pretraining, so it is narrow and shallow relative to the
# encoder. Defaults are sized for the 384-dim 15M proxy.
mae_decoder_dim: int = 256
mae_decoder_depth: int = 4
mae_decoder_heads: int = 8
# Per-patch target standardization (He et al.'s norm_pix_loss). Off makes
# the loss track patch loudness rather than structure.
mae_norm_pix: bool = True""",
"ObjectiveConfig: type + MAE decoder fields",
)
# ---- 2. export_weights: teacher is optional --------------------------------
patch(
ROOT / "runner.py",
""" tensors = {}
for name, tensor in model.student.state_dict().items():
tensors[f"student.{name}"] = tensor.detach().cpu().contiguous()
for name, tensor in model.teacher.state_dict().items():
tensors[f"teacher.{name}"] = tensor.detach().cpu().contiguous()
for name, tensor in model.decoder.state_dict().items():
tensors[f"decoder.{name}"] = tensor.detach().cpu().contiguous()""",
""" tensors = {}
# MAE has no teacher, so the parts are discovered rather than assumed. The
# student is written under the same "student." prefix either way, which is
# what probe.load_encoder keys on -- an MAE export and an EAT export are
# interchangeable to every readout.
for part in ("student", "teacher", "decoder"):
module = getattr(model, part, None)
if module is None:
continue
for name, tensor in module.state_dict().items():
tensors[f"{part}.{name}"] = tensor.detach().cpu().contiguous()""",
"export_weights: teacher-optional",
)
# ---- 3. objective dispatch -------------------------------------------------
patch(
ROOT / "runner.py",
"from .model import EATPretrainer, parameter_counts",
"from .mae import MAEPretrainer\nfrom .model import EATPretrainer, parameter_counts",
"runner: import MAEPretrainer",
)
patch(
ROOT / "runner.py",
" model = EATPretrainer(config).to(device)",
""" pretrainer = {"ufo": EATPretrainer, "mae": MAEPretrainer}.get(config.objective.type)
if pretrainer is None:
raise ValueError(f"unknown objective.type {config.objective.type!r}")
model = pretrainer(config).to(device)""",
"runner: objective dispatch",
)
# ---- 4. parameter_counts: only EAT carries a frozen twin -------------------
patch(
ROOT / "model.py",
""" "total_params": student * 2 + decoder, # teacher is a frozen copy""",
""" # EAT carries a frozen EMA twin of the student; MAE does not.
"total_params": student * (2 if getattr(model, "teacher", None) is not None else 1) + decoder,""",
"parameter_counts: teacher-aware total",
)
print("done")