Skip to content

[TRTLLM-13409][feat] anti-zombie worker cleanup (PR_SET_PDEATHSIG + tree-kill) - #16404

Closed
JunyiXu-nv wants to merge 4 commits into
NVIDIA:mainfrom
JunyiXu-nv:dev-junyix-feat-anti-zombie
Closed

[TRTLLM-13409][feat] anti-zombie worker cleanup (PR_SET_PDEATHSIG + tree-kill)#16404
JunyiXu-nv wants to merge 4 commits into
NVIDIA:mainfrom
JunyiXu-nv:dev-junyix-feat-anti-zombie

Conversation

@JunyiXu-nv

@JunyiXu-nv JunyiXu-nv commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • Bug Fixes

    • Improved cleanup of worker and descendant processes after fatal errors.
    • Reduced the risk of orphaned processes and lingering GPU memory when a parent process exits unexpectedly.
    • Added bounded waiting and resilient handling during process termination.
  • Tests

    • Added Linux coverage for parent-death handling and process-tree cleanup.
    • Included validation for repeated parent-death signal setup.

Description

When the proxy / MPI launcher dies abruptly (e.g. a watchdog hard-kill or a pod-kill), mpi4py worker processes can be left orphaned, keeping their CUDA context and holding GPU memory until the next run OOMs at model load and blames the wrong PR.

Add two anti-zombie helpers to tensorrt_llm._utils and wire them in:

  • set_parent_death_signal(): prctl(PR_SET_PDEATHSIG) so the kernel signals this process when its parent dies. Called at the top of worker_main, so every spawned executor worker self-terminates if its parent goes away. Linux-only; no-op elsewhere. Not called on the in-process LLM() path.
  • kill_process_tree(): SIGKILL a pid and all descendants (psutil-based, covers forked grandchildren), blocking until reaped. Wired into the proxy's pre_shutdown on the fatal-error path to reap the proxy's own descendant processes (postproc / local helpers).

Test Coverage

PR Checklist

Please review the following before submitting your PR:

  • PR description clearly explains what and why. If using CodeRabbit's summary, please make sure it makes sense.

  • PR Follows TRT-LLM CODING GUIDELINES to the best of your knowledge.

  • Test cases are provided for new code paths (see test instructions)

  • If PR introduces API changes, an appropriate PR label is added - either api-compatible or api-breaking. For api-breaking, include BREAKING in the PR title.

  • Any new dependencies have been scanned for license and vulnerabilities

  • CODEOWNERS updated if ownership changes

  • Documentation updated as needed

  • Update tava architecture diagram if there is a significant design change in PR.

  • The reviewers assigned automatically/manually are appropriate for the PR.

  • Please check this after reviewing the above items as appropriate for this PR.

GitHub Bot Help

To see a list of available CI bot commands, please comment /bot help.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds Linux process lifecycle utilities for parent-death signaling and recursive process-tree termination, integrates them into worker startup and fatal proxy shutdown, and adds subprocess-based Linux tests registered in the A10 test list.

Changes

Anti-zombie process lifecycle

Layer / File(s) Summary
Process lifecycle utilities
tensorrt_llm/_utils.py
Adds Linux PR_SET_PDEATHSIG setup and recursive process-tree killing with bounded waits and race handling.
Executor lifecycle integration
tensorrt_llm/executor/worker.py, tensorrt_llm/executor/proxy.py
Workers configure parent-death signaling at startup; fatal proxy shutdown cleans up descendant processes while logging cleanup failures.
Linux process lifecycle validation
tests/unittest/_utils/test_anti_zombie.py, tests/integration/test_lists/test-db/l0_a10.yml
Adds Linux-only subprocess tests for parent death, process-tree reaping, repeated signal setup, and A10 test-list registration.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Worker
  participant ParentDeathSignal
  participant libc
  Worker->>ParentDeathSignal: configure default SIGKILL
  ParentDeathSignal->>libc: call prctl(PR_SET_PDEATHSIG)
  libc-->>Worker: return success or OSError
Loading
sequenceDiagram
  participant GenerationExecutorProxy
  participant kill_process_tree
  participant psutil
  participant Descendants
  GenerationExecutorProxy->>kill_process_tree: clean descendants after fatal error
  kill_process_tree->>psutil: enumerate recursive children
  kill_process_tree->>Descendants: kill processes
  kill_process_tree->>psutil: wait for process exit
