Conversation
zswerth
force-pushed
the
fix_multirank_generation_profiling
branch
from
September 10, 2026 22:09
eec9ed2 to
c899af6
Compare
Multi-rank generation (inner vLLM tensor-parallel worker) nsys profiling
previously produced NO usable traces: at teardown the inner workers are
SIGKILLed, so nsys never writes the stream footer and the .qdstrm is an
incomplete, unconvertible capture. On the async engine the inner workers
never even received cudaProfilerStart. On some platforms the default
cuda-graph-trace=node HW-CUPTI path also SIGSEGVs at cudaProfilerStart.
Four minimal changes:
- distributed/worker_group_utils.py: drop cuda-graph-trace=node from the
policy/outer nsight config (fragile CUPTI; user can re-add via
NRL_NSYS_EXTRA_OPTIONS).
- models/generation/vllm/vllm_worker.py (_patch_vllm_nsight_config): honor
NRL_NSYS_EXTRA_OPTIONS so the trace target is overridable (e.g. cuda-sw),
drop cuda-graph-trace, and also patch the v2 Ray executor
(RayExecutorV2._build_runtime_env) -- the previous patch only rebound the v1
executor, which is inert now that v2 is the default.
- models/generation/vllm/vllm_worker_async.py: override start/stop_gpu_profiling
as async and AWAIT collective_rpc, so cudaProfilerStart/Stop actually reach
every inner worker on the async engine (the inherited sync methods dropped the
coroutine un-awaited); plus a short grace after stop so nsys can finalize.
- models/generation/vllm/vllm_backend.py (VllmInternalWorkerExtension.stop_gpu_profiling):
after cudaProfilerStop, finalize each inner worker's live nsys capture
NON-DESTRUCTIVELY via `nsys stop --session`. This writes a complete .nsys-rep
without killing the process, so no worker death and no engine fault-tolerance
cascade -- every rank finalizes (vs at most one before). Runs on all inner
workers via the existing collective_rpc("stop_gpu_profiling") broadcast.
A companion vLLM change (vllm/v1/executor/ray_executor_v2.py _build_runtime_env
deferred-capture nsight config) is required for the v2 EngineCore subprocess,
which imports vLLM pristine and does not see the NeMo-RL monkey-patch; that will
be filed as a separate vllm-project/vllm PR.
Signed-off-by: zswerth <zwertheimer@nvidia.com>
zswerth
force-pushed
the
fix_multirank_generation_profiling
branch
from
September 10, 2026 22:14
c899af6 to
04069c2
Compare
Makes multi-rank generation profiling self-contained in NeMo-RL: no vLLM source change and nothing to bind-mount into the container. The v2 RayExecutorV2 (default on vLLM >= 0.25) builds the inner TP-worker runtime_env inside the vLLM EngineCore subprocess, which is started with `spawn` and re-imports vLLM pristine -- so an in-process monkey-patch in vllm_worker.py cannot reach it (it only ever rebinds the class in the outer actor process). The prior commit's in-process RayExecutorV2._build_runtime_env wrap was therefore inert on the real v2 path; the working v2 path relied on an out-of-tree source patch to vllm/v1/executor/ray_executor_v2.py. Instead, register a `vllm.general_plugins` entry point. vLLM calls load_general_plugins() early in EVERY process it starts -- including the EngineCore subprocess -- so the plugin monkey-patches RayExecutorV2._build_runtime_env where it actually takes effect, installing the same deferred-capture nsight config used for the outer/policy workers (capture-range=cudaProfilerApi, gated by NRL_NSYS_PROFILE_STEP_RANGE, honoring NRL_NSYS_EXTRA_OPTIONS for the trace target). Finalization is unchanged: the awaited async start/stop_gpu_profiling reach every inner worker and VllmInternalWorkerExtension.stop_gpu_profiling finalizes each trace via `nsys stop --session`. - nemo_rl/utils/vllm_nsight_plugin.py: the plugin (no-op unless NRL_NSYS_WORKER_PATTERNS is set; no-ops on vLLM without the v2 executor). - pyproject.toml: register it under [project.entry-points."vllm.general_plugins"]. - vllm_worker.py: drop the inert in-process v2 wrap (the v1 executor patch, reachable in-process, is kept). Validated on the fast qwen3-32b TP=2 vehicle (stock vLLM 0.25.1, no source mount, v2 wrap removed): inner vllm_tp_worker traces flush for all ranks (8 engines x TP2 = 16), 0 segfaults; inner files are named vllm_tp_worker_<range>_%p (the plugin's template) not stock worker_process_%p, proving the plugin ran inside the EngineCore subprocess. Non-regressive at TP=1 (sync and async): gen + policy traces unchanged, plugin is a no-op there. Signed-off-by: zswerth <zwertheimer@nvidia.com>
Contributor
Author
|
A pending PR on NemoRL should fix all the added patches provided here for profiling enablement: #4090 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do ?
Fixes multi-rank generation-profiling so that every inner vLLM tensor-parallel worker writes a complete nsys trace (previously TP>1 generation profiling produced zero usable inner traces, and crashed at init).
Issues
No tracked issue; standalone fix
Multi-rank generation (inner vLLM tensor-parallel worker) nsys profiling previously produced NO usable traces: at teardown the inner workers are SIGKILLed, so nsys never writes the stream footer and the .qdstrm is an incomplete, unconvertible capture. On the async engine the inner workers never even received cudaProfilerStart. On aarch64 GB200 the default cuda-graph-trace=node HW-CUPTI path also SIGSEGVs at cudaProfilerStart.
Usage
Enable generation + policy nsys profiling as usual — inner TP-worker traces now finalize for all ranks:
Result: one 2:5.nsys-rep per rank — megatron_policy_worker_, vllm_generation_worker_* (outer), and one vllm_tp_worker_* per inner TP rank (the traces that were missing before).
Repro of the issue and validation of the fix
qwen3-32b, async engine, vLLM tensor_parallel_size=2 (so real inner Ray TP workers exist), single NVLink-local node, profile window 1:3. The two runs differ only by this patch:
Why it failed before:
Backward-compat (non-regression) — all PASS, 0 crashes:
Container Image: nvcr.io#nvidian/nemo-rl:nightly
All three use cases run the same GRPO entrypoint on a Ray-on-Slurm cluster; only the vLLM parallelism/engine and profile knobs differ. Common profiling env (set on the launch shell so it propagates to every node/worker):
Traces land per worker under each Ray session dir:
.../-logs/ray//session_*/logs/nsight/.nsys-rep
Use case 1 — async + TP>1 (the fix: inner-worker traces)
Expected: three trace classes — megatron_policy_worker_, vllm_async_generation_worker_ (outer), and vllm_tp_worker_1:3_%p (one per inner TP rank = #engines × TP). The vllm_tp_worker_ prefix (vs stock vLLM's worker_process_%p) is the proof the deferred-capture config reached the inner EngineCore subprocess. Before the fix: the inner workers segfault at init / produce zero finalized inner traces.
Use case 2 — sync + TP=1 (non-regression)
Expected: megatron_policy_worker_* + vllm_generation_worker_* only. At TP=1 each GPU is its own engine (no inner Ray workers), so there is no vllm_tp_worker; the plugin is a no-op. Run completes, 0 crashes.
Use case 3 — async + TP=1 (non-regression)
Expected: same as case 2 — vllm_generation_worker_* + megatron_policy_worker_*, no inner workers, 0 crashes.
Four minimal changes:
distributed/worker_group_utils.py: drop cuda-graph-trace=node from the policy/outer nsight config (fragile CUPTI on aarch64; user can re-add via NRL_NSYS_EXTRA_OPTIONS).
models/generation/vllm/vllm_worker.py (_patch_vllm_nsight_config): honor NRL_NSYS_EXTRA_OPTIONS so the trace target is overridable (e.g. cuda-sw on aarch64), drop cuda-graph-trace.
models/generation/vllm/vllm_worker_async.py: override start/stop_gpu_profiling as async and AWAIT collective_rpc, so cudaProfilerStart/Stop actually reach every inner worker on the async engine (the inherited sync methods dropped the coroutine un-awaited); plus a short grace after stop so nsys can finalize.
models/generation/vllm/vllm_backend.py (VllmInternalWorkerExtension.stop_gpu_profiling): after cudaProfilerStop, finalize each inner worker's live nsys capture NON-DESTRUCTIVELY via
nsys stop --session. This writes a complete .nsys-rep without killing the process, so no worker death and no engine fault-tolerance cascade -- every rank finalizes (vs at most one before). Runs on all inner workers via the existing collective_rpc("stop_gpu_profiling") broadcast.Before your PR is "Ready for review"
Pre checks: