Repository navigation
Keep a shared thread pool alive when the thread count is reset - #24770
shoumikhin wants to merge 1 commit into
Conversation
`_unsafe_reset_threadpool()` destroyed the current pthreadpool and created a new one. Delegates such as XNNPACK get that pthreadpool from `get_pthreadpool()` when a method is loaded and keep using it, so after a reset the loaded method ran on a freed pool and crashed or hung. From Python the reset is the only way to set the thread count, so calling it after `load_method` was enough to hit this. Now a pthreadpool that `get_pthreadpool()` has handed out stays alive with its size when a reset replaces it. XNNPACK methods loaded before the reset keep running on it, and methods loaded after it use the new pool. A reset back to a kept size reuses that pool, so at most one pool per size is kept, and `_unsafe_destroy_threadpool()` frees the kept pools too. A pool that was never handed out is destroyed as before, so a reset before loading costs nothing extra. Resizing a pool in place is not an option, because XNNPACK sizes per-thread buffers by the thread count when it plans a method. Fixes pytorch#24765
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/24770
Note: Links to docs will display an error until the docs builds have been completed. ❌ You can merge normally! (2 Unrelated Failures), 3 Unclassified FailuresAs of commit ce80ef8 with merge base 75af78c ( UNCLASSIFIED FAILURES - DrCI could not classify the following jobs because the workflow did not run on the merge base. The failures may be pre-existing on trunk or introduced by this PR:
FLAKY - The following job failed but was likely due to flakiness present on trunk:
BROKEN TRUNK - The following job failed but was 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. |
|
@claude /pr-review |
|
Claude finished @shoumikhin's task in 25m 5s —— View job This stops XNNPACK methods from running on a freed pool, but Detailed reviewPR Review: #24770SummaryA pthreadpool that API Design
Testing
RecommendationNeeds Discussion The blocker is a maintainer decision on how kept pools are owned. The PR's way is a sticky flag that any
|
What is wrong today
From Python,
_unsafe_reset_threadpool(n)is the only way to set the CPU thread count. If it runs after a method that uses XNNPACK is loaded, the next runs can crash or hang:On Linux x86_64 with an 8-thread default pool, a reset to 16 after loading crashed in 10 of 12 runs, and a reset to 4 also in 10 of 12. The Android bindings reset the pool each time a
Moduleis created, so a secondModulecan hit it too.Why it happens
The reset destroys the current pthreadpool and creates a new one. But XNNPACK got that pthreadpool from
get_pthreadpool()when the method was loaded, and it keeps using it. So the loaded method runs on a freed pool.Resizing the pool in place is not safe either. XNNPACK sizes its per-thread buffers by the thread count when it plans a method, and it does not plan again when the count changes.
What this change does
A reset no longer destroys a pthreadpool that
get_pthreadpool()has handed out. It stays alive with its size, next to the new one:_unsafe_destroy_threadpool()frees kept pools too.The header and the Python docstring now explain this.
Tests
ThreadPoolTest.ResetKeepsHandedOutPthreadpoolUsable: after a reset, the held pool still runs work on 2 threads, and a reset back to its size reuses it. It fails without the fix and passes with it.extension_threadpool_test, the native CPU engine tests, the Python tests for the pybindings and XNNPACK, and the LLM custom op tests that call the reset, on Linux x86_64. Tests that failed there fail on main too.Fixes #24765