Add opt-in multi-GPU sweep parallelism; fix GPU pinning and run dir races

For shape-heavy ops the autotune sweep dominates wall time, not the
measurement. _parallel_warmup_plugin shards the sweep across N GPUs and
hands the merged configs to the normal single-GPU serial measurement, so
timing stays comparable. Activated by PARALLEL_WARMUP_GPUS=N (silent
no-op when unset, so it can stay in the plugin list); output layout,
REPLAY_FROM usage and the latency table are unchanged.

Measurement is deliberately not parallelized: N processes saturating one
box couple through the power/thermal budget, so per-card latency gets
dragged by its neighbours by an amount that does not reproduce. Config
keys whose winner differs across shards are counted and reported as a
WARNING -- that count is how much to trust the run.

Fixes found while auditing:

- Shards no longer overwrite CUDA_VISIBLE_DEVICES with a bare shard
  index, which a caller who had selected idle cards (e.g. 6,7) would see
  re-interpreted as absolute ids 0,1 -- silently benchmarking on the busy
  cards they were avoiding.
- Interrupting the sweep no longer leaves N subprocesses holding GPUs;
  children are terminated and reaped before the exception propagates
  (BaseException, since KeyboardInterrupt is not an Exception).
- run_pytest.sh claims its output dir with a bare mkdir and retreats to
  a -2/-3 suffix on collision. The second-resolution timestamp meant two
  concurrent runs shared one directory and overwrote each other.
- Replay eviction failures now emit a distinct compile_*_no_evict marker.
  LibTuner.cache is a sqlite-backed ConfigCache with no __delitem__, so
  the bad config could not be dropped and the retry re-read it, while the
  log still claimed a clean fallback to live autotune.

Also trims comment density in both shell scripts and reworks the README:
promotes the parallel and cudagraph sections out from under the A/B flow,
groups the env table by purpose, documents that two record-mode runs are
not comparable, and marks ab_fold_test.sh as a caliber example whose
switch upstream has already removed.
This commit is contained in:
2026-07-29 12:14:12 +00:00
parent 7e0e8648f1
commit 1e1d612031
5 changed files with 439 additions and 66 deletions
+12 -3
View File
@@ -180,12 +180,21 @@ def _replay_run(original):
except Exception as exc:
if injected_key is not None:
# Recorded config failed under the current build: drop it and
# let run() autotune.
# let run() autotune. Only Autotuner.cache is a plain dict;
# LibTuner.cache is a sqlite-backed ConfigCache with no
# __delitem__, so eviction is impossible there -- say so in the
# marker instead of retrying with the same bad config and
# reporting a clean fallback.
evicted = True
try:
del self.cache[injected_key]
except Exception:
pass
_emit_marker(f"compile_{type(exc).__name__}")
evicted = False
# _no_evict means the retry below re-reads the same recorded
# config, so it is not a clean "fell back to live autotune":
# treat those measurement points as unverified.
suffix = "" if evicted else "_no_evict"
_emit_marker(f"compile_{type(exc).__name__}{suffix}")
return original(self, *args, **kwargs)
raise
return runner