Soulfate24 commited on
Commit
7ff3e96
·
verified ·
1 Parent(s): c2d9522

## [1.2.3] — 2026-08-20 — Perplexity Suite & 9B Benchmark Data

Browse files

## [1.2.3] — 2026-08-20 — Perplexity Suite & 9B Benchmark Data
# Changelog

All notable changes to the AutoRound + ASHQ1 suite are documented in this file.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

---

### Added
- **`03_perplexity_test.py`**: Automated perplexity evaluation tool wrapping `llama-perplexity`:
- Auto-detection of CUDA/ROCm compute backends and free VRAM.
- Adaptive `-ngl` layer offloading with CPU fallback.
- Automated retrieval and extraction of `wiki.test.raw` (Wikitext-2 reference corpus).
- Real-time chunk progression and ETA streaming.
- Comparative perplexity summary table with Δ relative to baseline.
- **Reference Benchmarks (`README.md`)**: Empirical PPL measurements on Ornith-1.5-9B:
- `Quality-36pc`: **8.0932** (baseline)
- `Compact-33pc`: **8.1290** (+0.0358 Δ)
- `Mini-27pc`: **9.5101** (+1.4169 Δ)

### Documentation
- Updated `USAGE.md` with Step 3 verification workflow.
- Updated toolchain index in `README.md`.

Files changed (3) hide show
  1. 03_perplexity_test.py +240 -0
  2. README.md +17 -0
  3. USAGE.md +12 -0
