Skip to content

Keep the CUDA memory pool warm between delegates - #22312

Open
shoumikhin wants to merge 1 commit into
pytorch:mainfrom
shoumikhin:fix/cuda-mempool-release-threshold
Open

Keep the CUDA memory pool warm between delegates#22312
shoumikhin wants to merge 1 commit into
pytorch:mainfrom
shoumikhin:fix/cuda-mempool-release-threshold

Conversation

@shoumikhin

@shoumikhin shoumikhin commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

The CUDA delegate allocates through the stream ordered allocator, whose pool
hands physical memory back to the driver whenever a synchronization observes a
pending free. With the default release threshold of zero that happens repeatedly
during one inference, so nearly every allocation has to map memory again.

Raising the threshold lets the pool keep the memory it already has. The threshold
caps what the pool may keep rather than granting extra headroom, so any fixed
value silently stops helping once a model's footprint passes it: a 64 MiB setting
against a 256 MiB working set still costs about 259 microseconds per allocation,
and at 1 GiB it is no better than no threshold at all. The maximum makes the win
independent of model size, and it is what PyTorch's own async allocator sets.

Because the memory is then held rather than returned at each synchronize, the
backend gives it back explicitly when the last delegate handle is destroyed.

The pool being configured is the device default pool, which is shared with every
other user of the async allocator in this process. So this reads the current
threshold, only ever raises it, remembers the previous value per device, and puts
it back on release, rather than overwriting a setting another user chose.

Two details that decide whether any of it works. The release trims every device
this code raised, taken from its own record, not whichever device the destroying
thread happens to be current on, which on a multi GPU host is often not the
device the model ran on. And it synchronizes before trimming, because the pool
can only release a free the driver has already observed and nothing else on the
teardown path waits.

A failure to configure the pool only costs speed, so it is logged and execution
continues, but the CUDA error is cleared rather than left latched for the next
kernel launch check to report against an unrelated kernel.

Test plan:

Three tests in backends/cuda/runtime/test/test_cuda_allocator.cpp. Each one
allocates, frees, synchronizes, and asserts the pool retained the block before
releasing it, so none of them can pass when the change is absent. The live
allocation test also checks that reserved memory falls but stays above the block
still in use, rather than asserting a driver guarantee that holds either way.

Measured, per allocation, allocating and freeing with a synchronize between:

Orin Nano 3790.79 us before, 2.34 us after
Thor 363.00 us before, 1.51 us after
H100 40.30 us before, 1.01 us after
A100 18.60 us before, 1.03 us after

A model split into 25 delegates went from about 714 to about 518 microseconds
median on an H100. Peak reserved memory is unchanged on a model whose footprint
fits in one pool block.

The pool calls have no equivalent in the HIP compatibility header, so both the
allocator's pool code and the three tests are compiled out on ROCm, and the
change is a no-op there.

Not measured: Windows, and whether a second copy of this translation unit in one
process keeps its own pool record.

@shoumikhin shoumikhin added the release notes: runtime Changes related to the core runtime which loads the program methods, initializes delegates, and runs label Aug 29, 2026
Copilot AI lite review requested due to automatic review settings August 29, 2026 17:52
@shoumikhin shoumikhin added the release notes: runtime Changes related to the core runtime which loads the program methods, initializes delegates, and runs label Aug 29, 2026
@pytorch-bot

pytorch-bot Bot commented Aug 29, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/22312

Note: Links to docs will display an error until the docs builds have been completed.

✅ You can merge normally! (1 Unrelated Failure)

As of commit 4fef258 with merge base c27baa8 (image):

BROKEN TRUNK - The following job failed but were present on the merge base:

👉 Rebase onto the `viable/strict` branch to avoid these failures

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Aug 29, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@linux-foundation-easycla

linux-foundation-easycla Bot commented Aug 29, 2026

Copy link
Copy Markdown

