Summary
VirtualMemoryResource keeps every physical allocation it ever made until the process exits. allocate() creates the physical memory with cuMemCreate, maps it, and never releases the handle that cuMemCreate returned. deallocate() retains the handle for the pointer, unmaps, frees the address range, and releases only the reference it just retained. The reference from cuMemCreate is never dropped, and the driver frees the memory only when all mappings are unmapped and all handle references are released (cuMemRelease).
_grow_allocation_slow_path() has the same shape: it retains the old handle to map it into the new range and never releases that reference after the transaction commits, so a grown buffer holds two unreleased references.
Evidence
On an H100 (CUDA 13.4 driver), allocating and closing 64 buffers of 64 MiB each through VirtualMemoryResource:
VMM_PROBE allocated_and_closed=4096MiB free_before=80544MiB free_after=76448MiB retained_after_close=4096MiB
Free device memory drops by exactly the amount allocated and does not come back after Buffer.close().
Suggested fix
allocate(): call cuMemRelease(handle) after cuMemMap succeeds, the way the programming guide's examples do; the mapping keeps the memory alive.
_grow_allocation_slow_path(): release the retained old handle after the new mapping is in place (and drop the release from the undo list at that point).
- Add a test that checks free memory before and after an allocate/close cycle, with a tolerance for allocator noise.
Refs: found while fixing #2877.
Summary
VirtualMemoryResourcekeeps every physical allocation it ever made until the process exits.allocate()creates the physical memory withcuMemCreate, maps it, and never releases the handle thatcuMemCreatereturned.deallocate()retains the handle for the pointer, unmaps, frees the address range, and releases only the reference it just retained. The reference fromcuMemCreateis never dropped, and the driver frees the memory only when all mappings are unmapped and all handle references are released (cuMemRelease)._grow_allocation_slow_path()has the same shape: it retains the old handle to map it into the new range and never releases that reference after the transaction commits, so a grown buffer holds two unreleased references.Evidence
On an H100 (CUDA 13.4 driver), allocating and closing 64 buffers of 64 MiB each through
VirtualMemoryResource:Free device memory drops by exactly the amount allocated and does not come back after
Buffer.close().Suggested fix
allocate(): callcuMemRelease(handle)aftercuMemMapsucceeds, the way the programming guide's examples do; the mapping keeps the memory alive._grow_allocation_slow_path(): release the retained old handle after the new mapping is in place (and drop the release from the undo list at that point).Refs: found while fixing #2877.