-
Notifications
You must be signed in to change notification settings - Fork 524
Fix Windows shutdown deadlock and leak with the Ruy backend #2076
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
Open
timo9378
wants to merge
4
commits into
OpenNMT:master
Choose a base branch
from
timo9378:fix-windows-ruy-shutdown-deadlock
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
48c8216
Fix Windows shutdown deadlock and leak with the Ruy backend
timo9378 c5c28b6
Scope the Ruy shutdown fix to Windows and add a regression test
timo9378 28e44fb
Add a standalone C++ repro for the Windows Ruy shutdown deadlock
timo9378 b804be6
Merge branch 'master' into fix-windows-ruy-shutdown-deadlock
timo9378 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| cmake_minimum_required(VERSION 3.15) | ||
| project(ct2_ruy_shutdown_repro CXX) | ||
|
|
||
| # Point this at a CTranslate2 source checkout (submodules initialized). | ||
| # cmake -DCT2_DIR=/path/to/CTranslate2 ... | ||
| if(NOT DEFINED CT2_DIR) | ||
| message(FATAL_ERROR "Pass -DCT2_DIR=<path to CTranslate2 source checkout>") | ||
| endif() | ||
|
|
||
| # Ruy-only CPU build: this is the backend that exhibits the shutdown deadlock. | ||
| set(WITH_RUY ON CACHE BOOL "" FORCE) | ||
| set(WITH_MKL OFF CACHE BOOL "" FORCE) | ||
| set(WITH_DNNL OFF CACHE BOOL "" FORCE) | ||
| set(WITH_CUDA OFF CACHE BOOL "" FORCE) | ||
| set(BUILD_CLI OFF CACHE BOOL "" FORCE) | ||
| set(OPENMP_RUNTIME "COMP" CACHE STRING "" FORCE) | ||
|
|
||
| # Build CTranslate2 as part of this project so the `ctranslate2` target carries all | ||
| # transitive static deps (ruy, cpu_features, ...) automatically. | ||
| add_subdirectory(${CT2_DIR} ctranslate2_build) | ||
|
|
||
| add_executable(repro repro.cpp) | ||
| target_link_libraries(repro PRIVATE ctranslate2) | ||
| target_compile_features(repro PRIVATE cxx_std_17) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| # Windows Ruy shutdown deadlock — minimal repro | ||
|
|
||
| Self-contained repro for the shutdown hang fixed by | ||
| [OpenNMT/CTranslate2#2076](https://github.com/OpenNMT/CTranslate2/pull/2076) | ||
| (reported downstream as jkawamoto/ctranslate2-rs#64). | ||
|
|
||
| `repro.cpp` builds a CPU int8 `Translator` (Ruy backend) with worker threads, runs one | ||
| large translation batch, then destroys the `Translator`. On an unpatched build the | ||
| per-thread `ruy::Context` destructor joins Ruy's thread pool from a worker thread that | ||
| is exiting under the Windows loader lock, and that join deadlocks. | ||
|
|
||
| ## The one thing that matters: batch size | ||
|
|
||
| The batch must be large enough that Ruy actually spawns its internal thread pool. The | ||
| destructor only deadlocks when there are Ruy worker threads to join; a tiny batch runs | ||
| single-threaded, has nothing to join, and shuts down cleanly even unpatched. That is | ||
| easy to trip over when writing a test. This repro uses a 512-sentence batch. | ||
|
|
||
| It is *not* specific to a CRT model or link mode. Measured on Windows 11, MSVC 14.44, | ||
| x64, unpatched CTranslate2 `0d8bcd36`, Ruy int8, `intra_threads=4`, `inter_threads=2`, | ||
| 512-sentence batch: | ||
|
|
||
| | Build of CTranslate2 + this repro | Result | | ||
| |---|---| | ||
| | static lib + static CRT (`/MT`) | **hangs on shutdown** | | ||
| | shared lib + dynamic CRT (`/MD`) | **hangs on shutdown** | | ||
|
|
||
| The `/MD` shared build is the configuration of the official wheels, so they are affected | ||
| too. Rebuilding either with #2076 applied, both exit cleanly (`SURVIVED`). | ||
|
|
||
| ## Build & run | ||
|
|
||
| Needs CMake, MSVC, and a CTranslate2 source checkout with submodules initialized | ||
| (`git submodule update --init --recursive`). No CUDA / MKL / oneDNN required. `CT2_DIR` | ||
| points at a CTranslate2 source tree; from here in the tree that is the repo root, | ||
| `../..`. Run these from this directory (`tools/ruy_shutdown_repro`): | ||
|
|
||
| ```sh | ||
| cmake -G "Visual Studio 17 2022" -A x64 \ | ||
| -DCMAKE_POLICY_DEFAULT_CMP0091=NEW \ | ||
| -DCT2_DIR=../.. \ | ||
| -DBUILD_SHARED_LIBS=OFF -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded \ | ||
| -S . -B build | ||
| cmake --build build --config Release --target repro | ||
| build/Release/repro.exe ../../tests/data/models/v2/aren-transliteration | ||
| ``` | ||
|
|
||
| For the shared `/MD` variant use `-DBUILD_SHARED_LIBS=ON | ||
| -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL`, and put the built `ctranslate2.dll` | ||
| (under `build/ctranslate2_build/Release`) on `PATH` before running. | ||
|
|
||
| Unpatched, the run prints `destroying Translator ...` and then hangs (kill it). With | ||
| #2076 applied it prints `SURVIVED: clean shutdown, no deadlock`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| // Minimal repro for the Windows Ruy shutdown deadlock (ctranslate2-rs#64 / #2076). | ||
| // | ||
| // It creates a CPU int8 Translator (Ruy backend) with worker threads, runs one | ||
| // translation, then destroys the Translator. On an unpatched build the per-thread | ||
| // ruy::Context destructor joins Ruy's internal thread pool from a worker thread | ||
| // that is exiting under the Windows loader lock, and that join deadlocks. | ||
| // | ||
| // The point of this repro: it only hangs when CTranslate2 is built with the STATIC | ||
| // CRT (/MT) and linked statically. Built with the dynamic CRT (/MD) as a shared | ||
| // library -- the configuration of the official wheels -- the same code shuts down | ||
| // cleanly. See README.md. | ||
| #include <ctranslate2/translator.h> | ||
| #include <ctranslate2/models/model.h> | ||
|
|
||
| #include <iostream> | ||
|
|
||
| int main(int argc, char** argv) { | ||
| if (argc < 2) { | ||
| std::cerr << "usage: repro <model_dir>\n"; | ||
| return 2; | ||
| } | ||
|
|
||
| ctranslate2::ReplicaPoolConfig pool_config; | ||
| pool_config.num_threads_per_replica = 4; // intra_threads: give Ruy a real thread pool | ||
|
|
||
| ctranslate2::models::ModelLoader model_loader(argv[1]); | ||
| model_loader.device = ctranslate2::Device::CPU; | ||
| model_loader.compute_type = ctranslate2::ComputeType::INT8; | ||
| model_loader.num_replicas_per_device = 2; // inter_threads: use worker threads | ||
|
|
||
| // A large batch so the int8 GEMM is big enough that Ruy actually spawns its | ||
| // internal thread pool. That is a precondition for the hang: the destructor only | ||
| // deadlocks if there are Ruy worker threads to join. A tiny batch runs | ||
| // single-threaded and shuts down cleanly even unpatched. | ||
| const std::vector<std::string> sentence = {"آ", "ت", "ز", "م", "و", "ن"}; | ||
| const std::vector<std::vector<std::string>> source(512, sentence); | ||
|
|
||
| std::cout << "translating a batch of " << source.size() << "..." << std::endl; | ||
| { | ||
| ctranslate2::Translator translator(model_loader, pool_config); | ||
| const auto results = translator.translate_batch(source); | ||
| std::cout << "output[0]:"; | ||
| for (const auto& token : results[0].hypotheses[0]) | ||
| std::cout << ' ' << token; | ||
| std::cout << "\ndestroying Translator (Ruy thread-pool join happens here)..." | ||
| << std::endl; | ||
| } // <-- unpatched + static /MT: the join deadlocks here and the process hangs. | ||
| std::cout << "SURVIVED: clean shutdown, no deadlock" << std::endl; | ||
| return 0; | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.