Skip to content

[None][fix] Release GIL around cudaLaunchHostFunc to fix guided-decoding host-func deadlock#17

Open
sopwg612 wants to merge 1 commit into
deep-main-v1.3.0rc16from
fix/guided-hostfunc-gil-deadlock-deep-main-rc16
Open

[None][fix] Release GIL around cudaLaunchHostFunc to fix guided-decoding host-func deadlock#17
sopwg612 wants to merge 1 commit into
deep-main-v1.3.0rc16from
fix/guided-hostfunc-gil-deadlock-deep-main-rc16

Conversation

@sopwg612

@sopwg612 sopwg612 commented Jul 1, 2026

Copy link
Copy Markdown

Summary

launchHostFunc held the Python GIL across cudaLaunchHostFunc. The CUDA driver serializes host-function dispatch on a stream, so on the eager (non-capturing) path a launch issued while a callback is in flight busy-waits in the driver. That in-flight callback runs cudaHostFuncTrampoline, which reacquires the GIL to execute Python (e.g. xgrammar in guided decoding). With the launching thread holding the GIL, the callback can never acquire it -> circular deadlock, zero forward progress, no crash.

Triggered by Eagle3 speculative decoding + xgrammar guided decoding. Captured live via py-spy --native on both TP ranks of a wedged engine:

  • Thread-3 (holds GIL): sched_yield -> libcuda -> launch_hostfunc -> GuidedDecoder.execute -> eagle3.forward
  • host-func worker: ~gil_scoped_release (reacquiring GIL) -> xgrammar fill_next_token_bitmask -> GuidedDecoder.build

Root cause

Regression from NVIDIA#13251, which correctly removed the call_guard (so the nanobind arg construct/destruct happens under the held GIL) but also removed the inner GIL release around the driver enqueue that NVIDIA#10028 had added.

Fix

Re-release the GIL only around cudaLaunchHostFunc, and only on the eager path (freeUserData == true). During graph capture no callback runs, so the GIL is kept to hold capture on a single thread. The release scope ends before the nanobind refcount work, preserving NVIDIA#13251's object-lifetime fix (nvbugs-5643631). Reached only via CapturableGuidedDecoder (one-model spec + guided); non-guided / non-spec paths are untouched.

Validation

  • Root-caused on a live ~25h-wedged repro (gpt-oss-120b-Turbo, tp2/ep2) via py-spy --native; both ranks show the identical cycle above.
  • Adds a regression guard test_hostfunc_gil_release_no_deadlock. NOTE: not executed in this PR (needs a GPU + a C++ rebuild); the production repro is the primary validation.
  • Interim mitigation for deployed rc16 without this build: disable Eagle3 spec-decode (removes the host-func guided path).

Scope

Fork hotfix targeting deep-main-v1.3.0rc16. The same patch is suitable for NVIDIA upstream (main) as a follow-up.

…ing host-func deadlock

launchHostFunc held the Python GIL across cudaLaunchHostFunc. The CUDA driver
serializes host-function dispatch on a stream, so in the eager (non-capturing)
path a launch issued while a callback is in flight busy-waits in the driver.
That in-flight callback runs cudaHostFuncTrampoline, which reacquires the GIL to
execute Python (e.g. xgrammar in guided decoding). With the launching thread
holding the GIL, the callback can never acquire it -> circular deadlock. Under
Eagle3 speculative decoding + xgrammar guided decoding this wedged the engine
with zero forward progress (observed via py-spy on both TP ranks).

This is a regression from NVIDIA#13251, which correctly dropped the call_guard (so the
nanobind arg construct/destruct happens under the held GIL) but also removed the
inner GIL release around the driver enqueue that NVIDIA#10028 had added.

Fix: re-release the GIL only around cudaLaunchHostFunc, and only on the eager
path (freeUserData == true). During graph capture no callback runs, so the GIL
is kept to hold capture on a single thread. The release scope ends before the
nanobind refcount work, preserving the object-lifetime fix from NVIDIA#13251.

Adds a regression guard in tests/unittest/bindings/test_hostfunc.py.

Signed-off-by: Sihan Wang <sihan@deepinfra.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant