A service started by systemd without User= runs as root with no HOME, so the driver keeps no JIT cache and every cuFFT plan is compiled again (50-130 ms each, under cuFFT's process-wide lock) - seconds of MR in the broker. Before anything touches CUDA, the broker and rugnux now point CUDA_CACHE_PATH at /var/tmp/jfjoch-cuda-cache-<uid> when the default cache is missing or unusable and the site has set no CUDA_CACHE_PATH. The directory is used only if it is a real directory of this user's, mode 0700; otherwise the start-up warning stays as before. Measured without HOME: a cuFFT plan 51 ms -> 0.5 ms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PXc1zD1yxHqaR1DGdCTHL4
157 lines
6.2 KiB
C++
157 lines
6.2 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include "CudaJitCache.h"
|
|
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <filesystem>
|
|
|
|
#ifdef __linux__
|
|
#include <cerrno>
|
|
#include <cstring>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/vfs.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
namespace {
|
|
// Why UsePrivateCudaJitCacheIfNeeded moved the cache, for LogCudaJitCache; empty when it did not.
|
|
std::string private_cache_reason;
|
|
}
|
|
|
|
#ifdef __linux__
|
|
namespace {
|
|
// statfs f_type of the filesystems a home directory is likely to be on. A network one is not wrong in
|
|
// itself, but each cuFFT plan opens and locks the cache about ten times: at 1 ms a round trip that is
|
|
// 15-20 ms a plan, at 5 ms as slow as no cache.
|
|
struct FsType {
|
|
long magic;
|
|
const char *name;
|
|
};
|
|
|
|
constexpr FsType fs_types[] = {
|
|
{0x6969, "nfs"}, {0x5346414F, "afs"}, {0x6B414653, "afs"}, {0x47504653, "gpfs"},
|
|
{0x0BD00BD0, "lustre"}, {0x00C36400, "ceph"}, {0x19830326, "beegfs"}, {0xFF534D42, "cifs"},
|
|
{0xFE534D42, "smb2"}, {0x517B, "smb"}, {0x01021997, "9p"}, {0x65735546, "fuse"},
|
|
{0x58465342, "xfs"}, {0xEF53, "ext4"}, {0x9123683E, "btrfs"}, {0x01021994, "tmpfs"},
|
|
{0x794C7630, "overlay"}, {0x2FC12FC1, "zfs"},
|
|
};
|
|
}
|
|
#endif
|
|
|
|
CudaJitCache InspectCudaJitCache() {
|
|
CudaJitCache c;
|
|
const char *path = std::getenv("CUDA_CACHE_PATH");
|
|
const char *disable = std::getenv("CUDA_CACHE_DISABLE");
|
|
const char *maxsize = std::getenv("CUDA_CACHE_MAXSIZE");
|
|
|
|
if (path != nullptr && *path != '\0')
|
|
c.path = path;
|
|
#ifdef _WIN32
|
|
else
|
|
c.path = "%APPDATA%\\NVIDIA\\ComputeCache";
|
|
#else
|
|
else if (const char *home = std::getenv("HOME"); home != nullptr && *home != '\0')
|
|
c.path = std::string(home) + "/.nv/ComputeCache";
|
|
#endif
|
|
|
|
if (disable != nullptr && std::string(disable) == "1")
|
|
c.problem = "CUDA_CACHE_DISABLE=1 turns it off";
|
|
else if (maxsize != nullptr && std::strtoull(maxsize, nullptr, 10) < 64 * 1024 * 1024)
|
|
c.problem = fmt::format("CUDA_CACHE_MAXSIZE={} keeps too little of it", maxsize);
|
|
else if (c.path.empty())
|
|
c.problem = "neither CUDA_CACHE_PATH nor HOME is set (a systemd service without User= has no HOME)";
|
|
|
|
#ifdef __linux__
|
|
if (c.path.empty())
|
|
return c;
|
|
// The driver makes the directory when it is missing, so the nearest one that exists must be writable.
|
|
std::error_code ec;
|
|
std::filesystem::path dir = c.path;
|
|
while (!std::filesystem::exists(dir, ec) && dir.has_parent_path() && dir != dir.parent_path())
|
|
dir = dir.parent_path();
|
|
|
|
struct statfs s{};
|
|
if (statfs(dir.c_str(), &s) == 0) {
|
|
c.filesystem = fmt::format("{:#x}", static_cast<unsigned long>(s.f_type));
|
|
for (const auto &t: fs_types)
|
|
if (static_cast<unsigned long>(s.f_type) == static_cast<unsigned long>(t.magic))
|
|
c.filesystem = t.name;
|
|
}
|
|
|
|
if (!c.problem.empty())
|
|
return c;
|
|
if (access(dir.c_str(), W_OK | X_OK) != 0) {
|
|
c.problem = fmt::format("{} is not writable ({})", dir.string(), std::strerror(errno));
|
|
return c;
|
|
}
|
|
// Each lookup opens the index read-write and read-locks it with fcntl: where either fails (a read-only
|
|
// cache, a filesystem without POSIX locks) the driver compiles every time, though the cache is there.
|
|
const std::string index = c.path + "/index";
|
|
if (std::filesystem::exists(index, ec)) {
|
|
const int fd = open(index.c_str(), O_RDWR);
|
|
if (fd < 0) {
|
|
c.problem = fmt::format("its index cannot be opened for writing ({})", std::strerror(errno));
|
|
return c;
|
|
}
|
|
struct flock lock{};
|
|
lock.l_type = F_RDLCK;
|
|
lock.l_whence = SEEK_SET;
|
|
if (fcntl(fd, F_SETLK, &lock) != 0 && errno != EAGAIN && errno != EACCES)
|
|
c.problem = fmt::format("its index cannot be locked ({})", std::strerror(errno));
|
|
lock.l_type = F_UNLCK;
|
|
fcntl(fd, F_SETLK, &lock);
|
|
close(fd);
|
|
}
|
|
#endif
|
|
return c;
|
|
}
|
|
|
|
void UsePrivateCudaJitCacheIfNeeded() {
|
|
#ifdef __linux__
|
|
if (const char *path = std::getenv("CUDA_CACHE_PATH"); path != nullptr && *path != '\0')
|
|
return;
|
|
if (const char *disable = std::getenv("CUDA_CACHE_DISABLE"); disable != nullptr && std::string(disable) == "1")
|
|
return;
|
|
const CudaJitCache c = InspectCudaJitCache();
|
|
if (c.problem.empty())
|
|
return;
|
|
|
|
// /var/tmp is shared with every user: the directory is used only if it is a real directory (not a
|
|
// symlink), this user's, and closed to everyone else.
|
|
const std::string dir = fmt::format("/var/tmp/jfjoch-cuda-cache-{}", getuid());
|
|
if (mkdir(dir.c_str(), 0700) != 0 && errno != EEXIST)
|
|
return;
|
|
struct stat st{};
|
|
if (lstat(dir.c_str(), &st) != 0 || !S_ISDIR(st.st_mode) || st.st_uid != getuid() || (st.st_mode & 077) != 0)
|
|
return;
|
|
setenv("CUDA_CACHE_PATH", dir.c_str(), 1);
|
|
private_cache_reason = c.problem;
|
|
#endif
|
|
}
|
|
|
|
void LogCudaJitCache(Logger &logger, double plan_ms) {
|
|
if (std::isnan(plan_ms))
|
|
return;
|
|
const CudaJitCache c = InspectCudaJitCache();
|
|
// Measured: 0.4-1.7 ms from a working cache, 60-130 ms with none.
|
|
const bool slow = plan_ms > 10.0;
|
|
const std::string where = c.path.empty() ? std::string("none")
|
|
: c.filesystem.empty() ? c.path : fmt::format("{} ({})", c.path, c.filesystem);
|
|
if (c.problem.empty() && !slow) {
|
|
logger.Info("CUDA JIT cache {}{}: a cuFFT plan takes {:.1f} ms", where,
|
|
private_cache_reason.empty() ? std::string()
|
|
: fmt::format(", used because {}", private_cache_reason),
|
|
plan_ms);
|
|
return;
|
|
}
|
|
logger.Warning("CUDA JIT cache {}: {}a cuFFT plan takes {:.0f} ms{}, which can make molecular "
|
|
"replacement several times slower. Give the process a cache "
|
|
"on a local disk that only its user can write: CUDA_CACHE_PATH=<dir>, for instance "
|
|
"Environment=CUDA_CACHE_PATH=/var/cache/jfjoch/nv in a systemd unit",
|
|
where, c.problem.empty() ? std::string() : c.problem + "; ", plan_ms,
|
|
slow ? " (~1 ms from a working cache)" : "");
|
|
}
|