prefill_block.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. """How long a long prefill holds up everything else.
  2. While one long prompt prefills: a stream that is already generating reports its
  3. gaps between tokens, short requests arriving every few seconds report their
  4. time to first token, and optionally a second long prompt arrives mid-way.
  5. python3 prefill_block.py http://HEAD:8002 [--words 90000] [--second-long]
  6. Random-word prompts, so nothing is served from the prefix cache.
  7. """
  8. import argparse
  9. import json
  10. import random
  11. import threading
  12. import time
  13. import urllib.request
  14. WORDS = ("alpha bravo charlie delta echo foxtrot golf hotel india juliet kilo lima mike november oscar papa "
  15. "quebec romeo sierra tango uniform victor whiskey xray yankee zulu river stone cloud ember maple "
  16. "harbor lantern meadow orbit prism quartz saddle timber velvet willow").split()
  17. def prompt(n_words, seed):
  18. rnd = random.Random(seed)
  19. return " ".join(rnd.choice(WORDS) for _ in range(n_words)) + "\nReply with one short sentence."
  20. def stream(url, model, text, max_tokens, t0, out):
  21. """Streams one completion; records (seconds since t0) of the first and every later token."""
  22. body = {"model": model, "prompt": text, "max_tokens": max_tokens, "temperature": 0, "stream": True}
  23. req = urllib.request.Request(url + "/v1/completions", json.dumps(body).encode(),
  24. {"Content-Type": "application/json"})
  25. out["sent"] = time.time() - t0
  26. out["tokens"] = []
  27. with urllib.request.urlopen(req, timeout=3600) as r:
  28. for raw in r:
  29. line = raw.decode().strip()
  30. if line.startswith("data:") and line != "data: [DONE]":
  31. if json.loads(line[5:])["choices"][0].get("text"):
  32. out["tokens"].append(time.time() - t0)
  33. out["done"] = time.time() - t0
  34. def main():
  35. ap = argparse.ArgumentParser()
  36. ap.add_argument("url")
  37. ap.add_argument("--model", default="glm53")
  38. ap.add_argument("--words", type=int, default=90000, help="long prompt size (~1.4 tokens per word)")
  39. ap.add_argument("--probes", type=int, default=6)
  40. ap.add_argument("--probe-every", type=float, default=2.0)
  41. ap.add_argument("--second-long", action="store_true")
  42. a = ap.parse_args()
  43. url = a.url.rstrip("/")
  44. t0 = time.time()
  45. runs, threads = {}, []
  46. def start(name, text, max_tokens, delay):
  47. def go():
  48. time.sleep(delay)
  49. runs[name] = {}
  50. stream(url, a.model, text, max_tokens, t0, runs[name])
  51. th = threading.Thread(target=go)
  52. th.start()
  53. threads.append(th)
  54. start("generating", prompt(40, 1), 3000, 0) # already streaming when the long prompt lands
  55. start("long", prompt(a.words, 2), 8, 3)
  56. if a.second_long:
  57. start("long2", prompt(a.words // 2, 3), 8, 9)
  58. for i in range(a.probes):
  59. start(f"probe{i}", prompt(20, 10 + i), 8, 5 + i * a.probe_every)
  60. for th in threads:
  61. th.join()
  62. long_ = runs["long"]
  63. lf = long_["tokens"][0] if long_["tokens"] else long_["done"]
  64. print(f"long prompt: sent {long_['sent']:.1f}s, first token {lf:.1f}s -> prefill {lf - long_['sent']:.1f}s")
  65. if "long2" in runs:
  66. l2 = runs["long2"]
  67. f2 = l2["tokens"][0] if l2["tokens"] else l2["done"]
  68. print(f"second long: sent {l2['sent']:.1f}s, first token {f2:.1f}s -> waited+prefilled {f2 - l2['sent']:.1f}s")
  69. toks = runs["generating"]["tokens"]
  70. gaps = [(b - a_, a_) for a_, b in zip(toks, toks[1:])]
  71. during = [g for g, at in gaps if long_["sent"] <= at <= lf + 1]
  72. print(f"generating stream: {len(toks)} tokens; gaps during the long prefill: max {max(during, default=0):.2f}s,"
  73. f" {sum(1 for g in during if g > 1)} over 1s, {sum(during):.1f}s total in {len(during)} gaps"
  74. f" (outside it: median {sorted(g for g, _ in gaps)[len(gaps) // 2]:.3f}s)")
  75. for i in range(a.probes):
  76. p = runs[f"probe{i}"]
  77. ttft = (p["tokens"][0] if p["tokens"] else p["done"]) - p["sent"]
  78. where = "during" if p["sent"] < lf else "after"
  79. print(f" probe {i}: sent {p['sent']:5.1f}s ({where} the long prefill), time to first token {ttft:5.2f}s")
  80. if __name__ == "__main__":
  81. main()