File size: 5,442 Bytes
04b149c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#!/usr/bin/env python3
"""MTP-Messreihe gegen einen llama-server. Deutsche Prompts, nach Textsorte
getrennt, weil die Draft-Trefferquote genau daran haengt.

Der Server meldet pro Anfrage draft_n / draft_n_accepted in `timings` — daraus
kommen die Zahlen, nichts wird geschaetzt. Jeder Prompt laeuft mehrfach, ausgewertet
wird der Median, weil Einzelmessungen um +/-6 Prozent schwanken.

  ./bench_mtp.py --port 8270 --runs 3 --out ergebnis.md
"""
import argparse
import json
import statistics
import sys
import time
import urllib.request

# Textsorten, bei denen MTP erfahrungsgemaess sehr unterschiedlich trifft.
PROMPTS = [
    ("Aufzaehlung",
     "Zaehle von 1 bis 60, nur die Zahlen mit Komma getrennt, sonst nichts."),
    ("Struktur/JSON",
     "Gib ein JSON-Objekt mit 12 deutschen Staedten aus, je mit den Feldern "
     "name, bundesland, einwohner. Nur das JSON, kein Text drumherum."),
    ("Code",
     "Schreibe eine Python-Funktion, die eine CSV-Datei einliest, nach der "
     "dritten Spalte sortiert und als JSON zurueckgibt. Nur Code."),
    ("Fliesstext",
     "Schreibe drei Absaetze darueber, warum der Rhein fuer die deutsche "
     "Industriegeschichte wichtig war."),
    ("Fachtext",
     "Erklaere in etwa 300 Woertern den Unterschied zwischen einem "
     "Asynchronmotor und einem Synchronmotor."),
    ("Kreativ",
     "Schreibe eine kurze Geschichte ueber einen Leuchtturmwaerter, der eine "
     "Flaschenpost findet."),
    ("Uebersetzung",
     "Uebersetze ins Deutsche: 'The quick assessment of the situation led the "
     "engineers to redesign the cooling system, which had failed twice during "
     "the summer of the previous year.'"),
    ("Wiederholung",
     "Wiederhole den Satz 'Die Maschine laeuft stabil.' genau 25 Mal, "
     "jeweils in einer neuen Zeile."),
]


def ask(port, prompt, max_tokens, timeout):
    body = json.dumps({
        "messages": [{"role": "user", "content": prompt}],
        "max_tokens": max_tokens,
        "enable_thinking": False,
        "cache_prompt": False,
        "temperature": 0.3,
    }).encode()
    req = urllib.request.Request(
        f"http://127.0.0.1:{port}/v1/chat/completions",
        data=body, headers={"Content-Type": "application/json"})
    t0 = time.time()
    with urllib.request.urlopen(req, timeout=timeout) as r:
        d = json.load(r)
    wall = time.time() - t0
    t = d.get("timings", {})
    return {
        "tps": t.get("predicted_per_second"),
        "n": t.get("predicted_n"),
        "draft_n": t.get("draft_n") or 0,
        "draft_acc": t.get("draft_n_accepted") or 0,
        "wall": wall,
    }


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--port", type=int, default=8270)
    ap.add_argument("--runs", type=int, default=3)
    ap.add_argument("--max-tokens", type=int, default=400)
    ap.add_argument("--timeout", type=int, default=600)
    ap.add_argument("--label", default="")
    ap.add_argument("--out", default="")
    args = ap.parse_args()

    rows = []
    for name, prompt in PROMPTS:
        tps, accs, drafted, accepted = [], [], 0, 0
        for i in range(args.runs):
            try:
                r = ask(args.port, prompt, args.max_tokens, args.timeout)
            except Exception as e:
                print(f"  {name} Lauf {i+1}: FEHLER {e}", file=sys.stderr)
                continue
            if r["tps"]:
                tps.append(r["tps"])
            drafted += r["draft_n"]
            accepted += r["draft_acc"]
            if r["draft_n"]:
                accs.append(r["draft_acc"] / r["draft_n"])
            print(f"  {name} Lauf {i+1}: {r['tps']:.1f} t/s, "
                  f"{r['draft_acc']}/{r['draft_n']} Draft-Treffer", file=sys.stderr)
        if not tps:
            continue
        rows.append({
            "sorte": name,
            "tps": statistics.median(tps),
            "tps_min": min(tps),
            "tps_max": max(tps),
            "quote": (accepted / drafted) if drafted else 0.0,
            "drafted": drafted,
            "accepted": accepted,
        })

    hdr = f"### Messreihe {args.label}\n\n" if args.label else "### Messreihe\n\n"
    md = hdr
    md += "| Textsorte | t/s (Median) | Spanne | Draft-Trefferquote | angenommen / erzeugt |\n"
    md += "|---|---|---|---|---|\n"
    for r in rows:
        md += (f"| {r['sorte']} | **{r['tps']:.1f}** | {r['tps_min']:.1f}{r['tps_max']:.1f} "
               f"| {r['quote']*100:.1f} % | {r['accepted']} / {r['drafted']} |\n")
    if rows:
        gd = sum(r["drafted"] for r in rows)
        ga = sum(r["accepted"] for r in rows)
        if gd:
            md += (f"\nGesamt: **{ga/gd*100:.1f} %** Trefferquote "
                   f"({ga} / {gd} Draft-Token), {args.runs} Laeufe je Textsorte, "
                   f"max_tokens={args.max_tokens}.\n")
        else:
            # Lauf ohne Draft-Modell: es gibt nichts zu treffen.
            tps_all = [r["tps"] for r in rows]
            md += (f"\nOhne Spekulation — keine Draft-Token. "
                   f"Median ueber alle Textsorten: **{statistics.median(tps_all):.1f} t/s** "
                   f"(Spanne {min(tps_all):.1f}{max(tps_all):.1f}), "
                   f"{args.runs} Laeufe je Textsorte, max_tokens={args.max_tokens}.\n")

    print(md)
    if args.out:
        open(args.out, "w").write(md)
        print(f"geschrieben: {args.out}", file=sys.stderr)


if __name__ == "__main__":
    main()