Skip to content

[GG] perf: preload CUDA-clean forkserver for local GPU workers - #194

Merged
lukealonso merged 3 commits into
local-inference-lab:dev/gilded-gnosisfrom
voipmonitor:perf/gg-cuda-clean-worker-forkserver-20260729
Jul 29, 2026
Merged

[GG] perf: preload CUDA-clean forkserver for local GPU workers#194
lukealonso merged 3 commits into
local-inference-lab:dev/gilded-gnosisfrom
voipmonitor:perf/gg-cuda-clean-worker-forkserver-20260729

Conversation

@voipmonitor

@voipmonitor voipmonitor commented Jul 29, 2026

Copy link
Copy Markdown

Summary

Add an opt-in, CUDA-clean forkserver path for local multi-GPU workers. This is a clean extraction of the startup optimization validated on Kimi K3 TP16; it contains none of the Kimi loader or release-overlay changes from the experimental branch.

When VLLM_WORKER_MULTIPROC_METHOD=forkserver:

  • the OpenAI process starts its EngineCore forkserver before accelerator initialization and preloads the async engine, multiprocess executor, and GPU worker stack;
  • EngineCore starts its own worker forkserver before message-queue setup and preloads the executor/worker modules;
  • gpu_worker imports kernel_warmup only at warmup time, keeping forkserver preload CUDA-clean;
  • startup fails closed if CUDA or XPU is already initialized instead of creating poisoned children;
  • existing NUMA/Ray/WSL force-spawn policy remains in place. The default method is unchanged.

This extends upstream vllm-project#45483. That PR makes the surviving forkserver path reachable but intentionally avoids preload. Preloading the second, EngineCore-owned worker forkserver is the part that removes repeated per-rank imports at high TP. The previous preload regression is addressed here by moving the identified CUDA-initializing import out of module scope and testing that contract.

Measured result

Matched Kimi K3 TP16 startup, Initializing a V1 LLM engine to Starting to load model:

Method Time
spawn 167 s
CUDA-clean forkserver 39 s

All 16 ranks entered distributed initialization within one second. Full EngineCore-to-ready time decreased from 407 s to 271 s. A repeat run completed model loading, CUDA graph capture, /health, and a correctness request.

Validation

  • tests/utils_/test_system_utils.py
  • tests/v1/worker/test_gpu_worker.py
  • Result: 14 passed
  • GPU import probe:
    • before imports: torch.cuda.is_initialized() == False
    • after multiproc_executor: False
    • after gpu_worker: False

The release helper should keep this opt-in until GLM TP8 and the canonical image complete matched spawn/forkserver E2E validation.

Summary by CodeRabbit

  • New Features

    • Added support for the forkserver multiprocessing method via VLLM_WORKER_MULTIPROC_METHOD.
    • Improved server/executor startup to initialize a clean forkserver before accelerator-related imports.
    • Added safeguards to prevent forkserver changes after CUDA/XPU initialization.
    • Kept GPU warmup dependencies lazily loaded to avoid premature accelerator initialization.
  • Tests

    • Added/expanded coverage for CLI serve bootstrapping, forkserver lifecycle behavior, multiprocessing configuration, and lazy GPU warmup imports.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 256e6d8b-5964-4f6d-a5bf-02f6e06e43a6

📥 Commits

Reviewing files that changed from the base of the PR and between 854735a and 30b2fe7.

📒 Files selected for processing (1)
  • vllm/v1/worker/gpu_worker.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • vllm/v1/worker/gpu_worker.py

📝 Walkthrough

Walkthrough

Forkserver support is added to multiprocessing configuration and lifecycle management. Serve entrypoints and executors initialize a clean forkserver with preloaded modules, while GPU warmup imports are deferred until call time. Tests cover configuration, lifecycle, startup, fallback, and lazy loading.

Changes

CUDA-clean forkserver support

