quantprobe 1.6.0__tar.gz → 1.6.2__tar.gz

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.
Files changed (28) hide show
  1. {quantprobe-1.6.0/quantprobe.egg-info → quantprobe-1.6.2}/PKG-INFO +1 -1
  2. {quantprobe-1.6.0 → quantprobe-1.6.2}/pyproject.toml +1 -1
  3. {quantprobe-1.6.0 → quantprobe-1.6.2}/quantprobe/__init__.py +1 -1
  4. quantprobe-1.6.2/quantprobe/__main__.py +9 -0
  5. {quantprobe-1.6.0 → quantprobe-1.6.2}/quantprobe/auto.py +59 -11
  6. {quantprobe-1.6.0 → quantprobe-1.6.2}/quantprobe/cli.py +3 -3
  7. {quantprobe-1.6.0 → quantprobe-1.6.2}/quantprobe/optimize.py +6 -5
  8. {quantprobe-1.6.0 → quantprobe-1.6.2}/quantprobe/plan.py +5 -1
  9. {quantprobe-1.6.0 → quantprobe-1.6.2/quantprobe.egg-info}/PKG-INFO +1 -1
  10. {quantprobe-1.6.0 → quantprobe-1.6.2}/quantprobe.egg-info/SOURCES.txt +1 -0
  11. {quantprobe-1.6.0 → quantprobe-1.6.2}/LICENSE +0 -0
  12. {quantprobe-1.6.0 → quantprobe-1.6.2}/README.md +0 -0
  13. {quantprobe-1.6.0 → quantprobe-1.6.2}/quantprobe/dashboard.py +0 -0
  14. {quantprobe-1.6.0 → quantprobe-1.6.2}/quantprobe/detect.py +0 -0
  15. {quantprobe-1.6.0 → quantprobe-1.6.2}/quantprobe/fetch.py +0 -0
  16. {quantprobe-1.6.0 → quantprobe-1.6.2}/quantprobe/probe.py +0 -0
  17. {quantprobe-1.6.0 → quantprobe-1.6.2}/quantprobe/runtime.py +0 -0
  18. {quantprobe-1.6.0 → quantprobe-1.6.2}/quantprobe/spec.py +0 -0
  19. {quantprobe-1.6.0 → quantprobe-1.6.2}/quantprobe/target.py +0 -0
  20. {quantprobe-1.6.0 → quantprobe-1.6.2}/quantprobe.egg-info/dependency_links.txt +0 -0
  21. {quantprobe-1.6.0 → quantprobe-1.6.2}/quantprobe.egg-info/entry_points.txt +0 -0
  22. {quantprobe-1.6.0 → quantprobe-1.6.2}/quantprobe.egg-info/requires.txt +0 -0
  23. {quantprobe-1.6.0 → quantprobe-1.6.2}/quantprobe.egg-info/top_level.txt +0 -0
  24. {quantprobe-1.6.0 → quantprobe-1.6.2}/setup.cfg +0 -0
  25. {quantprobe-1.6.0 → quantprobe-1.6.2}/tests/test_engine.py +0 -0
  26. {quantprobe-1.6.0 → quantprobe-1.6.2}/tests/test_evaluator.py +0 -0
  27. {quantprobe-1.6.0 → quantprobe-1.6.2}/tests/test_pipeline.py +0 -0
  28. {quantprobe-1.6.0 → quantprobe-1.6.2}/tests/test_transforms.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quantprobe
3
- Version: 1.6.0
3
+ Version: 1.6.2
4
4
  Summary: Probe-then-quantize for LLMs: measure a model's fragility curve, plan bit/tier placement by the tiered decode law, and emit ready-to-run llama.cpp recipes.
