|
34 | 34 | "CRITICAL", |
35 | 35 | ) |
36 | 36 |
|
| 37 | +# Sentinel distinguishing "argument omitted" from an explicit ``None`` in |
| 38 | +# :func:`set`. ``cache_dir=None`` must raise (caching is mandatory for |
| 39 | +# parallel-safe run isolation), while omitting it is a no-op. |
| 40 | +_UNSET = object() |
| 41 | + |
37 | 42 | _CLEANUP_KEYS = ( |
38 | 43 | "checkpoints", |
39 | 44 | "logs", |
@@ -251,6 +256,21 @@ def cache_dir(self, value: Optional[str]) -> None: |
251 | 256 | ) |
252 | 257 | if not value.strip(): |
253 | 258 | raise ValueError("cache_dir must not be empty") |
| 259 | + # Forbid relative paths: the run_dir is resolved with ``.resolve()`` |
| 260 | + # (manager) against the *current* CWD, which Hydra's ``job.chdir`` |
| 261 | + # silently changes — a relative cache_dir would then land in a |
| 262 | + # different (and CWD-dependent) place per job. We expanduser first |
| 263 | + # so ``~/...`` (which expands to an absolute path) is accepted and |
| 264 | + # stored verbatim for portability; only genuinely relative paths |
| 265 | + # like ``runs`` or ``./out`` are rejected. |
| 266 | + if not os.path.isabs(os.path.expanduser(value)): |
| 267 | + raise ValueError( |
| 268 | + f"cache_dir must be an absolute path, got {value!r}. " |
| 269 | + "A relative path is resolved against the current working " |
| 270 | + "directory, which Hydra's job.chdir changes — making the " |
| 271 | + "run directory non-deterministic across jobs. Pass an " |
| 272 | + "absolute path (or a '~/...' path)." |
| 273 | + ) |
254 | 274 | self._cache_dir = value |
255 | 275 |
|
256 | 276 | # -- requeue_checkpoint ---------------------------------------------------- |
@@ -334,7 +354,7 @@ def set( |
334 | 354 | log_rank: Optional[Union[int, Literal["all"]]] = None, |
335 | 355 | default_callbacks: Optional[Dict[str, bool]] = None, |
336 | 356 | default_loggers: Optional[Dict[str, bool]] = None, |
337 | | - cache_dir: Optional[str] = None, |
| 357 | + cache_dir: Union[str, object] = _UNSET, |
338 | 358 | requeue_checkpoint: Optional[bool] = None, |
339 | 359 | requeue_checkpoint_every_n_steps: Optional[int] = None, |
340 | 360 | exclude_bias_norm: Optional[bool] = None, |
@@ -382,8 +402,9 @@ def set( |
382 | 402 | ensuring no path collisions across parallel sweep jobs. |
383 | 403 | Defaults to ``~/.cache/stable-pretraining``. Can be |
384 | 404 | overridden via the ``SPT_CACHE_DIR`` environment variable. |
385 | | - Set to ``None`` to disable and preserve the standard |
386 | | - Lightning / Hydra directory behavior. |
| 405 | + ``None`` is **not** allowed — a cache directory is mandatory |
| 406 | + so every run gets a unique, parallel-safe output directory. |
| 407 | + Passing ``cache_dir=None`` raises ``ValueError``. |
387 | 408 |
|
388 | 409 | .. note:: |
389 | 410 | SLURM ``.out`` / ``.err`` files are created by the |
@@ -446,7 +467,16 @@ def set( |
446 | 467 | if default_loggers is not None: |
447 | 468 | cfg.default_loggers = default_loggers |
448 | 469 |
|
449 | | - if cache_dir is not None: |
| 470 | + if cache_dir is not _UNSET: |
| 471 | + if cache_dir is None: |
| 472 | + raise ValueError( |
| 473 | + "cache_dir cannot be None — a cache directory is mandatory so " |
| 474 | + "that every run gets a unique, parallel-safe output directory " |
| 475 | + "(without it, concurrent jobs collide on files such as " |
| 476 | + "wandb_resume.json in the working directory). Pass a path, set " |
| 477 | + "the SPT_CACHE_DIR environment variable, or leave it unset to " |
| 478 | + "use the default (~/.cache/stable-pretraining)." |
| 479 | + ) |
450 | 480 | cfg.cache_dir = cache_dir |
451 | 481 |
|
452 | 482 | if requeue_checkpoint is not None: |
|
0 commit comments