[None][test] disagg startup: detect and fail fast on worker OOM/crash#16456
Conversation
📝 WalkthroughWalkthroughMPI shutdown paths now provide lifecycle logging. Disaggregated integration workers use indexed artifact names, startup monitoring detects exited workers and registration stalls, failure logs are retained, and mixed-stress startup uses a 600-second timeout. ChangesMPI shutdown lifecycle
Disaggregated integration startup
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant setup_disagg_cluster
participant wait_for_disagg_server_ready
participant worker_processes
participant cluster_info
setup_disagg_cluster->>worker_processes: launch context and generation workers
setup_disagg_cluster->>wait_for_disagg_server_ready: wait for server readiness
setup_disagg_cluster->>worker_processes: check worker process exits
setup_disagg_cluster->>cluster_info: poll registered workers
wait_for_disagg_server_ready-->>setup_disagg_cluster: readiness result
cluster_info-->>setup_disagg_cluster: registration status
setup_disagg_cluster->>worker_processes: terminate on startup failure
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
/bot run |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
tests/integration/defs/disaggregated/disagg_test_utils.py (1)
86-95: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAnnotate the changed worker APIs and document the public wrapper arguments.
Add precise parameter and return types to all three signatures, plus
Args/Returnssections forrun_ctx_workerandrun_gen_worker. As per coding guidelines, “Annotate every function” and “document public function arguments.”Also applies to: 157-166, 184-193
🤖 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/integration/defs/disaggregated/disagg_test_utils.py` around lines 86 - 95, Annotate all three worker APIs—_run_worker, run_ctx_worker, and run_gen_worker—with precise parameter and return types, including optional and callback/configuration values as appropriate. Add Args and Returns sections to the docstrings of the public wrappers run_ctx_worker and run_gen_worker, documenting every exposed argument and the returned value. Do not alter their runtime 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/llmapi/mpi_session.py`:
- Line 131: Update the shutdown logging statement in the MPI session flow to use
the f-string !r conversion flag for reason instead of calling repr(reason),
preserving the existing “Reason to shutdown” message.
In `@tests/integration/defs/disaggregated/test_disaggregated.py`:
- Around line 853-858: Update the pending-task cleanup loop to suppress only
asyncio.CancelledError when awaiting each cancelled task; remove the broad
Exception catch so any non-cancellation failure from pending tasks propagates
instead of being discarded.
- Around line 821-830: Update the worker-registration polling logic around
last_worker_count so the no-progress timeout is only evaluated while count is
below the expected worker total; once all expected workers register, stop or
bypass the timer during subsequent readiness waits. Add a mocked slow-readiness
regression in the disaggregated integration tests to verify complete
registration does not raise the no-progress RuntimeError even when final server
readiness exceeds _NO_PROGRESS_TIMEOUT.
---
Nitpick comments:
In `@tests/integration/defs/disaggregated/disagg_test_utils.py`:
- Around line 86-95: Annotate all three worker APIs—_run_worker, run_ctx_worker,
and run_gen_worker—with precise parameter and return types, including optional
and callback/configuration values as appropriate. Add Args and Returns sections
to the docstrings of the public wrappers run_ctx_worker and run_gen_worker,
documenting every exposed argument and the returned value. Do not alter their
runtime behavior.
🪄 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: 6a2fbde5-7f53-4409-9416-9a38e619b176
📒 Files selected for processing (4)
tensorrt_llm/llmapi/mpi_session.pytests/integration/defs/conftest.pytests/integration/defs/disaggregated/disagg_test_utils.pytests/integration/defs/disaggregated/test_disaggregated.py
|
PR_Github #59541 [ run ] triggered by Bot. Commit: |
- Log work_dir path and worker log paths at launch so failures are easy to locate. - Fix worker log/config filename collision when multiple workers share port=0: use worker_index instead of port in filenames. - Detect worker crashes during startup via process.poll(); persist worker logs under cwd so they survive test teardown. - Fix workspace cleanup on overlayfs: use ignore_errors=True on rmtree since overlayfs may reject directory removal even with correct perms. - Fix swallowed crash exception: previously ticker.cancel() discarded any RuntimeError _tick() had raised; await ticker in a finally block so the exception propagates correctly. - Fix watchdog exception silently leaving wait_for_disagg_server_ready polling for up to server_start_timeout: run both as asyncio tasks with asyncio.wait(FIRST_COMPLETED) so whichever finishes first cancels the other. - Detect workers alive but hung (stuck in MPI cleanup after OOM) via /cluster_info: if no new workers register for 60s after the disagg server first becomes reachable, declare the cluster stuck. - Log startup progress every 10s (elapsed, remaining, workers registered) and setup duration after cluster is ready. - Lower server_start_timeout for mixed-stress from 7200s to 600s (10m). - Add logging to MpiSession.shutdown_abort and MpiPoolSession.shutdown to surface where MPI cleanup stalls after a worker crash. Signed-off-by: Brian Nguyen <brnguyen@nvidia.com>
Signed-off-by: Brian Nguyen <brnguyen@nvidia.com>
Signed-off-by: Brian Nguyen <brnguyen@nvidia.com>
Signed-off-by: Brian Nguyen <brnguyen@nvidia.com>
…meout Signed-off-by: Brian Nguyen <brnguyen@nvidia.com>
5030744 to
e38a5c5
Compare
|
/bot run |
|
PR_Github #59555 [ run ] triggered by Bot. Commit: |
|
PR_Github #59541 [ run ] completed with state |
|
PR_Github #59555 [ run ] completed with state
|
Signed-off-by: Brian Nguyen <brnguyen@nvidia.com>
|
/bot run |
nv-xtf
left a comment
There was a problem hiding this comment.
Question on the log-retention part — am I reading this right? The retained logs live under the llm_venv workspace, which the session fixture seems to rmtree at teardown, and IIUC the CI stage only uploads ${stageName}/ (the pytest --output-dir). If both hold, the full worker logs wouldn't reach the downloadable artifacts — is there an earlier collection step or another path I'm missing? If not (non-blocking, fine as a follow-up), copying them into --output-dir on failure might be all that's needed.
BowenFu
left a comment
There was a problem hiding this comment.
LGTM — disagg startup fail-fast on worker OOM via process.poll(); can't false-positive on long-lived servers, and the readiness poller is correctly cancelled/re-raised on a crash.
|
/bot run --disable-fail-fast |
|
PR_Github #59707 [ run ] triggered by Bot. Commit: |
|
@nv-xtf Good catch. For the tests in this PR, This iteration is mainly meant to make local testing easier. Explicitly copying logs into |
|
PR_Github #59707 [ run ] completed with state
|
|
/bot run |
|
PR_Github #59765 [ run ] triggered by Bot. Commit: |
|
PR_Github #59765 [ run ] completed with state |
…l init MpiPoolSession.shutdown() is reachable from __del__ (via shutdown_abort) and may run on a session that never completed __init__ -- e.g. after release_exit_joins() marks the pool dead, or in the released-session path exercised by test_proxy_fast_death.py:: test_pool_session_shutdown_never_blocks_after_release, which builds the object with __new__. shutdown() read self.n_workers (log line) and self._wait_shutdown unguarded, raising AttributeError, while the _pool_dead check right above already used getattr defensively. Guard both accesses with getattr, matching the existing _pool_dead style, so a released/partially-constructed session shuts down without crashing. This unbreaks a cross-PR merge-skew regression on main: the unguarded self.n_workers log came from NVIDIA#16456 while the __new__-based test came from NVIDIA#16312; each was green alone but the combination fails deterministically. Also add the missing D205 docstring blank lines the ruff-legacy gate flagged on the touched file. Verified: the whole test_proxy_fast_death.py file now passes (23 passed, was 1 failed). Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
…l init MpiPoolSession.shutdown() is reachable from __del__ (via shutdown_abort) and may run on a session that never completed __init__ -- e.g. after release_exit_joins() marks the pool dead, or in the released-session path exercised by test_proxy_fast_death.py:: test_pool_session_shutdown_never_blocks_after_release, which builds the object with __new__. shutdown() read self.n_workers (log line) and self._wait_shutdown unguarded, raising AttributeError, while the _pool_dead check right above already used getattr defensively. Guard both accesses with getattr, matching the existing _pool_dead style, so a released/partially-constructed session shuts down without crashing. This unbreaks a cross-PR merge-skew regression on main: the unguarded self.n_workers log came from NVIDIA#16456 while the __new__-based test came from NVIDIA#16312; each was green alone but the combination fails deterministically. Also add the missing D205 docstring blank lines the ruff-legacy gate flagged on the touched file. Verified: the whole test_proxy_fast_death.py file now passes (23 passed, was 1 failed). Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
…l init MpiPoolSession.shutdown() is reachable from __del__ (via shutdown_abort) and may run on a session that never completed __init__ -- e.g. after release_exit_joins() marks the pool dead, or in the released-session path exercised by test_proxy_fast_death.py:: test_pool_session_shutdown_never_blocks_after_release, which builds the object with __new__. shutdown() read self.n_workers (log line) and self._wait_shutdown unguarded, raising AttributeError, while the _pool_dead check right above already used getattr defensively. Guard both accesses with getattr, matching the existing _pool_dead style, so a released/partially-constructed session shuts down without crashing. This unbreaks a cross-PR merge-skew regression on main: the unguarded self.n_workers log came from NVIDIA#16456 while the __new__-based test came from NVIDIA#16312; each was green alone but the combination fails deterministically. Also add the missing D205 docstring blank lines the ruff-legacy gate flagged on the touched file. Verified: the whole test_proxy_fast_death.py file now passes (23 passed, was 1 failed). Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
…pool-session shutdown test The test builds MpiPoolSession via __new__ (a real spawn is neither needed nor wanted), but MpiPoolSession.shutdown() now reads n_workers (added by NVIDIA#16456 while the test was in flight) and _wait_shutdown, both set only in __init__. Provide them explicitly and drop the waive. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
…pool-session shutdown test The test builds MpiPoolSession via __new__ (a real spawn is neither needed nor wanted), but MpiPoolSession.shutdown() now reads n_workers (added by NVIDIA#16456 while the test was in flight) and _wait_shutdown, both set only in __init__. Provide them explicitly and drop the waive. Signed-off-by: JunyiXu-nv <219237550+JunyiXu-nv@users.noreply.github.com>
Summary
port=0: useworker_indexinstead ofportin filenames (worker_ctx_0.log,worker_ctx_1.log, etc.).process.poll(); persist worker logs undercwdso they survive test teardown.ignore_errors=Trueonshutil.rmtreesince overlayfs may reject directory removal even with correct permissions.ticker.cancel()discarded anyRuntimeError_tick()had raised;await tickerin afinallyblock so the exception propagates correctly.wait_for_disagg_server_readypolling for up toserver_start_timeout: run both as asyncio tasks withasyncio.wait(FIRST_COMPLETED)so whichever finishes first (success or exception) cancels the other./cluster_info: if no new workers register for 60s after the disagg server first becomes reachable, declare the cluster stuck.server_start_timeoutfor mixed-stress from 7200s to 600s (10m) — sufficient for an 8-GPU setup with a warm Triton cache.MpiSession.shutdown_abortandMpiPoolSession.shutdownto surface where MPI cleanup stalls after a worker crash.Test plan
RuntimeError: 1 worker(s) exited during startup: worker_gen_0.log (rc=1)instead of hanging indefinitely.wait_for_disagg_server_readycoroutine was not cancelled.Summary by CodeRabbit
Bug Fixes
Tests