[NV] llm-d: re-test max-tpt in-sweep with unique SLURM job-name fix#2117
[NV] llm-d: re-test max-tpt in-sweep with unique SLURM job-name fix#2117ezrasilvera wants to merge 28 commits into
Conversation
Add a new llm-d-vllm disaggregated (prefill/decode) multi-node benchmark path to InferenceX, orchestrated entirely by SLURM (no Kubernetes). vLLM engines with llm-d EPP routing, an Envoy front-end, and NIXL KV transfer. - benchmarks/llm-d/: container image (Dockerfile on vLLM v0.23 base), Envoy + EPP config, binaries.env, and an extraction helper that decouples epp/sidecar/envoy from the vLLM image. - benchmarks/multi_node/llm-d/: server.sh orchestrator (per-role TP/EP, prefill/decode coordinators, PREFILL_WORKERS multi-engine prefill), job.slurm (pyxis/enroot + docker paths, per-node model probe), submit.sh. - runners/launch_gb200-nv.sh: dispatch branch for FRAMEWORK=llm-d-vllm. - benchmarks/benchmark_lib.sh: anchor a relative lm_eval task yaml to the repo root so evals resolve inside the serving container. Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
Route dsv4 models through the vLLM tokenizer wrapper and allow deepseek_v4 as a --tokenizer-mode choice, bypassing HF AutoTokenizer which does not recognize the model on the benchmark image. Adds --trust-remote-code / --use-chat-template / --dsv4 passthroughs. Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
- benchmarks/multi_node/dsv4_fp4_gb200_llm-d-vllm.sh: GB200 wrapper (GPUS_PER_NODE=4) for the DSV4-Pro FP4 llm-d-vllm path. - llm-d-recipes/dsv4-fp4-gb200-mid-curve-megamoe.yaml: 1P+1D DEP8 recipe. - nvidia-master.yaml: register the mid-curve-megamoe (conc 256/512/1024) and high-tpt-megamoe (Option-B 2P+1D, conc 4096) config keys. - perf-changelog.yaml: changelog entry for the two keys. Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
…diagnostics The decode leader's prefill-readiness curl had no connect/max timeout, so a blackholed prefill endpoint made a single curl block forever - the 5-min deadline is only checked between calls, so it never fired. A 2P (PREFILL_WORKERS=2) run wedged ~7h on this before being cancelled. - Add --connect-timeout 5 --max-time 10 so an unreachable endpoint trips the 5-min deadline and FATALs cleanly instead of hanging the whole run. - Probe every prefill node (ALL_IPS[0:PREFILL_NODES]), not just IPS[0]: endpoints.yaml lists one prefill endpoint per node, and PREFILL_WORKERS>1 spans multiple independent DP engines, so IPS[0] only covered engine 1. - On failure, dump network diagnostics - source IP toward target, /proc/net/route on-link-vs-gateway subnet classification, bash /dev/tcp L4 connect test, ping, verbose curl - to separate a server-not-ready problem from a subnet/routing/firewall problem. Log the subnet layout up front too. Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
Two teardown fixes so a failed/hung benchmark can no longer wedge the whole SLURM allocation (a conc4096 2P run hung 8h until GitHub killed it at the max-execution limit, losing the server-log tarball): - server.sh coordinator: write the .bench_done release marker from an EXIT trap, so job.slurm scancels the allocation on ANY coordinator exit (failed bench, set -e abort, EPP/Envoy bring-up error), not only a clean finish. - benchmark_lib.sh: optional `timeout` wrapper around the bench client, gated on BENCH_TIMEOUT_S (default 2400s, set by server.sh); a hung client (asyncio shutdown wedged on thousands of failed in-flight requests) is killed instead of pinning 24 GPUs to TIME_LIMIT. The bench call is also made non-fatal so a multi-conc sweep records every point it can and always reaches teardown. Replace the leader-only .prom scraper with metrics_scrape.py, which scrapes EVERY prefill and decode vLLM (on the vLLM port, not the sidecar) plus EPP into one time-correlated CSV. This makes per-engine load visible - crucially whether EPP fans out across all prefill engines - which the old PREFILL_LEADER_IP-only scraper could not show for PREFILL_WORKERS>1. Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
dsv4-fp4-gb200-llm-d-vllm-smoke-2p1d: two independent 1-node DEP4 prefill engines (PREFILL_NODES=2 PREFILL_WORKERS=2) + 1 DEP4 decode = 3 nodes / 12 GPUs, ISL/OSL 1024/128, single conc 128 x ~512 prompts, RANDOM_RANGE_RATIO=1.0. Runs in minutes; validates the per-engine metrics scraper and confirms EPP fans out across both prefill engines before spending the 6-node high-tpt run. Reuses the mid-curve recipe. Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
The 3-node DEP4 smoke (1 node/engine) OOMs at model load: DSV4-Pro's MegaMoE needs each EP rank to hold 1/4 of the experts at EP4 (~2x the per-GPU weight of the validated EP8), which does not fit in HBM (run 28588980717: "CUDA Error: out of memory at cumem_allocator.cpp:139"). DEP8 (2 nodes/engine) is the minimum viable EP. Smoke is now PREFILL_NODES=4 PREFILL_WORKERS=2 (2x DEP8) + DECODE_NODES=2 = 6 nodes, same layout as high-tpt but a tiny workload (conc 128), so it validates the scraper + EPP fan-out fast on the real topology. Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
LLMD_LB_MODE (recipe env, default hybrid). multi-port switches both roles to --data-parallel-multi-port-external-lb + --data-parallel-supervisor-port and drops --api-server-count, so each DP rank serves on VLLM_PORT+rank (base+global rank; a worker node binds VLLM_PORT+START_RANK..). The decode pd-sidecar gets --data-parallel-size=$DP_SIZE, which per data_parallel.go makes it expose SIDECAR_PORT+i -> vLLM VLLM_PORT+i; per node only its local rank ports work, so endpoints.yaml lists each node's local rank ports (node j: base + j*GPUS_PER_NODE .. +GPUS_PER_NODE). Health waits + the metrics scraper follow the same per-rank ports (scraper hits the vLLM port, never the sidecar). Mirrors the upstream GENERIC wide-ep-lws guide (DeepSeek-R1), which uses per-rank ports for P/D; our DSV4 path was hybrid-lb (one port/node). Recipe dsv4-fp4-gb200-megamoe-mp.yaml = mid-curve engine config + LLMD_LB_MODE multi-port + EPP scorers matched to the upstream router config (drop kv-cache-utilization-scorer; prefill = prefix-cache/queue/active-request, decode = active-request only). Keys smoke-2p1d-mp (fast validation) and high-tpt-megamoe-mp (conc4096 A/B vs the hybrid high-tpt key) select it. Hybrid path is unchanged (default), so existing keys are byte-identical. Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
mp-smoke 28596473295 showed every prefill node (leader AND worker) binds
VLLM_PORT+local_index (8200-8203), not base+global rank: --data-parallel-start-rank
only sets DP rank IDs, the HTTP servers are always the node-local range. My
base+global assumption made the health gate probe a dead worker port
(10.30.1.39:8204 not ready). Fix everywhere to base+local:
- HEALTH_PORT / SIDECAR_HEALTH_PORT = VLLM_PORT / SIDECAR_PORT (local rank-0).
- sidecar --data-parallel-size = DP_SIZE_LOCAL (each node exposes its 4 local
ranks at SIDECAR_PORT..+3 -> vLLM VLLM_PORT..+3).
- endpoints + scraper: each node lists base..base+GPUS_PER_NODE-1 (same range
on every node, distinct address).
Also fix a double 'export export LLMD_LB_MODE' from overlapping edits.
Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
… bundle At conc4096 both Dynamo (8153 tok/s/GPU) and upstream llm-d PR #1737 (8858) succeed on the IDENTICAL 2P TP1/DP8/EP8 layout we use, while ours collapses to 8.3% completion. The difference is the engine bundle, so this recipe applies it: prefill gpu-mem 0.95, max-model-len 9280, max-num-seqs 16, max-num-batched-tokens 32768, no-enable-flashinfer-autotune; decode gpu-mem 0.9, max-model-len 9280, no-flashinfer-autotune. Same EPP/env/topology + kv producer-consumer + hybrid-lb as mid-curve, to isolate the engine bundle. The max-model-len/no-autotune/ max-num-seqs guards are what let this 32768/0.95 bundle bring up without the two OOMs our earlier speed-bundle hit. Keys tuned-smoke + tuned-high-tpt select it. Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
The no-kube Envoy front door (benchmarks/llm-d/envoy.yaml) defined the epp ext_proc cluster and the original_dst cluster with no circuit_breakers block, so Envoy applied its DEFAULT per-cluster thresholds of max_requests=1024 and max_pending_requests=1024. The ext_proc filter holds one long-lived HTTP/2 stream to EPP for the entire lifetime of every request, so at max-concurrency 4096 only 1024 active + 1024 pending = 2048 requests ever reached EPP; the remaining ~18432 (warmups + prompts) got a fast 500 and never saw the engine. This capped every high-tpt run at exactly 1024 completions / 2048 disagg decisions, identically across engine configs (mid-curve, Dynamo-tuned) and LB modes (hybrid, multi-port) - because the cap was the front door, not the engine. It is why Dynamo (no Envoy) and upstream llm-d (k8s gateway) hit 8000+ tok/s/GPU on the same TP1/DP8/EP8 2P1D layout where we stalled at ~1783. Fix: add circuit_breakers with thresholds far above max-concurrency to both clusters. Evidence: envoy_access RESPONSE_CODE histogram = 2048x200 + 18432x500 in all three walled runs; disagg_decision_total pinned at 2048 in all three. Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
…0000 Align the circuit_breakers thresholds added in the previous commit to the exact values from the authoritative upstream preset - the llm-d-router-standalone Helm chart (llm-d-inference-scheduler config/charts/llm-d-router-standalone/values.yaml lines 191-208): max_connections/max_pending_requests/max_requests = 40000 on both the ext_proc (EPP) and original_dst clusters, plus max_retries 1024 on ext_proc. Both clear max-concurrency 4096 with wide headroom; 40000 just matches upstream rather than an arbitrary ceiling. Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
… upstream) The prior CB commit lifted the epp ext_proc cluster to 40000 but left the original_dst cluster at 1000000. Both clear max-concurrency 4096 identically (this is the exact config the 8452 tok/s/GPU high-tpt confirmation run used), but pin original_dst to 40000 too so both clusters match each other and the upstream llm-d-router-standalone preset. Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
…util + decode prefix-cache) Align the mid-curve/high-tpt recipe's EPP scheduling profiles to the upstream generic wide-ep-lws router config (llm-d guides/wide-ep-lws/router/wide-ep-lws.values.yaml): prefill = prefix-cache(3) + queue(2) + active-request(2) decode = active-request only Removed: kv-cache-utilization-scorer from prefill (upstream does not use it; its weight becomes active-request(2)); and prefix-cache-scorer from decode - at weight 3 it outweighed active-request(2) and pinned decode to one pod on a shared prompt prefix, producing the ~2:1 decode routing imbalance (decode-0 1389 vs decode-1 659). Upstream decode routes on active-request alone and balances. Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
…keys Pre-PR cleanup of debug/experiment scaffolding added while root-causing the conc4096 wall (now fixed via the Envoy circuit-breaker change). Removed: - benchmarks/multi_node/llm-d-recipes/dsv4-fp4-gb200-megamoe-mp.yaml (multi-port LB experiment - dead: NIXL KV-expiry) - benchmarks/multi_node/llm-d-recipes/dsv4-fp4-gb200-megamoe-tuned.yaml (Dynamo engine-bundle A/B - superseded; the circuit-breaker fix, not engine args, was the wall) - nvidia-master.yaml keys: smoke-2p1d, smoke-2p1d-mp, high-tpt-megamoe-mp, tuned-smoke, tuned-high-tpt (experiment/smoke keys tied to the above) Kept: mid-curve-megamoe + high-tpt-megamoe keys, the envoy circuit_breakers fix, the teardown/timeout fix, and the DSV4 tokenizer plumbing. Deferred to a follow-up (after the mid-curve rerun validates and its scraper CSV is analysed): removing the per-engine vLLM metrics scraper and the multi-port branches in server.sh. Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
…pper - Remove metrics_scrape.py + its scraper bring-up/stop/conc-tag in server.sh - Remove dead multi-port LB path (LLMD_LB_MODE and all branches); hybrid only - Remove EPP --v=4 and NCCL/UCX transport debug logging (keep UCX_TLS pin) - Rename dsv4_fp4_gb200_llm-d-vllm.sh -> ...-disagg.sh; update launcher SCRIPT_NAME Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
Comment-only cleanup across the llm-d-vllm GB200 assets: - recipe header rewritten to describe the common per-engine shape and the two topologies (mid-curve 1P1D / high-tpt 2P1D) it backs - drop historical tuning-log comments from the recipe, launcher, envoy.yaml, binaries.env, and Dockerfile Verified: no non-comment lines changed; envoy circuit_breakers (40000) intact. Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
Include dsv4-fp4-gb200-llm-d-vllm-high-tpt-megamoe (2P1D DEP8, conc 4096) alongside the mid-curve key so a labeled PR sweep validates both topologies, not just mid-curve. Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
- llm-d/README.md: document endpoints.yaml as one entry per node (not per leader) to match server.sh add_role(); fix example ports (prefill 8200, decode 8000) and rewrite the rationale section (C2) - perf-changelog.yaml: canonicalize pr-link XXX -> 2050 (C4) Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
…ent (C1) Bump the EPP and P/D routing-sidecar source images from the moving -dev:main tags to release :v0.9.0 in both binaries.env and the combined-image Dockerfile, so a rebuild is reproducible. Verified both tags ship linux/arm64 and still expose /app/epp and /app/pd-sidecar (statically-linked aarch64). Envoy pin unchanged. Also reword the binaries.env header (PR #2050 review C1): the old comment claimed the file is 'not used', but job.slurm still sources it and conditionally bind-mounts the binaries via LLMD_BIN_DIR. Clarify it is an optional mounted-binary override, a no-op by default, and fix the 'build an use' -> 'build and use' typo. Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
…ild-arg
BuildKit rejects `COPY --from=${VAR}` with 'variable expansion is not
supported for --from'. Promote VLLM_BASE and the three source-image ARGs
(EPP/sidecar/envoy) to global scope, bind each to a named FROM stage
(epp_src / sidecar_src / envoy_src), and COPY --from those stage aliases.
No change to defaults or --build-arg override behavior; still in sync
with binaries.env.
Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
…sses The eval-only jobs failed 'eval metadata concurrency does not match workflow request' even though gsm8k accuracy passed (0.95 >= 0.91). Cause: run_eval/append_lm_eval_summary read EVAL_CONCURRENT_REQUESTS and CONC (benchmark_lib.sh), never EVAL_CONC, so with CONC empty meta_env.json recorded conc=1 while validate_scores.py --expected-concs checked 4096. Mirror the AMD multi-node servers: before run_eval, set EVAL_CONCURRENT_REQUESTS from EVAL_CONC (fallback: max of the x-delimited BENCH_MAX_CONCURRENCY) and export CONC so the stamped concurrency matches the workflow request. Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
Add dsv4-fp4-gb200-llm-d-vllm-max-tpt-megamoe: 3 prefill (DEP8) + 1 decode (DEP8) at conc=4096, mirroring NVIDIA/srt-slurm disagg-gb200-max-tpt-megamoe (engine-identical to high-tpt; only prefill scale-out 2->3), so it reuses the same CONFIG_FILE. Validated standalone via e2e run 28711505502 at 8797 tok/s/GPU (median TTFT 58 ms) - above high-tpt's 8241 tok/s/GPU, since the extra prefill instance relieves the conc=4096 prefill bottleneck. Adds a matching perf-changelog entry under PR #2050. Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
Move the max-tpt config-key to the top of the changelog entry so it dispatches first in the serialized multi-node matrix. The prior sweep (28714892207) had max-tpt land last; a transient decode-node slowdown degraded that point (4.5x TPOT vs high-tpt on an identical decode config). Running it first surfaces its result early and re-tests the transient before the shorter mid/high legs finish. Ordering only - all three points still run and land on the graph. Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
max-tpt (3P1D conc4096) succeeds standalone (8747 tok/s) but dies in-sweep: the decode's cross-node gloo TCP coordination hits 'Connection reset by peer' during the conc4096 benchmark warmup burst -> EngineDeadError cascade. No OOM / CUDA / hardware fault in any rank - it is a transient distributed coordination reset under load. Raise the torch-distributed process-group timeout (1800s, matching srt-slurm) on both roles so a momentarily-stalled peer is not dropped mid-burst. Applies to mid/high/max (shared CONFIG_FILE). Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
Triggers the sweep to validate the TORCH_DISTRIBUTED_DEFAULT_TIMEOUT=1800 fix for max-tpt in-sweep (real curve run -> unofficial graph), rather than a throwaway e2e. Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
Max-tpt (3P+1D DEP8 conc4096) passes as standalone e2e but fails when run alongside mid-curve + high-tpt in the same sweep matrix. Drop mid-curve and high-tpt from the sweep entry temporarily to confirm whether the failure is sweep-harness-specific. Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
… scancel The max-tpt (3P+1D DEP8 conc4096) benchmark passes as a standalone e2e but its throughput job was killed by an external SIGTERM ~10 min into weight load in the isolated in-sweep run 28789948711, before the server came up. No result JSON was produced, so the launch step failed with "No benchmark result files found". The engine logs show a clean load with zero errors, and the twin eval job (same server config, separate nodes) passed gsm8k at em_strict 0.959 - so this is not a config or engine problem. submit.sh set the SLURM --job-name to "$RUNNER_NAME" (e.g. "gb200-nv_1"), which is stable across runs. The workflow "Slurm cleanup" steps run "scancel --user=$USER --name=<runner.name>" at pre-run and post-run, so a bare runner-name allocation can be cancelled by any concurrent or adjacent job on the shared cluster+user that shares the runner name - not only its own cleanup. Fix: include GITHUB_RUN_ID and GITHUB_RUN_ATTEMPT in the SLURM job name so an exact "--name=<runner.name>" scancel from elsewhere no longer matches this allocation. The runner-name prefix is kept first so per-runner cleanup and greps still recognize the job. Also isolates the sweep to the single max-tpt config-key to re-test the fix in isolation. If max-tpt now passes in-sweep, the name-scoped scancel was the cause; if it still fails, this rules it out. Signed-off-by: Ezra Silvera <ezra@il.ibm.com>
10aeffc to
8dcb9ee
Compare
There was a problem hiding this comment.
Additional findings (outside current diff — PR may have been updated during review):
-
🟡
configs/nvidia-master.yaml:9075-9078— The merge-blocker comment at lines 9075-9078 says 'theimage:on both llm-d-vllm keys below (mid-curve + high-tpt) is a personal build ... Republish ... before merge', but this PR adds a THIRD sibling keydsv4-fp4-gb200-llm-d-vllm-max-tpt-megamoe(line ~9160) that also usesghcr.io/ezrasilvera/llm-d-nokube-vllm:vllm0.23— and it is the config this PR is actively benchmarking (per the perf-changelog entry). Updating the enumeration to "all three (mid-curve + high-tpt + max-tpt)" avoids a comment-driven cleanup leaving max-tpt on the personal image at merge time.Extended reasoning...
What's stale. The block introduced in the earlier llm-d PR added a merge-blocker note above the mid-curve/high-tpt entries. Verbatim: "PR follow-up (merge-blocking): the
image:on both llm-d-vllm keys below (mid-curve + high-tpt) is a personal build. Republish it to the org registry ... and update theseimage:lines before merge." This PR then adds a third sibling —dsv4-fp4-gb200-llm-d-vllm-max-tpt-megamoe— that reuses the sameghcr.io/ezrasilvera/llm-d-nokube-vllm:vllm0.23image, so the enumeration "both ... (mid-curve + high-tpt)" is now numerically wrong (three, not two).\n\nWhy it matters. The comment is explicitly a merge-blocking TODO whose intent is republish and swap all siblingimage:lines. A reader executing that TODO by following the enumeration literally — the way in-file comments are normally read — could swap the two named keys and leavemax-tpt-megamoepointing at the personal registry. Given that this PR is specifically the max-tpt re-test (per its title and the perf-changelog entry it adds), leaving that one entry on the personal image would be a real regression for the very config this PR ships.\n\nStep-by-step proof.\n1. Reviewer (or the PR author on the last pass) reads nvidia-master.yaml:9075-9078, sees "theimage:on both llm-d-vllm keys below (mid-curve + high-tpt) is a personal build".\n2. They locate the two enumerated keys:dsv4-fp4-gb200-llm-d-vllm-mid-curve-megamoe(line 9079) anddsv4-fp4-gb200-llm-d-vllm-high-tpt-megamoe(line ~9115), swap bothimage:lines to the republished org URL.\n3. They stop, because the comment said "both". The third keydsv4-fp4-gb200-llm-d-vllm-max-tpt-megamoeat line ~9160 remains onghcr.io/ezrasilvera/llm-d-nokube-vllm:vllm0.23.\n4. Sweep runs against the personal image for the config this PR advertises as validated.\n\nAddressing the refutation. The refutation argues (a) all threeimage:lines are visible immediately below the comment, (b)grep ezrasilveracatches all three, and (c) perf-changelog announcesmax-tpt-megamoe. All true, but each depends on the cleanup being done via a search rather than by following the comment's enumeration verbatim. The whole reason this comment exists is that a maintainer will read it before merge — and that reader is being told there are two entries when there are three. Fix cost is one line; the alternative is trusting every future reader to spot the mismatch on their own.\n\nFix. Either (preferred) republish and swap all threeimage:lines before merge and drop the comment, or update the enumeration to "the three llm-d-vllm keys below (mid-curve + high-tpt + max-tpt)" so a comment-driven cleanup covers max-tpt. Severity is "nit" — this is a comment consistency issue, not a bug in shipped behavior; the underlying personal-image concern is already flagged by the same comment. -
🟡
perf-changelog.yaml:4560-4566— Thepr-linkfor the newdsv4-fp4-gb200-llm-d-vllm-max-tpt-megamoeentry (perf-changelog.yaml:4566) points to/pull/2050, but this PR is #2117 and the description block (updated in HEAD commit 10aeffc) describes THIS PR's specific SLURM--job-namefix. If#2050is being intentionally used as the canonical parent for all llm-d entries, feel free to dismiss; otherwise consider updating to/pull/2117to match the convention used by sibling entries (#1994, #2010, #2077, etc.).Extended reasoning...
What / how it manifests
perf-changelog.yaml:4566 sets
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2050for thedsv4-fp4-gb200-llm-d-vllm-max-tpt-megamoeentry. Per the PR metadata, this is PR #2117 ([NV] llm-d: re-test max-tpt in-sweep with unique SLURM job-name fix).Why the description text points at #2117, not #2050
The description block reads:
- "Isolated max-tpt (3P+1D DEP8 conc4096) re-test with unique SLURM job-name fix"
- "max-tpt passes standalone e2e but was killed mid weight-load in sweep (run 28789948711)"
- "Fix: SLURM --job-name now includes GITHUB_RUN_ID/ATTEMPT so a name-scoped scancel from another job on the shared cluster cannot cancel this allocation"
Every one of these three bullets describes the SLURM
--job-namefix that lives in this PR's HEAD commit (10aeffc [NV] llm-d: unique SLURM job-name to protect max-tpt from name-scoped scancel), which touchesbenchmarks/multi_node/llm-d/submit.shto appendGITHUB_RUN_ID/GITHUB_RUN_ATTEMPT. The referenced run28789948711is the failed sweep noted in PR #2117's description. None of this content lives under PR #2050.Why the file convention says #2117
The other recent entries in
perf-changelog.yamlall use their own PR number aspr-link(e.g.#1994for the minimaxm3-fp4-b300 high-conc add,#2006for the mtp update,#2007for the B200 eagle update,#2010for the DSV4 GB300 dynamo update). The natural reading is thatpr-linkpoints at the PR that lands the entry — under that reading, this entry should read/pull/2117.Step-by-step
- PR #2117 is open with head
10aeffc.git logon this branch shows it and its recent commits. - That head commit modifies the exact perf-changelog block at lines 4560-4566 to describe the SLURM job-name fix.
- The submit.sh diff in this same PR is exactly that fix:
SLURM_JOB_NAME="${RUNNER_NAME}-${GITHUB_RUN_ID:-manual}-${GITHUB_RUN_ATTEMPT:-0}", replacing the prior bare-$RUNNER_NAME job name — which is what the description bullets attribute to "this PR". - So the description text is unambiguously about #2117, while
pr-linksays #2050.
Addressing the counter-argument
A refuting verifier noted that an earlier branch commit (
06f8b3c [NV] llm-d: address PR #2050 review nits (docs + changelog pr-link)) explicitly canonicalizedpr-link: XXX -> 2050for the ORIGINAL llm-d entry (lines 4538-4541, about mid-curve/high-tpt), and that commite149e9a(which added the max-tpt entry) says "Adds a matching perf-changelog entry under PR #2050". If the author's intent is that #2050 stays the canonical parent for every llm-d changelog entry — even ones landed by descendant PRs — this is by design, not a bug. That is a legitimate reading and is why this is filed as a nit, not a normal issue.The countervailing signal is that HEAD commit
10aeffc(in THIS PR, not #2050) rewrote the description text to describe THIS PR's fix specifically, which is inconsistent with "the entry belongs to #2050": if #2050 were truly canonical, the description would describe #2050's content, not #2117's.Impact
Documentation-only. No functional or CI impact. Audit trails from the changelog to PR context would follow the wrong link (readers land on #2050 expecting to see the SLURM job-name fix, and find the original llm-d landing PR instead).
Fix
If unintentional: change line 4566 to
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2117.
If intentional (#2050 as canonical parent): dismiss this comment; consider phrasing the description in past-tense parent-PR terms rather than "this PR" terms to avoid the mismatch.
|
|
||
| PYXIS_ENV_LIST="NUM_NODES,PREFILL_NODES,DECODE_NODES,ALL_IPS,PREFILL_LEADER_IP,DECODE_LEADER_IP" | ||
| PYXIS_ENV_LIST+=",PREFILL_WORKERS,DECODE_WORKERS" | ||
| PYXIS_ENV_LIST+=",PREFILL_DP_ADDR,DECODE_DP_ADDR,MODEL_NAME,GPUS_PER_NODE" | ||
| PYXIS_ENV_LIST+=",PREFILL_DP_SIZE,DECODE_DP_SIZE" | ||
| PYXIS_ENV_LIST+=",BENCH_INPUT_LEN,BENCH_OUTPUT_LEN,BENCH_MAX_CONCURRENCY" | ||
| PYXIS_ENV_LIST+=",BENCH_REQUEST_RATE,BENCH_RANDOM_RANGE_RATIO,BENCH_NUM_PROMPTS_MULTIPLIER" | ||
| PYXIS_ENV_LIST+=",RUN_EVAL,EVAL_ONLY,EVAL_CONC,FRAMEWORK,PRECISION,MODEL_PREFIX" | ||
| PYXIS_ENV_LIST+=",RUNNER_TYPE,RESULT_FILENAME,SPEC_DECODING,IS_MULTINODE,CONFIG_FILE" |
There was a problem hiding this comment.
🟡 The pyxis branch of job.slurm builds PYXIS_ENV_LIST (lines 211-218) as an explicit --container-env whitelist but omits MAX_FAILURE_RATE, while the docker branch at line 167 forwards it via -e MAX_FAILURE_RATE=${MAX_FAILURE_RATE:-0.05}. Since launch_gb200-nv.sh sets LLMD_CONTAINER_ENGINE=pyxis unconditionally for the GB200 llm-d-vllm path, the recipe's MAX_FAILURE_RATE=0.5 never reaches the container and benchmark_serving.py:971 falls back to the hardcoded 0.05 default. Fix: append ,MAX_FAILURE_RATE to PYXIS_ENV_LIST (symmetric with the docker -e line).
Extended reasoning...
What / where. The pyxis srun in benchmarks/multi_node/llm-d/job.slurm uses --container-env="$PYXIS_ENV_LIST" to whitelist which env vars propagate into the container. PYXIS_ENV_LIST is built at lines 211-218 and enumerates 30 variables — but MAX_FAILURE_RATE is not one of them. The docker branch at line 167 explicitly forwards it via -e MAX_FAILURE_RATE=${MAX_FAILURE_RATE:-0.05}, and the surrounding PYXIS_ENV_LIST mirrors the docker -e list otherwise. The author's own comment at lines 197-198 ("Pre-export non-SLURM static vars so pyxis --container-env= whitelisting can pick them up") confirms the whitelist design intent.
Step-by-step proof.
benchmarks/multi_node/llm-d/submit.sh:88runsexport MAX_FAILURE_RATE=${MAX_FAILURE_RATE:-0.05}— value reaches sbatch env.- All three new GB200 llm-d-vllm recipes set
MAX_FAILURE_RATE=0.5in additional-settings (configs/nvidia-master.yaml:9106,:9152,:9196). runners/launch_gb200-nv.shsetsLLMD_CONTAINER_ENGINE=pyxisunconditionally — the docker branch is dead code on this SKU.job.slurmtranslates the sbatch env through--container-env="$PYXIS_ENV_LIST"; sinceMAX_FAILURE_RATEis not in the list, it is dropped at the container boundary.- Inside the container
utils/bench_serving/benchmark_serving.py:970-972doesos.environ.get("MAX_FAILURE_RATE", "0.05")— it silently gets 0.05. - At line 975,
failure_rate > 0.05triggersraise SystemExit("FAIL: request failure rate ...")and no result JSON is written.
Impact on this PR. The concrete impact is contingent on the actual failure rate exceeding 5% at conc=4096 with 3P+1D DEP8. The standalone e2e run (28748597199, ~8747 tok/s/GPU) referenced in the PR description already passed on the same pyxis path with the same missing forwarding — strong empirical evidence that either pyxis is propagating the variable via default env inheritance on this cluster, or that the natural failure rate is well under 5% and the 0.05 default is sufficient. Either way, this in-sweep re-test is likely to reflect the same behavior. The current isolated run's observed failure was a SIGTERM during weight load (before any requests were served), which is orthogonal to MAX_FAILURE_RATE.
Why still worth flagging. The docker/pyxis asymmetry is a real contract inconsistency — every other variable the docker branch -es is explicitly enumerated in PYXIS_ENV_LIST. The recipes explicitly opted into a 0.5 tolerance, and if a future transient 5xx spike (well within that tolerance) ever pushes the failure rate above 5%, the run would fail with a symptom (FAIL: request failure rate) that could be confused with the SIGTERM cause this PR is trying to isolate.
Fix. One-line append to PYXIS_ENV_LIST at line 218 or similar:
PYXIS_ENV_LIST+=",MAX_FAILURE_RATE"
Symmetric with the docker -e MAX_FAILURE_RATE=${MAX_FAILURE_RATE:-0.05} line at 167. Also worth adding MAX_FAILURE_RATE to the pre-export block at lines 208-209 for symmetry, though it's already exported by submit.sh.
|
see unofficial run visualizer at https://inferencex.semianalysis.com/inference?unofficialRun=28918650203 |
|
Diagnostic complete. The isolated max-tpt re-test with the unique SLURM job-name fix passed (run 28918650203, 8730 tok/s/GPU), confirming the in-sweep failure was a name-scoped scancel, not the config. Folding the fix and restoring the full curve (mid-curve + high-tpt) into llm-d-initial (PR #2050); closing this diagnostic PR. |
What
Isolated re-test of the max-tpt (3P+1D DEP8 conc4096) llm-d-vllm multinode
benchmark with a fix for the in-sweep failure.
Why
max-tpt passes as a standalone e2e (run 28748597199, ~8747 tok/s/GPU) but its
throughput job was killed by an external SIGTERM ~10 min into weight load in
the isolated in-sweep run 28789948711 - before the server came up, so no
result JSON was produced and the launch step failed with "No benchmark result
files found". The engine loaded cleanly with zero errors and the twin eval job
(same server config, separate nodes) passed gsm8k at em_strict 0.959, so this
is not a config or engine problem.
Fix
submit.shset the SLURM--job-nameto$RUNNER_NAME(e.g.gb200-nv_1),which is stable across runs. The workflow "Slurm cleanup" steps run
scancel --user=$USER --name=<runner.name>at pre-run and post-run, so abare runner-name allocation can be cancelled by any concurrent/adjacent job on
the shared cluster+user that shares the runner name. This change appends
GITHUB_RUN_IDandGITHUB_RUN_ATTEMPTto the job name so an exact--name=<runner.name>scancel from elsewhere no longer matches thisallocation.
The sweep is isolated to the single max-tpt config-key. If max-tpt now passes
in-sweep, the name-scoped scancel was the cause; if it still fails, this rules
it out and points to an environmental/scheduling cause.