From c1c200b7cd348012e1c158b6f93cda6b8a295214 Mon Sep 17 00:00:00 2001 From: Gahow Wang Date: Thu, 23 Jul 2026 16:55:55 +0800 Subject: [PATCH] Add decode batch-grid stability experiment --- runs/frontier-decode-batch-grid-v0/.gitignore | 3 + .../analyze_decode_trace.py | 195 ++ .../experiment-card.md | 68 + .../figure-prototype.png | Bin 0 -> 84805 bytes .../figure-prototype.svg | 1870 +++++++++++++++++ runs/frontier-decode-batch-grid-v0/fleet.toml | 46 + .../jobs-smoke-r1.toml | 16 + .../jobs-smoke-r2.toml | 16 + .../materialize_decode_batch.py | 63 + .../plot_figure_prototype.py | 47 + .../run_decode_profile.sh | 174 ++ 11 files changed, 2498 insertions(+) create mode 100644 runs/frontier-decode-batch-grid-v0/.gitignore create mode 100755 runs/frontier-decode-batch-grid-v0/analyze_decode_trace.py create mode 100644 runs/frontier-decode-batch-grid-v0/experiment-card.md create mode 100644 runs/frontier-decode-batch-grid-v0/figure-prototype.png create mode 100644 runs/frontier-decode-batch-grid-v0/figure-prototype.svg create mode 100644 runs/frontier-decode-batch-grid-v0/fleet.toml create mode 100644 runs/frontier-decode-batch-grid-v0/jobs-smoke-r1.toml create mode 100644 runs/frontier-decode-batch-grid-v0/jobs-smoke-r2.toml create mode 100755 runs/frontier-decode-batch-grid-v0/materialize_decode_batch.py create mode 100644 runs/frontier-decode-batch-grid-v0/plot_figure_prototype.py create mode 100755 runs/frontier-decode-batch-grid-v0/run_decode_profile.sh diff --git a/runs/frontier-decode-batch-grid-v0/.gitignore b/runs/frontier-decode-batch-grid-v0/.gitignore new file mode 100644 index 0000000..011358f --- /dev/null +++ b/runs/frontier-decode-batch-grid-v0/.gitignore @@ -0,0 +1,3 @@ +fleet-artifacts/ +fleet-state/ +remote-outputs/ diff --git a/runs/frontier-decode-batch-grid-v0/analyze_decode_trace.py b/runs/frontier-decode-batch-grid-v0/analyze_decode_trace.py new file mode 100755 index 0000000..2ccea1f --- /dev/null +++ b/runs/frontier-decode-batch-grid-v0/analyze_decode_trace.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +"""Summarize graph-on vLLM Torch traces by decode component.""" + +from __future__ import annotations + +import argparse +import gzip +import json +import math +import statistics +from collections import Counter, defaultdict +from pathlib import Path + + +COMPONENT_NAMES = ( + "attention", + "linear_norm_rope", + "router", + "moe", + "collective", + "output_head", + "other", +) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument("--trace-root", type=Path, required=True) + parser.add_argument("--label", required=True) + parser.add_argument("--output", type=Path, required=True) + return parser.parse_args() + + +def classify(name: str, occurrences: int, steps: int) -> str: + lower = name.lower() + if any(token in lower for token in ("nccl", "allreduce", "all_reduce")): + return "collective" + if "topkgating" in lower or "fused_topk" in lower: + return "router" + if any( + token in lower + for token in ( + "fused_moe", + "moefcgemm", + "tensorrt_llm::kernels::cutlass_kernels", + "groupproblemshape", + "memcpy32_post", + ) + ): + return "moe" + if any( + token in lower + for token in ( + "flashattn", + "flashattnfwd", + "reshape_and_cache", + "prepare_varlen_num_blocks", + ) + ): + return "attention" + if "nvjet" in lower and occurrences <= steps * 2: + return "output_head" + if any( + token in lower + for token in ( + "nvjet", + "cublaslt", + "rms_norm", + "rsqrt", + "triton_red_fused_2", + "triton_poi_fused_3", + "triton_red_fused_0", + "triton_poi_fused_1", + ) + ): + return "linear_norm_rope" + return "other" + + +def stats(values: list[float]) -> dict[str, float | int]: + ordered = sorted(values) + return { + "n": len(values), + "mean_ms": statistics.fmean(values), + "population_std_ms": statistics.pstdev(values), + "p50_ms": statistics.median(ordered), + "p95_ms": ordered[math.ceil(0.95 * len(ordered)) - 1], + } + + +def load_events(path: Path) -> list[dict]: + opener = gzip.open if path.suffix == ".gz" else open + with opener(path, "rt") as source: + return json.load(source)["traceEvents"] + + +def analyze_rank(path: Path) -> dict: + events = load_events(path) + kernels = [event for event in events if event.get("cat") == "kernel"] + all_windows = sorted( + ( + event + for event in events + if event.get("cat") == "gpu_user_annotation" + and str(event.get("name", "")).startswith("execute_") + ), + key=lambda event: float(event["ts"]), + ) + if not all_windows: + raise ValueError(f"{path}: no GPU execute annotations") + window_names = Counter(str(window["name"]) for window in all_windows) + selected_name = window_names.most_common(1)[0][0] + windows = [ + window for window in all_windows if str(window["name"]) == selected_name + ] + + selected: list[dict] = [] + step_kernels: list[list[dict]] = [] + for window in windows: + start = float(window["ts"]) + end = start + float(window["dur"]) + current = [ + kernel for kernel in kernels if start <= float(kernel["ts"]) < end + ] + selected.extend(current) + step_kernels.append(current) + + occurrences = Counter(str(kernel["name"]) for kernel in selected) + component_steps: dict[str, list[float]] = defaultdict(list) + busy_steps: list[float] = [] + wall_steps = [float(window["dur"]) / 1000.0 for window in windows] + for current in step_kernels: + per_component: dict[str, float] = defaultdict(float) + for kernel in current: + name = str(kernel["name"]) + per_component[classify(name, occurrences[name], len(windows))] += ( + float(kernel["dur"]) / 1000.0 + ) + for name in COMPONENT_NAMES: + component_steps[name].append(per_component[name]) + busy_steps.append(sum(per_component.values())) + + return { + "trace": str(path), + "selected_execute_annotation": selected_name, + "execute_annotation_histogram": dict(sorted(window_names.items())), + "steps": len(windows), + "execute_wall": stats(wall_steps), + "gpu_kernel_busy": stats(busy_steps), + "non_kernel_gap": stats( + [wall - busy for wall, busy in zip(wall_steps, busy_steps)] + ), + "components": { + name: stats(values) for name, values in component_steps.items() + }, + } + + +def main() -> None: + args = parse_args() + traces = sorted(args.trace_root.rglob("*.pt.trace.json*")) + if not traces: + raise SystemExit(f"no traces under {args.trace_root}") + ranks = [analyze_rank(path) for path in traces] + payload = { + "schema": "frontier-decode-batch-trace.v1", + "label": args.label, + "contract": { + "timing": "CUDA graph-on GPU execute annotations", + "component_time": "sum of CUDA kernel durations inside execute range", + "tp_aggregation": "per-rank; slowest rank mean approximates critical path", + }, + "ranks": ranks, + "rank_summary": { + "ranks": len(ranks), + "slowest_rank_execute_mean_ms": max( + rank["execute_wall"]["mean_ms"] for rank in ranks + ), + "slowest_rank_kernel_busy_mean_ms": max( + rank["gpu_kernel_busy"]["mean_ms"] for rank in ranks + ), + "component_rank_mean_ms": { + name: statistics.fmean( + rank["components"][name]["mean_ms"] for rank in ranks + ) + for name in COMPONENT_NAMES + }, + }, + } + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n") + + +if __name__ == "__main__": + main() diff --git a/runs/frontier-decode-batch-grid-v0/experiment-card.md b/runs/frontier-decode-batch-grid-v0/experiment-card.md new file mode 100644 index 0000000..541c135 --- /dev/null +++ b/runs/frontier-decode-batch-grid-v0/experiment-card.md @@ -0,0 +1,68 @@ +# 实验 EXP-DECODE-BATCH-GRID:BC-8 是否可由稳定 whole-layer b2--b8 curve 修复 + +> **状态:** entry gate PASS,待 TP4/b2 GPU smoke +> +> Parent campaign:[`../frontier-simulator-gap-campaign-v0/README.md`](../frontier-simulator-gap-campaign-v0/README.md) + +## Claim 与决策 + +- **Parent claim:** 最新 1h trace 中 TP4 的 TPOT/E2E 仍稳定正偏约 10--23%; + 历史 BC-8 knee-right 真机排序为 `TP24 常数外推,导致 TP4 residence + 被高估;稳定 whole-layer curve 可恢复排序。 + - H2:给定 batch 的 whole-layer curve 已准,错误来自 simulator 与 vLLM + batch formation/event semantics 不同。 +- **事前预测:** 若 H1 成立,TP4/b2 repeat CV≤10%,其 median 比当前进入 + BC-8 的 curve 低至少 0.5 ms,注入后 `TP410% 时追加第三次,取 + median,不允许从单次 favorable sample 选值。 + - smoke 稳定后补 TP2/TP4 × b{2,4,6,8};b>4 不再使用 b4 常数外推。 + - whole-layer 注入只替换 pure-decode service curve,不改变 prefill。 + - BC-8 排序仍错则停止 profile 修补,升级 event-level state telemetry。 + +## Setup + +- **自变量:** TP `{2,4}`;pure-decode batch `{2,4,6,8}`;fresh-process + repeat `{1,2}`,不稳定 cell 追加 repeat 3。 +- **控制变量:** Qwen3-30B-A3B BF16;H20;vLLM 0.20;context=2048; + output=128;graph-on;MNS=16;no prefix caching;同模型/runtime/cache。 +- **Hardware:** dash1--dash4;每轮调度前要求目标主机 8×H20 + idle/healthy。每主机同时只启动一个 engine,避免 CPU/JIT contention。 +- **Metrics:** critical-rank execute wall;MoE/attention/collective/other + kernels;repeat median/CV;sim same-state residual;BC-8 TPOT 完整排序。 +- **Baselines:** + - 旧 serving grid:TP4/b2 execute=`5.044 ms`,单 process 内 + population std=`1.561 ms`,不足以进入判决链。 + - BC-8:real `4.305<4.449<4.876 ms`;sim + `5.12<5.49<5.86 ms`(TP2/TP4/TP1 对应完整排序见结果审计)。 + +## 预期产物与 review + +- **预期数据:** 每 cell trace、request result、runtime/GPU provenance; + `results/grid.json`;BC-8 counterfactual 与 verdict。 +- **Figure prototype:** `figure-prototype.png`。左图画 b2 repeat instability + 与目标 CI,右图画 BC-8 real/sim/corrected 排序。 +- **人工 review:** campaign 已批准按第三优先级推进。 +- **Review 意见:** 先 TP4/b2 smoke;不在稳定性 gate 前铺完整网格。 + +## 复现信息 + +- **Frontier baseline:** `deadc4a321f0baaa534c6ebd17f974123733cdc2`; + joint serving-path curves + structured-attention analysis branch。 +- **Remote:** 复用 dash1 的 clean detached experiment worktree;canonical + checkout 的用户 dirty changes 不动。 +- **Known limits:** 固定 2048→128 state-matched workload;本实验只支持 + decode curve 与 BC-8,不外推到 chat prefill shape 或其它模型。 + +## 结果 + +- **Entry gate:** PASS。TP4 1h trace TPOT mean 正偏 + `+10.6%--+23.0%`,E2E mean `+9.8%--+19.2%`;BC-8 完整排序仍错。 +- **观察事实:** 待 GPU。 +- **Decision:** 待 GPU。 diff --git a/runs/frontier-decode-batch-grid-v0/figure-prototype.png b/runs/frontier-decode-batch-grid-v0/figure-prototype.png new file mode 100644 index 0000000000000000000000000000000000000000..b1db93b79baaaecea26ede38f49a97e1ddb02ead GIT binary patch literal 84805 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

g4Pf@Z+Ip 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

qUK2aK;0|h8b0m| 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 + + + + + + + 2026-07-23T16:49:49.913314 + image/svg+xml + + + Matplotlib v3.10.9, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/runs/frontier-decode-batch-grid-v0/fleet.toml b/runs/frontier-decode-batch-grid-v0/fleet.toml new file mode 100644 index 0000000..6d34c27 --- /dev/null +++ b/runs/frontier-decode-batch-grid-v0/fleet.toml @@ -0,0 +1,46 @@ +version = 1 + +[paths] +state_dir = "runs/frontier-decode-batch-grid-v0/fleet-state" +artifacts_dir = "runs/frontier-decode-batch-grid-v0/fleet-artifacts" + +[ssh] +connect_timeout_sec = 10 + +[scheduler] +gpu_free_memory_mb = 1024 +gpu_free_utilization_pct = 10 +prefer_pack = true + +# Code is synchronized by Git. The mandatory scp pass uses an empty directory. +[sync] +mode = "scp" +local_path = "/tmp/frontier-decode-batch-grid-v0-empty-sync" + +[[hosts]] +name = "dash1" +ssh_alias = "dash1" +enabled = true +sync_remote_path = "/home/admin/cpfs/wjh/aituner/aituner-frontier-decode-batch-grid-v0" +fleet_root = "/home/admin/cpfs/wjh/aituner/gpu-fleet-decode-batch-grid-v0/dash1" + +[[hosts]] +name = "dash2" +ssh_alias = "dash2" +enabled = true +sync_remote_path = "/home/admin/cpfs/wjh/aituner/aituner-frontier-decode-batch-grid-v0" +fleet_root = "/home/admin/cpfs/wjh/aituner/gpu-fleet-decode-batch-grid-v0/dash2" + +[[hosts]] +name = "dash3" +ssh_alias = "dash3" +enabled = true +sync_remote_path = "/home/admin/cpfs/wjh/aituner/aituner-frontier-decode-batch-grid-v0" +fleet_root = "/home/admin/cpfs/wjh/aituner/gpu-fleet-decode-batch-grid-v0/dash3" + +[[hosts]] +name = "dash4" +ssh_alias = "dash4" +enabled = true +sync_remote_path = "/home/admin/cpfs/wjh/aituner/aituner-frontier-decode-batch-grid-v0" +fleet_root = "/home/admin/cpfs/wjh/aituner/gpu-fleet-decode-batch-grid-v0/dash4" diff --git a/runs/frontier-decode-batch-grid-v0/jobs-smoke-r1.toml b/runs/frontier-decode-batch-grid-v0/jobs-smoke-r1.toml new file mode 100644 index 0000000..d531d0b --- /dev/null +++ b/runs/frontier-decode-batch-grid-v0/jobs-smoke-r1.toml @@ -0,0 +1,16 @@ +version = 1 + +[[jobs]] +name = "decode-batch-tp4-b2-r1-20260723" +gpus = 4 +gpu_model = "H20" +hosts = ["dash1"] +command = "timeout --signal=TERM --kill-after=60s 1800 bash runs/frontier-decode-batch-grid-v0/run_decode_profile.sh" +artifacts = ["runs/frontier-decode-batch-grid-v0/remote-outputs/tp4-b2-r1"] + +[jobs.env] +TP = "4" +PROFILE_BATCH = "2" +SERVER_PORT = "9831" +OUTPUT_ROOT = "runs/frontier-decode-batch-grid-v0/remote-outputs/tp4-b2-r1" +FLASHINFER_WORKSPACE_BASE = "/tmp/frontier-component-flashinfer-v4" diff --git a/runs/frontier-decode-batch-grid-v0/jobs-smoke-r2.toml b/runs/frontier-decode-batch-grid-v0/jobs-smoke-r2.toml new file mode 100644 index 0000000..55aa1d4 --- /dev/null +++ b/runs/frontier-decode-batch-grid-v0/jobs-smoke-r2.toml @@ -0,0 +1,16 @@ +version = 1 + +[[jobs]] +name = "decode-batch-tp4-b2-r2-20260723" +gpus = 4 +gpu_model = "H20" +hosts = ["dash1"] +command = "timeout --signal=TERM --kill-after=60s 1800 bash runs/frontier-decode-batch-grid-v0/run_decode_profile.sh" +artifacts = ["runs/frontier-decode-batch-grid-v0/remote-outputs/tp4-b2-r2"] + +[jobs.env] +TP = "4" +PROFILE_BATCH = "2" +SERVER_PORT = "9832" +OUTPUT_ROOT = "runs/frontier-decode-batch-grid-v0/remote-outputs/tp4-b2-r2" +FLASHINFER_WORKSPACE_BASE = "/tmp/frontier-component-flashinfer-v4" diff --git a/runs/frontier-decode-batch-grid-v0/materialize_decode_batch.py b/runs/frontier-decode-batch-grid-v0/materialize_decode_batch.py new file mode 100755 index 0000000..64ba032 --- /dev/null +++ b/runs/frontier-decode-batch-grid-v0/materialize_decode_batch.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +"""Create simultaneous, prefix-disjoint requests for a fixed decode batch.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument("--model", type=Path, required=True) + parser.add_argument("--batch", type=int, required=True) + parser.add_argument("--input-tokens", type=int, default=2048) + parser.add_argument("--output-tokens", type=int, default=128) + parser.add_argument("--output", type=Path, required=True) + return parser.parse_args() + + +def main() -> None: + args = parse_args() + if min(args.batch, args.input_tokens, args.output_tokens) <= 0: + raise ValueError("batch and token counts must be positive") + if args.input_tokens + args.output_tokens > 40960: + raise ValueError("request exceeds the server max model length") + + from transformers import AutoTokenizer + + tokenizer = AutoTokenizer.from_pretrained(args.model, trust_remote_code=True) + special = set(tokenizer.all_special_ids) + candidates = [ + token for token in range(tokenizer.vocab_size) if token not in special + ] + if len(candidates) < args.batch + 1: + raise ValueError("tokenizer has too few non-special token IDs") + + base = candidates[0] + args.output.parent.mkdir(parents=True, exist_ok=True) + with args.output.open("w") as output: + for index in range(args.batch): + row = { + "source_index": index, + "arrived_at": 0.0, + "input_length": args.input_tokens, + "output_length": args.output_tokens, + "session_id": index, + "runtime_block_ids": [], + "body": { + "prompt": [ + candidates[index + 1], + *([base] * (args.input_tokens - 1)), + ], + "min_tokens": args.output_tokens, + "max_tokens": args.output_tokens, + "ignore_eos": True, + }, + } + output.write(json.dumps(row, separators=(",", ":")) + "\n") + + +if __name__ == "__main__": + main() diff --git a/runs/frontier-decode-batch-grid-v0/plot_figure_prototype.py b/runs/frontier-decode-batch-grid-v0/plot_figure_prototype.py new file mode 100644 index 0000000..3d0e727 --- /dev/null +++ b/runs/frontier-decode-batch-grid-v0/plot_figure_prototype.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +"""Render the preregistered EXP-DECODE-BATCH-GRID schematic.""" + +from pathlib import Path + +import matplotlib.pyplot as plt +import numpy as np + +ROOT = Path(__file__).resolve().parent + + +def main() -> None: + fig, axes = plt.subplots(1, 2, figsize=(9.6, 4.0)) + + ax = axes[0] + repeats = np.array([3.7, 5.0, 6.4]) + ax.scatter([1, 2, 3], repeats, color="#d95f02", s=45, label="old/possible repeats") + ax.axhspan(4.1, 4.6, color="#1b9e77", alpha=0.18, label="stable target band") + ax.axhline(np.median(repeats), color="#7570b3", ls="--", label="median") + ax.set(xlabel="TP4/b2 fresh-process repeat", ylabel="execute wall (ms)") + ax.set_title("A. Stability gate before curve fit") + ax.set_xticks([1, 2, 3]) + ax.legend(frameon=False, fontsize=8) + + ax = axes[1] + labels = ["TP2", "TP4", "TP1"] + real = [4.305, 4.449, 4.876] + sim = [5.12, 5.86, 5.49] + corrected = [4.7, 4.9, 5.3] + x = np.arange(3) + width = 0.25 + ax.bar(x - width, real, width, label="real", color="#1b9e77") + ax.bar(x, sim, width, label="current sim", color="#d95f02") + ax.bar(x + width, corrected, width, label="target corrected", color="#7570b3") + ax.set_xticks(x, labels) + ax.set_ylabel("BC-8 TPOT (ms)") + ax.set_title("B. Complete-order recovery gate") + ax.legend(frameon=False, fontsize=8) + + fig.suptitle("EXP-DECODE-BATCH-GRID preregistered figure prototype") + fig.tight_layout() + fig.savefig(ROOT / "figure-prototype.png", dpi=180) + fig.savefig(ROOT / "figure-prototype.svg") + + +if __name__ == "__main__": + main() diff --git a/runs/frontier-decode-batch-grid-v0/run_decode_profile.sh b/runs/frontier-decode-batch-grid-v0/run_decode_profile.sh new file mode 100755 index 0000000..079eaf0 --- /dev/null +++ b/runs/frontier-decode-batch-grid-v0/run_decode_profile.sh @@ -0,0 +1,174 @@ +#!/usr/bin/env bash + +set -euo pipefail + +TP="${TP:?TP is required}" +PROFILE_BATCH="${PROFILE_BATCH:?PROFILE_BATCH is required}" +OUTPUT_ROOT="${OUTPUT_ROOT:?OUTPUT_ROOT is required}" +SERVER_PORT="${SERVER_PORT:?SERVER_PORT is required}" +VENV_ROOT="${VENV_ROOT:-/home/admin/cpfs/wjh/venvs/vllm-0.20.0-cu129-workload-regime-v2}" +MODEL_ROOT="${MODEL_ROOT:-/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B}" +GPU_MEMORY_UTILIZATION="${GPU_MEMORY_UTILIZATION:-0.92}" +SERVER_READY_ATTEMPTS="${SERVER_READY_ATTEMPTS:-900}" +ACTIVE_ITERATIONS="${ACTIVE_ITERATIONS:-16}" +SCRIPT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_ROOT}/../.." && pwd)" +MATERIALIZER="${SCRIPT_ROOT}/materialize_decode_batch.py" +CLIENT="${PROJECT_ROOT}/runs/frontier-fidelity-envelope-v1/qwen30_exact_trace_client.py" +SERVER_PID="" + +if [[ ! "${PROFILE_BATCH}" =~ ^(2|4|6|8)$ ]]; then + echo "ERROR: PROFILE_BATCH must be 2, 4, 6, or 8" >&2 + exit 1 +fi +IFS=',' read -r -a GPU_IDS <<< "${CUDA_VISIBLE_DEVICES:?GPU allocation is required}" +if [[ "${#GPU_IDS[@]}" -ne "${TP}" ]]; then + echo "ERROR: TP=${TP}, but CUDA_VISIBLE_DEVICES=${CUDA_VISIBLE_DEVICES}" >&2 + exit 1 +fi + +mkdir -p "${OUTPUT_ROOT}" +OUTPUT_ROOT="$(cd "${OUTPUT_ROOT}" && pwd)" +mkdir -p "${OUTPUT_ROOT}/logs" "${OUTPUT_ROOT}/provenance" \ + "${OUTPUT_ROOT}/requests" "${OUTPUT_ROOT}/results" \ + "${OUTPUT_ROOT}/trace-staging" "${OUTPUT_ROOT}/traces/profile" +exec > >(tee -a "${OUTPUT_ROOT}/logs/controller.log") 2>&1 + +cleanup() { + if [[ -n "${SERVER_PID}" ]] && kill -0 "${SERVER_PID}" 2>/dev/null; then + kill -TERM -- "-${SERVER_PID}" 2>/dev/null || true + for _ in $(seq 1 30); do + kill -0 "${SERVER_PID}" 2>/dev/null || break + sleep 1 + done + kill -KILL -- "-${SERVER_PID}" 2>/dev/null || true + fi + SERVER_PID="" +} +trap cleanup EXIT INT TERM + +sha256sum "${BASH_SOURCE[0]}" "${MATERIALIZER}" "${CLIENT}" \ + "${MODEL_ROOT}/config.json" > "${OUTPUT_ROOT}/provenance/inputs.sha256" +"${VENV_ROOT}/bin/python" -c \ + 'import torch, transformers, vllm; print(f"torch={torch.__version__}"); print(f"transformers={transformers.__version__}"); print(f"vllm={vllm.__version__}")' \ + > "${OUTPUT_ROOT}/provenance/runtime.versions" +nvidia-smi --query-gpu=index,name,uuid,driver_version,memory.total \ + --format=csv,noheader > "${OUTPUT_ROOT}/provenance/gpus.before.csv" +ps -eo user,pid,ppid,etimes,pcpu,pmem,args --sort=pid \ + > "${OUTPUT_ROOT}/provenance/processes.before.txt" +uptime > "${OUTPUT_ROOT}/provenance/uptime.before.txt" +env | sort > "${OUTPUT_ROOT}/provenance/environment.txt" + +PROFILE_CONFIG="$("${VENV_ROOT}/bin/python" - "${OUTPUT_ROOT}/trace-staging" \ + "${ACTIVE_ITERATIONS}" <<'PY' +import json +import sys + +print(json.dumps({ + "profiler": "torch", + "torch_profiler_dir": sys.argv[1], + "torch_profiler_with_stack": False, + "torch_profiler_record_shapes": True, + "torch_profiler_use_gzip": True, + "ignore_frontend": True, + "wait_iterations": 0, + "warmup_iterations": 2, + "active_iterations": int(sys.argv[2]), +}, separators=(",", ":"))) +PY +)" +printf '%s\n' "${PROFILE_CONFIG}" \ + > "${OUTPUT_ROOT}/provenance/profiler-config.json" + +export TOKENIZERS_PARALLELISM=false +export VLLM_USE_V1=1 +export HF_HUB_OFFLINE=1 +export TRANSFORMERS_OFFLINE=1 +export FLASHINFER_WORKSPACE_BASE="${FLASHINFER_WORKSPACE_BASE:-${OUTPUT_ROOT}/flashinfer-workspace}" +mkdir -p "${FLASHINFER_WORKSPACE_BASE}" +ulimit -n 65536 + +"${VENV_ROOT}/bin/python" "${MATERIALIZER}" --model "${MODEL_ROOT}" \ + --batch "${PROFILE_BATCH}" --input-tokens 2048 --output-tokens 128 \ + --output "${OUTPUT_ROOT}/requests/b${PROFILE_BATCH}.jsonl" + +printf 'LAUNCH host=%s tp=%s batch=%s gpus=%s output=%s\n' \ + "$(hostname)" "${TP}" "${PROFILE_BATCH}" "${CUDA_VISIBLE_DEVICES}" \ + "${OUTPUT_ROOT}" +setsid "${VENV_ROOT}/bin/vllm" serve "${MODEL_ROOT}" \ + --host 127.0.0.1 --port "${SERVER_PORT}" \ + --served-model-name qwen30-decode-batch-profile \ + --tensor-parallel-size "${TP}" \ + --gpu-memory-utilization "${GPU_MEMORY_UTILIZATION}" \ + --max-model-len 40960 --max-num-batched-tokens 8192 --max-num-seqs 16 \ + --no-enable-prefix-caching --enable-chunked-prefill --no-enable-log-requests \ + --enable-logging-iteration-details --profiler-config "${PROFILE_CONFIG}" \ + > "${OUTPUT_ROOT}/logs/server.log" 2>&1 & +SERVER_PID=$! + +READY=0 +for _ in $(seq 1 "${SERVER_READY_ATTEMPTS}"); do + if curl -fsS --max-time 2 \ + "http://127.0.0.1:${SERVER_PORT}/v1/models" \ + > "${OUTPUT_ROOT}/results/models.json" 2>/dev/null; then + READY=1 + break + fi + if ! kill -0 "${SERVER_PID}" 2>/dev/null; then + tail -200 "${OUTPUT_ROOT}/logs/server.log" + exit 1 + fi + sleep 3 +done +if [[ "${READY}" -ne 1 ]]; then + echo "ERROR: server readiness timeout" >&2 + tail -200 "${OUTPUT_ROOT}/logs/server.log" + exit 1 +fi + +run_client() { + local label="$1" + "${VENV_ROOT}/bin/python" "${CLIENT}" \ + --port "${SERVER_PORT}" \ + --requests-file "${OUTPUT_ROOT}/requests/b${PROFILE_BATCH}.jsonl" \ + --served-model qwen30-decode-batch-profile \ + --output "${OUTPUT_ROOT}/results/${label}.json" \ + --tpot-slo-ms 150 --timeout-seconds 1800 +} + +# Exercise the same graph and scheduler path twice before profiling. +run_client warmup-1 +run_client warmup-2 + +curl -fsS -X POST "http://127.0.0.1:${SERVER_PORT}/start_profile" \ + > "${OUTPUT_ROOT}/logs/start-profile.txt" +run_client profile + +deadline=$((SECONDS + 120)) +while (( SECONDS < deadline )); do + trace_count="$(find "${OUTPUT_ROOT}/trace-staging" -maxdepth 1 -type f \ + -name '*.pt.trace.json*' | wc -l)" + if (( trace_count >= TP )); then + break + fi + sleep 2 +done +trace_count="$(find "${OUTPUT_ROOT}/trace-staging" -maxdepth 1 -type f \ + -name '*.pt.trace.json*' | wc -l)" +if (( trace_count < TP )); then + echo "ERROR: expected ${TP} rank traces, found ${trace_count}" >&2 + exit 1 +fi +find "${OUTPUT_ROOT}/trace-staging" -maxdepth 1 -type f \ + -name '*.pt.trace.json*' -exec mv -t "${OUTPUT_ROOT}/traces/profile" {} + +curl -fsS -X POST "http://127.0.0.1:${SERVER_PORT}/stop_profile" \ + > "${OUTPUT_ROOT}/logs/stop-profile.txt" + +cleanup +nvidia-smi --query-gpu=index,name,uuid,driver_version,memory.total \ + --format=csv,noheader > "${OUTPUT_ROOT}/provenance/gpus.after.csv" +find "${OUTPUT_ROOT}" -type f \ + ! -path '*/provenance/artifacts.sha256' -print0 \ + | sort -z | xargs -0 sha256sum \ + > "${OUTPUT_ROOT}/provenance/artifacts.sha256" +echo DECODE_BATCH_PROFILE_COMPLETE