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.
@@ -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).