5
5
  Author: Federico Sciuca
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "quantprobe"
7
- version = "1.6.0"
7
+ version = "1.6.2"
8
8
  description = "Probe-then-quantize for LLMs: measure a model's fragility curve, plan bit/tier placement by the tiered decode law, and emit ready-to-run llama.cpp recipes."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -8,4 +8,4 @@ Three commands, all grounded in measured laws (see LAWS.md in the repo):
8
8
  Every constant in `plan` is fitted from measurements published in the repo, including two
9
9
  pre-registered hardware predictions confirmed within 8%.
10
10
  """
11
- __version__ = "1.6.0"
11
+ __version__ = "1.6.2"
@@ -0,0 +1,9 @@
1
+ """`python -m quantprobe` — identical to the `quantprobe` console script.
2
+
3
+ The PATH-proof entry point: on Windows, `pip install` often lands the .exe in a
4
+ user-site Scripts folder that is not on PATH; this always works regardless.
5
+ """
6
+ from .cli import main
7
+
8
+ if __name__ == "__main__":
9
+ main()
@@ -24,17 +24,51 @@ MODEL_REPOS = {
24
24
  "laguna-s": ("unsloth/Laguna-S-2.1-GGUF", 117.6, 8, 2.5, True),
25
25
  "gemma-12b": ("unsloth/gemma-4-12b-it-GGUF", 11.9, 11.9, 11.9, False),
26
26
  "mistral-7b": ("unsloth/Mistral-7B-Instruct-v0.3-GGUF", 7.2, 7.2, 7.2, False),
27
+ "qwen3-235b": ("unsloth/Qwen3-235B-A22B-GGUF", 235.1, 22, 7.5, True), # total exact (HF safetensors)
28
+ "glm-4.7": ("unsloth/GLM-4.7-GGUF", 358.3, 32, 8, True), # total exact; a/ne [est]
29
+ "glm-744b": ("unsloth/GLM-5.2-GGUF", 753.3, 32, 8, True), # GLM-5.2, total exact 753.3B; a/ne [est]
30
+ "kimi-k2.6": ("unsloth/Kimi-K2.6-GGUF", 1058.6, 32, 6, True), # total exact; a/ne [est]
31
+ "gpt-oss-120b": ("unsloth/gpt-oss-120b-GGUF", 120.4, 5.1, 1.8, True), # total exact, A5.1B official
32
+ "llama-70b": ("unsloth/Llama-3.3-70B-Instruct-GGUF", 70.6, 70.6, 70.6, False),
33
+ "deepseek-16b": ("bartowski/DeepSeek-Coder-V2-Lite-Instruct-GGUF", 15.7, 2.4, 1.3, True),
27
34
  }
28
35
 
29
36
 
30
37
  def list_ggufs(repo):
31
- """[(path, size_bytes)] for a HF repo, via the public tree API."""
32
- req = urllib.request.Request(f"https://huggingface.co/api/models/{repo}/tree/main",
38
+ """[(path, size_bytes)] for a HF repo, via the public tree API (recursive: big repos
39
+ keep quants in subfolders). Split multi-part files (-00001-of-000NN) are grouped into
40
+ ONE logical entry: path = first part, size = sum of parts — so effective-bits stays honest."""
41
+ import re
42
+ req = urllib.request.Request(f"https://huggingface.co/api/models/{repo}/tree/main?recursive=true",
33
43
  headers={"User-Agent": "quantprobe-auto"})
34
44
  with urllib.request.urlopen(req, timeout=30) as r:
35
- return [(f["path"], f.get("size", 0)) for f in json.load(r)
36
- if f["path"].endswith(".gguf") and f.get("size", 0) > 1e8
37
- and "mmproj" not in f["path"].lower() and "draft" not in f["path"].lower()]
45
+ raw = [(f["path"], f.get("size", 0)) for f in json.load(r)
46
+ if f["path"].endswith(".gguf") and f.get("size", 0) > 1e8
47
+ and "mmproj" not in f["path"].lower() and "draft" not in f["path"].lower()]
48
+ groups, singles = {}, []
49
+ for path, size in raw:
50
+ m = re.match(r"^(.*)-(\d{5})-of-(\d{5})\.gguf$", path)
51
+ if m:
52
+ g = groups.setdefault(m.group(1), {"total": 0, "first": None, "n": int(m.group(3))})
53
+ g["total"] += size
54
+ if m.group(2) == "00001":
55
+ g["first"] = path
56
+ else:
57
+ singles.append((path, size))
58
+ for g in groups.values():
59
+ if g["first"]:
60
+ singles.append((g["first"], g["total"]))
61
+ return singles
62
+
63
+
64
+ def split_parts(first_part):
65
+ """All part paths of a split file, derived from the -00001-of-000NN first part."""
66
+ import re
67
+ m = re.match(r"^(.*)-(\d{5})-of-(\d{5})\.gguf$", first_part)
68
+ if not m:
69
+ return [first_part]
70
+ base, n = m.group(1), int(m.group(3))
71
+ return [f"{base}-{i:05d}-of-{n:05d}.gguf" for i in range(1, n + 1)]
38
72
 
39
73
 
40
74
  WIKI_URL = "https://huggingface.co/datasets/ggml-org/ci/resolve/main/wikitext-2-raw-v1.zip"
@@ -136,9 +170,19 @@ def run(a):
136
170
  continue
137
171
  scored.append((abs(bits - want_bits), -cfgs[0][1], path, size, bits, cfgs[0]))
138
172
  if not scored:
139
- raise SystemExit("no usable quant in that repo for this machine")
173
+ avail = sorted(set(round(s * 8 / (t * 1e9), 1) for _, s in files))
174
+ raise SystemExit(
175
+ f"no ready-to-run quant in {repo} for this machine yet (available bit-levels: {avail}).\n"
176
+ " Big new models often publish low-bit quants days after release. Meanwhile:\n"
177
+ " - predictions for any bits now: quantprobe plan --model <preset> --bits 2.5\n"
178
+ + (" - or build your own from the high-precision source: re-run with --custom\n"
179
+ if pick_source(files, t) else ""))
140
180
  scored.sort()
141
181
  _, _, path, size, bits, best = scored[0]
182
+ if abs(bits - want_bits) > 2:
183
+ print(f"\n[quantprobe auto] note: no ~{want_bits:g}-bit-class file in {repo} yet - closest "
184
+ f"available is {bits:.1f}-bit. Low-bit quants for new models often land days after "
185
+ f"release; plan/optimize predict any bits meanwhile.")
142
186
 
143
187
  if getattr(a, "custom", False) and want_bits >= 3.5 and not getattr(a, "force_custom", False):
144
188
  print(f"\n[quantprobe auto --custom] this machine doesn't need the surgery: the optimizer")
@@ -179,20 +223,24 @@ def run(a):
179
223
  a.gguf = out; a.bits = None
180
224
  runtime.run(a)
181
225
  return out
226
+ parts = split_parts(path)
182
227
  print(f"\n[quantprobe auto] optimizer wants ~{want_bits:g}-bit; closest file in {repo}:")
183
- print(f" {path} ({size/1e9:.1f} GB, {bits:.2f} effective bits)")
228
+ print(f" {path} ({size/1e9:.1f} GB, {bits:.2f} effective bits"
229
+ + (f", {len(parts)} parts" if len(parts) > 1 else "") + ")")
184
230
  print(f" predicted on this machine: {best[1]:.1f} tok/s ({best[0]})")
185
231
  if getattr(a, "dry", False):
186
232
  print(" (--dry: nothing downloaded)")
187
233
  return path
188
- # 3. fetch it (resumable), then hand off
234
+ # 3. fetch it (resumable; all parts when split), then hand off
189
235
  from . import fetch as fetchmod
190
236
  dest = getattr(a, "dir", None) or "./models"
191
237
  import os
192
238
  os.makedirs(dest, exist_ok=True)
193
- ok = fetchmod.fetch(repo, dest, path, fetchmod.token())
194
- if not ok:
195
- raise SystemExit("download failed (it resumes: re-run the same command)")
239
+ for i, part in enumerate(parts):
240
+ if len(parts) > 1:
241
+ print(f"[quantprobe auto] part {i+1}/{len(parts)}: {os.path.basename(part)}")
242
+ if not fetchmod.fetch(repo, dest, part, fetchmod.token()):
243
+ raise SystemExit("download failed (it resumes: re-run the same command)")
196
244
  full = os.path.join(dest, os.path.basename(path))
197
245
  print(f"\n[quantprobe auto] ready. Run it:")
198
246
  print(f" quantprobe run --gguf {full}")
@@ -2,7 +2,7 @@
2
2
  from __future__ import annotations
3
3
  import argparse
4
4
 
5
- from .plan import numlist
5
+ from .plan import numlist, MODELS as _MODELS
6
6
 
7
7
 
8
8
  def main():
@@ -30,7 +30,7 @@ def main():
30
30
  p.add_argument("--dry-run", action="store_true")
31
31
 
32
32
  q = sub.add_parser("plan", help="evaluate every bit/tier placement, predict tok/s (Law 4)")
33
- q.add_argument("--model", default=None, help="preset: qwen3-30b deepseek-16b gemma-12b mistral-7b glm-air glm-744b")
33
+ q.add_argument("--model", default=None, help="preset: " + " ".join(sorted(_MODELS)))
34
34
  q.add_argument("--machine", default=None, help="preset: 2016-xmp 2016 gaming ddr5 colibri")
35
35
  q.add_argument("--bits", type=float, default=None, help="effective bits/weight (default: read from --gguf, else 2.5)")
36
36
  q.add_argument("--total", type=float, help="total params (B)")
@@ -93,7 +93,7 @@ def main():
93
93
 
94
94
  au = sub.add_parser("auto", help="ONE command: model in, running setup out - optimizer picks the bits, the closest quant is fetched, run command printed")
95
95
  au.add_argument("target", nargs="?", default=None,
96
- help="model preset (qwen3-30b, qwen3-coder, glm-air, laguna-s, gemma-12b, mistral-7b) or a HF GGUF repo id; omit it and quantprobe ASKS (interactive)")
96
+ help="model preset (qwen3-30b, qwen3-coder, glm-air, laguna-s, gemma-12b, mistral-7b, qwen3-235b, glm-4.7, glm-744b, kimi-k2.6, gpt-oss-120b, llama-70b, deepseek-16b) or a HF GGUF repo id; omit it and quantprobe ASKS (interactive)")
97
97
  au.add_argument("--dir", default="./models", help="download directory (default ./models)")
98
98
  au.add_argument("--run", action="store_true", help="launch chat immediately after the download")
99
99
  hwargs(au)
@@ -13,7 +13,8 @@ Levers and their measured gates:
13
13
  prune REAP-class 50% expert pruning (total x0.82, file shrinks, active bytes UNCHANGED).
14
14
  Measured +39% out-of-domain perplexity (pre-registration #8) — domain-specialized,
15
15
  never ranked first without --allow-prune.
16
- hardware euro-priced deltas from the projections table: XMP (free), +16GB RAM (~40), NVMe (~100).
16
+ hardware upgrade deltas from the projections table: XMP (free), +16GB RAM, NVMe. Ranked by
17
+ relative cost internally; no currency figures are shown (region- and time-dependent).
17
18
  """
