Compare commits
2 Commits
19c443e3bc
...
54e1f5266a
| Author | SHA1 | Date | |
|---|---|---|---|
| 54e1f5266a | |||
| 3f997fda14 |
107
analysis/mb5_pd_ablation/README.md
Normal file
107
analysis/mb5_pd_ablation/README.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# PD-disagg vs colocation — controlled reuse & concurrency axes (v2)
|
||||
|
||||
Self-contained results for the **controlled-variable** redo of the MB5 PD-vs-colo
|
||||
ablation. Supersedes the confounded first cut (held input fixed and sliced the
|
||||
prefix, so "more reuse" was entangled with "less prefill"). All arms route through
|
||||
the proxy at fair **APC parity** (session-routed producers reach the same prefix-cache
|
||||
hit rate as colo), so PD loses on *structure*, not on broken cache.
|
||||
|
||||
- **Config arms:** `colo` = 8×kv_both (8C-proxy, session-affinity); PD = `6P+2D / 4P+4D / 2P+6D`.
|
||||
- **Driver:** closed-loop N (`REPLAY_MAX_INFLIGHT`) + fixed think-time; `gen_synthetic_trace.py --mode regular`.
|
||||
- **PD-arm wall-cap:** collapsed PD arms drain pathologically slowly, so PD arms run with a
|
||||
wall-deadline (`REPLAY_MAX_DURATION`; un-run turns counted as failures → honest completion%);
|
||||
**colo is uncapped** so the reference is always fully measured.
|
||||
- **Hardware:** run on **dash2** (8×H20). dash0's RDMA NICs were faulted for Mooncake during
|
||||
this work (could not init the transfer engine; needs an admin reset — no sudo); dash2's NICs
|
||||
are healthy. cpfs/venv/data are shared across the boxes.
|
||||
|
||||
---
|
||||
|
||||
## 1. Reuse / APC axis — fixed real prefill, vary cached prefix
|
||||
|
||||
N=8. Hold the **real new-prefill work per turn constant** (`--delta-len`) and grow the cached
|
||||
prefix → reuse = prefix/(prefix+delta). Three shapes isolate output vs delta:
|
||||
|
||||
| | delta (real prefill/turn) | output | role |
|
||||
|---|---|---|---|
|
||||
| **A** | 2048 | 256 | original |
|
||||
| **C** | 2048 | 128 | A vs C = pure **output** 256→128 |
|
||||
| **B** | 1024 | 128 | C vs B = pure **delta** 2048→1024 |
|
||||
|
||||
**PD-best advantage** = colo E2E-p90 / best-PD E2E-p90 (>1 ⇒ PD wins):
|
||||
|
||||
| reuse% | A d2048/o256 | C d2048/o128 | B d1024/o128 |
|
||||
|---|---|---|---|
|
||||
| 20 | 1.34 | 1.41 | — |
|
||||
| 50 | 1.36 | 1.37 | — |
|
||||
| 67 | **1.47** | **1.49** | **1.27** |
|
||||
| 80 | 1.31 | 1.23 | 1.25 |
|
||||
| 90 | 1.15 | 1.01 | — |
|
||||
| 95 | 0.87 | 0.89 | 0.89 |
|
||||
|
||||

|
||||
|
||||
**Findings:**
|
||||
1. **Output length is ~negligible.** A and C (same delta) track each other across the whole
|
||||
range — halving output barely moves PD's advantage.
|
||||
2. **Delta (real prefill/turn) is the dominant shape factor.** B (delta=1024) sits clearly
|
||||
below A/C at mid reuse (67%: 1.27 vs ~1.48). More real prefill per turn → bigger PD win,
|
||||
because PD-disagg's benefit is isolating prefill from decode — more prefill to isolate.
|
||||
3. **Crossover to colo at reuse ~90–95% is robust** across all three shapes: PD always loses
|
||||
the high-reuse / large-resident-context corner (it must KV-transfer the whole resident
|
||||
context every turn for a few hundred new tokens; colo keeps it local).
|
||||
|
||||
*Caveat:* the clean, uncapped, 100%-completion comparison region is reuse **20–80%** (carries
|
||||
findings 1–2). At reuse 90/95% the PD arms collapse and C's points are capped-completion, while
|
||||
A/B are full-drain — comparable in direction, not in exact PD completion%.
|
||||
|
||||
Data: `fig1_reuse_fixed.json` (A), `fig1_reuse_d2048_o128.json` (C), `fig1_reuse_d1024_o128.json` (B).
|
||||
|
||||
---
|
||||
|
||||
## 2. Concurrency axis — agentic corner, sweep N
|
||||
|
||||
in=32768 (prefix 32256 + delta 512, **reuse 0.984**), out=128; closed-loop N ∈ {8,16,32,48,64,96,128};
|
||||
PD arms capped 600s, colo uncapped.
|
||||
|
||||
| N | **colo** completion · E2E-mean · TPS | best PD-arm completion |
|
||||
|---|---|---|
|
||||
| 8 | **256/256** · 2.4s · 326 | 6P+2D 256/256 |
|
||||
| 16 | **512/512** · 3.5s · 462 | 6P+2D 439/512 (86%) |
|
||||
| 32 | **1024/1024** · 13.3s · 190 | all PD **<27%** |
|
||||
| 48 | **1536/1536** · 24.9s · 168 | all PD <32% |
|
||||
| 64 | **2048/2048** · 38.4s · 166 | all PD <31% |
|
||||
| 96 | **3072/3072** · 60.0s · 171 | PD **2–7%** |
|
||||
| 128 | **4096/4096** · 80.8s · 181 | 4P+4D 6%, 2P+6D <1% |
|
||||
|
||||

|
||||
|
||||
**Finding:** **colo completes 100% of requests at every concurrency level** — it degrades
|
||||
*gracefully* (latency rises 2.4s→81s, nothing dropped). **Every static PD split collapses, and
|
||||
progressively earlier as N rises**: PD is viable only at N≤8–16; by N≥32 it drops 70–99% of
|
||||
requests while its prefix-cache hit-rate craters to ~0%. colo's elastic pool absorbs the
|
||||
time-varying P/D demand; the static partition + per-turn 32k KV-transfer cannot. (Latency
|
||||
percentiles count successes only, so they *understate* PD — read them with the completion column.)
|
||||
|
||||
Data: `fig3_conc32k.json`.
|
||||
|
||||
*Caveat:* N=128 6P+2D is missing (one transient vLLM/Mooncake startup flake at the end); does
|
||||
not change the picture (all PD arms are already collapsed by N=128). The SLO auto-stop in the
|
||||
driver is a no-op (a stdout-capture bug), so the full grid ran — more points, not fewer.
|
||||
|
||||
---
|
||||
|
||||
## 3. Reproduce
|
||||
|
||||
```bash
|
||||
# on a box with healthy Mooncake/RDMA NICs (dash2), cpfs mounted:
|
||||
R=/home/admin/cpfs/wjh/agentic-kv-fresh
|
||||
# reuse axis (three shapes): DELTA/OL pick the shape; tag carries _o${OL}
|
||||
ssh dash2 "cd $R && DELTA=2048 OL=256 bash microbench/fresh_setup/run_reuse_fixed.sh"
|
||||
ssh dash2 "cd $R && DELTA=2048 OL=128 bash microbench/fresh_setup/run_reuse_fixed.sh"
|
||||
ssh dash2 "cd $R && DELTA=1024 OL=128 bash microbench/fresh_setup/run_reuse_fixed.sh"
|
||||
# concurrency axis (capped):
|
||||
ssh dash2 "cd $R && NLIST='8 16 32 48 64 96 128' CONC_PD_MAXDUR=600 bash microbench/fresh_setup/run_conc.sh"
|
||||
# render (reads the *.json in this dir):
|
||||
python microbench/fresh_setup/plot_pd_crossover.py
|
||||
```
|
||||
1
analysis/mb5_pd_ablation/fig3_conc32k.json
Normal file
1
analysis/mb5_pd_ablation/fig3_conc32k.json
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 171 KiB |
18
microbench/fresh_setup/gpu_monitor.sh
Executable file
18
microbench/fresh_setup/gpu_monitor.sh
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
# Sample GPU utilization every 5s, output CSV
|
||||
# Usage: bash gpu_monitor.sh <output_file> [interval_s]
|
||||
# Runs until killed (Ctrl+C or kill)
|
||||
|
||||
OUT="${1:-/tmp/gpu_util.csv}"
|
||||
INTERVAL="${2:-5}"
|
||||
|
||||
echo "timestamp,gpu,util_pct,mem_used_mb,mem_total_mb,power_w" > "$OUT"
|
||||
|
||||
while true; do
|
||||
TS=$(date +%s.%N)
|
||||
nvidia-smi --query-gpu=index,utilization.gpu,memory.used,memory.total,power.draw \
|
||||
--format=csv,noheader,nounits 2>/dev/null | while IFS=', ' read -r idx util mem_used mem_total power; do
|
||||
echo "$TS,$idx,$util,$mem_used,$mem_total,$power"
|
||||
done >> "$OUT"
|
||||
sleep "$INTERVAL"
|
||||
done
|
||||
@@ -154,28 +154,33 @@ def fig_conc():
|
||||
g = by_axis(load("fig3_conc32k.json"),
|
||||
lambda n: (int(m.group(1)) if (m := re.search(r"_N(\d+)_", n)) else None))
|
||||
xs = sorted(g)
|
||||
fig, axes = plt.subplots(1, 3, figsize=(15, 4.2))
|
||||
ax1, ax2, ax3 = axes
|
||||
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(15, 4.2))
|
||||
|
||||
# (a) request completion % — the headline (latency percentiles count successes
|
||||
# only, so they understate PD; completion is the honest collapse signal).
|
||||
for arm in ["colo", *PD_ARMS]:
|
||||
ax1.plot(xs, series(g, xs, arm, "e2e_mean"), **STYLE[arm])
|
||||
ax1.axhline(10.0, color="red", ls=":", lw=1, label="SLO (mean E2E 10s)")
|
||||
ax1.set_yscale("log"); ax1.set_xticks(xs); ax1.set_xticklabels(xs, fontsize=7)
|
||||
ax1.set_xlabel("concurrent sessions N"); ax1.set_ylabel("E2E latency mean (s, log)")
|
||||
ax1.set_title("(a) mean-E2E vs concurrency"); ax1.legend(fontsize=8); ax1.grid(alpha=.3, which="both")
|
||||
comp = [(g[x][arm]["n"] / g[x][arm]["req"] * 100) if arm in g[x] else None for x in xs]
|
||||
ax1.plot(xs, comp, **STYLE[arm])
|
||||
ax1.axhline(100, color="grey", ls=":", lw=1)
|
||||
ax1.set_xticks(xs); ax1.set_xticklabels(xs, fontsize=7)
|
||||
ax1.set_xlabel("concurrent sessions N"); ax1.set_ylabel("request completion (%)")
|
||||
ax1.set_title("(a) completion: colo 100%, PD collapses"); ax1.legend(fontsize=8); ax1.grid(alpha=.3)
|
||||
|
||||
for arm in ["colo", *PD_ARMS]:
|
||||
ax2.plot(xs, series(g, xs, arm, "tps"), **STYLE[arm])
|
||||
ax2.set_xticks(xs); ax2.set_xticklabels(xs, fontsize=7)
|
||||
ax2.set_xlabel("concurrent sessions N"); ax2.set_ylabel("throughput (tok/s)")
|
||||
ax2.set_title("(b) TPS: colo scales, PD plateaus/drops"); ax2.legend(fontsize=8); ax2.grid(alpha=.3)
|
||||
ax2.plot(xs, series(g, xs, arm, "e2e_mean"), **STYLE[arm])
|
||||
ax2.axhline(10.0, color="red", ls=":", lw=1, label="SLO 10s")
|
||||
ax2.set_yscale("log"); ax2.set_xticks(xs); ax2.set_xticklabels(xs, fontsize=7)
|
||||
ax2.set_xlabel("concurrent sessions N"); ax2.set_ylabel("E2E latency mean (s, log)")
|
||||
ax2.set_title("(b) mean-E2E (successes only)"); ax2.legend(fontsize=8); ax2.grid(alpha=.3, which="both")
|
||||
|
||||
for arm in ["colo", *PD_ARMS]:
|
||||
ax3.plot(xs, [v * 100 if v is not None else None for v in series(g, xs, arm, "apc")], **STYLE[arm])
|
||||
ax3.plot(xs, series(g, xs, arm, "tps"), **STYLE[arm])
|
||||
ax3.set_xticks(xs); ax3.set_xticklabels(xs, fontsize=7)
|
||||
ax3.set_xlabel("concurrent sessions N"); ax3.set_ylabel("producer prefix-cache hit-rate (%)")
|
||||
ax3.set_title("(c) APC vs concurrency"); ax3.legend(fontsize=8); ax3.grid(alpha=.3)
|
||||
fig.suptitle("Fig 3 — Concurrency axis (in32768/out128, reuse~0.984): sweep N by 8 to the 10s-SLO ceiling",
|
||||
fontsize=11, y=1.02)
|
||||
ax3.set_xlabel("concurrent sessions N"); ax3.set_ylabel("throughput (tok/s)")
|
||||
ax3.set_title("(c) TPS"); ax3.legend(fontsize=8); ax3.grid(alpha=.3)
|
||||
fig.suptitle("Fig 3 — Concurrency axis (in32768/out128, reuse~0.984, PD capped 600s / colo uncapped): "
|
||||
"colo degrades gracefully (100% completion), PD collapses earlier as N rises",
|
||||
fontsize=10, y=1.02)
|
||||
fig.tight_layout(); p = OUT / "fig3_concurrency_axis.png"; fig.savefig(p, dpi=130, bbox_inches="tight")
|
||||
print("wrote", p)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user