File size: 6,544 Bytes
046ace0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
"""Parse what `llama-bench` prints, markdown table or `-o json`, into benchmark rows.

People paste this output, so it is untrusted text. Only the measured numbers and a few
settings are kept. The JSON form carries the model's full file path, CPU model and
build machine details; none of that is stored.
"""

from __future__ import annotations

import json
import re
from dataclasses import dataclass

MAX_INPUT = 40_000

_GPU_NAME = re.compile(
    r"(NVIDIA (?:GeForce )?(?:RTX|GTX|RTX PRO|Tesla|A|H|L)\s?[A-Za-z0-9 ]{1,24}?)(?=[,;|\n]|$| \()"
    r"|(AMD Radeon[A-Za-z0-9 ]{1,30}?)(?=[,;|\n]|$| \()"
    r"|(Apple M[1-9](?: (?:Pro|Max|Ultra))?)"
    r"|(Intel\(R\) Arc\(TM\) [A-Za-z0-9 ]{1,12}|Intel Arc [A-Za-z0-9 ]{1,12})",
)
_BACKENDS = ("CUDA", "ROCm", "Metal", "Vulkan", "SYCL", "CPU", "BLAS", "RPC", "MUSA", "CANN", "OpenCL")


@dataclass
class BenchRow:
    test: str                 # "pp512", "tg128", "pp512+tg128"
    n_prompt: int
    n_gen: int
    tps: float
    stdev: float | None
    backend: str | None
    n_gpu_layers: int | None
    model_type: str | None    # "qwen3moe 30B.A3B Q4_K - Medium"
    size_gib: float | None
    params_b: float | None
    n_threads: int | None
    flash_attn: bool | None
    type_k: str | None
    gpu_name: str | None
    build: str | None

    def to_dict(self) -> dict:
        return dict(self.__dict__)


def _backend(text: str | None) -> str | None:
    if not text:
        return None
    found = [b for b in _BACKENDS if b.lower() in text.lower()]
    found = [b for b in found if b not in ("BLAS", "RPC") or len(found) == 1]
    return ",".join(found[:2]) or None


def gpu_name(text: str | None) -> str | None:
    if not text:
        return None
    m = _GPU_NAME.search(text)
    if not m:
        return None
    name = next(g for g in m.groups() if g)
    return re.sub(r"\s+", " ", name).strip()[:40]


def _int(v):
    try:
        return int(v)
    except (TypeError, ValueError):
        return None


def _tests(n_prompt: int, n_gen: int) -> str:
    if n_prompt and n_gen:
        return f"pp{n_prompt}+tg{n_gen}"
    return f"pp{n_prompt}" if n_prompt else f"tg{n_gen}"


def parse_json(text: str) -> list[BenchRow]:
    data = json.loads(text)
    if isinstance(data, dict):
        data = [data]
    rows = []
    for d in data:
        if not isinstance(d, dict) or "avg_ts" not in d:
            continue
        n_prompt, n_gen = _int(d.get("n_prompt")) or 0, _int(d.get("n_gen")) or 0
        size = d.get("model_size")
        params = d.get("model_n_params")
        rows.append(BenchRow(
            test=_tests(n_prompt, n_gen), n_prompt=n_prompt, n_gen=n_gen, tps=float(d["avg_ts"]),
            stdev=float(d["stdev_ts"]) if d.get("stdev_ts") is not None else None,
            backend=_backend(str(d.get("backends") or "")), n_gpu_layers=_int(d.get("n_gpu_layers")),
            model_type=str(d.get("model_type") or "")[:60] or None,
            size_gib=round(size / 2**30, 2) if isinstance(size, (int, float)) else None,
            params_b=round(params / 1e9, 2) if isinstance(params, (int, float)) else None,
            n_threads=_int(d.get("n_threads")),
            flash_attn=bool(d["flash_attn"]) if d.get("flash_attn") is not None else None,
            type_k=str(d.get("type_k") or "")[:8] or None,
            gpu_name=gpu_name(str(d.get("gpu_info") or "")),
            build=str(d.get("build_commit") or "")[:12] or None,
        ))
    return rows


_ROW_SPLIT = re.compile(r"\s*\|\s*")


def parse_markdown(text: str) -> list[BenchRow]:
    lines = [l.strip() for l in text.splitlines()]
    header = None
    rows = []
    build = None
    m = re.search(r"build:\s*([0-9a-f]{6,12})", text)
    if m:
        build = m.group(1)
    gpu = gpu_name(text)
    for line in lines:
        if not line.startswith("|"):
            continue
        cells = [c.strip() for c in line.strip().strip("|").split("|")]
        if header is None:
            if "t/s" in cells and "test" in cells:
                header = [c.strip().lower() for c in cells]
            continue
        if set(line.replace("|", "").strip()) <= set("-: "):
            continue
        if len(cells) != len(header):
            continue
        rec = dict(zip(header, (c.strip() for c in cells)))
        test = rec.get("test", "")
        tm = re.fullmatch(r"(?:pp(\d+))?\+?(?:tg(\d+))?(?:\s*@\s*d\d+)?", test)
        ts = re.match(r"([0-9.]+)(?:\s*±\s*([0-9.]+))?", rec.get("t/s", ""))
        if not tm or not ts or not test:
            continue
        size = re.match(r"([0-9.]+)\s*GiB", rec.get("size", ""))
        params = re.match(r"([0-9.]+)\s*B", rec.get("params", ""))
        fa = rec.get("fa")
        rows.append(BenchRow(
            test=test.split("@")[0].strip(), n_prompt=_int(tm.group(1)) or 0, n_gen=_int(tm.group(2)) or 0,
            tps=float(ts.group(1)), stdev=float(ts.group(2)) if ts.group(2) else None,
            backend=_backend(rec.get("backend")), n_gpu_layers=_int(rec.get("ngl")),
            model_type=(rec.get("model") or "")[:60] or None,
            size_gib=float(size.group(1)) if size else None, params_b=float(params.group(1)) if params else None,
            n_threads=_int(rec.get("threads")), flash_attn=(fa == "1") if fa in ("0", "1") else None,
            type_k=(rec.get("type_k") or "")[:8] or None, gpu_name=gpu, build=build,
        ))
    return rows


def parse(text: str) -> list[BenchRow]:
    text = (text or "")[:MAX_INPUT]
    stripped = text.strip()
    if stripped.startswith(("[", "{")):
        try:
            return parse_json(stripped)
        except (ValueError, TypeError):
            pass
    return parse_markdown(text)


def summarize(rows: list[BenchRow]) -> dict | None:
    """The numbers the dataset stores: prompt processing and generation speed, one row."""
    if not rows:
        return None
    pp = next((r for r in rows if r.n_prompt and not r.n_gen), None)
    tg = next((r for r in rows if r.n_gen and not r.n_prompt), None)
    ref = tg or pp or rows[0]
    return {
        "prompt_tokens": pp.n_prompt if pp else None, "prompt_tps": pp.tps if pp else None,
        "generation_tokens": tg.n_gen if tg else None, "generation_tps": tg.tps if tg else None,
        "backend": ref.backend, "n_gpu_layers": ref.n_gpu_layers, "bench_model_type": ref.model_type,
        "bench_size_gib": ref.size_gib, "bench_params_b": ref.params_b, "n_threads": ref.n_threads,
        "flash_attn": ref.flash_attn, "type_k": ref.type_k, "gpu_name": ref.gpu_name, "build": ref.build,
    }