CLA Missing ID

  • ❌ The email address for the commit (4fef258) is not linked to the GitHub account, preventing the EasyCLA check. Consult this Help Article and GitHub Help to resolve. (To view the commit's email address, add .patch at the end of this PR page's URL.) For further assistance with EasyCLA, please visit our EasyCLA portal and chat with our support bot.

@shoumikhin
shoumikhin marked this pull request as draft August 29, 2026 19:36
@shoumikhin
shoumikhin force-pushed the fix/cuda-mempool-release-threshold branch from ba2b029 to ea11a4d Compare August 29, 2026 21:41
@shoumikhin shoumikhin changed the title Keep the CUDA memory pool from releasing memory between delegates Keep the CUDA memory pool warm between delegates Aug 29, 2026
@shoumikhin
shoumikhin marked this pull request as ready for review August 29, 2026 21:41
Copilot AI review requested due to automatic review settings August 29, 2026 21:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@shoumikhin
shoumikhin force-pushed the fix/cuda-mempool-release-threshold branch from ea11a4d to aab59d3 Compare August 30, 2026 18:02
Copilot AI review requested due to automatic review settings August 30, 2026 18:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

The CUDA delegate allocates through the stream ordered allocator, whose pool
hands physical memory back to the driver whenever a synchronization observes a
pending free. With the default release threshold of zero that happens repeatedly
during one inference, so nearly every allocation has to map memory again.

Raising the threshold lets the pool keep the memory it already has. The threshold
caps what the pool may keep rather than granting extra headroom, so any fixed
value silently stops helping once a model's footprint passes it: a 64 MiB setting
against a 256 MiB working set still costs about 259 microseconds per allocation,
and at 1 GiB it is no better than no threshold at all. The maximum makes the win
independent of model size, and it is what PyTorch's own async allocator sets.

Because the memory is then held rather than returned at each synchronize, the
backend gives it back explicitly when the last delegate handle is destroyed.

The pool being configured is the device default pool, which is shared with every
other user of the async allocator in this process. So this reads the current
threshold, only ever raises it, remembers the previous value per device, and puts
it back on release, rather than overwriting a setting another user chose.

Two details that decide whether any of it works. The release trims every device
this code raised, taken from its own record, not whichever device the destroying
thread happens to be current on, which on a multi GPU host is often not the
device the model ran on. And it synchronizes before trimming, because the pool
can only release a free the driver has already observed and nothing else on the
teardown path waits.

A failure to configure the pool only costs speed, so it is logged and execution
continues, but the CUDA error is cleared rather than left latched for the next
kernel launch check to report against an unrelated kernel.

Test plan:

Three tests in backends/cuda/runtime/test/test_cuda_allocator.cpp. Each one
allocates, frees, synchronizes, and asserts the pool retained the block before
releasing it, so none of them can pass when the change is absent. The live
allocation test also checks that reserved memory falls but stays above the block
still in use, rather than asserting a driver guarantee that holds either way.

Measured, per allocation, allocating and freeing with a synchronize between:

  Orin Nano  3790.79 us before, 2.34 us after
  Thor        363.00 us before, 1.51 us after
  H100         40.30 us before, 1.01 us after
  A100         18.60 us before, 1.03 us after

A model split into 25 delegates went from about 714 to about 518 microseconds
median on an H100. Peak reserved memory is unchanged on a model whose footprint
fits in one pool block.

The pool calls have no equivalent in the HIP compatibility header, so both the
allocator's pool code and the three tests are compiled out on ROCm, and the
change is a no-op there.

Not measured: Windows, and whether a second copy of this translation unit in one
process keeps its own pool record.
@shoumikhin
shoumikhin force-pushed the fix/cuda-mempool-release-threshold branch from aab59d3 to 4fef258 Compare August 30, 2026 19:27
Copilot AI review requested due to automatic review settings August 30, 2026 19:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. release notes: runtime Changes related to the core runtime which loads the program methods, initializes delegates, and runs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants