Coverage for orchestrant/benchmark/determinism.py: 100%
19 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-18 00:48 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-18 00:48 +0000
1"""Does this lane sample at temperature 0? The evidence bench_compare trusts.
3A report's `determinism_probe` decides how bench_compare reads it: a lane
4recorded deterministic has its single-draw flips judged strictly, as real
5regressions. So a change to this probe changes what a report CLAIMS, and the
6tools that run it (bench_tools, bench_coding) put this file in their
7`tool_sha256`.
9It used to live in provenance.py, and those tools fingerprinted that whole
10module for its sake — so every edit to the provenance plumbing (a new field, a
11runtime-detection fix) read on the next comparison as "BENCHMARK SOURCE
12CHANGED — a score difference may be the grader", while the grader was
13provably untouched. provenance re-exports these names, so existing imports
14keep working.
15"""
17import hashlib
20PROBE_PROMPT = "Write one sentence about the sea."
23SPACER_PROMPT = "Reply with the single word: ok"
26def determinism_probe(base_url, model, post, prompt=PROBE_PROMPT, max_tokens=48):
27 """Send the same request twice, with another between; did the outputs match?
29 `post(url, payload) -> dict` is injected so this can run without a server
30 (tests) and so callers pick the transport. "Deterministic" here means two
31 draws at temperature 0 agreed byte-for-byte — evidence, not proof, and it is
32 recorded as such so a --repeats 1 flip can be read for what it is.
34 The prompt must leave the model real choices. The first version asked for
35 "the single word: ready" in 8 tokens — an answer with almost no entropy,
36 which a SAMPLING lane repeats verbatim. On GenieX v0.6.1 it recorded the
37 QAIRT lane as deterministic while two open-ended requests at temperature 0
38 came back different, and bench_compare then read that lane's single-draw
39 flips as real regressions.
41 The two draws are NOT sent back to back. On GenieX (v0.6.1 and v0.7.0,
42 measured 2026-09-24) an identical request sent twice in a row takes a cache
43 path that changes the reply on both lanes — llama.cpp prefills 0 tokens and
44 samples the first token from the previous reply's logits; QAIRT reuses
45 part of the dialog — while after any other request both answer as if cold.
46 A spacer request between the draws measures the sampler, not that bug.
47 """
48 payload = {
49 "model": model,
50 "temperature": 0,
51 "max_tokens": max_tokens,
52 "stream": False,
53 "messages": [{"role": "user", "content": prompt}],
54 }
55 spacer = {
56 **payload,
57 "max_tokens": 1,
58 "messages": [{"role": "user", "content": SPACER_PROMPT}],
59 }
60 outputs = []
61 try:
62 for i in range(2):
63 if i:
64 post(f"{base_url}/v1/chat/completions", spacer)
65 reply = post(f"{base_url}/v1/chat/completions", payload)
66 content = (
67 (reply.get("choices") or [{}])[0].get("message", {}).get("content")
68 )
69 if content is None:
70 raise ValueError("reply carried no message content")
71 outputs.append(content)
72 except Exception as e:
73 return {
74 "deterministic": None,
75 "requests": len(outputs),
76 "prompt": prompt,
77 "error": f"{type(e).__name__}: {e}"[:200],
78 }
79 return {
80 "deterministic": outputs[0] == outputs[1],
81 "requests": 3,
82 "spacer": True,
83 "prompt": prompt,
84 "output_sha256": [hashlib.sha256(o.encode()).hexdigest()[:16] for o in outputs],
85 "error": None,
86 }