Fix JIT cache crash for rename-race losers on shared filesystems#684
Fix JIT cache crash for rename-race losers on shared filesystems#684Oseltamivir wants to merge 2 commits into
Conversation
On shared filesystems (e.g. an NFS-mounted $HOME/.deep_ep), the winner's renamed cache directory is not necessarily visible yet to a process on another node that lost the rename race: the earlier cache miss primed a negative lookup cache entry that can outlive the failed rename, so the final get() asserts (compiler.hpp:158). Load the loser's own equivalent just-compiled artifacts instead of re-reading the winner's directory.
🤖 ds-review-bot Code Reviewv4The PR makes the rename-race LOSER self-sufficient instead of reading the winner's directory across NFS clients. The cache miss in build() primes a negative lookup on the loser's client that can outlive the failed atomic rename, so a stat of the winner's dir is still answered "does not exist" until the parent attribute cache expires; the old get(dir_path) then returns nullptr and EP_HOST_ASSERT kills the rank (and hangs the job). Loading the loser's own just-compiled artifacts (which are equivalent by cache key: kernel name, source, flags and compiler signature), caching them under the canonical dir_path, and deleting the tmp dir is the correct, root-cause-aligned fix. Warm starts and the per-process cache are unchanged; graceful on non-rename failures. The implementation in compiler.hpp/cache.hpp is correct and compiles. v3This PR fixes a real crash on the losing side of the JIT compile/rename race when the cache lives on a shared filesystem (NFS). Previously, after a failed rename the losing rank called get(dir_path) and asserted runtime != nullptr; on NFS the initial cache-miss can prime a negative lookup cache entry that outlives the failed rename, so the stat reports the winner's directory as missing and the rank dies. The fix makes the losing path self-sufficient: it loads the runtime from its own just-compiled temporary directory (equivalent artifacts, since the cache key hashes name+signature+flags+code), caches it under the canonical dir_path key via the new KernelRuntimeCache::put(), then removes the temp dir with safe_remove_all. I verified the correctness claims against the code:
The change is minimal (2 files), well-documented, turns a fatal crash into graceful recovery on any rename failure, and leaves the writer-side fsync/atomic-rename hardening and warm-start behavior unchanged. LGTM. Files reviewed: 2 |
…path invariant, debug-log the fallback branch
Fixes #683.
Problem
On a shared filesystem (the default
$HOME/.deep_epis typically NFS on multi-node clusters), the losing side of the compile/rename race inCompiler::build()crashes atEP_HOST_ASSERT(runtime != nullptr): its rename fails authoritatively at the server, but the winner's directory may not be visible to its NFS client yet, the initial cache miss primed a negative lookup cache entry that can outlive the failed rename. Full mechanism in #683.Change
Make the losing path self-sufficient instead of requiring cross-client visibility:
csrc/jit/compiler.hpp: on rename failure, load the runtime from this process's own just-compiled temp directory (equivalent artifacts. The cache key hashes kernel name, source, flags, and compiler signature), cache it under the canonicaldir_pathkey, then delete the temp directory.csrc/jit/cache.hpp: addKernelRuntimeCache::put(key_path, load_path)to construct from one path and cache under another.This is safe because the
KernelRuntimeconstructor consumes the files eagerly (cuobjdump -symbols, thencudaLibraryLoadFromFile/cuModuleLoad) and keeps only GPU handles. Warm starts still read the shared cache exactly as before; other rename failures (e.g. permissions) now degrade gracefully instead of asserting (scope: the rename failure itself — a subsequent failure to load the loser's own artifacts remains fatal, with the samecheck_validitydiagnostics as any other cache entry).Testing
Two 8-GPU H200 nodes (16 ranks), JIT cache on shared NFS, cold start,
ElasticBufferconstruction plus a dispatch/combine round trip per rank (EP_JIT_DEBUG=1):compiler.hpp:158while JIT-building the dispatch/combine kernels; the other node hangs in the next collective.Loading CUBIN: <cache>/tmp/<uuid>/kernel.cubin), and the sharedtmp/is empty afterwards.EP_JIT_DEBUGfallback log fired 75/75 times, each paired 1:1 with a tmp-dir CUBIN load, 16/16 ranks green.