Layer / File(s) Summary
Forkserver contract and lifecycle
vllm/envs.py, vllm/utils/system_utils.py, tests/utils_/test_system_utils.py
Adds forkserver configuration, clean-forkserver ownership and initialization guards, preload handling, start-method fallback, and lifecycle tests.
Serve startup and executor integration
vllm/entrypoints/cli/main.py, vllm/entrypoints/openai/api_server.py, vllm/v1/executor/multiproc_executor.py, tests/entrypoints/test_cli_main.py
Initializes the clean forkserver from serve startup paths and the multiprocess executor before multiprocessing context resolution.
Lazy CUDA warmup import
vllm/v1/worker/gpu_worker.py, tests/v1/worker/test_gpu_worker.py
Replaces eager warmup importing with a call-time wrapper and verifies deferred loading.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI_main
  participant ensure_cuda_clean_forkserver
  participant multiprocessing_forkserver
  CLI_main->>CLI_main: inspect serve command and forkserver configuration
  CLI_main->>ensure_cuda_clean_forkserver: initialize preloaded forkserver
  ensure_cuda_clean_forkserver->>multiprocessing_forkserver: start forkserver
  multiprocessing_forkserver-->>CLI_main: return before command imports
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: preloading a CUDA-clean forkserver for local GPU workers.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@voipmonitor

Copy link
Copy Markdown
Author

Updated the CUDA-clean warmup boundary to compose cleanly with #172. gpu_worker now exposes a same-name lazy wrapper instead of editing the warmup call site; #172 can keep _warmup_kernels_once() unchanged while the forkserver snapshot remains CUDA-clean. Verified by composing #145/#172/#175/#179/#184/#185/#190/#194, a GPU import probe (torch.cuda.is_initialized() == false before and after importing gpu_worker), and targeted tests (5 passed).

@voipmonitor
voipmonitor force-pushed the perf/gg-cuda-clean-worker-forkserver-20260729 branch from 7804621 to 2f3dfb5 Compare July 29, 2026 15:34
@voipmonitor

Copy link
Copy Markdown
Author

E2E follow-up: the original revision still allowed the normal vllm serve CLI to initialize CUDA before build_async_engine_client(). In that ordering, vLLM correctly replaced the requested forkserver with spawn, so the optimization was not active in the production helper.

The latest commit starts the CUDA-clean API forkserver at the beginning of the serve CLI, before importing command modules. The helper is process-idempotent: the API lifecycle reuses its existing clean server, while the forked EngineCore starts its own clean worker forkserver. A forkserver requested after untracked CUDA initialization still fails closed to spawn.

Validation on the same GLM-5.2 image/config/cache, TP8/DCP1/MTP0, GPUs 0-7:

mode all worker ranks in distributed init container start to API ready
warm spawn 14.65 s spread 130.23 s
warm forkserver 2.26 s spread 118.12 s

Forkserver improved full warm startup by 12.11 s (9.3%). The process tree showed separate API and EngineCore forkservers with the expected preload lists, no spawn fallback was logged, InstantTensor loading completed, /health returned 200, and a correctness request returned 4.

Tests: 19 targeted tests passed, including CLI-before-import bootstrap, idempotence, CUDA fail-closed behavior, and lazy worker warmup. The direct preload probe kept torch.cuda.is_initialized() false before and after starting the server.

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
vllm/utils/system_utils.py (2)

122-153: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Idempotency silently drops later preload_modules lists.

Once _cuda_clean_forkserver_owner_pid matches the current PID, ensure_cuda_clean_forkserver returns immediately regardless of the preload_modules passed in. If a future call site needs modules the first caller didn't preload, that need is silently unmet with no log/warning to reveal the mismatch.

♻️ Proposed fix: warn on preload mismatch
     if _cuda_clean_forkserver_is_running():
+        missing = set(preload_modules) - set(_cuda_clean_forkserver_preload_modules)
+        if missing:
+            logger.warning(
+                "CUDA-clean forkserver already running without preload "
+                "modules %s; they will not be preloaded.", sorted(missing)
+            )
         return

(requires tracking the applied preload list in a new module-level variable)

🤖 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 `@vllm/utils/system_utils.py` around lines 122 - 153, Update
ensure_cuda_clean_forkserver to track the preload modules applied by the current
forkserver, and when it is already running compare them with the later
preload_modules request. Warn about any mismatch before returning, while
preserving idempotent behavior and existing validation for starting a new
forkserver.

122-133: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Docstring missing Args/Raises sections.

ensure_cuda_clean_forkserver takes preload_modules/set_start_method and raises RuntimeError/ValueError, but the docstring documents neither.

As per coding guidelines, "Use Google-style docstrings in Python code, with Args:/Returns:/Raises: sections instead of reStructuredText/Sphinx fields such as :param:, :return:, and :rtype:."

