summaryrefslogtreecommitdiff
path: root/src/eval_tune/lazy_eval_margins/hist_percentile.py
blob: bd904442d802a9163682359979898f1dc2d40b05 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#!/usr/bin/env python3
"""
Read any percentile out of the raw lazy/super-lazy-eval histograms
persisted by eval.c's _PersistRawHistogramsToFile (called automatically
every time a DUMP_RESIDUAL_SAMPLES build runs `calibrate residual`).

Usage:
    python3 hist_percentile.py <histograms_file> [percentiles...]

    python3 hist_percentile.py residual_raw_histograms_guess2_30k.txt
    python3 hist_percentile.py residual_raw_histograms_guess2_30k.txt 0.93 0.97 0.985

With no percentile arguments, prints a default useful spread for every
bucket of all three histograms (residual, sl_residual, king). With
percentile arguments (0.0-1.0), prints just those, letting you probe
any exact percentile the fixed comment-block charts in eval.c didn't
happen to include -- no re-running the 30k pool required, as long as
this file (or a fresher one) still exists.

File format (one line per histogram/bucket):
    <name> <bucket 0-7> <min> <max> <bin0-count> <bin1-count> ... <binN-1-count>
Bin i covers [RESID_HIST_MIN + i*RESID_HIST_BIN_WIDTH,
              RESID_HIST_MIN + (i+1)*RESID_HIST_BIN_WIDTH).
These constants are echoed in the file's own header comment and also
hardcoded below (must match eval.c's #defines -- they haven't changed
since this was built, but check if eval.c's RESID_HIST_* macros ever
do).
"""
import sys

BIN_WIDTH = 20
MIN_VAL = -900
DEFAULT_PERCENTILES = [0.01, 0.02, 0.05, 0.10, 0.15, 0.50,
                        0.85, 0.90, 0.95, 0.98, 0.99]


def load(path):
    hists = {}
    with open(path) as f:
        for line in f:
            if line.startswith('#') or not line.strip():
                continue
            parts = line.split()
            name, bucket = parts[0], int(parts[1])
            mn, mx = int(parts[2]), int(parts[3])
            bins = list(map(int, parts[4:]))
            hists[(name, bucket)] = (mn, mx, bins)
    return hists


def percentile(bins, total, p):
    target = p * total
    running = 0
    for i, c in enumerate(bins):
        running += c
        if running >= target:
            return MIN_VAL + i * BIN_WIDTH + BIN_WIDTH // 2
    return MIN_VAL + len(bins) * BIN_WIDTH


def main():
    if len(sys.argv) < 2:
        print(__doc__)
        sys.exit(1)
    path = sys.argv[1]
    percentiles = [float(x) for x in sys.argv[2:]] or DEFAULT_PERCENTILES

    hists = load(path)
    names = sorted(set(name for name, _ in hists.keys()))
    for name in names:
        print(f"--- {name} ---")
        for b in range(8):
            if (name, b) not in hists:
                continue
            mn, mx, bins = hists[(name, b)]
            total = sum(bins)
            if total == 0:
                print(f"  bucket {b}: no samples")
                continue
            vals = " ".join(f"p{p*100:g}={percentile(bins, total, p)}"
                             for p in percentiles)
            print(f"  bucket {b}: n={total} min={mn} {vals} max={mx}")


if __name__ == "__main__":
    main()