Spaces:
Running
Running
| """MPS smoke test for Exp 4 Phase 3 — run on the owner's M5 Mac, no assistant needed. | |
| Everything the analysis needs is printed between the REPORT BEGIN/END banners — | |
| copy that whole block back. Partial failures still produce a report (each stage | |
| catches and records its own error). | |
| Setup on a fresh Mac (Terminal): | |
| python3 -m venv ~/mps-test && source ~/mps-test/bin/activate | |
| pip install torch transformers pillow numpy | |
| python scripts/mps_smoke_test.py # (or run from any folder) | |
| If the report shows MPS ops missing, run once more with fallback to measure the | |
| realistic mixed speed: | |
| PYTORCH_ENABLE_MPS_FALLBACK=1 python scripts/mps_smoke_test.py | |
| """ | |
| from __future__ import annotations | |
| import os | |
| import platform | |
| import time | |
| import traceback | |
| import numpy as np | |
| import torch | |
| from PIL import Image | |
| MODEL_ID = "tue-mps/eomt-dinov3-ade-semantic-large-512" | |
| REVISION = "1d1f172700ab69371df7afe778fe133fff7dd521" # keep in sync with segmentation.MODEL_REVISIONS | |
| def synthetic_tile(size: int) -> Image.Image: | |
| rng = np.random.default_rng(0) | |
| return Image.fromarray(rng.integers(0, 255, (size, size, 3), dtype=np.uint8)) | |
| def _sync(device: str) -> None: | |
| if device == "mps": | |
| torch.mps.synchronize() | |
| def time_forward(model, inputs, device, runs: int = 3) -> float: | |
| model = model.to(device).eval() | |
| moved = {k: (v.to(device) if hasattr(v, "to") else v) for k, v in inputs.items()} | |
| with torch.inference_mode(): | |
| model(**moved) # warm-up | |
| _sync(device) | |
| times = [] | |
| for _ in range(runs): | |
| t0 = time.perf_counter() | |
| model(**moved) | |
| _sync(device) | |
| times.append(time.perf_counter() - t0) | |
| return min(times) | |
| def time_train_step(model, inputs, device) -> float: | |
| model = model.to(device).train() | |
| moved = {k: (v.to(device) if hasattr(v, "to") else v) for k, v in inputs.items()} | |
| optim = torch.optim.AdamW(model.parameters(), lr=1e-5) | |
| t0 = time.perf_counter() | |
| out = model(**moved) | |
| loss = out.masks_queries_logits.float().mean() + out.class_queries_logits.float().mean() | |
| loss.backward() | |
| optim.step() | |
| optim.zero_grad(set_to_none=True) | |
| _sync(device) | |
| return time.perf_counter() - t0 | |
| def stage(report: dict, name: str, fn): | |
| try: | |
| report[name] = fn() | |
| print(f"{name}: {report[name]}") | |
| except Exception: | |
| report[name] = "FAILED" | |
| print(f"{name}: FAILED") | |
| print(traceback.format_exc()) | |
| def main() -> None: | |
| print("=" * 66) | |
| print("MPS SMOKE REPORT BEGIN — copy everything down to REPORT END") | |
| print("=" * 66) | |
| print(f"machine: {platform.machine()} | {platform.platform()}") | |
| print(f"python: {platform.python_version()} | torch: {torch.__version__}") | |
| print(f"PYTORCH_ENABLE_MPS_FALLBACK={os.getenv('PYTORCH_ENABLE_MPS_FALLBACK', '<unset>')}") | |
| print(f"mps available: {torch.backends.mps.is_available()} (built: {torch.backends.mps.is_built()})") | |
| report: dict = {} | |
| try: | |
| from transformers import AutoImageProcessor, AutoModelForUniversalSegmentation | |
| processor = AutoImageProcessor.from_pretrained(MODEL_ID, revision=REVISION) | |
| model = AutoModelForUniversalSegmentation.from_pretrained(MODEL_ID, revision=REVISION) | |
| print(f"model: {MODEL_ID} ({sum(p.numel() for p in model.parameters()) / 1e6:.0f}M params)") | |
| except Exception: | |
| print("MODEL LOAD FAILED:") | |
| print(traceback.format_exc()) | |
| print("REPORT END") | |
| return | |
| inputs_512 = processor(images=synthetic_tile(512), return_tensors="pt") | |
| inputs_1280 = processor(images=synthetic_tile(1280), return_tensors="pt") | |
| stage(report, "cpu_forward_512_s", lambda: round(time_forward(model, inputs_512, "cpu"), 2)) | |
| stage(report, "cpu_forward_1280_s", lambda: round(time_forward(model, inputs_1280, "cpu"), 2)) | |
| if not torch.backends.mps.is_available(): | |
| print("NO MPS DEVICE — training would need the cloud-GPU fallback.") | |
| print("REPORT END") | |
| return | |
| stage(report, "mps_forward_512_s", lambda: round(time_forward(model, inputs_512, "mps"), 2)) | |
| stage(report, "mps_forward_1280_s", lambda: round(time_forward(model, inputs_1280, "mps"), 2)) | |
| stage(report, "mps_train_step_512_s", lambda: round(time_train_step(model, inputs_512, "mps"), 2)) | |
| # second step is the honest one (first includes optimizer-state allocation) | |
| stage(report, "mps_train_step_512_s_2nd", lambda: round(time_train_step(model, inputs_512, "mps"), 2)) | |
| try: | |
| alloc = torch.mps.current_allocated_memory() / 1e9 | |
| print(f"mps_allocated_gb: {alloc:.1f}") | |
| except Exception: | |
| pass | |
| step = report.get("mps_train_step_512_s_2nd") | |
| if isinstance(step, float): | |
| print(f"epoch_math: 2000 crops / batch 1 -> {2000 * step / 60:.0f} min/epoch at this step time") | |
| print("VERDICT: MPS training viable" if step < 30 else | |
| "VERDICT: MPS works but slow — consider cloud GPU for the real fine-tune") | |
| else: | |
| print("VERDICT: train step failed on MPS — retry with PYTORCH_ENABLE_MPS_FALLBACK=1; " | |
| "if it still fails, forward-only on MPS is usable and training goes to cloud GPU") | |
| print("=" * 66) | |
| print("REPORT END") | |
| print("=" * 66) | |
| if __name__ == "__main__": | |
| main() | |