| from __future__ import annotations |
| from typing import Any, Dict |
| import os |
| from pathlib import Path |
| try: |
| import wandb |
| except Exception: |
| wandb = None |
| from .distributed import is_main_process |
|
|
|
|
| def _read_text(path: Path) -> str | None: |
| try: |
| if path.exists(): |
| text = path.read_text().strip() |
| return text or None |
| except Exception: |
| pass |
| return None |
|
|
|
|
| def _write_text(path: Path, text: str) -> None: |
| try: |
| path.parent.mkdir(parents=True, exist_ok=True) |
| path.write_text(text.strip() + "\n") |
| except Exception: |
| |
| pass |
|
|
|
|
| def _env_int(name: str, default: int) -> int: |
| try: |
| return int(os.environ.get(name, default)) |
| except Exception: |
| return default |
|
|
|
|
| def init_wandb(cfg: Dict[str, Any], run_name: str | None = None): |
| """Initialize W&B on rank 0 only and never crash training if W&B is slow/down. |
| |
| Resume behavior: |
| - If wandb.id or WANDB_RUN_ID is provided, use it. |
| - Otherwise, if <output_dir>/wandb_run_id.txt exists, reuse that ID. |
| - If a run ID is used, default resume mode is "allow" unless overridden by |
| wandb.resume or WANDB_RESUME. |
| |
| Reliability behavior: |
| - WANDB_INIT_TIMEOUT controls wandb.init timeout seconds (default 300). |
| - WANDB_SERVICE_WAIT controls W&B service startup wait seconds (default 300). |
| - If wandb.init raises any exception, training continues with W&B disabled. |
| """ |
| wb_cfg = cfg.get("wandb", {}) |
| if not wb_cfg.get("enabled", False) or not is_main_process() or wandb is None: |
| return None |
|
|
| mode = os.environ.get("WANDB_MODE", wb_cfg.get("mode", "online")) |
| entity = wb_cfg.get("entity") or os.environ.get("WANDB_ENTITY") or None |
| project = os.environ.get("WANDB_PROJECT", wb_cfg.get("project", "SACFlow-FM")) |
|
|
| out_dir = Path(cfg.get("output_dir", ".")) |
| run_id_file = out_dir / "wandb_run_id.txt" |
|
|
| run_id = ( |
| wb_cfg.get("id") |
| or os.environ.get("WANDB_RUN_ID") |
| or _read_text(run_id_file) |
| ) |
|
|
| resume_mode = wb_cfg.get("resume") or os.environ.get("WANDB_RESUME") |
| if run_id and resume_mode is None: |
| resume_mode = "allow" |
|
|
| init_timeout = _env_int("WANDB_INIT_TIMEOUT", int(wb_cfg.get("init_timeout", 300))) |
| service_wait = _env_int("WANDB_SERVICE_WAIT", int(wb_cfg.get("service_wait", 300))) |
|
|
| |
| os.environ.setdefault("WANDB_DIR", str(out_dir / "wandb")) |
| os.environ.setdefault("WANDB_CACHE_DIR", str(out_dir / "wandb_cache")) |
| os.environ.setdefault("WANDB_CONFIG_DIR", str(out_dir / "wandb_config")) |
|
|
| try: |
| settings = wandb.Settings(init_timeout=init_timeout, _service_wait=service_wait) |
| run = wandb.init( |
| project=project, |
| entity=entity, |
| mode=mode, |
| name=run_name, |
| id=run_id, |
| resume=resume_mode, |
| tags=wb_cfg.get("tags", []), |
| config=cfg, |
| settings=settings, |
| ) |
| except Exception as e: |
| print( |
| f"[W&B WARNING] wandb.init failed ({type(e).__name__}: {e}). " |
| "Continuing training with W&B disabled for this run. " |
| "To avoid this, use WANDB_MODE=offline or increase WANDB_INIT_TIMEOUT.", |
| flush=True, |
| ) |
| return None |
|
|
| |
| |
| if getattr(run, "id", None): |
| _write_text(run_id_file, run.id) |
|
|
| return run |
|
|
|
|
| def wandb_log(run, data: Dict[str, Any], step: int | None = None): |
| if run is not None: |
| try: |
| run.log(data, step=step) |
| except Exception as e: |
| print(f"[W&B WARNING] wandb.log failed: {e}", flush=True) |
|
|
|
|
| def wandb_finish(run): |
| if run is not None: |
| try: |
| run.finish() |
| except Exception as e: |
| print(f"[W&B WARNING] wandb.finish failed: {e}", flush=True) |
|
|