fix(vllm-omni): avoid PDEATHSIG for thread-launched workers - #276
Conversation
CjhHa1
left a comment
There was a problem hiding this comment.
Confirmed the diagnosis independently, and the thread-scoping explanation in this PR is the correct one.
We hit the same failure from a different direction: a 4-replica layout="separate" vLLM-Omni diffusion topology running diffusion/bagel/bagel_vllmomni with the plain synchronous DiffusionTrainer. Every replica logged AsyncOmniEngine initialized in Ns immediately followed by Diffusion worker(s) died unexpectedly: ['DiffusionWorker-0'], and the first generate failed with DiffusionExecutor is closed. Toggling the prctl call took worker deaths from 5 to 0. So the bug is neither single-GPU- nor SD3-specific, and it reproduces without the async recipe.
Two notes worth putting on the record:
-
The anchor watchdog keeps a process-level reparent check (
original_ppid != 1 and os.getppid() != original_ppid->os._exit(1)). That check is effectively a coarse process-scoped analogue of PDEATHSIG, and the fact that it never fired during our runs is what independently confirms this PR's framing: the spawning process stays alive, only the stage-init thread exits. Had the parent process actually exited, that branch would have reaped the worker within one 5s poll regardless ofprctl. Leaving it as-is is correct. -
install_fate_sharinghas exactly one call site, so the new required keyword-only argument is safe. The sibling_DiffrlPatchedTarget/wrap_mp_process_for_childrencopy inunirl/rollout/engine/sglang_diffusion/_patches/hijack.pynever armed PDEATHSIG, so there is no parallel fix needed there.
One non-blocking caveat for future readers: gating on Process.start()'s calling thread is right for spawn and fork because the creating thread is the one calling start(). It would not hold under forkserver, where the child is forked by the forkserver process. vLLM-Omni forces spawn, so this is not a concern today.
We had a duplicate fix in flight (#277) that deleted the prctl call outright. Closing that one in favour of this PR, since it was filed first and its stated rationale is the accurate one.
|
Settling the mechanism, because the now-closed #277 claimed the opposite in its description: that a standalone probe on this kernel showed PDEATHSIG firing on parent process death but "not on creating-thread exit". If that were true, this PR's main-thread gating would be treating the wrong variable. It is not true — the probe behind that claim hit a race. Measured on the same kernel as the bug report, The parent process stayed alive the whole time; only the creating thread returned. The child took The reason a probe can conclude "process-scoped" is a race that is easy to miss: the child arms PDEATHSIG itself, asynchronously, some time after Same binary, same kernel, opposite conclusion, purely from whether the creator thread outlives the child's So the framing in this PR is the correct one, and the Withdrawing the caveat in my earlier review about needing to keep this narrow for safety reasons unrelated to the mechanism: the mechanism now checks out end to end. |
|
Follow-up data on why gating is the right shape, rather than dropping PDEATHSIG outright. I had one open question about this PR: if nothing in the current topology is actually created from a parent's main thread, the gate would be a no-op and the simpler "just delete it" change would be equivalent. Measured, that is not the case. Which thread creates what (vllm_omni 0.20.0, the pinned version)
Gate probeThat topology replicated with the three patch variants, real processes, real
The gate does fire, on exactly one class, and that class survives. Also worth recording: before the fix the blast radius is wider than #275 reported — What the retained signal buysThe anchor poll is a Python thread inside the worker, so it needs the GIL. Simulating a worker wedged in a C call with
The control shows the watchdog logic itself is sound — it fails specifically when the worker cannot run Python. That is the scenario CaveatsThe thread attribution is from reading the 0.20.0 source. The kill/survival numbers are measured on the same kernel as the bug report ( |
Summary
Linux associates
PR_SET_PDEATHSIGwith the thread that starts the child process. vLLM-Omni diffusion workers are started by temporary stage-initialization threads, so they were killed when those threads exited even though the owning process remained alive.Only arm PDEATHSIG when
Process.start()is called from the parent process's main thread. The existing anchor PID/PPID watchdog remains enabled for all children.Related Issue
Fixes #275
Test Plan
Tested on one NVIDIA H20 with Python 3.12, PyTorch 2.11.0+cu129, vLLM 0.20.0+cu129, and vLLM-Omni 0.20.0.
Model:
stabilityai/stable-diffusion-3.5-mediumRecipe:
diffusion/sd3/sd3_vllmomniDataset:
datasets/pickscore/train.txtResult: generation completed successfully and
rollout 1/1finished.AR TP-worker termination was not tested; this validation was limited to the one-GPU diffusion reproduction from #275.
Compatibility / Risk
No API, configuration, checkpoint, or data-format changes.
Children started by helper threads now rely on the existing anchor PID/PPID watchdog. Main-thread-started workers retain PDEATHSIG.
Reviewer Notes
The regression was introduced by
c1da41bin #260. No overlapping fix PR was found.Implementation and PR drafting were AI-assisted. I reviewed the diff and ran the GPU reproduction above.
Checklist