03_perplexity_test.py ADDED
@@ -0,0 +1,240 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Perplexity sweep over GGUF tiers using llama-perplexity, with optional wiki.test.raw reference corpus."""
3
+ import glob,os,re,shutil,subprocess,sys,threading,time,urllib.request,zipfile
4
+
5
+ if os.name=="nt":os.environ.setdefault("KMP_AFFINITY","disabled")
6
+ os.environ.setdefault("GGML_CUDA_ENABLE_UNIFIED_MEMORY","1")
7
+
8
+ SCRIPT_DIR=os.path.dirname(os.path.abspath(__file__))
9
+ LLAMA_CPP=os.path.join(SCRIPT_DIR,"llama-cpp")
10
+ WIKI_URL="https://huggingface.co/datasets/ggml-org/ci/resolve/main/wikitext-2-raw-v1.zip?download=true"
11
+ WIKI_PATH=os.path.join(SCRIPT_DIR,"wiki.test.raw")
12
+ FALLBACK_TXT=os.path.join(SCRIPT_DIR,"01_create-calibration-dataset-and-imatrix","experimental.txt")
13
+ CHUNKS=64
14
+ CTX=2048
15
+ THREADS=max(1,(os.cpu_count() or 4)-1)
16
+
17
+ def _detect_gpu():
18
+ if sys.platform=="win32" or shutil.which("nvidia-smi"):
19
+ try:
20
+ out=subprocess.check_output(
21
+ ["nvidia-smi","--query-gpu=memory.total,memory.used","--format=csv,noheader,nounits"],
22
+ stderr=subprocess.DEVNULL,text=True,timeout=5
23
+ ).strip().splitlines()[0]
24
+ tot,usd=[int(x.strip()) for x in out.split(",")]
25
+ return "cuda",tot,max(0,tot-usd)
26
+ except Exception:pass
27
+ try:
28
+ out=subprocess.check_output(["rocm-smi","--showmeminfo","vram","--csv"],stderr=subprocess.DEVNULL,text=True,timeout=5)
29
+ m=re.search(r"(\d+)\s*MiB",out)
30
+ if m:
31
+ tot=int(m.group(1))
32
+ return "rocm",tot,tot
33
+ except Exception:pass
34
+ return None,0,0
35
+
36
+ GPU_BACKEND,GPU_TOTAL_VRAM,GPU_FREE_VRAM=_detect_gpu()
37
+
38
+ def _ngl_for_model(model_path,vram_free):
39
+ if vram_free<=0:return 0
40
+ model_mib=os.path.getsize(model_path)/1024/1024
41
+ if model_mib+768<=vram_free:return 99
42
+ headroom=max(0,vram_free-768)
43
+ return max(1,min(99,int(99*(headroom/model_mib))))
44
+
45
+ def find_binary():
46
+ names=("llama-perplexity.exe","llama-perplexity")
47
+ if os.path.isdir(LLAMA_CPP):
48
+ cand=[]
49
+ for root,dirs,files in os.walk(LLAMA_CPP):
50
+ dirs[:]=[d for d in dirs if d.lower() not in(".git","models","sources","vendor")]
51
+ for n in names:
52
+ if n in files:cand.append(os.path.join(root,n))
53
+ if cand:
54
+ def score(p):
55
+ s=p.lower()
56
+ return(0 if"release"in s else 1 if"debug"not in s else 2,len(s))
57
+ cand.sort(key=score)
58
+ return cand[0]
59
+ for n in names:
60
+ p=shutil.which(n)
61
+ if p:return p
62
+ return None
63
+
64
+ def fetch_wiki():
65
+ if os.path.isfile(WIKI_PATH) and os.path.getsize(WIKI_PATH)>1_000_000:
66
+ print(f"[Corpus] Using existing {WIKI_PATH}")
67
+ return WIKI_PATH
68
+ print("[Corpus] Downloading wiki.test.raw …")
69
+ tmp=WIKI_PATH+".zip"
70
+ try:
71
+ req=urllib.request.Request(WIKI_URL,headers={"User-Agent":"perplexity-test/1.0"})
72
+ with urllib.request.urlopen(req,timeout=300) as r,open(tmp,"wb") as f:
73
+ shutil.copyfileobj(r,f)
74
+ with zipfile.ZipFile(tmp) as z:
75
+ member=next((n for n in z.namelist() if n.endswith("wiki.test.raw")),None)
76
+ if member is None:raise ValueError("wiki.test.raw absent from ZIP")
77
+ with z.open(member) as src,open(WIKI_PATH,"wb") as out:
78
+ shutil.copyfileobj(src,out)
79
+ print(f"[Corpus] Saved {WIKI_PATH} ({os.path.getsize(WIKI_PATH)/1024:.0f} KiB)")
80
+ return WIKI_PATH
81
+ except Exception as e:
82
+ for junk in(tmp,WIKI_PATH):
83
+ try:os.remove(junk)
84
+ except OSError:pass
85
+ print(f"[Corpus] ✗ Download failed ({e})")
86
+ if os.path.isfile(FALLBACK_TXT):
87
+ print(f"[Corpus] Falling back to {FALLBACK_TXT} — Δ between tiers stays valid, absolute PPL differs from published wiki benchmarks.")
88
+ return FALLBACK_TXT
89
+ return None
90
+
91
+ def pick_models():
92
+ seen=set();out=[]
93
+ for p in sorted(glob.glob(os.path.join(SCRIPT_DIR,"*.gguf"))):
94
+ if p in seen:continue
95
+ seen.add(p)
96
+ bn=os.path.basename(p).lower()
97
+ if any(bn.startswith(x) for x in("mmproj","imatrix")) or any(x in bn for x in("-bench","_bench",".tmp",".bak")):continue
98
+ out.append(p)
99
+ return out
100
+
101
+ def menu(models):
102
+ print("\nAvailable models:")
103
+ for i,p in enumerate(models,1):
104
+ mib=os.path.getsize(p)/1024/1024
105
+ ngl=_ngl_for_model(p,GPU_FREE_VRAM)
106
+ tag=f"ngl={ngl}" if ngl>0 else "CPU"
107
+ print(f" {i:2d}. {os.path.basename(p):<48} ({mib:5.0f} MiB · {tag})")
108
+ print(" a. ALL · q. quit")
109
+ while True:
110
+ choice=input("Select model(s) [1/a/1,3,5/q]: ").strip().lower()
111
+ if choice in("q","quit",""):return None
112
+ if choice=="a":return models
113
+ try:
114
+ idx=[int(x) for x in re.split(r"[,\s]+",choice) if x]
115
+ if idx and all(1<=i<=len(models) for i in idx):
116
+ return [models[i-1] for i in dict.fromkeys(idx)]
117
+ except ValueError:pass
118
+ print(" Invalid selection.")
119
+
120
+ def parse_output(text):
121
+ ppl=tps=None
122
+ m=re.search(r"Final estimate:\s*PPL\s*=\s*([\d.]+)",text,re.I)
123
+ if not m:m=re.search(r"final estimate of PPL:\s*([\d.]+)",text,re.I)
124
+ if not m:m=re.search(r"^\s*ppl\s*=\s*([\d.]+)",text,re.M)
125
+ if m:
126
+ ppl=float(m.group(1))
127
+ else:
128
+ chunks=re.findall(r"\[\d+\]([\d.]+)",text)
129
+ if chunks:ppl=float(chunks[-1])
130
+ t=re.search(r"([\d.]+)\s*tokens per second",text,re.I)
131
+ if t:tps=float(t.group(1))
132
+ else:
133
+ p=re.search(r"([\d.]+)\s*seconds per pass",text,re.I)
134
+ if p:tps=CTX/max(float(p.group(1)),0.001)
135
+ return ppl,tps
136
+
137
+ def _get_flags(binary):
138
+ try:help_txt=subprocess.run([binary,"-h"],capture_output=True,text=True,timeout=5).stdout
139
+ except Exception:help_txt=""
140
+ extra=[]
141
+ if "-fa" in help_txt or "--flash-attn" in help_txt:extra+=["-fa","on"]
142
+ return extra
143
+
144
+ def _run_single(binary,model,corpus,ngl,flags,label):
145
+ cmd=[binary,"-m",model,"-f",corpus,"-c",str(CTX),"-b","512","-ub","512","-t",str(THREADS),"--chunks",str(CHUNKS),"--no-warmup"]
146
+ if ngl>0:cmd+=["-ngl",str(ngl)]
147
+ cmd+=flags
148
+ print(f"\n── {label} ──")
149
+ print(f" ngl={ngl} · ctx={CTX} · batch=512 · chunks={CHUNKS} · threads={THREADS}"+(" · Flash-Attention" if "-fa" in flags else ""))
150
+ t0=time.perf_counter()
151
+ try:
152
+ proc=subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True)
153
+ except FileNotFoundError:
154
+ print(" ✗ binary not found");return None
155
+ out_lines=[];err_lines=[]
156
+ current_chunk=0
157
+ last_ppl=""
158
+ lock=threading.Lock()
159
+ def on_text(line):
160
+ nonlocal current_chunk,last_ppl
161
+ m=re.search(r"\[(\d+)\]([\d.]+)?",line)
162
+ if m:
163
+ current_chunk=int(m.group(1))
164
+ if m.group(2):last_ppl=f" PPL={m.group(2)}"
165
+ with lock:
166
+ if current_chunk>0:
167
+ el=time.perf_counter()-t0
168
+ speed=current_chunk/el if el>0 else 0
169
+ rem=(CHUNKS-current_chunk)/speed if speed>0 else 0
170
+ sys.stdout.write(f"\r [{current_chunk:2d}/{CHUNKS}] {current_chunk/CHUNKS*100:4.1f}%{last_ppl} · {el:.0f}s elapsed · ETA {rem:.0f}s ")
171
+ sys.stdout.flush()
172
+ def drain(stream,buf):
173
+ for line in stream:
174
+ buf.append(line)
175
+ on_text(line)
176
+ th_out=threading.Thread(target=drain,args=(proc.stdout,out_lines),daemon=True)
177
+ th_err=threading.Thread(target=drain,args=(proc.stderr,err_lines),daemon=True)
178
+ th_out.start();th_err.start()
179
+ proc.wait()
180
+ th_out.join(timeout=3);th_err.join(timeout=3)
181
+ sys.stdout.write("\r"+" "*90+"\r")
182
+ elapsed=time.perf_counter()-t0
183
+ out="".join(out_lines)+"\n"+"".join(err_lines)
184
+ ppl,tps=parse_output(out)
185
+ if proc.returncode!=0 or ppl is None:
186
+ return None
187
+ tps=tps or (CHUNKS*CTX/elapsed if elapsed>0 else 0)
188
+ print(f" PPL={ppl:.4f} · {tps:.1f} tok/s · {elapsed:.1f}s")
189
+ return ppl,tps
190
+
191
+ def run_ppl(binary,model,corpus,flags,label):
192
+ ngl=_ngl_for_model(model,GPU_FREE_VRAM)
193
+ res=_run_single(binary,model,corpus,ngl,flags,label)
194
+ if res is not None:return res
195
+ if ngl>0:
196
+ print(" ⚠ Run failed on GPU — retrying CPU-only …")
197
+ res=_run_single(binary,model,corpus,0,[],label)
198
+ if res is not None:return res
199
+ print(" ✗ Run failed.")
200
+ return None
201
+
202
+ def main():
203
+ binary=find_binary()
204
+ if not binary:
205
+ print("Error: llama-perplexity binary not found under llama-cpp/.");sys.exit(1)
206
+ print(f"Binary : {binary}")
207
+ if GPU_BACKEND:
208
+ print(f"GPU : {GPU_BACKEND} · {GPU_FREE_VRAM}/{GPU_TOTAL_VRAM} MiB VRAM available")
209
+ else:
210
+ print("GPU : none detected — CPU only")
211
+ corpus=fetch_wiki()
212
+ if not corpus:
213
+ print("Error: no corpus available.");sys.exit(1)
214
+ models=pick_models()
215
+ if not models:
216
+ print("Error: no model-*.gguf found next to this script.");sys.exit(1)
217
+ sel=menu(models)
218
+ if not sel:return
219
+ flags=_get_flags(binary)
220
+ results=[]
221
+ for m in sel:
222
+ res=run_ppl(binary,m,corpus,flags,os.path.basename(m))
223
+ if res:results.append((os.path.basename(m),res[0],res[1]))
224
+ if len(results)<2:
225
+ if results:print(f"\n Final result: {results[0][0]} → PPL = {results[0][1]:.4f}")
226
+ return
227
+ base=results[0][1]
228
+ print("\n"+"="*64)
229
+ print(" PERPLEXITY SUMMARY (lower is better)")
230
+ print("="*64)
231
+ print(f" {'Model':<46}{'PPL':>9}{'+Δ':>9}{'Speed':>11}")
232
+ print(f" {'-'*46}{'-'*9}{'-'*9}{'-'*11}")
233
+ for name,ppl,tps in results:
234
+ print(f" {name:<46}{ppl:>9.4f}{ppl-base:>+9.4f}{tps:>8.1f} t/s")
235
+ print(f"\n Δ relative to {results[0][0]} · corpus: {os.path.basename(corpus)}")
236
+ if corpus==WIKI_PATH:
237
+ print(" wiki.test.raw corpus: absolute PPL comparable to llama.cpp published benchmarks.")
238
+
239
+ if __name__=="__main__":
240
+ main()
README.md CHANGED
@@ -74,6 +74,22 @@ All tiers maintain strict byte-budget percentages relative to the original unqua
74
  >
75
  > Measured on a 9B `qwen35` source (17 091 MiB BF16): Nano **21.10%**, Mini **27.01%**, Compact **33.06%**, Quality **35.49%**. `Quality` stops short of its nominal target because the surviving upgrades are indivisible blocks of 129 MiB and above.
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  ### 🎯 Recommended Minimum Tiers by Model Size
78
 
79
  Smaller parameter architectures require higher relative bit precision to prevent degradation of core reasoning representations:
@@ -94,6 +110,7 @@ Smaller parameter architectures require higher relative bit precision to prevent
94
  | `01_create-calibration-dataset-and-imatrix.py` | End-to-end dataset builder (Agentic/Frontier/Logic) and GPU-autotuned `llama-imatrix` runner. |
95
  | `01b_BF16-GGUF-modules-fusion.py` | Lossless merger combining base models, vision projectors (`mmproj`), and MTP heads. |
96
  | `02_BF16-GGUF-to-ASHQ1.py` | Automated orchestrator executing batch quantization across all target tiers. |
 
97
  | `ASHQ1.py` | Core hybrid quantization optimizer with greedy knapsack utility scheduling and tied-weight detection. |
98
  | `ASHQ1-mmproj.py` | Vision projector quantizer applying selective deep-block boosting and critical layer pinning. |
99
 
 
74
  >
75
  > Measured on a 9B `qwen35` source (17 091 MiB BF16): Nano **21.10%**, Mini **27.01%**, Compact **33.06%**, Quality **35.49%**. `Quality` stops short of its nominal target because the surviving upgrades are indivisible blocks of 129 MiB and above.
76
 
77
+ ### Perplexity Benchmarks (Ornith-1.5-9B)
78
+
79
+ Evaluated on `wiki.test.raw` (Wikitext-2), `n_ctx=2048`, 64 chunks, Flash-Attention enabled:
80
+
81
+ | Tier | Size | VRAM Budget | PPL | Δ vs Quality | Speed (RTX 8GB) |
82
+ |---|---|---|---|---|---|
83
+ | **Quality-36pc** | 6.06 GiB | ~7.5 GiB | **8.0932** | baseline | ~1241 tok/s |
84
+ | **Compact-33pc** | 5.65 GiB | ~7.0 GiB | **8.1290** | +0.0358 | ~1241 tok/s |
85
+ | **Mini-27pc** | 4.62 GiB | ~5.8 GiB | **9.5101** | +1.4169 | ~1442 tok/s |
86
+ | **Nano-21pc** | 3.60 GiB | ~4.5 GiB | 430.7392 | collapse | ~1321 tok/s |
87
+
88
+ > **Takeaways:**
89
+ > - `Quality-36pc` provides near-lossless perplexity for production inference.
90
+ > - `Compact-33pc` loses only **0.0358 PPL** while saving ~416 MiB, ideal for 8 GB VRAM setups.
91
+ > - `Mini-27pc` maintains strong conversational coherence under tight memory constraints.
92
+
93
  ### 🎯 Recommended Minimum Tiers by Model Size
94
 
95
  Smaller parameter architectures require higher relative bit precision to prevent degradation of core reasoning representations:
 
110
  | `01_create-calibration-dataset-and-imatrix.py` | End-to-end dataset builder (Agentic/Frontier/Logic) and GPU-autotuned `llama-imatrix` runner. |
111
  | `01b_BF16-GGUF-modules-fusion.py` | Lossless merger combining base models, vision projectors (`mmproj`), and MTP heads. |
112
  | `02_BF16-GGUF-to-ASHQ1.py` | Automated orchestrator executing batch quantization across all target tiers. |
113
+ | `03_perplexity_test.py` | Perplexity validation suite using `llama-perplexity` over reference corpora.
114
  | `ASHQ1.py` | Core hybrid quantization optimizer with greedy knapsack utility scheduling and tied-weight detection. |
115
  | `ASHQ1-mmproj.py` | Vision projector quantizer applying selective deep-block boosting and critical layer pinning. |
116
 
USAGE.md CHANGED
@@ -15,6 +15,7 @@ workspace/
15
  ├── 01_create-calibration-dataset-and-imatrix.py
16
  ├── 01b_BF16-GGUF-modules-fusion.py
17
  ├── 02_BF16-GGUF-to-ASHQ1.py
 
18
  ├── ASHQ1.py
19
  ├── ASHQ1-mmproj.py
20
  ├── llama-cpp/ # Cloned or linked llama.cpp repository
@@ -88,6 +89,17 @@ python ASHQ1.py --model model-BF16.gguf --imatrix imatrix.gguf --tier quality --
88
 
89
  ---
90
 
 
 
 
 
 
 
 
 
 
 
 
91
  ## 🎯 3. Recommended Minimum Tiers by Model Size
92
 
93
  Smaller parameter architectures require higher relative bit precision to prevent degradation of core reasoning representations:
 
15
  ├── 01_create-calibration-dataset-and-imatrix.py
16
  ├── 01b_BF16-GGUF-modules-fusion.py
17
  ├── 02_BF16-GGUF-to-ASHQ1.py
18
+ ├── 03_perplexity_test.py
19
  ├── ASHQ1.py
20
  ├── ASHQ1-mmproj.py
21
  ├── llama-cpp/ # Cloned or linked llama.cpp repository
 
89
 
90
  ---
91
 
92
+ ### Step 3: Perplexity Evaluation (`03_perplexity_test.py`)
93
+
94
+ Interactive CLI tool to benchmark GGUF files against `wiki.test.raw` (auto-downloaded from HuggingFace) or a local corpus:
95
+
96
+ - Auto-detects NVIDIA (CUDA) and AMD (ROCm) hardware and available VRAM.
97
+ - Automatically sets `-ngl`, `-b 512`, `-ub 512`, and `-fa` (Flash-Attention).
98
+ - Real-time ETA and chunk progression streaming.
99
+ - Generates a comparative summary table with Δ PPL.
100
+
101
+ ---
102
+
103
  ## 🎯 3. Recommended Minimum Tiers by Model Size
104
 
105
  Smaller parameter architectures require higher relative bit precision to prevent degradation of core reasoning representations: