-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Keep the CUDA memory pool warm between delegates #22312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
1528408
0211c01
0133ced
fb3889d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -470,6 +470,8 @@ class ET_EXPERIMENTAL CudaBackend final | |
|
|
||
| mutable_state_note_handle(handle); | ||
|
|
||
| live_handles_.fetch_add(1, std::memory_order_acq_rel); | ||
|
|
||
| return (DelegateHandle*)handle; // Return the handle post-processing | ||
| } | ||
|
|
||
|
|
@@ -864,6 +866,21 @@ class ET_EXPERIMENTAL CudaBackend final | |
|
|
||
| mutable_state_forget_handle(handle); | ||
|
|
||
| // Waited on before the stream reference goes, so the frees this handle | ||
| // queued are observed by the driver. Without this the pool trim at the end | ||
| // of teardown sees them as still pending and gives back nothing, and the | ||
| // work is being abandoned anyway. | ||
| if (handle->cuda_stream != nullptr && *handle->cuda_stream != nullptr) { | ||
| const cudaError_t sync_err = cudaStreamSynchronize(*handle->cuda_stream); | ||
| if (sync_err != cudaSuccess) { | ||
| ET_LOG( | ||
| Error, | ||
| "cudaStreamSynchronize failed during teardown: %s.", | ||
| cudaGetErrorString(sync_err)); | ||
| (void)cudaGetLastError(); | ||
| } | ||
| } | ||
|
|
||
| // The CUDA stream is managed by shared_ptr in the handle. | ||
| // It will be automatically destroyed when the last handle using it | ||
| // is destroyed. Just reset our reference. | ||
|
|
@@ -892,6 +909,14 @@ class ET_EXPERIMENTAL CudaBackend final | |
| } | ||
|
|
||
| delete handle; | ||
|
|
||
| // The allocator lets the device pool keep freed memory so that repeated | ||
| // delegate execution does not pay to map it again. Nothing is running on | ||
| // this backend once the last handle is gone, so hand that memory back | ||
| // rather than hold it for the life of the process. | ||
| if (live_handles_.fetch_sub(1, std::memory_order_acq_rel) == 1) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we only do this when
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It already is, one level down, so I have left the call as it is. release_cached_memory does nothing on ROCm: the body is guarded and the ROCm branch just discards the index. Guarding the call site too would repeat that, and the handle count around it is not CUDA specific, so it has to run on every build. Happy to add the guard anyway if you would rather see it stated at the call site. |
||
| CudaAllocator::release_cached_memory(-1); | ||
| } | ||
| } | ||
|
|
||
| private: | ||
|
|
@@ -906,6 +931,10 @@ class ET_EXPERIMENTAL CudaBackend final | |
| mutable std::mutex cuda_stream_mutex_; | ||
| std::shared_ptr<cudaStream_t> shared_cuda_stream_ = nullptr; | ||
|
|
||
| // Delegates alive right now. The device memory pool is shared, so it can only | ||
| // be released once none of them are left. | ||
| mutable std::atomic<size_t> live_handles_{0}; | ||
|
|
||
| // Whether to enable cross-method caching for legacy dense-blob artifacts. | ||
| // Toggled by the kWeightSharingAcrossMethods runtime backend option. Default | ||
| // OFF; versioned FQN artifacts do not consult this option. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the one I cannot settle. The mechanism reads as real and an independent review reached the same conclusion, but I have no MSVC toolchain, so I can neither reproduce it nor rule it out. If it holds, Windows keeps the unlimited retention threshold and loses the release entirely, which is worse than today rather than merely unimproved. Flagging it as the open item on this change and would value a check from someone who can build that row.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Following up on this: I went through the build files again and I do not think it holds, so I am withdrawing it as an open item.
The two copies are real. The shim library is shared, it does not export C++ symbols on that toolchain, and this source is compiled into the backend as well there, so there are two copies of the pool map.
The part I had not checked is whether the second copy is ever filled. It is not. Neither shim source references the allocator at all. The only production allocate and free calls come from the tensor storage header, which the backend includes, and that is the same place the release runs from. So both sides use the backend's copy, and the shim library's copy stays empty and has nothing to trim.
I still cannot build that row, so this is from reading the build files rather than running them. But it no longer looks like something that needs a second pair of eyes before this lands.