Loading

Suggested reviewers: asfiyab-nvidia, yihuilu512

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise, ticketed, and clearly summarizes the anti-zombie cleanup change.
Description check ✅ Passed The description explains the issue and solution, but the Test Coverage section is empty.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🧹 Nitpick comments (4)
tests/unittest/_utils/test_anti_zombie.py (2)

57-141: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add explicit None return annotations.

All three test_* functions are non-returning and should declare -> None.

As per coding guidelines, “Annotate every function, use None for non-returning functions.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unittest/_utils/test_anti_zombie.py` around lines 57 - 141, Add an
explicit -> None return annotation to each of the three test functions:
test_prctl_kills_child_when_parent_dies,
test_kill_process_tree_reaps_grandchildren, and
test_set_parent_death_signal_idempotent. Do not alter their behavior or
surrounding test logic.

Source: Coding guidelines


98-115: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Cover the proxy’s include_parent=False cleanup path.

This test only exercises include_parent=True, while tensorrt_llm/executor/proxy.py Lines 629-633 relies on False. Add a case that verifies descendants exit while TOP_PID remains alive, then explicitly reap the top process. Coverage is otherwise insufficient for the integration branch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unittest/_utils/test_anti_zombie.py` around lines 98 - 115, The test
coverage in test_kill_process_tree_reaps_grandchildren only exercises
include_parent=True; add a case using kill_process_tree with
include_parent=False that verifies CHILD_PID and GRANDCHILD_PID are reaped while
TOP_PID remains alive, then explicitly terminate and reap TOP_PID to avoid
leaking the process.

Source: Path instructions

tensorrt_llm/executor/proxy.py (1)

629-637: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Narrow the cleanup exception boundary.

except Exception also hides programming errors in the new fatal-shutdown path. Handle expected process races (psutil.Error and, if applicable, OSError) explicitly; let unrelated defects remain visible.

