Skip to main content

inference-decode-step-budget

Measure the low-concurrency (c=1..c=8) decode hot-path of a live vLLM pod FAST and CORRECTLY: where each token's time actually goes (GPU-busy vs host-idle gap vs comm), whether the workload is kernel-bound / host-bound / comm-bound, and what the addressable lever is. Uses vLLM's native /start_profile + /stop_profile HTTP endpoints (enable once, then restart-free shell-in captures in seconds). Encodes the correctness gates that prevent contaminated or under-counted budgets (clean single-stream driver, GPU-busy must include CUDA-graph execution, reconcile against driver-measured TPOT, reject load-time/lull/truncated captures). Triggers on "decode step budget", "c=1 hot path", "where does decode time go", "TPOT breakdown", "is decode kernel-bound or host-bound", "latency-tier profile", "GPU idle during decode", "start_profile", "torch profiler vllm", or any combination of "decode / c=1 / latency / TPOT / ITL" with "profile / budget / breakdown / hot-path / where" on a vllm deploy.

Informations de source

Dépôt
cfregly/claude-gpu-perf-tune
Dernière activité de la source
14 juin 2026 à 03:33
Langue détectée de SKILL.md
anglais
Étoiles
0
Forks
0

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Explorateur de fichiers
3 fichiers

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
inference-decode-step-budget
last_validated
2026-05-29T00:00:00.000Z
description
Measure the low-concurrency (c=1..c=8) decode hot-path of a live vLLM pod FAST and CORRECTLY: where each token's time actually goes (GPU-busy vs host-idle gap vs comm), whether the workload is kernel-bound / host-bound / comm-bound, and what the addressable lever is. Uses vLLM's native /start_profile + /stop_profile HTTP endpoints (enable once, then restart-free shell-in captures in seconds). Encodes the correctness gates that prevent contaminated or under-counted budgets (clean single-stream driver, GPU-busy must include CUDA-graph execution, reconcile against driver-measured TPOT, reject load-time/lull/truncated captures). Triggers on "decode step budget", "c=1 hot path", "where does decode time go", "TPOT breakdown", "is decode kernel-bound or host-bound", "latency-tier profile", "GPU idle during decode", "start_profile", "torch profiler vllm", or any combination of "decode / c=1 / latency / TPOT / ITL" with "profile / budget / breakdown / hot-path / where" on a vllm deploy.
allowed-tools
["Bash(kubectl:exec,*)","Bash(kubectl:cp,*)","Bash(kubectl:get,*)","Bash(kubectl:debug,*)","Bash(kubectl:port-forward,*)","Bash(curl:*)","Bash(python3:*)","Bash(nsys:*)","Bash(jq:*)","Bash(sha256sum:*)","Read","Write"]
# inference-decode-step-budget ## Purpose Answer "where does c=1 (low-concurrency) decode time actually go, and what moves it?" for a live vLLM deploy, **quickly and without the mistakes that make the answer wrong.** The deliverable is the **decode-step budget**: of the measured TPOT, how many ms are (a) GPU kernel/graph execution, (b) host-idle gap (Python scheduler + launch + sampling + spec-decode orchestration), and (c) comm. From that split the skill classifies the workload as **kernel-bound / host-bound / comm-bound** and names the addressable lever - so nobody spends days tuning a kernel that is 12% of TPOT. Done naively this measurement is slow and error-prone: repeated vLLM restarts under nsys, prefill-contaminated captures, and GPU-busy numbers that silently miss CUDA-graph execution. The fast path + correctness gates below make it a 5-minute, repeatable workflow. ## The fast path (enable once, iterate restart-free) The deployed vLLM exposes a **profiler HTTP API** gated on `ProfilerConfig` (`vllm/config/profiler.py` + `vllm/entrypoints/serve/profile/api_router.py` in the vLLM source tree): | Endpoint | Effect | |---|---| | `POST /start_profile` | begin a profiling window on the running engine | | `POST /stop_profile` | end the window. Flush the trace to disk | Two backends, both restart-free **after** a one-time enable: - `--profiler-config.profiler=cuda` + nsys `--capture-range=cudaProfilerApi --capture-range-end=stop --cuda-graph-trace=node` - **the accurate-budget default.** vLLM calls `cudaProfilerStart/Stop` on the endpoints. Nsys captures the timeline with CUDA-graph kernels resolved and **low overhead**, so the absolute GPU-busy% is trustworthy. Needs the nsys sidecar. - `--profiler-config.profiler=torch` `--profiler-config.torch_profiler_dir=/profiling` - kineto trace, **no sidecar**. Use for a **quick SHAPE check only** (graphs-in-play? host-gap-dominant? which ops?). **Do NOT trust its absolute busy%**: measured on a GLM-5.1 TP=8 NVFP4+graph canary, the torch profiler imposed a **~6x decode slowdown** (36 ms/tok vs true 6 ms/tok) and inflated kernel durations, so kineto read **~20% GPU-busy vs the nsys-accurate 12%**. The one robust kineto signal is the **inter-step host gap** (median 12.35 ms, matched nsys's 11.4 ms) - enough to confirm host-bound, not enough for the real budget. Set `torch_profiler_with_stack=false` + `warmup_iterations=0` (record-all, bound by a SHORT generation) - stack tracing ON + record-all OOM-crashed the engine at gpu-mem-util 0.85. ```mermaid flowchart LR enable["1x: --profiler-config on the deploy (one restart) OR standing profile deploy"] shell["kubectl exec into the pod"] start["curl -XPOST localhost:8000/start_profile"] drive["drive CLEAN single-stream c=1 (assets/clean-driver.sh)"] stop["curl -XPOST localhost:8000/stop_profile"] pull["copy trace to PVC (server-side cp), pull from persistent analyzer pod"] analyze["assets/decode_budget.py over KERNEL u GRAPH_TRACE u MEMCPY"] enable --> shell --> start --> drive --> stop --> pull --> analyze analyze -->|next config, NO restart| start ``` Per-iteration cost: **seconds**, not the ~5 min a vllm restart costs. ## When to use - "Where does my c=1 / latency-tier decode time go?" / "Is decode kernel-bound or host-bound?" / "Why is TPOT X ms?" - Before proposing any decode kernel optimization - to confirm the kernel is actually on the critical path (it usually is not at c=1). - A/B of a decode-affecting config (cudagraph mode, spec-decode K, attention backend) where you need the per-step budget, not just end-to-end tok/s. Do **not** use this skill for: - Per-kernel hardware-counter / roofline detail - that is [`inference-kernel-ncu-profile`](/plugins/profile-and-optimize/skills/inference-kernel-ncu-profile/SKILL.md). - Prefill / throughput-tier (high-concurrency) hot-spots - use [`inference-kernel-profile`](/plugins/profile-and-optimize/skills/inference-kernel-profile/SKILL.md) + [`analyze-zymtrace-workload`](/plugins/profile-and-optimize/skills/analyze-zymtrace-workload/SKILL.md). - End-to-end tok/s sweeps - that is [`inference-perf-bench`](/plugins/profile-and-optimize/skills/inference-perf-bench/SKILL.md). ## Example prompts - "what's the c=1 decode-step budget for glm-inference-latency" - "is GLM-5.1 decode kernel-bound or host-bound at concurrency 1" - "profile the decode hot path without restarting the pod" - "where does the 6ms/token go" - `/inference-decode-step-budget --deploy glm-inference-latency --concurrency 1` ## Engine gate: vLLM only (SGLang -> nsys fallback) This skill is **vLLM-specific**: the fast path is vLLM's native HTTP `/start_profile` + `/stop_profile` (torch-profiler) endpoints. **SGLang serve pods do not expose `/start_profile`**, so this skill fails closed on an SGLang arm. For the SGLang decode hot-path, fall back to a per-kernel **nsys timeline** (launch-wrap `sglang.launch_server` under nsys. Drive a c=1 single-stream window instead of c=64), then read the GPU-busy-vs-host-gap structure off the nsys CUDA-API trace (the same where/why question, captured a different way). The engine-agnostic SoL tiers (zymtrace L1, DCGM L3) apply to SGLang unchanged. ## Prerequisites (fail closed) 1. kubectl write access to the target namespace **and** credentials that will not expire mid-run (if access goes through Teleport, run `tsh status` FIRST and re-`tsh login` before starting - a mid-run expiry is a known time-sink). 2. The target is a **canary / non-production** deploy, or a confirmed maintenance window - enabling the profiler endpoint requires one restart. 3. A writable trace dir mounted in the pod (`/profiling` emptyDir or a PVC) and, for cross-restart durability, an RWX PVC the analyzer pod can also mount. 4. For the nsys escalation only: the nsys sidecar image + the pod must NOT already have `CUDA_INJECTION64_PATH` set by zymtrace (see the ncu skill's "Blocker 1"). Latency canaries typically have no zymtrace injection. ## Interaction style Iterative. Do one phase, report the single most important number, then ask before the next. Never dump all diagnostics at once. Mirror [`k8s-troubleshooting`](/plugins/profile-and-optimize/skills/k8s-troubleshooting/SKILL.md). ## Workflow ### Phase 0: confirm intent + check prior art State back the deploy, namespace, concurrency, and shape. **Before proposing any experiment, grep the deploy's prior evidence bundles and summaries** - a sweep that was already settled (e.g. an MTP `num_speculative_tokens` sweep) wastes a cycle if re-run. Confirm cluster access is live. ### Phase 1: enable the profiler endpoint (one-time) Default (on-demand patch, ~5 min restart, then unlimited fast captures): ```bash # add to the deploy's vllm args: # --profiler-config.profiler=torch # --profiler-config.torch_profiler_dir=/profiling # + a /profiling emptyDir|PVC mount, then kubectl apply (Recreate). ``` Standing option (zero setup next time): apply the out-of-Service-selector `<deploy>-profile` deploy so production routing is untouched. Confirm `/start_profile` is mounted: `curl -s -o /dev/null -w '%{http_code}' localhost:8000/start_profile` returns 200 (not 404). ### Phase 2: drive a CLEAN single-stream c=1 workload + capture Gate 1 (clean driver): tiny prompt + long generation + `ignore_eos`, ONE request in flight -> pure decode, no prefill, no inter-request gaps. ```bash kubectl -n $NS exec $POD -c $CTR -- bash -c ' curl -sS -XPOST localhost:8000/start_profile # assets/clean-driver.sh inlined: ~5s of steady c=1 decode curl -sS localhost:8000/v1/completions -H "Content-Type: application/json" \ -d "{\"model\":\"$MODEL\",\"prompt\":\"Count slowly:\",\"max_tokens\":512,\"ignore_eos\":true,\"temperature\":0.0}" -o /dev/null curl -sS -XPOST localhost:8000/stop_profile' ``` The torch profiler `active_iterations` / `delay_iterations` schedule (`ProfilerConfig`) bounds the window so it cannot OOM (Gate 6: keep windows modest at gpu-mem-util >= 0.85). ### Phase 3: pull the trace + compute the budget Copy the trace to the PVC **server-side** (`cp /profiling/... /models/...`) to survive any pod cycle, then analyze from a long-lived analyzer pod (RWX PVC mount. Keep it alive across iterations). Run: ```bash python3 assets/decode_budget.py <trace> # auto-detects nsys .sqlite or kineto .json[.gz] ``` It reports per-device **true GPU-busy = union(KERNEL, GRAPH_TRACE, MEMCPY)**, the idle-gap histogram, the inter-step cadence, and the per-step busy-vs-idle-vs-comm split. ### Phase 4: apply the correctness gates, then classify Run the gates (next section). Only then classify: - **host-bound** (GPU idle >> busy per step): lever = host path - cudagraph coverage, spec-decode host orchestration, graph the sampling/LM-head tail. Kernel tuning is futile. - **kernel/compute-bound** (GPU busy ~= step, one kernel dominates): escalate to [`inference-kernel-ncu-profile`](/plugins/profile-and-optimize/skills/inference-kernel-ncu-profile/SKILL.md) on that kernel. - **comm-bound** (all-reduce/all-gather dominates busy): NCCL/NVLS env + TP/topology. Report the budget table + the verdict + the single highest-leverage lever. ## Correctness gates (the anti-mistake checklist) These are mandatory. Each one prevents a class of wrong answer seen in practice. 1. **Clean driver.** Single-stream, tiny prompt, long gen, `ignore_eos`. A `vllm bench serve --random-input-len 2048` driver mixes prefill + leaves inter-request lulls -> the budget is contaminated. Verify the driver round ran continuously (no connect errors / 500s) before trusting the trace. 2. **GPU-busy MUST include CUDA-graph execution.** vLLM runs the decode forward inside CUDA graphs. Counting only `CUPTI_ACTIVITY_KIND_KERNEL` under-counts busy by the graph time (it lands in `GRAPH_TRACE`). Always union KERNEL + GRAPH_TRACE + MEMCPY. Detect graphs via the `cudaGraphLaunch` count (huge vs `cudaLaunchKernel` -> graphs in play). Use `--cuda-graph-trace=node` (nsys) to resolve the kernels inside. 3. **Reconcile against driver-measured TPOT.** Compute tokens/step (MTP emits ~accept_len tok/step) and check `step_cadence / tokens_per_step` == benchmark TPOT within ~10%. If it doesn't reconcile, the capture is wrong. 4. **Capture-quality gate.** Reject a trace that has no CUDA-kernel data (caught a load-time-only / lull window), shows no repeating decode-step pattern (caught a between-request lull), or is truncated (caught a crash mid-window). **But first rule out the CUDA image-vs-driver CUPTI skew (Gate 0):** on GB300 nodes a CUDA 12.9 image vs a 13.0 driver makes CUPTI fail to init (`CUPTI_ERROR_INVALID_DEVICE`) -> 0 kernels for ALL CUPTI clients (torch, nsys, ncu) regardless of hygiene. Grep the kineto log for `CUDA versions. CUPTI/Runtime/Driver` -> a 12.x/13.x split needs a CUDA-13 image or zymtrace, not a re-capture. 5. **Prior-art gate.** Grep prior bundles + summaries before proposing an experiment. Do not re-run settled sweeps. 6. **Modest window.** Keep the profiler window short (a few seconds / `active_iterations` low) - long windows OOM at gpu-mem-util >= 0.85. 7. **Credential pre-flight.** Verify cluster access first (e.g. `tsh status` for Teleport). Refresh before starting. ## Assets - [`assets/clean-driver.sh`](/plugins/profile-and-optimize/skills/inference-decode-step-budget/assets/clean-driver.sh) - workload-agnostic clean single-stream decode driver (env: `MODEL`, `PROMPT`, `MAX_TOKENS`, `CONCURRENCY`, `ROUNDS`). Reports per-round wall-time so you get TPOT for the reconciliation gate for free. - [`assets/decode_budget.py`](/plugins/profile-and-optimize/skills/inference-decode-step-budget/assets/decode_budget.py) - auto-detects nsys `.sqlite` (via `nsys export`) or kineto `.json[.gz]`. Prints the true-busy budget, idle-gap histogram, inter-step cadence, and the busy-vs-idle-vs-comm per-step split with the capture-quality verdict. ## Worked example (canonical) GLM-5.1 NVFP4 + MLA + DSA + MTP, TP=8, B200, c=1: `step ~13.1 ms = ~1.71 ms GPU-busy (12%) + ~11.4 ms host-idle (88%)`, TPOT 6.21 ms/tok (MTP ~2 tok/step) -> **host-bound**. Kernels + graphs are 12% of TPOT so kernel work cannot move c=1. ## Cost + risk - One-time ~5 min restart to enable the endpoint (on-demand) or zero (standing deploy). Per-capture cost is seconds. - torch profiler adds modest overhead during the window only. Kineto traces are typically 10-200 MB. - The profiler endpoint is dev-only (vLLM logs a warning) - do not enable on production-routed pods. Use a canary or out-of-selector deploy. ## Pairs with - [`inference-kernel-ncu-profile`](/plugins/profile-and-optimize/skills/inference-kernel-ncu-profile/SKILL.md) - when Phase 4 says kernel-bound, get per-kernel %SoL / occupancy. - [`inference-kernel-profile`](/plugins/profile-and-optimize/skills/inference-kernel-profile/SKILL.md) - nsys timeline for prefill / throughput tier. - [`inference-perf-bench`](/plugins/profile-and-optimize/skills/inference-perf-bench/SKILL.md) - the end-to-end TPOT the budget must reconcile against. - [`evidence-bundle-init`](/plugins/profile-and-optimize/skills/evidence-bundle-init/SKILL.md) - scaffold the bundle first. ## Full-context reporting (no bare numbers) Per the canon "Every performance number carries its full context (no bare numbers)" (`docs/METHODOLOGY.md` "Full-context reporting"): every number this skill emits MUST carry its full measurement-context descriptor, and every comparison MUST be matched on it. A bare `tok/s` / TPOT / BW / %SoL / speedup is a defect - it cannot set a default, ship a config, or appear in a report. - **Identity:** model (+HF path), hardware (exact ceiling token `GB300`/`B200`), quant, kv-cache dtype. - **Parallelism:** TP, DP (replicas), PP, EP, parallel_strategy. - **Serving cfg:** max-num-seqs, max-num-batched-tokens, gpu-memory-utilization, max-model-len, cudagraph_mode/enforce_eager, async_scheduling, prefix-caching. - **Workload:** dataset, ISL/OSL (or mean in/out tokens), concurrency, num-prompts. - **Regime:** warm vs cold. Latency vs throughput tier.
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub