overnight_quality.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. #!/usr/bin/env python3
  2. """Correctness gate for the overnight TP3 campaign (thinking off, greedy unless noted).
  3. Checks: exact-answer tasks, JSON validity, generated code executed against tests,
  4. the same tasks fired four at a time (concurrency), greedy repeatability, and
  5. long-context needle retrieval at the requested sizes. Writes a JSON report whose
  6. greedy outputs can be diffed against another configuration's.
  7. Usage: BASE_URL=http://10.0.0.1:8888 python3 overnight_quality.py OUT.json [needle_k_tokens,...]
  8. """
  9. from __future__ import annotations
  10. import concurrent.futures
  11. import hashlib
  12. import json
  13. import os
  14. import random
  15. import re
  16. import sys
  17. import time
  18. import urllib.request
  19. BASE = os.environ.get("BASE_URL", "http://10.0.0.1:8888").rstrip("/")
  20. MODEL = os.environ.get("SERVED_MODEL_NAME", "deepseek-v4.1-flash")
  21. def chat(prompt, max_tokens=256, temperature=0.0, timeout=1800):
  22. body = {"model": MODEL, "messages": [{"role": "user", "content": prompt}],
  23. "max_tokens": max_tokens, "temperature": temperature,
  24. "chat_template_kwargs": {"thinking": False}}
  25. req = urllib.request.Request(BASE + "/v1/chat/completions", data=json.dumps(body).encode(),
  26. headers={"Content-Type": "application/json"}, method="POST")
  27. t = time.perf_counter()
  28. with urllib.request.urlopen(req, timeout=timeout) as r:
  29. obj = json.loads(r.read())
  30. return obj["choices"][0]["message"].get("content") or "", obj.get("usage", {}), time.perf_counter() - t
  31. def check_code(text):
  32. m = re.search(r"```(?:python)?\n(.*?)```", text, re.S)
  33. src = m.group(1) if m else text
  34. ns = {}
  35. try:
  36. exec(src, ns) # model-written code, run locally on the benchmark host for the gate
  37. f = ns["is_prime"]
  38. cases = {0: False, 1: False, 2: True, 3: True, 4: False, 17: True, 21: False, 97: True, 7919: True, 7917: False}
  39. return all(bool(f(k)) == v for k, v in cases.items())
  40. except Exception:
  41. return False
  42. def check_json(text):
  43. m = re.search(r"\{.*\}", text, re.S)
  44. try:
  45. o = json.loads(m.group(0))
  46. return o.get("name") == "Ada" and o.get("age") == 36 and isinstance(o.get("skills"), list) and len(o["skills"]) == 3
  47. except Exception:
  48. return False
  49. TASKS = [
  50. ("mul", "What is 17 * 23? Reply with only the number.", lambda t: t.strip().rstrip(".") == "391"),
  51. ("speed", "A train covers 120 km in 1.5 hours at constant speed. What is its speed in km/h? Reply with only the number.",
  52. lambda t: re.sub(r"[^0-9.]", "", t.strip()) in ("80", "80.0")),
  53. ("capital", "What is the capital city of Australia? Reply with one word.", lambda t: "canberra" in t.lower()),
  54. ("json", "Return only a JSON object with keys name (the string \"Ada\"), age (the integer 36) and skills "
  55. "(a list of exactly three strings). No prose, no code fence.", check_json),
  56. ("code", "Write a Python function is_prime(n: int) -> bool that returns whether n is prime (n may be 0 or 1). "
  57. "Reply with a single python code block and nothing else.", check_code),
  58. ("order", "Sort these words alphabetically and reply with them comma-separated, nothing else: pear, apple, mango, fig, banana",
  59. lambda t: re.sub(r"\s", "", t.lower()).rstrip(".") == "apple,banana,fig,mango,pear"),
  60. ("count", "How many times does the letter r appear in the word 'strawberry'? Reply with only the number.",
  61. lambda t: t.strip().rstrip(".") == "3"),
  62. ("sum", "Compute 1234 + 5678 - 999. Reply with only the number.", lambda t: t.strip().rstrip(".") == "5913"),
  63. ]
  64. REPEAT_PROMPT = ("Write a detailed, flowing essay of about 800 words on the history and engineering "
  65. "of lighthouses, from the Pharos of Alexandria to automated LED beacons. Use full "
  66. "paragraphs only: no lists, no headings, no markdown.")
  67. def needle(k_tokens, seed=7):
  68. rng = random.Random(seed + k_tokens)
  69. code = "".join(rng.choice("ABCDEFGHJKLMNPQRSTUVWXYZ23456789") for _ in range(8))
  70. sentences = ["The archive room was quiet except for the hum of the ventilation system.",
  71. "Researchers catalogued every folder by date, region and subject heading.",
  72. "Rain streaked the tall windows while the afternoon light faded slowly.",
  73. "A maintenance log recorded routine inspections of the heating pipes.",
  74. "The committee postponed its decision until the next quarterly meeting."]
  75. n = int(k_tokens * 1000 / 13) # ~13 tokens per sentence
  76. body = [rng.choice(sentences) for _ in range(n)]
  77. pos = n * 37 // 100
  78. body.insert(pos, f"Important: the vault access code is {code}. Remember it.")
  79. prompt = " ".join(body) + "\n\nWhat is the vault access code mentioned in the text above? Reply with only the code."
  80. t0 = time.perf_counter()
  81. try:
  82. text, usage, dt = chat(prompt, max_tokens=32)
  83. return {"k": k_tokens, "prompt_tokens": usage.get("prompt_tokens"), "ok": code in text,
  84. "answer": text[:80], "secs": dt, "error": None}
  85. except Exception as e:
  86. return {"k": k_tokens, "ok": False, "error": repr(e), "secs": time.perf_counter() - t0}
  87. def main():
  88. out = sys.argv[1]
  89. needles = [int(x) for x in sys.argv[2].split(",")] if len(sys.argv) > 2 and sys.argv[2] else []
  90. rep = {"tasks": {}, "concurrent": {}, "repeat": {}, "needles": []}
  91. for name, prompt, ok in TASKS:
  92. text, usage, dt = chat(prompt)
  93. rep["tasks"][name] = {"ok": bool(ok(text)), "text": text[:400],
  94. "sha": hashlib.sha256(text.encode()).hexdigest()[:16]}
  95. with concurrent.futures.ThreadPoolExecutor(4) as ex:
  96. futs = {ex.submit(chat, p): (n, ok) for n, p, ok in TASKS}
  97. for f in concurrent.futures.as_completed(futs):
  98. n, ok = futs[f]
  99. try:
  100. text = f.result()[0]
  101. rep["concurrent"][n] = {"ok": bool(ok(text)),
  102. "same_as_c1": hashlib.sha256(text.encode()).hexdigest()[:16] == rep["tasks"][n]["sha"]}
  103. except Exception as e:
  104. rep["concurrent"][n] = {"ok": False, "error": repr(e)}
  105. shas = [hashlib.sha256(chat(REPEAT_PROMPT, max_tokens=256)[0].encode()).hexdigest()[:16] for _ in range(3)]
  106. rep["repeat"] = {"shas": shas, "distinct": len(set(shas))}
  107. for k in needles:
  108. rep["needles"].append(needle(k))
  109. n_ok = sum(v["ok"] for v in rep["tasks"].values())
  110. c_ok = sum(v.get("ok", False) for v in rep["concurrent"].values())
  111. rep["summary"] = {"tasks_ok": f"{n_ok}/{len(TASKS)}", "concurrent_ok": f"{c_ok}/{len(TASKS)}",
  112. "concurrent_same_as_c1": sum(v.get("same_as_c1", False) for v in rep["concurrent"].values()),
  113. "greedy_repeat_distinct": rep["repeat"]["distinct"],
  114. "needles": [(n["k"], n.get("prompt_tokens"), n["ok"], round(n["secs"], 1)) for n in rep["needles"]]}
  115. json.dump(rep, open(out, "w"), indent=1)
  116. print(json.dumps(rep["summary"]))
  117. for n, v in rep["tasks"].items():
  118. if not v["ok"]:
  119. print("FAIL", n, repr(v["text"][:200]))
  120. if __name__ == "__main__":
  121. main()