quantgap 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- quantgap/__init__.py +3 -0
- quantgap/backends/__init__.py +60 -0
- quantgap/backends/base.py +118 -0
- quantgap/backends/hf.py +77 -0
- quantgap/backends/llamacpp.py +99 -0
- quantgap/backends/mlx.py +86 -0
- quantgap/backends/openai_compat.py +164 -0
- quantgap/cli.py +521 -0
- quantgap/config.py +60 -0
- quantgap/hub/__init__.py +1 -0
- quantgap/hub/packs.py +142 -0
- quantgap/hub/resolve.py +247 -0
- quantgap/hub/submit.py +87 -0
- quantgap/models.py +202 -0
- quantgap/pack/__init__.py +4 -0
- quantgap/pack/reader.py +142 -0
- quantgap/pack/runner.py +301 -0
- quantgap/pack/writer.py +162 -0
- quantgap/probes/__init__.py +11 -0
- quantgap/probes/build.py +484 -0
- quantgap/probes/data/SOURCES.md +15 -0
- quantgap/probes/data/probes_v1.jsonl +160 -0
- quantgap/probes/registry.py +34 -0
- quantgap/probes/render.py +158 -0
- quantgap/report/__init__.py +0 -0
- quantgap/report/chart.py +92 -0
- quantgap/report/jsonout.py +25 -0
- quantgap/report/table.py +82 -0
- quantgap/score/__init__.py +4 -0
- quantgap/score/compare.py +33 -0
- quantgap/score/damage.py +103 -0
- quantgap/score/kld.py +73 -0
- quantgap/score/recommend.py +78 -0
- quantgap/score/run.py +260 -0
- quantgap/score/stats.py +33 -0
- quantgap/tokcheck.py +71 -0
- quantgap/util.py +95 -0
- quantgap-0.1.0.dist-info/METADATA +311 -0
- quantgap-0.1.0.dist-info/RECORD +42 -0
- quantgap-0.1.0.dist-info/WHEEL +4 -0
- quantgap-0.1.0.dist-info/entry_points.txt +2 -0
- quantgap-0.1.0.dist-info/licenses/LICENSE +202 -0
quantgap/probes/build.py
ADDED
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
"""Build probe set v1 from open sources. Run by maintainers, never by users.
|
|
2
|
+
|
|
3
|
+
python -m quantgap.probes.build --out src/quantgap/probes/data/probes_v1.jsonl
|
|
4
|
+
|
|
5
|
+
The output file is frozen and checked in. This script is only here so anyone can see where the
|
|
6
|
+
texts came from (see SOURCES.md next to the data file). Re-running it later may give different
|
|
7
|
+
text because Wikipedia changes. The checked-in file is the source of truth, not this script.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
import random
|
|
15
|
+
import re
|
|
16
|
+
import time
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
import httpx
|
|
20
|
+
|
|
21
|
+
UA = {"User-Agent": "quantgap-probe-builder/0.1 (open-source LLM quant tool)"}
|
|
22
|
+
|
|
23
|
+
GROUP_SIZES = {
|
|
24
|
+
"general": 30,
|
|
25
|
+
"chat": 30,
|
|
26
|
+
"code": 28,
|
|
27
|
+
"math": 28,
|
|
28
|
+
"multilingual": 32,
|
|
29
|
+
"json": 12,
|
|
30
|
+
}
|
|
31
|
+
GREEDY_PER_GROUP = 8
|
|
32
|
+
|
|
33
|
+
LANG_CHARS = {"es": 2200, "hi": 1300, "zh": 750, "ar": 1300}
|
|
34
|
+
LANG_COUNT = {"hi": 8, "zh": 8, "es": 8, "ar": 8}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _get_json(client: httpx.Client, url: str, params: dict) -> dict:
|
|
38
|
+
for attempt in range(4):
|
|
39
|
+
r = client.get(url, params=params, headers=UA, timeout=60)
|
|
40
|
+
if r.status_code == 429 or r.status_code >= 500:
|
|
41
|
+
time.sleep(3 * (attempt + 1))
|
|
42
|
+
continue
|
|
43
|
+
r.raise_for_status()
|
|
44
|
+
return r.json()
|
|
45
|
+
raise RuntimeError(f"giving up on {url} {params}")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _wiki_row(client: httpx.Client, lang: str, offset: int) -> tuple[str, str] | None:
|
|
49
|
+
"""One Wikipedia article (CC BY-SA) from the wikimedia/wikipedia dataset on the Hub."""
|
|
50
|
+
try:
|
|
51
|
+
data = _get_json(
|
|
52
|
+
client,
|
|
53
|
+
"https://datasets-server.huggingface.co/rows",
|
|
54
|
+
{"dataset": "wikimedia/wikipedia", "config": f"20231101.{lang}", "split": "train",
|
|
55
|
+
"offset": offset, "length": 1},
|
|
56
|
+
) # fmt: skip
|
|
57
|
+
except (RuntimeError, httpx.HTTPError):
|
|
58
|
+
return None # the server can fail on some offsets; the caller just tries the next one
|
|
59
|
+
rows = data.get("rows") or []
|
|
60
|
+
if not rows:
|
|
61
|
+
return None
|
|
62
|
+
r = rows[0]["row"]
|
|
63
|
+
return r["title"], r["text"]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _wiki_pick(client: httpx.Client, lang: str, count: int, min_chars: int, start: int, step: int):
|
|
67
|
+
"""`count` articles, walking through the dataset in fixed steps (deterministic)."""
|
|
68
|
+
out, offset = [], start
|
|
69
|
+
while len(out) < count and offset < start + step * count * 6:
|
|
70
|
+
got = _wiki_row(client, lang, offset)
|
|
71
|
+
offset += step
|
|
72
|
+
if got and len(got[1]) >= min_chars:
|
|
73
|
+
out.append(got)
|
|
74
|
+
return out
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _clip(text: str, n: int) -> str:
|
|
78
|
+
text = re.sub(r"\n{3,}", "\n\n", text).strip()
|
|
79
|
+
if len(text) <= n:
|
|
80
|
+
return text
|
|
81
|
+
cut = text[:n]
|
|
82
|
+
para = cut.rfind("\n")
|
|
83
|
+
if para > n * 0.6:
|
|
84
|
+
return cut[:para].rstrip()
|
|
85
|
+
for end in (". ", "。", "।", "؟ ", ". "):
|
|
86
|
+
i = cut.rfind(end)
|
|
87
|
+
if i > n * 0.6:
|
|
88
|
+
return cut[: i + len(end)].rstrip()
|
|
89
|
+
return cut.rstrip()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def build_general(client) -> list[dict]:
|
|
93
|
+
rows = []
|
|
94
|
+
for t, text in _wiki_pick(client, "en", GROUP_SIZES["general"], 1800, start=1000, step=311):
|
|
95
|
+
rows.append(
|
|
96
|
+
dict(group="general", kind="text", lang="en", text=_clip(text, 2200),
|
|
97
|
+
license="CC BY-SA 4.0", source=f"wikipedia:en:{t}")
|
|
98
|
+
) # fmt: skip
|
|
99
|
+
return rows
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def build_multilingual(client) -> list[dict]:
|
|
103
|
+
rows = []
|
|
104
|
+
min_chars = {"es": 1800, "hi": 1000, "zh": 500, "ar": 1000}
|
|
105
|
+
for li, lang in enumerate(["hi", "zh", "es", "ar"]):
|
|
106
|
+
for t, text in _wiki_pick(client, lang, LANG_COUNT[lang], min_chars[lang],
|
|
107
|
+
start=500 + li * 97, step=211): # fmt: skip
|
|
108
|
+
rows.append(
|
|
109
|
+
dict(group="multilingual", kind="text", lang=lang,
|
|
110
|
+
text=_clip(text, LANG_CHARS[lang]), license="CC BY-SA 4.0",
|
|
111
|
+
source=f"wikipedia:{lang}:{t}")
|
|
112
|
+
) # fmt: skip
|
|
113
|
+
return rows
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def build_math(client) -> list[dict]:
|
|
117
|
+
data = _get_json(
|
|
118
|
+
client,
|
|
119
|
+
"https://datasets-server.huggingface.co/rows",
|
|
120
|
+
{"dataset": "openai/gsm8k", "config": "main", "split": "train", "offset": 0, "length": 100},
|
|
121
|
+
)
|
|
122
|
+
rows = []
|
|
123
|
+
for item in data["rows"]:
|
|
124
|
+
r = item["row"]
|
|
125
|
+
answer = re.sub(r"<<[^>]*>>", "", r["answer"])
|
|
126
|
+
answer = answer.replace("####", "The answer is")
|
|
127
|
+
text = f"Question: {r['question']}\nLet's think step by step.\n{answer}"
|
|
128
|
+
rows.append(
|
|
129
|
+
dict(group="math", kind="text", lang="en", text=text, license="MIT",
|
|
130
|
+
source=f"gsm8k:train:{item['row_idx']}")
|
|
131
|
+
) # fmt: skip
|
|
132
|
+
if len(rows) == GROUP_SIZES["math"]:
|
|
133
|
+
break
|
|
134
|
+
return rows
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def build_chat(client) -> list[dict]:
|
|
138
|
+
rows = []
|
|
139
|
+
for offset in range(0, 1000, 100):
|
|
140
|
+
data = _get_json(
|
|
141
|
+
client,
|
|
142
|
+
"https://datasets-server.huggingface.co/rows",
|
|
143
|
+
{"dataset": "databricks/databricks-dolly-15k", "config": "default", "split": "train",
|
|
144
|
+
"offset": offset, "length": 100},
|
|
145
|
+
) # fmt: skip
|
|
146
|
+
for item in data["rows"]:
|
|
147
|
+
r = item["row"]
|
|
148
|
+
if r.get("context"):
|
|
149
|
+
continue
|
|
150
|
+
if not (250 <= len(r["response"]) <= 1500) or len(r["instruction"]) > 400:
|
|
151
|
+
continue
|
|
152
|
+
rows.append(
|
|
153
|
+
dict(group="chat", kind="chat", lang="en",
|
|
154
|
+
messages=[{"role": "user", "content": r["instruction"].strip()},
|
|
155
|
+
{"role": "assistant", "content": r["response"].strip()}],
|
|
156
|
+
license="CC BY-SA 3.0", source=f"dolly-15k:{item['row_idx']}")
|
|
157
|
+
) # fmt: skip
|
|
158
|
+
if len(rows) == GROUP_SIZES["chat"]:
|
|
159
|
+
return rows
|
|
160
|
+
return rows
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# Hand-written snippets in other languages (own work, Apache-2.0).
|
|
164
|
+
OTHER_CODE = [
|
|
165
|
+
(
|
|
166
|
+
"javascript",
|
|
167
|
+
"""// Debounce: run `fn` only after `ms` milliseconds of silence.
|
|
168
|
+
function debounce(fn, ms) {
|
|
169
|
+
let timer = null;
|
|
170
|
+
return function (...args) {
|
|
171
|
+
clearTimeout(timer);
|
|
172
|
+
timer = setTimeout(() => fn.apply(this, args), ms);
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const onResize = debounce(() => {
|
|
177
|
+
console.log("window size", window.innerWidth, window.innerHeight);
|
|
178
|
+
}, 200);
|
|
179
|
+
window.addEventListener("resize", onResize);
|
|
180
|
+
""",
|
|
181
|
+
),
|
|
182
|
+
(
|
|
183
|
+
"c",
|
|
184
|
+
"""#include <stdio.h>
|
|
185
|
+
#include <stdlib.h>
|
|
186
|
+
|
|
187
|
+
typedef struct Node {
|
|
188
|
+
int value;
|
|
189
|
+
struct Node *next;
|
|
190
|
+
} Node;
|
|
191
|
+
|
|
192
|
+
Node *push(Node *head, int value) {
|
|
193
|
+
Node *n = malloc(sizeof *n);
|
|
194
|
+
if (!n) return head;
|
|
195
|
+
n->value = value;
|
|
196
|
+
n->next = head;
|
|
197
|
+
return n;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
int main(void) {
|
|
201
|
+
Node *list = NULL;
|
|
202
|
+
for (int i = 0; i < 5; i++) list = push(list, i * i);
|
|
203
|
+
for (Node *p = list; p; p = p->next) printf("%d\\n", p->value);
|
|
204
|
+
while (list) { Node *next = list->next; free(list); list = next; }
|
|
205
|
+
return 0;
|
|
206
|
+
}
|
|
207
|
+
""",
|
|
208
|
+
),
|
|
209
|
+
(
|
|
210
|
+
"rust",
|
|
211
|
+
"""use std::collections::HashMap;
|
|
212
|
+
|
|
213
|
+
fn word_counts(text: &str) -> HashMap<String, usize> {
|
|
214
|
+
let mut counts = HashMap::new();
|
|
215
|
+
for word in text.split_whitespace() {
|
|
216
|
+
let key = word.trim_matches(|c: char| !c.is_alphanumeric()).to_lowercase();
|
|
217
|
+
if !key.is_empty() {
|
|
218
|
+
*counts.entry(key).or_insert(0) += 1;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
counts
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
fn main() {
|
|
225
|
+
let counts = word_counts("the quick brown fox jumps over the lazy dog the end");
|
|
226
|
+
let mut pairs: Vec<_> = counts.into_iter().collect();
|
|
227
|
+
pairs.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
|
|
228
|
+
for (word, n) in pairs.iter().take(3) {
|
|
229
|
+
println!("{word}: {n}");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
""",
|
|
233
|
+
),
|
|
234
|
+
(
|
|
235
|
+
"sql",
|
|
236
|
+
"""-- Top 5 customers by total spend in 2025, with their order counts.
|
|
237
|
+
SELECT c.id,
|
|
238
|
+
c.name,
|
|
239
|
+
COUNT(o.id) AS orders,
|
|
240
|
+
SUM(o.total_cents) / 100.0 AS spend
|
|
241
|
+
FROM customers AS c
|
|
242
|
+
JOIN orders AS o ON o.customer_id = c.id
|
|
243
|
+
WHERE o.created_at >= '2025-01-01'
|
|
244
|
+
AND o.created_at < '2026-01-01'
|
|
245
|
+
AND o.status = 'paid'
|
|
246
|
+
GROUP BY c.id, c.name
|
|
247
|
+
HAVING SUM(o.total_cents) > 0
|
|
248
|
+
ORDER BY spend DESC
|
|
249
|
+
LIMIT 5;
|
|
250
|
+
""",
|
|
251
|
+
),
|
|
252
|
+
(
|
|
253
|
+
"go",
|
|
254
|
+
"""package main
|
|
255
|
+
|
|
256
|
+
import (
|
|
257
|
+
"fmt"
|
|
258
|
+
"sync"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
func main() {
|
|
262
|
+
var wg sync.WaitGroup
|
|
263
|
+
results := make(chan int, 10)
|
|
264
|
+
for i := 1; i <= 10; i++ {
|
|
265
|
+
wg.Add(1)
|
|
266
|
+
go func(n int) {
|
|
267
|
+
defer wg.Done()
|
|
268
|
+
results <- n * n
|
|
269
|
+
}(i)
|
|
270
|
+
}
|
|
271
|
+
wg.Wait()
|
|
272
|
+
close(results)
|
|
273
|
+
sum := 0
|
|
274
|
+
for r := range results {
|
|
275
|
+
sum += r
|
|
276
|
+
}
|
|
277
|
+
fmt.Println("sum of squares:", sum)
|
|
278
|
+
}
|
|
279
|
+
""",
|
|
280
|
+
),
|
|
281
|
+
(
|
|
282
|
+
"bash",
|
|
283
|
+
"""#!/usr/bin/env bash
|
|
284
|
+
set -euo pipefail
|
|
285
|
+
|
|
286
|
+
# Keep only the newest N backups in a folder.
|
|
287
|
+
dir="${1:?usage: prune.sh DIR [KEEP]}"
|
|
288
|
+
keep="${2:-7}"
|
|
289
|
+
|
|
290
|
+
mapfile -t files < <(ls -1t "$dir"/*.tar.gz 2>/dev/null || true)
|
|
291
|
+
if (( ${#files[@]} > keep )); then
|
|
292
|
+
for f in "${files[@]:keep}"; do
|
|
293
|
+
echo "removing $f"
|
|
294
|
+
rm -- "$f"
|
|
295
|
+
done
|
|
296
|
+
fi
|
|
297
|
+
""",
|
|
298
|
+
),
|
|
299
|
+
]
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _python_chunks(src_root: Path, n: int) -> list[tuple[str, str]]:
|
|
303
|
+
"""Chunks of this project's own Python code (Apache-2.0), cut at blank lines."""
|
|
304
|
+
skip = {"probes", "__pycache__"}
|
|
305
|
+
files = sorted(
|
|
306
|
+
p for p in src_root.rglob("*.py")
|
|
307
|
+
if not (set(p.relative_to(src_root).parts) & skip) and p.stat().st_size > 1500
|
|
308
|
+
) # fmt: skip
|
|
309
|
+
chunks: list[tuple[str, str]] = []
|
|
310
|
+
for p in files:
|
|
311
|
+
text = p.read_text(encoding="utf-8")
|
|
312
|
+
blocks = text.split("\n\n\n")
|
|
313
|
+
cur = ""
|
|
314
|
+
for b in blocks:
|
|
315
|
+
if len(cur) + len(b) > 1800 and len(cur) > 600:
|
|
316
|
+
chunks.append(
|
|
317
|
+
(f"quantgap:{p.relative_to(src_root).as_posix()}", cur.strip() + "\n")
|
|
318
|
+
)
|
|
319
|
+
cur = ""
|
|
320
|
+
cur += b + "\n\n\n"
|
|
321
|
+
if len(cur) > 600:
|
|
322
|
+
chunks.append((f"quantgap:{p.relative_to(src_root).as_posix()}", cur.strip() + "\n"))
|
|
323
|
+
# Spread picks across files so the group is varied.
|
|
324
|
+
rng = random.Random(0)
|
|
325
|
+
rng.shuffle(chunks)
|
|
326
|
+
return chunks[:n]
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def build_code(src_root: Path) -> list[dict]:
|
|
330
|
+
rows = []
|
|
331
|
+
for lang, text in OTHER_CODE:
|
|
332
|
+
rows.append(
|
|
333
|
+
dict(group="code", kind="text", lang=lang, text=text, license="Apache-2.0",
|
|
334
|
+
source=f"handwritten:{lang}")
|
|
335
|
+
) # fmt: skip
|
|
336
|
+
for src, text in _python_chunks(src_root, GROUP_SIZES["code"] - len(OTHER_CODE)):
|
|
337
|
+
rows.append(
|
|
338
|
+
dict(group="code", kind="text", lang="python", text=text, license="Apache-2.0",
|
|
339
|
+
source=src)
|
|
340
|
+
) # fmt: skip
|
|
341
|
+
return rows
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
_NAMES = ["Asha", "Mateo", "Li Wei", "Fatima", "Noah", "Priya", "Lucas", "Amara", "Hiro", "Sofia",
|
|
345
|
+
"Omar", "Ingrid"] # fmt: skip
|
|
346
|
+
_CITIES = ["Pune", "Lagos", "Oslo", "Lima", "Osaka", "Cairo", "Porto", "Quito", "Hanoi", "Leeds",
|
|
347
|
+
"Accra", "Turin"] # fmt: skip
|
|
348
|
+
_ITEMS = ["notebook", "kettle", "backpack", "lamp", "headphones", "water bottle", "desk fan",
|
|
349
|
+
"keyboard", "umbrella", "camera strap", "tea set", "yoga mat"] # fmt: skip
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def build_json() -> list[dict]:
|
|
353
|
+
rng = random.Random(7)
|
|
354
|
+
rows = []
|
|
355
|
+
for i in range(GROUP_SIZES["json"]):
|
|
356
|
+
name, city, item = _NAMES[i], _CITIES[i], _ITEMS[i]
|
|
357
|
+
kind = i % 4
|
|
358
|
+
if kind == 0:
|
|
359
|
+
instr = (
|
|
360
|
+
f"Return a JSON object describing the person {name} who lives in {city}. "
|
|
361
|
+
"Use the keys name, city, age and hobbies. Output only the JSON."
|
|
362
|
+
)
|
|
363
|
+
obj = {
|
|
364
|
+
"name": name,
|
|
365
|
+
"city": city,
|
|
366
|
+
"age": rng.randint(19, 64),
|
|
367
|
+
"hobbies": rng.sample(["chess", "cycling", "cooking", "painting", "hiking"], 2),
|
|
368
|
+
}
|
|
369
|
+
elif kind == 1:
|
|
370
|
+
instr = (
|
|
371
|
+
f"Return a JSON object for an order of one {item} shipped to {city}. "
|
|
372
|
+
"Use the keys order_id, item, quantity, price and shipping. Output only the JSON."
|
|
373
|
+
)
|
|
374
|
+
obj = {
|
|
375
|
+
"order_id": f"A-{rng.randint(1000, 9999)}",
|
|
376
|
+
"item": item,
|
|
377
|
+
"quantity": rng.randint(1, 4),
|
|
378
|
+
"price": round(rng.uniform(5, 90), 2),
|
|
379
|
+
"shipping": {"city": city, "express": bool(rng.getrandbits(1))},
|
|
380
|
+
}
|
|
381
|
+
elif kind == 2:
|
|
382
|
+
instr = (
|
|
383
|
+
f"Return a JSON object with a list of three tasks for {name}. Each task has "
|
|
384
|
+
"the keys title, done and priority. Output only the JSON."
|
|
385
|
+
)
|
|
386
|
+
obj = {
|
|
387
|
+
"owner": name,
|
|
388
|
+
"tasks": [
|
|
389
|
+
{"title": t, "done": bool(rng.getrandbits(1)), "priority": rng.randint(1, 3)}
|
|
390
|
+
for t in rng.sample(
|
|
391
|
+
[
|
|
392
|
+
"write report",
|
|
393
|
+
"book tickets",
|
|
394
|
+
"call plumber",
|
|
395
|
+
"buy milk",
|
|
396
|
+
"fix bike",
|
|
397
|
+
"renew passport",
|
|
398
|
+
],
|
|
399
|
+
3,
|
|
400
|
+
)
|
|
401
|
+
],
|
|
402
|
+
}
|
|
403
|
+
else:
|
|
404
|
+
instr = (
|
|
405
|
+
f"Return a JSON object for the weather in {city} with the keys city, "
|
|
406
|
+
"temperature_c, conditions and wind. wind has speed_kmh and direction. "
|
|
407
|
+
"Output only the JSON."
|
|
408
|
+
)
|
|
409
|
+
obj = {
|
|
410
|
+
"city": city,
|
|
411
|
+
"temperature_c": rng.randint(-5, 38),
|
|
412
|
+
"conditions": rng.choice(["sunny", "cloudy", "rain", "fog"]),
|
|
413
|
+
"wind": {
|
|
414
|
+
"speed_kmh": rng.randint(0, 40),
|
|
415
|
+
"direction": rng.choice(["N", "NE", "E", "SE", "S", "SW", "W", "NW"]),
|
|
416
|
+
},
|
|
417
|
+
}
|
|
418
|
+
text = instr + "\n" + json.dumps(obj, indent=2)
|
|
419
|
+
rows.append(
|
|
420
|
+
dict(group="json", kind="text", lang="en", text=text, license="CC0-1.0",
|
|
421
|
+
source="generated:build.py", greedy_prompt=instr + "\n")
|
|
422
|
+
) # fmt: skip
|
|
423
|
+
return rows
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def build(src_root: Path) -> list[dict]:
|
|
427
|
+
with httpx.Client(follow_redirects=True) as client:
|
|
428
|
+
groups = {
|
|
429
|
+
"general": build_general(client),
|
|
430
|
+
"chat": build_chat(client),
|
|
431
|
+
"code": build_code(src_root),
|
|
432
|
+
"math": build_math(client),
|
|
433
|
+
"multilingual": build_multilingual(client),
|
|
434
|
+
"json": build_json(),
|
|
435
|
+
}
|
|
436
|
+
rows: list[dict] = []
|
|
437
|
+
for g, items in groups.items():
|
|
438
|
+
for j, row in enumerate(items):
|
|
439
|
+
row["greedy"] = j < GREEDY_PER_GROUP
|
|
440
|
+
row.setdefault("greedy_prompt", None)
|
|
441
|
+
row.setdefault("text", None)
|
|
442
|
+
row.setdefault("messages", None)
|
|
443
|
+
row["seq_id"] = len(rows)
|
|
444
|
+
rows.append(row)
|
|
445
|
+
print(f"{g}: {len(items)} rows")
|
|
446
|
+
return rows
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def write(rows: list[dict], out: Path) -> None:
|
|
450
|
+
keys = ["seq_id", "group", "kind", "lang", "text", "messages", "license", "source", "greedy",
|
|
451
|
+
"greedy_prompt"] # fmt: skip
|
|
452
|
+
with open(out, "w", encoding="utf-8", newline="\n") as f:
|
|
453
|
+
for r in rows:
|
|
454
|
+
f.write(json.dumps({k: r[k] for k in keys}, ensure_ascii=False) + "\n")
|
|
455
|
+
# SOURCES.md table
|
|
456
|
+
lines = ["# Probe set v1 sources", "",
|
|
457
|
+
"Every row of `probes_v1.jsonl` records its license and source. Summary:", "",
|
|
458
|
+
"| Group | Source | License | Rows |", "|---|---|---|---|"] # fmt: skip
|
|
459
|
+
summary: dict[tuple[str, str, str], int] = {}
|
|
460
|
+
for r in rows:
|
|
461
|
+
src = r["source"].split(":")[0]
|
|
462
|
+
summary[(r["group"], src, r["license"])] = (
|
|
463
|
+
summary.get((r["group"], src, r["license"]), 0) + 1
|
|
464
|
+
)
|
|
465
|
+
for (g, s, lic), n in summary.items():
|
|
466
|
+
lines.append(f"| {g} | {s} | {lic} | {n} |")
|
|
467
|
+
lines += ["", "Wikipedia text is CC BY-SA 4.0 (author credit: the article editors, see the page "
|
|
468
|
+
"history of each article named in `source`). Dolly-15k is CC BY-SA 3.0. GSM8K is MIT. "
|
|
469
|
+
"Code and JSON rows are Apache-2.0 / CC0 (written for this project)."] # fmt: skip
|
|
470
|
+
(out.parent / "SOURCES.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def main() -> None:
|
|
474
|
+
ap = argparse.ArgumentParser()
|
|
475
|
+
ap.add_argument("--out", default="src/quantgap/probes/data/probes_v1.jsonl")
|
|
476
|
+
ap.add_argument("--src-root", default="src/quantgap")
|
|
477
|
+
a = ap.parse_args()
|
|
478
|
+
rows = build(Path(a.src_root))
|
|
479
|
+
write(rows, Path(a.out))
|
|
480
|
+
print(f"wrote {len(rows)} rows to {a.out}")
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
if __name__ == "__main__":
|
|
484
|
+
main()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Probe set v1 sources
|
|
2
|
+
|
|
3
|
+
Every row of `probes_v1.jsonl` records its license and source. Summary:
|
|
4
|
+
|
|
5
|
+
| Group | Source | License | Rows |
|
|
6
|
+
|---|---|---|---|
|
|
7
|
+
| general | wikipedia | CC BY-SA 4.0 | 30 |
|
|
8
|
+
| chat | dolly-15k | CC BY-SA 3.0 | 30 |
|
|
9
|
+
| code | handwritten | Apache-2.0 | 6 |
|
|
10
|
+
| code | quantgap | Apache-2.0 | 22 |
|
|
11
|
+
| math | gsm8k | MIT | 28 |
|
|
12
|
+
| multilingual | wikipedia | CC BY-SA 4.0 | 32 |
|
|
13
|
+
| json | generated | CC0-1.0 | 12 |
|
|
14
|
+
|
|
15
|
+
Wikipedia text is CC BY-SA 4.0 (author credit: the article editors, see the page history of each article named in `source`). Dolly-15k is CC BY-SA 3.0. GSM8K is MIT. Code and JSON rows are Apache-2.0 / CC0 (written for this project).
|