📝 Proposed docstring fix
     """Start a forkserver before this process initializes an accelerator.
 
     A forkserver child inherits the server's runtime state. Starting that
     server after CUDA/XPU initialization would reproduce the same unsafe state
     inheritance as a direct fork, so fail closed instead of creating workers
     that can fail later during device initialization.
+
+    Args:
+        preload_modules: Module names to import inside the forkserver before
+            it starts accepting fork requests.
+        set_start_method: Whether to also force the global multiprocessing
+            start method to "forkserver".
+
+    Raises:
+        RuntimeError: If CUDA or XPU is already initialized in this process.
+        ValueError: If ``preload_modules`` is empty.
     """
🤖 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 `@vllm/utils/system_utils.py` around lines 122 - 133, Update the docstring for
ensure_cuda_clean_forkserver to use Google-style sections: document
preload_modules and set_start_method under Args, and document the RuntimeError
and ValueError conditions under Raises. Keep the existing behavior and
explanatory summary unchanged.

Source: Coding guidelines

vllm/entrypoints/cli/main.py (1)

27-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate CUDA-clean-forkserver preload list across two files. The exact same three-module preload list is hardcoded in both vllm/entrypoints/cli/main.py and vllm/entrypoints/openai/api_server.py. Since ensure_cuda_clean_forkserver applies whichever list arrives first (idempotent, first-caller-wins), extracting this into one shared constant avoids the two literals silently drifting apart if only one is updated later.

  • vllm/entrypoints/cli/main.py#L27-L34: import a shared _SERVE_FORKSERVER_PRELOAD_MODULES constant (e.g. from vllm.utils.system_utils) instead of the inline list literal.
  • vllm/entrypoints/openai/api_server.py#L130-L136: use the same shared constant instead of re-declaring the identical list literal.
🤖 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 `@vllm/entrypoints/cli/main.py` around lines 27 - 34, The CUDA-clean forkserver
preload modules are duplicated across both entry points. Define or reuse a
shared _SERVE_FORKSERVER_PRELOAD_MODULES constant, import it in
vllm/entrypoints/cli/main.py lines 27-34, and pass it to
ensure_cuda_clean_forkserver; update vllm/entrypoints/openai/api_server.py lines
130-136 to use the same constant instead of its inline list.
🤖 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 `@vllm/v1/worker/gpu_worker.py`:
- Around line 95-101: Update the kernel_warmup function docstring to include
Google-style Args and Returns sections documenting the worker parameter and its
None return value, while preserving the existing summary and lazy-import
behavior.

---

Nitpick comments:
In `@vllm/entrypoints/cli/main.py`:
- Around line 27-34: The CUDA-clean forkserver preload modules are duplicated
across both entry points. Define or reuse a shared
_SERVE_FORKSERVER_PRELOAD_MODULES constant, import it in
vllm/entrypoints/cli/main.py lines 27-34, and pass it to
ensure_cuda_clean_forkserver; update vllm/entrypoints/openai/api_server.py lines
130-136 to use the same constant instead of its inline list.

In `@vllm/utils/system_utils.py`:
- Around line 122-153: Update ensure_cuda_clean_forkserver to track the preload
modules applied by the current forkserver, and when it is already running
compare them with the later preload_modules request. Warn about any mismatch
before returning, while preserving idempotent behavior and existing validation
for starting a new forkserver.
- Around line 122-133: Update the docstring for ensure_cuda_clean_forkserver to
use Google-style sections: document preload_modules and set_start_method under
Args, and document the RuntimeError and ValueError conditions under Raises. Keep
the existing behavior and explanatory summary unchanged.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fd912023-7bb3-4ff9-8881-2052c07b0466

📥 Commits

Reviewing files that changed from the base of the PR and between 4247d67 and 854735a.

📒 Files selected for processing (9)
  • tests/entrypoints/test_cli_main.py
  • tests/utils_/test_system_utils.py
  • tests/v1/worker/test_gpu_worker.py
  • vllm/entrypoints/cli/main.py
  • vllm/entrypoints/openai/api_server.py
  • vllm/envs.py
  • vllm/utils/system_utils.py
  • vllm/v1/executor/multiproc_executor.py
  • vllm/v1/worker/gpu_worker.py

Comment thread vllm/v1/worker/gpu_worker.py
@lukealonso
lukealonso merged commit c1acc51 into local-inference-lab:dev/gilded-gnosis Jul 29, 2026
1 check passed
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.

2 participants