Paper section: PD-sep scaffold + drop --enforce-eager from launch scripts
Adds analysis/pd_sep_paper_section/ as the home for the "PD separation is net negative under agentic workloads" paper section: plot scripts for C1 (workload chars), C6 (roofline), C7 (routing-vs-PD-sep lever), the C6/C7 PDFs already rendered, and a README mapping candidate claims to required figures plus open re-run items. Removes --enforce-eager from bench.sh and all active launch scripts so cuda graphs are captured -- the prior methodology suppressed one of PD-sep's structural advantages (D-node fixed-shape decode). Legacy scripts under scripts/legacy/ are intentionally untouched as historical records. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
144
analysis/pd_sep_paper_section/scripts/plot_roofline.py
Normal file
144
analysis/pd_sep_paper_section/scripts/plot_roofline.py
Normal file
@@ -0,0 +1,144 @@
|
||||
"""C6: roofline plot for Qwen3-Coder-30B-A3B on H20.
|
||||
|
||||
Reproduces the analytical roofline used in scripts/compute_roofline.py and
|
||||
plots it as a single PDF: AI vs achievable throughput, with annotated
|
||||
operating points for prefill at reuse {0, 70, 90, 95}% and decode.
|
||||
|
||||
The constants must stay in lockstep with compute_roofline.py. If you change
|
||||
one, change the other.
|
||||
"""
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
# ---- model constants (mirror scripts/compute_roofline.py) ----
|
||||
L, D, H_KV, D_HEAD, D_FFN = 48, 2048, 4, 128, 6144
|
||||
K_EXPERTS = 8
|
||||
BYTES = 2 # bf16
|
||||
|
||||
# ---- H20 ----
|
||||
PEAK_FLOPS = 148e12
|
||||
HBM_BW = 4.0e12
|
||||
RIDGE = PEAK_FLOPS / HBM_BW # ~37
|
||||
|
||||
|
||||
def attn_prefill_flops(seq_len, new_tokens):
|
||||
d_kv = H_KV * D_HEAD
|
||||
qkv = new_tokens * (D * D * 2 + D * d_kv * 2 * 2)
|
||||
attn = new_tokens * seq_len * D * 2 * 2
|
||||
out = new_tokens * D * D * 2
|
||||
return (qkv + attn + out) * L
|
||||
|
||||
|
||||
def attn_prefill_bytes(seq_len, new_tokens, cached_tokens):
|
||||
d_kv = H_KV * D_HEAD
|
||||
weight = D * (D + 2 * d_kv + D) * BYTES * L
|
||||
cached_kv = cached_tokens * 2 * d_kv * BYTES * L
|
||||
act = new_tokens * D * BYTES * 2 * L
|
||||
new_kv = new_tokens * 2 * d_kv * BYTES * L
|
||||
return weight + cached_kv + act + new_kv
|
||||
|
||||
|
||||
def ffn_flops(n):
|
||||
return 3 * n * D * D_FFN * 2 * K_EXPERTS * L
|
||||
|
||||
|
||||
def ffn_bytes(n):
|
||||
weight = K_EXPERTS * 3 * D * D_FFN * BYTES * L
|
||||
act = n * D * BYTES * 2 * L
|
||||
return weight + act
|
||||
|
||||
|
||||
def point(seq_len, reuse):
|
||||
cached = int(seq_len * reuse)
|
||||
new = max(1, seq_len - cached)
|
||||
f = attn_prefill_flops(seq_len, new) + ffn_flops(new)
|
||||
b = attn_prefill_bytes(seq_len, new, cached) + ffn_bytes(new)
|
||||
return f, b, new
|
||||
|
||||
|
||||
def decode_point(seq_len):
|
||||
f = attn_prefill_flops(seq_len, 1) + ffn_flops(1)
|
||||
b = attn_prefill_bytes(seq_len, 1, seq_len) + ffn_bytes(1)
|
||||
return f, b
|
||||
|
||||
|
||||
def plot(out_path, seq_len=64000):
|
||||
fig, ax = plt.subplots(figsize=(6.5, 4.2))
|
||||
|
||||
ai_grid = np.logspace(-1, 5, 400)
|
||||
achievable = np.minimum(ai_grid * HBM_BW, PEAK_FLOPS) / 1e12
|
||||
ax.plot(ai_grid, achievable, color="#222", lw=1.5, label="H20 roofline")
|
||||
|
||||
ax.axvline(RIDGE, color="#888", ls=":", lw=1)
|
||||
ax.text(RIDGE, 420, f"ridge = {RIDGE:.0f}", color="#666",
|
||||
fontsize=8, ha="center", va="top",
|
||||
bbox=dict(boxstyle="round,pad=0.2", fc="white", ec="none", alpha=0.85))
|
||||
|
||||
ax.axhline(PEAK_FLOPS / 1e12, color="#aaa", ls="--", lw=0.6)
|
||||
ax.text(2, PEAK_FLOPS / 1e12 * 1.08, "compute ceiling (148 TFLOPS bf16)",
|
||||
fontsize=8, color="#666", ha="left")
|
||||
|
||||
# operating points: use a legend (not annotations with leader lines, since
|
||||
# all 4 prefill points sit on the compute ceiling and would overlap).
|
||||
reuses = [0.0, 0.7, 0.9, 0.95]
|
||||
colors = ["#d62728", "#ff7f0e", "#2ca02c", "#1f77b4"]
|
||||
for reuse, color in zip(reuses, colors):
|
||||
f, b, new = point(seq_len, reuse)
|
||||
ai = f / b
|
||||
thpt = min(ai * HBM_BW, PEAK_FLOPS) / 1e12
|
||||
ax.scatter([ai], [thpt], color=color, s=80, zorder=5,
|
||||
edgecolor="white", linewidth=1.2,
|
||||
label=f"prefill reuse={int(reuse*100):>2}% "
|
||||
f"(new={new:>6,} tok, AI={ai:>6,.0f})")
|
||||
|
||||
f, b = decode_point(seq_len)
|
||||
ai_dec = f / b
|
||||
thpt_dec = min(ai_dec * HBM_BW, PEAK_FLOPS) / 1e12
|
||||
ax.scatter([ai_dec], [thpt_dec], color="#8c564b", s=80, marker="D",
|
||||
zorder=5, edgecolor="white", linewidth=1.2,
|
||||
label=f"decode (per-token, seqlen={seq_len:,}, AI={ai_dec:.1f})")
|
||||
|
||||
ax.legend(loc="lower right", fontsize=8.5, framealpha=0.95,
|
||||
prop={"family": "monospace", "size": 8})
|
||||
|
||||
ax.set_xscale("log")
|
||||
ax.set_yscale("log")
|
||||
ax.set_xlim(0.5, 1e5)
|
||||
ax.set_ylim(0.5, 500)
|
||||
ax.set_xlabel("Arithmetic intensity (FLOP/byte)")
|
||||
ax.set_ylabel("Achievable throughput (TFLOPS)")
|
||||
ax.set_title(
|
||||
f"Prefill stays compute-bound even at 95% reuse "
|
||||
f"(Qwen3-Coder-30B-A3B, H20, seqlen={seq_len:,})",
|
||||
fontsize=10,
|
||||
)
|
||||
ax.grid(True, which="both", alpha=0.25)
|
||||
fig.tight_layout()
|
||||
fig.savefig(out_path, bbox_inches="tight")
|
||||
plt.close(fig)
|
||||
print(f"[C6] wrote {out_path}")
|
||||
for reuse in reuses:
|
||||
f, b, new = point(seq_len, reuse)
|
||||
print(f" reuse={int(reuse*100):>3}% new={new:>6,} AI={f/b:>8.1f} "
|
||||
f"bound={'COMPUTE' if f/b > RIDGE else 'MEMORY'}")
|
||||
f, b = decode_point(seq_len)
|
||||
print(f" decode AI={f/b:>8.1f} bound={'COMPUTE' if f/b > RIDGE else 'MEMORY'}")
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--seq-len", type=int, default=64000)
|
||||
ap.add_argument("--outdir", default="analysis/pd_sep_paper_section/figures")
|
||||
args = ap.parse_args()
|
||||
out = Path(args.outdir)
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
plot(out / "fig_c6_roofline.pdf", seq_len=args.seq_len)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user