gen_synthetic_trace (vanilla Poisson, zero prefix reuse — the regime where PD-disagg is expected to win), mutate_trace (morph reuse/burst/skew toward the agentic regime), and plot_crossover. Emits the replayer's JSONL schema. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
116 lines
4.8 KiB
Python
116 lines
4.8 KiB
Python
"""Render the PD-disagg crossover figure from analyze_goodput.py JSONs.
|
|
|
|
Two sweeps bracket the prefill<->decode bottleneck axis:
|
|
D2: fixed input, grow OUTPUT -> decode-bound -> PD_advantage rises above 1
|
|
D1: fixed output, grow INPUT -> prefill-bound -> PD_advantage falls below 1
|
|
|
|
Top row: PD_advantage (4P+4D / colo SLO-goodput) vs swept dim, y=1 = crossover.
|
|
Bottom row: completion rate, colo vs 4P+4D.
|
|
Agentic operating region (input p50~33k, output p50~92) annotated.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import matplotlib
|
|
matplotlib.use("Agg")
|
|
import matplotlib.pyplot as plt
|
|
|
|
CROSS = Path("analysis/crossover")
|
|
OUT = Path("figs/crossover_pd_advantage.png")
|
|
|
|
|
|
def load_series(prefix: str, key_re: str):
|
|
pts = []
|
|
for f in sorted(CROSS.glob(f"{prefix}*_goodput.json")):
|
|
m = re.search(key_re, f.name)
|
|
if not m:
|
|
continue
|
|
d = json.loads(f.read_text())
|
|
g = d["slo_grid"][0]["arms"]
|
|
pts.append({
|
|
"x": int(m.group(1)),
|
|
"adv": g["4P+4D"]["pd_advantage"],
|
|
"colo_att": g["8C-proxy"]["attainment"],
|
|
"pd_att": g["4P+4D"]["attainment"],
|
|
"colo_compl": d["arms"]["8C-proxy"]["completion_rate"],
|
|
"pd_compl": d["arms"]["4P+4D"]["completion_rate"],
|
|
"colo_ampl": d["arms"]["8C-proxy"]["amplification"],
|
|
"pd_ampl": d["arms"]["4P+4D"]["amplification"],
|
|
})
|
|
pts.sort(key=lambda p: p["x"])
|
|
return pts
|
|
|
|
|
|
def main():
|
|
d2 = load_series("d2_o", r"d2_o(\d+)_") # x = output length
|
|
d1 = load_series("d1_i", r"d1_i(\d+)_") # x = input length
|
|
|
|
fig, ax = plt.subplots(2, 2, figsize=(12, 8))
|
|
|
|
def adv_plot(a, pts, xlabel, title, agentic_x, agentic_label):
|
|
CAP = 50.0 # display cap for "colo=0, PD>0" (PD wins outright)
|
|
line_x, line_y = [], []
|
|
for p in pts:
|
|
adv, colo_att, pd_att = p["adv"], p["colo_att"], p["pd_att"]
|
|
finite = isinstance(adv, (int, float)) and adv == adv
|
|
if finite:
|
|
line_x.append(p["x"]); line_y.append(max(adv, 1e-3))
|
|
elif colo_att == 0 and pd_att > 0: # PD wins outright
|
|
line_x.append(p["x"]); line_y.append(CAP)
|
|
a.annotate("PD wins\n(colo=0)", (p["x"], CAP), fontsize=7, ha="center", va="top")
|
|
else: # both fail SLO -> not a PD win
|
|
a.scatter([p["x"]], [1.0], marker="x", color="gray", zorder=5)
|
|
a.annotate("both\nfail SLO", (p["x"], 1.0), fontsize=7, ha="center",
|
|
va="bottom", color="gray")
|
|
a.plot(line_x, line_y, "o-", color="tab:blue", lw=2)
|
|
a.axhline(1.0, color="k", ls="--", lw=1, label="crossover (PD=colo)")
|
|
a.set_xscale("log"); a.set_yscale("log")
|
|
a.set_xlabel(xlabel)
|
|
a.set_ylabel("PD_advantage = goodput(4P+4D)/goodput(colo)")
|
|
a.set_title(title)
|
|
a.axvline(agentic_x, color="tab:red", ls=":", lw=1.5)
|
|
a.annotate(agentic_label, (agentic_x, 1.2), color="tab:red", fontsize=8,
|
|
rotation=90, va="bottom", ha="right")
|
|
a.legend(fontsize=8, loc="best")
|
|
a.grid(True, which="both", alpha=0.3)
|
|
|
|
def compl_plot(a, pts, xlabel, title):
|
|
xs = [p["x"] for p in pts]
|
|
a.plot(xs, [100*p["colo_compl"] for p in pts], "s-", color="tab:orange", label="colo (8C-proxy)")
|
|
a.plot(xs, [100*p["pd_compl"] for p in pts], "o-", color="tab:blue", label="PD (4P+4D)")
|
|
a.set_xscale("log")
|
|
a.set_xlabel(xlabel)
|
|
a.set_ylabel("completion rate (%)")
|
|
a.set_title(title)
|
|
a.set_ylim(0, 105)
|
|
a.legend(fontsize=8, loc="best")
|
|
a.grid(True, alpha=0.3)
|
|
|
|
adv_plot(ax[0][0], d2, "output length (tokens), fixed input=2048, q12",
|
|
"D2 decode-bound sweep — PD wins as output grows",
|
|
92, "agentic out~92")
|
|
adv_plot(ax[0][1], d1, "input length (tokens), fixed output=64, q4",
|
|
"D1 prefill-bound sweep — PD collapses as input grows",
|
|
33533, "agentic in~33k")
|
|
compl_plot(ax[1][0], d2, "output length (tokens)", "D2 completion")
|
|
compl_plot(ax[1][1], d1, "input length (tokens)", "D1 completion")
|
|
|
|
fig.suptitle("PD-disaggregation vs colocation: the prefill<->decode bottleneck crossover\n"
|
|
"(single node 8xH20, vLLM 0.18.1 chunked-prefill; zero-reuse synthetic)",
|
|
fontsize=11)
|
|
fig.tight_layout(rect=[0, 0, 1, 0.96])
|
|
OUT.parent.mkdir(parents=True, exist_ok=True)
|
|
fig.savefig(OUT, dpi=130)
|
|
print(f"wrote {OUT}")
|
|
# also dump the numeric series for the record
|
|
print("D2 (output -> adv):", [(p["x"], round(p["adv"],2) if p["adv"]==p["adv"] else "inf") for p in d2])
|
|
print("D1 (input -> adv):", [(p["x"], round(p["adv"],2) if p["adv"]==p["adv"] else "inf") for p in d1])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|