perf-repeat.py 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
  1. """Decode rate over N short-prompt runs, plus DFlash2 draft acceptance.
  2. Acceptance length, not rate, is what predicts throughput for a k-token drafter.
  3. """
  4. import json, re, statistics, sys, time, urllib.request
  5. BASE, MODEL, N = sys.argv[1], sys.argv[2], int(sys.argv[3])
  6. def metrics():
  7. raw = urllib.request.urlopen(BASE.replace("/v1", "") + "/metrics", timeout=30).read().decode()
  8. out = {}
  9. for key in ("spec_decode_num_accepted_tokens_total", "spec_decode_num_draft_tokens_total"):
  10. m = re.search(rf"^vllm:{key}\S*\s+([0-9.e+]+)$", raw, re.M)
  11. if m:
  12. out[key] = float(m.group(1))
  13. return out
  14. def once(prompt, max_tokens):
  15. body = {"model": MODEL, "messages": [{"role": "user", "content": prompt}],
  16. "max_tokens": max_tokens, "temperature": 0, "stream": True,
  17. "stream_options": {"include_usage": True}}
  18. req = urllib.request.Request(BASE + "/chat/completions", json.dumps(body).encode(),
  19. {"Content-Type": "application/json"})
  20. t0 = time.time(); ttft = None; usage = None
  21. with urllib.request.urlopen(req, timeout=1800) as r:
  22. for raw in r:
  23. line = raw.decode("utf-8", "replace").strip()
  24. if not line.startswith("data:"): continue
  25. d = line[5:].strip()
  26. if d == "[DONE]": break
  27. ev = json.loads(d)
  28. if ev.get("usage"): usage = ev["usage"]
  29. for ch in ev.get("choices", []):
  30. delta = ch.get("delta", {})
  31. if (delta.get("content") or delta.get("reasoning")) and ttft is None:
  32. ttft = time.time() - t0
  33. total = time.time() - t0
  34. completion = (usage or {}).get("completion_tokens", 0)
  35. return (completion - 1) / (total - ttft) if ttft and total > ttft else 0
  36. before = metrics()
  37. rates = [once("Explain unified memory on GB10 in two sentences.", 200) for _ in range(N)]
  38. after = metrics()
  39. acc = after.get("spec_decode_num_accepted_tokens_total", 0) - before.get("spec_decode_num_accepted_tokens_total", 0)
  40. draft = after.get("spec_decode_num_draft_tokens_total", 0) - before.get("spec_decode_num_draft_tokens_total", 0)
  41. print(f" decode tok/s over {N} runs: " + " ".join(f"{r:.1f}" for r in sorted(rates)))
  42. print(f" median {statistics.median(rates):.1f} mean {statistics.mean(rates):.1f} "
  43. f"spread {max(rates)-min(rates):.1f}")
  44. if draft:
  45. print(f" draft: accepted {acc:.0f} of {draft:.0f} = {acc/draft:.3f}; "
  46. f"accepted per step {acc/(draft/7):.2f} of 7")