Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(profiling): support Dynamo Nsight capture - #411

Open
kyleliang-nv wants to merge 2 commits into
mainfrom
kylliang/agentperf-targeted-nsys
Open

fix(profiling): support Dynamo Nsight capture#411
kyleliang-nv wants to merge 2 commits into
mainfrom
kylliang/agentperf-targeted-nsys

Conversation

@kyleliang-nv

@kyleliang-nv kyleliang-nv commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fix iteration-triggered Dynamo Nsight capture by using every selected worker's DYN_SYSTEM_PORT and the correct /engine/control/start_profile and /engine/control/stop_profile routes.

  • Preserve existing all-worker wrapping: per-phase capture_scope defaults to all. Targeting one worker_index / worker_rank requires explicit capture_scope: selected.
  • Validate selected physical worker/rank bounds before submission.
  • Preserve leader-only control for native Dynamo sidecars and direct vLLM: all-process capture wraps followers but sends control once through the leader.
  • Prepend optional Nsight library paths inside the container without replacing its LD_LIBRARY_PATH.
  • Expose non-TRT-LLM Nsight trace/range options and an Ubuntu CLI setup script for x86_64 and Arm64.

Backend scope

Dynamo-hosted vLLM/SGLang use the system-server control routes. Direct SGLang retains its native /start_profile and /stop_profile; versions without a start_step request field start immediately and honor only the derived num_steps (pre-existing behavior).

TRT-LLM remains executor-driven through TLLM_PROFILE_START_STOP, without benchmark-side HTTP control. The new nsys_trace and capture_range_end fields remain explicitly non-TRT-LLM in this PR.

For vLLM dp_launch_mode: per_gpu, each physical DP process has its own Nsight wrapper/report. With per_node, one wrapped process owns multiple local DP ranks. Targeting selectors are ignored in capture_scope: all.

This is a generic Dynamo profiling fix; it adds no benchmark-specific lifecycle changes.

Review updates

  • Restored backward-compatible all-worker capture as the default and made selected-process tests explicitly opt in.
  • Added real-topology regression coverage: two logical workers spanning four nodes, with both per-GPU and per-node layouts, validating physical ranks and every allocated system port.
  • Documented validation-time topology/port-allocation assumptions.
  • Removed unrelated signature/docstring formatting churn.
  • Documented using nsys_trace instead of duplicating --trace in extra_nsys_args.
  • Kept both execution-extension display guards: the outer OR also covers non-profiling extensions; the inner guard is necessary. Tests cover profiling-only display and custom-benchmark display with profiling disabled.

Relationship to #264

Includes the Dynamo route correction independently proposed in #264, plus physical-worker targeting, all-process control, topology-aware validation, and regression coverage. It does not depend on #264.

Validation

Rebased on main at 4d3e8bd81b60f8472940da85676c73d239327488.

  • Focused profiling/dry-run/measurement-window suite: 141 passed.
  • Full local suite: 2,168 passed, 2 skipped, 6 deselected; the six failures were rerun on unchanged main with the same environment and all reproduced (macOS CPU-affinity, shell-path, and subprocess-tooling assumptions).
  • All 556 recipes validate.
  • Source Ruff lint/format, profiling-test lint/format, shell syntax, and git diff --check pass.
  • Full ty check reports the same 11 diagnostics as unchanged main, with no new diagnostics. CI currently treats this check as advisory.
  • Fresh GitHub Linux CI is green on 7ac824c: lint, full tests (including coverage), mock/server integration, recipe validation, and the advisory type-check job. The separate copyright check also passed.

No new GPU/Nsight hardware capture was performed for this review revision.

@kyleliang-nv
kyleliang-nv force-pushed the kylliang/agentperf-targeted-nsys branch 2 times, most recently from 4df2c5a to 8f34973 Compare September 10, 2026 16:45
@kyleliang-nv kyleliang-nv changed the title feat(profiling): target AgentPerf Nsight captures fix(profiling): support Dynamo Nsight capture Sep 10, 2026
@kyleliang-nv
kyleliang-nv force-pushed the kylliang/agentperf-targeted-nsys branch from 8f34973 to a1b96e1 Compare September 10, 2026 17:28
@kyleliang-nv
kyleliang-nv marked this pull request as ready for review September 11, 2026 04:59

@zbpatel zbpatel 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.

Inline review comments from the overlapping profiling work.

Comment thread src/srtctl/core/schema.py Outdated

start_step: int | None = None # Step to start profiling
stop_step: int | None = None # Step to stop profiling
capture_scope: Literal["selected", "all"] = "selected"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Defaulting capture_scope to "selected" (with worker_index=0/worker_rank=0) is a silent behavior change for existing type: nsys (iteration-triggered) recipes on multi-worker / multi-rank vLLM/SGLang. Before this PR, worker_stage wrapped every process (if profiling.is_nsys:) and the benchmark received all leader endpoints via _logical_worker_endpoints(). After this PR, an existing recipe with only start_step/stop_step set will, with no config change, wrap only worker 0 / rank 0 and send only that one control endpoint to the benchmark.

Two options:

  • Default capture_scope: "all" so existing recipes keep wrapping everything, and opt into targeted capture explicitly; or
  • Keep "selected" but call this out loudly in the PR description / a changelog note with the one-line migration (capture_scope: all).

