fix: thread_local reentrant guard — prevent cudaMalloc infinite recursion
CUB CachingDeviceAllocator 内部在 cache miss 时调 cudaMalloc, 被我们的 LD_PRELOAD 再次拦截 → DeviceAllocate → cudaMalloc → 无限递归 → segfault。 加 thread_local bool inside_cub 标志: 外部调用 → CUB allocator (带缓存) CUB 内部调用 → 直接走 dlsym(RTLD_NEXT) 的真实 cudaMalloc
This commit is contained in:
@@ -8,6 +8,10 @@
|
|||||||
* caching allocator. Strips expandable_segments from
|
* caching allocator. Strips expandable_segments from
|
||||||
* PYTORCH_CUDA_ALLOC_CONF before libtorch reads it.
|
* PYTORCH_CUDA_ALLOC_CONF before libtorch reads it.
|
||||||
*
|
*
|
||||||
|
* CRITICAL: CUB's allocator internally calls cudaMalloc/cudaFree for
|
||||||
|
* cache misses. We use a thread-local reentrant guard so internal calls
|
||||||
|
* go straight to the real CUDA runtime, avoiding infinite recursion.
|
||||||
|
*
|
||||||
* Source: CCCL cub/cub/util_allocator.cuh (BSD-3, NVIDIA)
|
* Source: CCCL cub/cub/util_allocator.cuh (BSD-3, NVIDIA)
|
||||||
* Build: bash build_cccl_preload.sh
|
* Build: bash build_cccl_preload.sh
|
||||||
*/
|
*/
|
||||||
@@ -24,32 +28,21 @@
|
|||||||
|
|
||||||
/* ========================================================================
|
/* ========================================================================
|
||||||
* Configuration for BI-V100 (32GB × 4 cards)
|
* Configuration for BI-V100 (32GB × 4 cards)
|
||||||
*
|
|
||||||
* CUB CachingDeviceAllocator parameters:
|
|
||||||
* bin_growth = 2 (power-of-2 bins: 256B, 512B, 1KB, ... 4GB)
|
|
||||||
* min_bin = 8 (2^8 = 256B minimum allocation)
|
|
||||||
* max_bin = 32 (2^32 = 4GB maximum cached bin)
|
|
||||||
* max_cached = 8GB per device
|
|
||||||
*
|
|
||||||
* More granular bins (growth=2) than CUB default (growth=8) because
|
|
||||||
* PyTorch tensor sizes vary widely in inference.
|
|
||||||
* ======================================================================== */
|
* ======================================================================== */
|
||||||
|
|
||||||
static constexpr unsigned int ALLOC_BIN_GROWTH = 2;
|
static constexpr unsigned int ALLOC_BIN_GROWTH = 2;
|
||||||
static constexpr unsigned int ALLOC_MIN_BIN = 8; /* 256 bytes */
|
static constexpr unsigned int ALLOC_MIN_BIN = 8; /* 2^8 = 256 bytes */
|
||||||
static constexpr unsigned int ALLOC_MAX_BIN = 32; /* 4 GB */
|
static constexpr unsigned int ALLOC_MAX_BIN = 32; /* 2^32 = 4 GB */
|
||||||
static constexpr size_t ALLOC_MAX_CACHED = (size_t)8 * 1024 * 1024 * 1024; /* 8GB */
|
static constexpr size_t ALLOC_MAX_CACHED = (size_t)8 * 1024 * 1024 * 1024;
|
||||||
|
|
||||||
/* ---- Global allocator singleton ---- */
|
/* ---- Global allocator singleton ---- */
|
||||||
using CubAllocator = CUB_NS_QUALIFIER::CachingDeviceAllocator;
|
using CubAllocator = CUB_NS_QUALIFIER::CachingDeviceAllocator;
|
||||||
|
|
||||||
static CubAllocator& get_allocator() {
|
static CubAllocator& get_allocator() {
|
||||||
static CubAllocator instance(
|
static CubAllocator instance(
|
||||||
ALLOC_BIN_GROWTH,
|
ALLOC_BIN_GROWTH, ALLOC_MIN_BIN, ALLOC_MAX_BIN,
|
||||||
ALLOC_MIN_BIN,
|
|
||||||
ALLOC_MAX_BIN,
|
|
||||||
ALLOC_MAX_CACHED,
|
ALLOC_MAX_CACHED,
|
||||||
true /* skip_cleanup: CoreX may tear down CUDA before our dtor */
|
true /* skip_cleanup: CoreX may tear down CUDA before dtor */
|
||||||
);
|
);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
@@ -57,6 +50,17 @@ static CubAllocator& get_allocator() {
|
|||||||
static bool g_preload_active = false;
|
static bool g_preload_active = false;
|
||||||
static bool g_debug = false;
|
static bool g_debug = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reentrant guard: CUB's DeviceAllocate/DeviceFree internally call
|
||||||
|
* cudaMalloc/cudaFree (for cache misses and evictions). Without this
|
||||||
|
* guard, our intercept would call DeviceAllocate again → infinite
|
||||||
|
* recursion → stack overflow → segfault.
|
||||||
|
*
|
||||||
|
* When inside_cub == true, cudaMalloc/cudaFree go straight to the
|
||||||
|
* real CUDA runtime via dlsym(RTLD_NEXT).
|
||||||
|
*/
|
||||||
|
static thread_local bool inside_cub = false;
|
||||||
|
|
||||||
/* ---- Real cudaMalloc/cudaFree via dlsym(RTLD_NEXT) ---- */
|
/* ---- Real cudaMalloc/cudaFree via dlsym(RTLD_NEXT) ---- */
|
||||||
using RealMalloc_t = cudaError_t (*)(void**, size_t);
|
using RealMalloc_t = cudaError_t (*)(void**, size_t);
|
||||||
using RealFree_t = cudaError_t (*)(void*);
|
using RealFree_t = cudaError_t (*)(void*);
|
||||||
@@ -109,11 +113,8 @@ static void cccl_preload_init() {
|
|||||||
alloc_conf, clean.empty() ? "(unset)" : clean.c_str());
|
alloc_conf, clean.empty() ? "(unset)" : clean.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Initialize allocator */
|
|
||||||
auto& alloc = get_allocator();
|
auto& alloc = get_allocator();
|
||||||
if (g_debug) {
|
if (g_debug) alloc.debug = true;
|
||||||
alloc.debug = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_preload_active = true;
|
g_preload_active = true;
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
@@ -125,20 +126,29 @@ static void cccl_preload_init() {
|
|||||||
|
|
||||||
/* ========================================================================
|
/* ========================================================================
|
||||||
* cudaMalloc / cudaFree intercepts
|
* cudaMalloc / cudaFree intercepts
|
||||||
|
*
|
||||||
|
* outside CUB → route to CUB CachingDeviceAllocator (bin + cache)
|
||||||
|
* inside CUB → pass through to real cudaMalloc/cudaFree (no recursion)
|
||||||
* ======================================================================== */
|
* ======================================================================== */
|
||||||
|
|
||||||
extern "C" cudaError_t cudaMalloc(void** devPtr, size_t size)
|
extern "C" cudaError_t cudaMalloc(void** devPtr, size_t size)
|
||||||
{
|
{
|
||||||
if (!g_preload_active) {
|
if (!g_preload_active || inside_cub) {
|
||||||
return get_real_malloc()(devPtr, size);
|
return get_real_malloc()(devPtr, size);
|
||||||
}
|
}
|
||||||
return get_allocator().DeviceAllocate(devPtr, size);
|
inside_cub = true;
|
||||||
|
cudaError_t err = get_allocator().DeviceAllocate(devPtr, size);
|
||||||
|
inside_cub = false;
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" cudaError_t cudaFree(void* devPtr)
|
extern "C" cudaError_t cudaFree(void* devPtr)
|
||||||
{
|
{
|
||||||
if (!g_preload_active || devPtr == nullptr) {
|
if (!g_preload_active || inside_cub || devPtr == nullptr) {
|
||||||
return get_real_free()(devPtr);
|
return get_real_free()(devPtr);
|
||||||
}
|
}
|
||||||
return get_allocator().DeviceFree(devPtr);
|
inside_cub = true;
|
||||||
|
cudaError_t err = get_allocator().DeviceFree(devPtr);
|
||||||
|
inside_cub = false;
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user