18
19
  from __future__ import annotations
19
20
  from . import plan as planmod
@@ -28,9 +29,9 @@ REALIZE = {
28
29
  }
29
30
  HW_DELTAS = [
30
31
  ("as-is", 0, {}),
31
- ("+16GB RAM (~40 EUR used)", 40, {"rc": +16}),
32
- ("NVMe SSD (~100 EUR)", 100, {"db_min": 3.5}),
33
- ("+RAM & NVMe (~140 EUR)", 140, {"rc": +16, "db_min": 3.5}),
32
+ ("+16GB RAM (used)", 40, {"rc": +16}),
33
+ ("NVMe SSD", 100, {"db_min": 3.5}),
34
+ ("+RAM & NVMe", 140, {"rc": +16, "db_min": 3.5}),
34
35
  ]
35
36
 
36
37
 
@@ -121,7 +122,7 @@ def run(a):
121
122
  print(f" {headline}\n")
122
123
  for i, r in enumerate(ranked[:6]):
123
124
  star = "*" if i == 0 else " "
124
- euro = "free" if r["euro"] == 0 else f"~{r['euro']}EUR"
125
+ euro = "free" if r["euro"] == 0 else "hw upgrade"
125
126
  print(f" {star} {r['tps']:6.1f} tok/s quality x{r['q']:.2f} {euro:>7s} {r['desc']}")
126
127
  for tag in r["tags"]:
127
128
  print(f" [{tag}]")
@@ -17,7 +17,11 @@ MODELS = {
17
17
  "gemma-12b": dict(t=11.9, a=11.9, ne=11.9, moe=False, kvp=65536, hint="Gemma 4 12B"), # [est] SWA: long-ctx slope from global layers only
18
18
  "mistral-7b": dict(t=7.2, a=7.2, ne=7.2, moe=False, kvp=131072, hint="Mistral 7B"), # 32L x 8KV x 128d (exact)
19
19
  "glm-air": dict(t=110, a=12, ne=2.7, moe=True, kvp=94208, hint="GLM-4.5-Air 106B"), # [est]
20
- "glm-744b": dict(t=744, a=32, ne=8, moe=True, kvp=188416, hint="GLM-5.2 744B"), # [est]
20
+ "glm-744b": dict(t=753.3, a=32, ne=8, moe=True, kvp=188416, hint="GLM-5.2 (753B)"), # total exact (HF safetensors); a/ne/kvp [est]
21
+ "qwen3-235b": dict(t=235.1, a=22, ne=7.5, moe=True, kvp=192512, hint="Qwen3-235B-A22B"), # total exact; kvp: 94L x 4KV x 128d [est]
22
+ "kimi-k2.6": dict(t=1058.6, a=32, ne=6, moe=True, kvp=70272, hint="Kimi K2.6 (1T MLA)"), # total exact; kvp: 61L x 576 MLA latent [est]
23
+ "gpt-oss-120b": dict(t=120.4, a=5.1, ne=1.8, moe=True, kvp=73728, hint="gpt-oss-120b"), # total+active official; kvp: 36L x 8KV x 64d [est]
24
+ "llama-70b": dict(t=70.6, a=70.6, ne=70.6, moe=False, kvp=327680, hint="Llama-3.3-70B"), # dense; kvp: 80L x 8KV x 128d
21
25
  }
22
26
  DEFAULT_KVP = 98304 # custom models without --kv-per-pos: typical GQA mid-size (Qwen3-30B class)
23
27
  ETA_KV = 0.70 # KV-read efficiency. Single-point calibration: measured tg32 d0->d16384
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quantprobe
3
- Version: 1.6.0
3
+ Version: 1.6.2
4
4
  Summary: Probe-then-quantize for LLMs: measure a model's fragility curve, plan bit/tier placement by the tiered decode law, and emit ready-to-run llama.cpp recipes.
5
5
  Author: Federico Sciuca
6
6
  License: MIT
@@ -2,6 +2,7 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  quantprobe/__init__.py
5
+ quantprobe/__main__.py
5
6
  quantprobe/auto.py
6
7
  quantprobe/cli.py
7
8
  quantprobe/dashboard.py
File without changes
File without changes
File without changes