I'd lean toward defaulting to "all" for backward compat — targeted capture is the new capability, so it's the one that should opt in. If "selected" stays the default, please add a test pinning the default-on-multi-worker behavior so the change is intentional and visible.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 7ac824c: capture_scope now defaults to all, preserving existing all-worker wrapping. Targeted capture requires explicit selected. Added a regression that runs the real allocator for two logical workers across four nodes, with both per_gpu and per_node layouts, and verifies all physical ranks are wrapped and every allocated DYN_SYSTEM_PORT reaches the helper.

Comment thread src/srtctl/core/schema.py

# Non-TRT-LLM Nsight activity domains. ``cuda-sw`` can be selected
# explicitly where software tracing is preferred over hardware tracing.
nsys_trace: str = "cuda,nvtx"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nsys_trace and capture_range_end are scoped to non-TRT-LLM only — _get_nsys_prefix_trtllm still hardcodes cuda,nvtx,ucx / stop / --sample=none / --cuda-graph-trace=node, so setting either field on a TRT-LLM run is silently ignored.

To be clear, this isn't a regression: the TRT-LLM hardcoding is preexisting, and extra_nsys_args (which is applied on the TRT-LLM path) still works as before. It's just that the two new fields don't apply there.

This will be addressed in #412, which routes all of these flags through shared helpers (_nsys_trace_domain, _nsys_cuda_graph_trace_mode, _nsys_sample_arg) across all four get_nsys_prefix_* branches, so the override surface is uniform across backends and there's no silent non-TRT-LLM-only scope. No action needed in this PR — flagging so reviewers know the gap is closed in the follow-up.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agreed; keeping this PR scoped to Dynamo capture. The schema/docs/PR description explicitly mark nsys_trace and capture_range_end as non-TRT-LLM. No TRT-LLM behavior change here.

Comment thread src/srtctl/core/schema.py Outdated
return None

def get_env_vars(self, mode: str, profile_dir: str) -> dict[str, str]:
def get_env_vars(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This hunk rewraps get_env_vars from a one-line signature to multi-line and deletes the blank line between the Args block and Returns: in the docstring. It's unrelated to the PR's goal, adds diff noise, and removing the Args/Returns blank line breaks Google-style docstring section separation (and may trip a docstring linter). Please revert this hunk.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 7ac824c: restored the single-line get_env_vars signature and the blank line between Args and Returns.

Comment thread src/srtctl/core/schema.py
"for you."
)

def _profiling_worker_ranks(self, mode: Literal["prefill", "decode", "agg"]) -> set[int]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Building a synthetic Endpoint with fake node names and delegating to the real backend.endpoints_to_processes is a neat way to derive the valid rank set without duplicating per-backend topology logic — and it works because Endpoint.gpu_indices is per-node and the per_gpu DP branch keys node_rank off dp_rank driven by the gpu_indices count.

Two small asks:

  1. Add a one-line comment noting the assumption it relies on — e.g. # mirrors Endpoint's uniform-per-node gpu_indices assumption; gpus_per_worker must be a multiple of gpus_per_node for multi-node workers — so the next reader doesn't have to re-derive it.
  2. It runs a full endpoints_to_processes (including port allocation) during validation just to read node_rank. Cheap, and the fake node names isolate any shared port allocator, but worth a brief note that this is validation-time work.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in 7ac824c. The comment now explains that this mirrors allocate_endpoints: multi-node workers get a uniform full per-node GPU set and partial-node workers a contiguous subset. A second comment notes that validation expansion uses a fresh port allocator, without reserving live ports or consuming runtime allocations. Added tests comparing the validation rank set with real allocated per_gpu/per_node topologies and their system ports.

Comment thread src/srtctl/cli/submit.py
details.add_row("benchmark", "container_image", config.benchmark.container_image)

profiling = config.profiling
if profiling.enabled:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

show_extensions now gates on or config.profiling.enabled (line 416), so the inner if profiling.enabled: on line 437 is redundant. Drop one of the two checks.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I retained both guards after checking the surrounding condition: show_extensions is an OR of custom benchmark/container, observability, telemetry, Mooncake, and profiling. A custom benchmark can make the outer condition true while profiling is disabled, so removing the inner check would display disabled profiling. Removing profiling from the outer condition would hide profiling-only configurations. Added a clarifying comment and regression assertions for both cases in 7ac824c.

Comment thread docs/profiling.md
| `*.capture_scope` | Capture one selected process or all physical processes | `selected` |
| `*.worker_index` | Logical worker selected for iteration-based nsys | `0` |
| `*.worker_rank` | Physical process rank selected within that worker | `0` |
| `nsys_trace` | Non-TRT-LLM Nsight activity domains | `cuda,nvtx` |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nsys_trace and extra_nsys_args: ["--trace=..."] both target the same nsys -t flag. A user setting both (e.g. nsys_trace: "cuda,nvtx,osrt" plus extra_nsys_args: ["--trace=osrt"]) will pass two -t/--trace flags and rely on nsys's last-wins behavior. Worth a one-line note here recommending nsys_trace over --trace= in extra_nsys_args when the dedicated field exists.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added the note in 7ac824c: for non-TRT-LLM workers prefer nsys_trace and do not also pass --trace through extra_nsys_args; otherwise duplicate options depend on Nsight argument parsing.

@kyleliang-nv
kyleliang-nv force-pushed the kylliang/agentperf-targeted-nsys branch from b9f0485 to 7ac824c Compare September 13, 2026 20:48
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