As per coding guidelines, “Catch the narrowest possible exceptions.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tensorrt_llm/executor/proxy.py` around lines 629 - 637, Update the cleanup
exception handler around kill_process_tree in the fatal-error shutdown path to
catch only expected process-race exceptions, specifically psutil.Error and
OSError where applicable. Preserve the existing debug logging for those
failures, while allowing unrelated programming errors to propagate.

Source: Coding guidelines

tensorrt_llm/_utils.py (1)

407-440: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Complete the public helper contracts.

Annotate sig (for example, int | None) and add Google-style Args: documentation for both exported helpers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tensorrt_llm/_utils.py` around lines 407 - 440, Complete the public contracts
for set_parent_death_signal and kill_process_tree by adding an explicit type
annotation for set_parent_death_signal’s sig parameter, allowing None, and
adding Google-style Args: sections documenting each parameter in both helpers,
including defaults and behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tensorrt_llm/_utils.py`:
- Around line 425-428: Update the parent-death setup helper around libc.prctl to
accept the expected parent PID, arm PR_SET_PDEATHSIG, then compare the current
getppid() with that expected PID and terminate the worker if it changed.
Preserve the existing prctl error handling, and add a regression test covering
the launcher exiting during this startup window.

In `@tests/unittest/_utils/test_anti_zombie.py`:
- Around line 57-75: Update test_prctl_kills_child_when_parent_dies to
initialize child_pid before the try block and, in finally, best-effort terminate
and reap the child when it was created but remains alive, while preserving the
existing parent cleanup.

---

Nitpick comments:
In `@tensorrt_llm/_utils.py`:
- Around line 407-440: Complete the public contracts for set_parent_death_signal
and kill_process_tree by adding an explicit type annotation for
set_parent_death_signal’s sig parameter, allowing None, and adding Google-style
Args: sections documenting each parameter in both helpers, including defaults
and behavior.

In `@tensorrt_llm/executor/proxy.py`:
- Around line 629-637: Update the cleanup exception handler around
kill_process_tree in the fatal-error shutdown path to catch only expected
process-race exceptions, specifically psutil.Error and OSError where applicable.
Preserve the existing debug logging for those failures, while allowing unrelated
programming errors to propagate.

In `@tests/unittest/_utils/test_anti_zombie.py`:
- Around line 57-141: Add an explicit -> None return annotation to each of the
three test functions: test_prctl_kills_child_when_parent_dies,
test_kill_process_tree_reaps_grandchildren, and
test_set_parent_death_signal_idempotent. Do not alter their behavior or
surrounding test logic.
- Around line 98-115: The test coverage in
test_kill_process_tree_reaps_grandchildren only exercises include_parent=True;
add a case using kill_process_tree with include_parent=False that verifies
CHILD_PID and GRANDCHILD_PID are reaped while TOP_PID remains alive, then
explicitly terminate and reap TOP_PID to avoid leaking the process.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: a79c6740-f1a2-4db5-90e6-196bfaab4213

📥 Commits

Reviewing files that changed from the base of the PR and between d3436a0 and b0cfcdc.

📒 Files selected for processing (5)
  • tensorrt_llm/_utils.py
  • tensorrt_llm/executor/proxy.py
  • tensorrt_llm/executor/worker.py
  • tests/integration/test_lists/test-db/l0_a10.yml
  • tests/unittest/_utils/test_anti_zombie.py

Comment thread tensorrt_llm/_utils.py
Comment thread tests/unittest/_utils/test_anti_zombie.py Outdated
@JunyiXu-nv
JunyiXu-nv force-pushed the dev-junyix-feat-anti-zombie branch 2 times, most recently from 2fa3dca to a0190df Compare July 15, 2026 06:00
@JunyiXu-nv

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #59422 [ run ] triggered by Bot. Commit: a0190df Link to invocation

@BowenFu BowenFu left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

expected_parent_pid is tested but never passed by a caller — worker_main (worker.py:191) calls bare set_parent_death_signal(), so the arming-race self-kill never runs on a real path. Worth wiring the proxy PID for the spawn case, or noting it's deferred so it doesn't read as dead code.

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #59422 [ run ] completed with state SUCCESS. Commit: a0190df
/LLM/main/L0_MergeRequest_PR pipeline #47892 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@JunyiXu-nv
JunyiXu-nv force-pushed the dev-junyix-feat-anti-zombie branch from 200ee4b to 43d6470 Compare July 16, 2026 14:16
@JunyiXu-nv

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

Comment thread tensorrt_llm/_utils.py Outdated
@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #59723 [ run ] triggered by Bot. Commit: 43d6470 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #59723 [ run ] completed with state SUCCESS. Commit: 43d6470
/LLM/main/L0_MergeRequest_PR pipeline #48150 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #60867 [ run ] completed with state FAILURE. Commit: 2f1bfa9
/LLM/main/L0_MergeRequest_PR pipeline #49139 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@JunyiXu-nv

Copy link
Copy Markdown
Collaborator Author

/bot run

Resolve conflicts:
- tensorrt_llm/executor/proxy.py: keep both the anti-zombie
  kill_process_tree call at the end of pre_shutdown() and the new
  _get_next_client_id / _cleanup_multi_frontend_ipc_dir methods added
  on main.
- tests/integration/test_lists/test-db/l0_cpu_x86.yml: keep both the
  new test_anti_zombie.py entry and the new test_multi_frontend_routing.py
  entry.

Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
Addresses review nit from @brnguyen2: signal, ctypes, time (stdlib) and
psutil (third-party) were lazily imported inside set_parent_death_signal
and kill_process_tree. Move them to the module-level imports for
consistency with the rest of _utils.py, which already imports numpy,
nvtx, mpi4py unconditionally.

Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
@JunyiXu-nv
JunyiXu-nv force-pushed the dev-junyix-feat-anti-zombie branch from a0bc207 to 457a7d7 Compare July 23, 2026 02:54
@JunyiXu-nv

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61179 [ run ] triggered by Bot. Commit: 457a7d7 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61179 [ run ] completed with state FAILURE. Commit: 457a7d7
/LLM/main/L0_MergeRequest_PR pipeline #49428 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@JunyiXu-nv

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61478 [ run ] triggered by Bot. Commit: 457a7d7 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61478 [ run ] completed with state SUCCESS. Commit: 457a7d7
/LLM/main/L0_MergeRequest_PR pipeline #49701 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@JunyiXu-nv

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61686 [ run ] triggered by Bot. Commit: 457a7d7 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61686 [ run ] completed with state FAILURE. Commit: 457a7d7
/LLM/main/L0_MergeRequest_PR pipeline #49892 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@JunyiXu-nv

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61694 [ run ] triggered by Bot. Commit: 457a7d7 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61694 [ run ] completed with state FAILURE. Commit: 457a7d7
/LLM/main/L0_MergeRequest_PR pipeline #49899 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@JunyiXu-nv

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61782 [ run ] triggered by Bot. Commit: 457a7d7 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #61782 [ run ] completed with state FAILURE. Commit: 457a7d7
/LLM/main/L0_MergeRequest_PR pipeline #49982 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@JunyiXu-nv

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #62105 [ run ] triggered by Bot. Commit: 457a7d7 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #62105 [ run ] completed with state SUCCESS. Commit: 457a7d7
/LLM/main/L0_MergeRequest_PR pipeline #50288 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@JunyiXu-nv

Copy link
Copy Markdown
Collaborator Author

Pipeline #50288 failed on one stage, GB200-4_GPUs-PyTorch-4, with two tests failing. Both are a storage-mount drop, not a defect in this PR:

  • TestLlama3_1_8BInstruct::test_fp8_4gpus[tp4-fp8kv=False-attn_backend=FLASHINFER-torch_compile=False]
  • TestNemotronV3Ultra::test_nvfp4_4gpus_block_reuse[ADP4_MTP]

Root exception in both: BrokenPipeError: [Errno 108] Cannot send after transport endpoint shutdown while reading model weight shards — /scratch.trt_llm_data/llm-models/llama-3.1-model/Llama-3.1-8B-Instruct-FP8/model-00001-of-... and /scratch.trt_llm_data/llm-models/NVIDIA-Nemotron-3-Ultra-550B-A55B-NVFP4/model-00054-of-.... That surfaces as Executor worker died during initializationRuntimeError: Executor worker returned error (proxy.py:665).

Why this is not the PR:

  • Two unrelated tests and two unrelated models failed at the same timestamp with the same errno on the same mount — the signature of a shared filesystem dropping, not of a code path.
  • The workers died from a Python exception, not a signal. The log contains zero occurrences of Signals.SIGKILL, killed by signal or Terminated. This matters specifically for this PR: if PR_SET_PDEATHSIG had fired, the worker would have been SIGKILLed, which is uncatchable and would leave no Python traceback at all. We have a full traceback, so PDEATHSIG did not fire.
  • Zero occurrences of set_parent_death_signal, kill_process_tree or pre_shutdown anywhere in the stage log.

Re-running CI.

@JunyiXu-nv

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #62131 [ run ] triggered by Bot. Commit: 457a7d7 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #62131 [ run ] completed with state SUCCESS. Commit: 457a7d7
/LLM/main/L0_MergeRequest_PR pipeline #50310 completed with status: 'SUCCESS'

CI Report

Link to invocation

@YihuiLu512 YihuiLu512 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could this mechanism fail to trigger because an MpiPoolSession worker is parented by the MPI daemon rather than the proxy, and the proxy exiting does not mean the daemon exits?

# Anti-zombie: if our parent (proxy / MPI launcher) dies abruptly, have the
# kernel SIGKILL this worker so it can't orphan and leak GPU memory.
try:
set_parent_death_signal()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

the registration scope of prctl(PR_SET_PDEATHSIG) is thread-level, but its kill effect is process-level.

In the case of MPICommExecutor:

  • (a) the host process of MpiCommSession may be launched from a worker thread by an external script (such as a test framework), which could cause unintended termination;
  • (b) worker_main for rank 0 runs in the host process’s thread pool, which could result in the entire process being killed.

These two factors may amplify each other’s impact. Please evaluate whether this behavior is expected.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Similarly for MpiPoolSession, although tasks are currently executed on the main thread, this relies on mpi4py's internal implementation details. If task dispatch is ever moved to a worker thread, the protection would silently disappear when that thread exits after worker_main returns.

Should we add an assertion to ensure that it must be registered in a callback of the main thread?

# not the proxy's children and are covered by PR_SET_PDEATHSIG instead.
if self._fatal_error is not None:
try:
kill_process_tree(os.getpid(),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Would directly killing the process tree be overly aggressive?

For example:

  • (a) the shadow pool prefetched in session_prefetcher for the next test
  • (b) multiple LLM instances within the same process

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

#16770 session_prefetcher was merged yesterday

@JunyiXu-nv

Copy link
Copy Markdown
Collaborator Author

Closing — the motivating premise does not survive measurement

This PR adds two mechanisms against orphaned workers holding GPU memory. All three parts have now been measured on real hardware, and none of them justifies merging. Closing rather than reworking, with the evidence recorded here so the next person does not re-derive it.

1. kill_process_tree takes down its own process

kill_process_tree(os.getpid(), include_parent=False) in pre_shutdown() reaches orted, which is a direct child of the Python process. Killing it takes the host down about a second later. Control arm differing by only the nine-line block:

TREATMENT (this PR)                      CONTROL
t=4.61  sentinel GONE, orted GONE         sentinel + orted stay ALIVE
t=5.61  self GONE                         t=19.4 self exits normally
EXIT_RC=205                              EXIT_RC=0

It also kills unrelated children the proxy never spawned, destroys an entire pytest session rather than failing one test, and would SIGKILL trtllm-serve --num_serve_frontends attached frontends instead of draining them. This is the best-replicated result of the round (3×3 and 4×2 arms).

The code comment justifying it is also wrong: "MPI-spawned workers are not the proxy's children" — they are grandchildren via orted, well inside psutil.children(recursive=True).

2. A scoped replacement was written, and is also wrong

Scoping the kill to registered worker PIDs removes the self-kill. But it SIGKILLs every rank before the graceful exit sentinel is sent (_kill_worker_processes() at proxy.py:704, sentinel ~10 lines later) — converting an orderly teardown into an unconditional kill in exactly the case cited to justify it (workers alive, so the sentinel would have worked).

3. PR_SET_PDEATHSIG has no measurable effect

The premise was that an abruptly-killed parent leaves workers holding their CUDA context "until the job's wall-clock pod-kill". Measured across every variant we could construct, GPU memory came back in 0.75–4 s.

The obvious objection — that only SIGSTOP had been tested, which is the one unresponsive state with a dedicated OS-level rescue — was taken seriously and tested directly. A rank was driven into a genuine busy-spin (torch.cuda._sleep + cudaDeviceSynchronize) and verified in that state before any kill: /proc/<pid>/stat = R, 100–200 % CPU, syscall = running, gdb backtrace spinning in cuCtxSynchronize_v2 inside libcuda.so.1, 75 512 MiB held per rank.

Arm Runs Ranks die GPU back to 0 MiB
main, no PDEATHSIG 3/3 +1.25 s ≤ +5.4 s
this PR, PDEATHSIG armed 2/2 +1.25 s ≤ +5.4 s

Survivors after a 360 s watch: none, in either arm.

Why the spin does not protect the rank. orted signals the ranks' process groups from outside — strace shows SIGCONT at +0.14 s, SIGTERM at +1.14 s, SIGKILL at +1.58 s — and a default-disposition SIGTERM kills a process regardless of what its main thread is doing in userspace. Separately, when orted is killed too, the ranks still self-exit with code 205 via OpenMPI's daemon-loss abort, which runs on the PMIx/OOB progress thread rather than the wedged main thread.

And a structural point that settles it independently of timing: PR_SET_PDEATHSIG armed in worker_main keys off orted's death, not the proxy's, because orted is the workers' direct parent. In the one documented orphan case (2026-07-22, ~35 GB held), orted survived. So the mechanism in this PR could not have fired in the very scenario that motivated it.

What remains open, honestly

The 2026-07-22 orphan case is still unexplained. Process topology was identical and it could not be reproduced, so the differentiator is something else — plausibly the broken-PCIe-P2P node state wedging orted itself. That remains an unexplained outlier rather than a general property, and if it is real, the mechanism that would address it is an out-of-band per-node reaper (ST-5), which does not depend on orted — not this PR.

Not measured

Single node, TP=2, one model, inside the devel container. Multi-node, Ray, and disaggregated topologies were not tested. Deep-tree PDEATHSIG was not tested — only the documented worker-under-orted case.

Full evidence — watcher timelines, evidence.json, strace traces, the harness and the applied patch — is preserved at tmp/trtllm-serve-ai-dev/data/public/multi_gpu_error_handling/impl/busy-spin-orphan-2026-07-31/, and the analysis is written up in design-round2-2026-07-30.html §2.1–2.2 and subtask-designs-2026-07-30.html §7.

Thanks to the reviewers who spent time on this — the thread-scope and blast-radius questions raised here were what prompted the measurements that settled it.

@JunyiXu-nv JunyiXu-nv closed this Jul 31, 2026
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.

7 participants