quantbench-engine 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.
quantbench/__init__.py ADDED
@@ -0,0 +1,76 @@
1
+ """quantbench: Unified Quantization Engine, Bit-Packing Runtime & Quality-vs-Speed Benchmark."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from quantbench.bench import ParetoBenchmark, ParetoPoint
6
+ from quantbench.eval import (
7
+ compute_sliding_window_perplexity,
8
+ compute_sqnr,
9
+ generate_synthetic_corpus,
10
+ )
11
+ from quantbench.nn.model_convert import convert_model_to_quant
12
+ from quantbench.nn.quant_linear import QuantLinear
13
+ from quantbench.quant.affine import (
14
+ dequantize_absmax,
15
+ dequantize_affine,
16
+ quantize_absmax,
17
+ quantize_affine,
18
+ )
19
+ from quantbench.quant.algorithms import (
20
+ awq_quantize_weights,
21
+ compute_activation_channel_means,
22
+ compute_activation_hessian,
23
+ compute_smoothquant_scale,
24
+ extract_activation_channel_scales,
25
+ gptq_quantize_weights,
26
+ search_best_awq_scale,
27
+ smooth_linear_weights,
28
+ )
29
+ from quantbench.quant.blockwise import dequantize_blockwise, quantize_blockwise
30
+ from quantbench.quant.fp8 import FP8Format, dequantize_fp8, quantize_fp8
31
+ from quantbench.quant.packing import (
32
+ decompress_2_4,
33
+ pack_2_4_indices,
34
+ pack_int4,
35
+ pack_ternary,
36
+ prune_and_compress_2_4,
37
+ unpack_2_4_indices,
38
+ unpack_int4,
39
+ unpack_ternary,
40
+ )
41
+
42
+ __all__ = [
43
+ "__version__",
44
+ "QuantLinear",
45
+ "convert_model_to_quant",
46
+ "quantize_affine",
47
+ "dequantize_affine",
48
+ "quantize_absmax",
49
+ "dequantize_absmax",
50
+ "quantize_blockwise",
51
+ "dequantize_blockwise",
52
+ "quantize_fp8",
53
+ "dequantize_fp8",
54
+ "FP8Format",
55
+ "pack_int4",
56
+ "unpack_int4",
57
+ "pack_ternary",
58
+ "unpack_ternary",
59
+ "pack_2_4_indices",
60
+ "unpack_2_4_indices",
61
+ "prune_and_compress_2_4",
62
+ "decompress_2_4",
63
+ "compute_smoothquant_scale",
64
+ "extract_activation_channel_scales",
65
+ "smooth_linear_weights",
66
+ "compute_activation_channel_means",
67
+ "search_best_awq_scale",
68
+ "awq_quantize_weights",
69
+ "compute_activation_hessian",
70
+ "gptq_quantize_weights",
71
+ "compute_sqnr",
72
+ "compute_sliding_window_perplexity",
73
+ "generate_synthetic_corpus",
74
+ "ParetoBenchmark",
75
+ "ParetoPoint",
76
+ ]
@@ -0,0 +1,22 @@
1
+ # Copyright 2026 Prajwal7-2002
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Benchmarking tools and Pareto frontier evaluation."""
16
+
17
+ from quantbench.bench.pareto import ParetoBenchmark, ParetoPoint
18
+
19
+ __all__ = [
20
+ "ParetoBenchmark",
21
+ "ParetoPoint",
22
+ ]
@@ -0,0 +1,182 @@
1
+ # Copyright 2026 Prajwal7-2002
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Pareto evaluation engine: Quality vs Compression trade-off analysis."""
16
+
17
+ from __future__ import annotations
18
+
19
+ import copy
20
+ from dataclasses import dataclass
21
+ from typing import Any, Dict, List, Optional
22
+
23
+ import torch
24
+ import torch.nn as nn
25
+
26
+ from quantbench.eval.perplexity import compute_sliding_window_perplexity
27
+ from quantbench.eval.sqnr import compute_sqnr
28
+ from quantbench.nn.model_convert import convert_model_to_quant
29
+ from quantbench.nn.quant_linear import QuantLinear
30
+
31
+
32
+ @dataclass
33
+ class ParetoPoint:
34
+ """Single configuration measurement on the Pareto frontier."""
35
+
36
+ name: str
37
+ scheme: str
38
+ group_size: int
39
+ compression_ratio: float
40
+ memory_mb: float
41
+ perplexity: float
42
+ perplexity_delta: float
43
+ avg_sqnr_db: float
44
+
45
+
46
+ class ParetoBenchmark:
47
+ """Benchmark model configurations across precision, memory, and perplexity frontiers."""
48
+
49
+ def __init__(
50
+ self,
51
+ base_model: nn.Module,
52
+ test_tokens: torch.Tensor,
53
+ window_size: int = 256,
54
+ stride: int = 128,
55
+ ) -> None:
56
+ self.base_model = base_model
57
+ self.test_tokens = test_tokens
58
+ self.window_size = window_size
59
+ self.stride = stride
60
+
61
+ def run_sweep(
62
+ self,
63
+ schemes: Optional[List[Dict[str, Any]]] = None,
64
+ exclude_modules: Optional[List[str]] = None,
65
+ ) -> List[ParetoPoint]:
66
+ """Run Pareto sweep across specified quantization schemes."""
67
+ if schemes is None:
68
+ schemes = [
69
+ {"name": "FP16 Baseline", "scheme": "fp16", "group_size": -1},
70
+ {"name": "INT8 AbsMax", "scheme": "int8", "group_size": -1},
71
+ {"name": "INT4 (G=128)", "scheme": "int4", "group_size": 128},
72
+ {"name": "INT4 (G=64)", "scheme": "int4", "group_size": 64},
73
+ {"name": "BitNet Ternary", "scheme": "ternary", "group_size": -1},
74
+ {"name": "Ampere 2:4 Sparse", "scheme": "sparse24", "group_size": -1},
75
+ ]
76
+
77
+ # 1. Evaluate baseline FP16
78
+ base_ppl = compute_sliding_window_perplexity(
79
+ self.base_model,
80
+ self.test_tokens,
81
+ window_size=self.window_size,
82
+ stride=self.stride,
83
+ )
84
+
85
+ base_bytes = sum(
86
+ p.numel() * 2 for p in self.base_model.parameters()
87
+ )
88
+ base_mb = base_bytes / (1024 * 1024)
89
+
90
+ points: List[ParetoPoint] = []
91
+
92
+ for cfg in schemes:
93
+ name = cfg["name"]
94
+ scheme = cfg["scheme"]
95
+ group_size = cfg.get("group_size", 64)
96
+
97
+ if scheme == "fp16":
98
+ points.append(
99
+ ParetoPoint(
100
+ name=name,
101
+ scheme=scheme,
102
+ group_size=group_size,
103
+ compression_ratio=1.0,
104
+ memory_mb=round(base_mb, 2),
105
+ perplexity=round(base_ppl, 3),
106
+ perplexity_delta=0.0,
107
+ avg_sqnr_db=100.0,
108
+ )
109
+ )
110
+ continue
111
+
112
+ # Clone base model for isolated in-place conversion
113
+ cloned_model = copy.deepcopy(self.base_model)
114
+
115
+ # Extract unquantized linear weights for layer-level SQNR comparison
116
+ orig_weights: Dict[str, torch.Tensor] = {}
117
+ for m_name, module in cloned_model.named_modules():
118
+ if isinstance(module, nn.Linear):
119
+ orig_weights[m_name] = module.weight.detach().clone()
120
+
121
+ # In-place quantization
122
+ _, stats = convert_model_to_quant(
123
+ cloned_model,
124
+ scheme=scheme,
125
+ group_size=group_size,
126
+ exclude_modules=exclude_modules,
127
+ )
128
+
129
+ # Compute layer-level SQNR
130
+ sqnrs: List[float] = []
131
+ for m_name, module in cloned_model.named_modules():
132
+ if isinstance(module, QuantLinear) and m_name in orig_weights:
133
+ dequant_w = module.dequantize_weight()
134
+ sqnrs.append(compute_sqnr(orig_weights[m_name], dequant_w))
135
+
136
+ avg_sqnr = sum(sqnrs) / len(sqnrs) if sqnrs else 0.0
137
+
138
+ # Evaluate quantized perplexity
139
+ q_ppl = compute_sliding_window_perplexity(
140
+ cloned_model,
141
+ self.test_tokens,
142
+ window_size=self.window_size,
143
+ stride=self.stride,
144
+ )
145
+
146
+ points.append(
147
+ ParetoPoint(
148
+ name=name,
149
+ scheme=scheme,
150
+ group_size=group_size,
151
+ compression_ratio=round(stats["compression_ratio"], 2),
152
+ memory_mb=round(stats["quant_memory_mb"], 2),
153
+ perplexity=round(q_ppl, 3),
154
+ perplexity_delta=round(q_ppl - base_ppl, 3),
155
+ avg_sqnr_db=round(avg_sqnr, 1),
156
+ )
157
+ )
158
+
159
+ return points
160
+
161
+ @staticmethod
162
+ def render_ascii_table(points: List[ParetoPoint]) -> str:
163
+ """Render a clean GitHub Markdown / terminal ASCII table of the Pareto frontier."""
164
+ lines = [
165
+ "| Configuration | Scheme | Group | Memory (MB) | Compression "
166
+ "| Perplexity | Delta PPL | SQNR (dB) |",
167
+ "|---|---|---|---|---|---|---|---|",
168
+ ]
169
+ for p in points:
170
+ grp = str(p.group_size) if p.group_size > 0 else "None"
171
+ delta = (
172
+ f"+{p.perplexity_delta:.3f}"
173
+ if p.perplexity_delta > 0
174
+ else f"{p.perplexity_delta:.3f}"
175
+ )
176
+ row = (
177
+ f"| {p.name:<18} | {p.scheme:<8} | {grp:<5} | {p.memory_mb:>10.2f} | "
178
+ f"{p.compression_ratio:>10.2f}x | {p.perplexity:>10.3f} | {delta:>9} | "
179
+ f"{p.avg_sqnr_db:>8.1f} |"
180
+ )
181
+ lines.append(row)
182
+ return "\n".join(lines)
quantbench/cli.py ADDED
@@ -0,0 +1,301 @@
1
+ """Command Line Interface for quantbench."""
2
+
3
+ import typer
4
+ from rich.console import Console
5
+ from rich.panel import Panel
6
+ from rich.table import Table
7
+
8
+ import quantbench
9
+ from quantbench.quant.packing import (
10
+ pack_int4,
11
+ pack_ternary,
12
+ prune_and_compress_2_4,
13
+ unpack_int4,
14
+ unpack_ternary,
15
+ )
16
+
17
+ app = typer.Typer(
18
+ name="quantbench",
19
+ help="First-principles LLM quantization engine and Pareto benchmark.",
20
+ add_completion=False,
21
+ )
22
+ console = Console()
23
+
24
+
25
+ def version_callback(value: bool):
26
+ if value:
27
+ ver = quantbench.__version__
28
+ console.print(f"[bold cyan]quantbench[/bold cyan] version [bold green]{ver}[/bold green]")
29
+ raise typer.Exit()
30
+
31
+
32
+ @app.callback()
33
+ def main(
34
+ version: bool = typer.Option(
35
+ None,
36
+ "--version",
37
+ "-v",
38
+ help="Show version and exit.",
39
+ callback=version_callback,
40
+ is_eager=True,
41
+ ),
42
+ ):
43
+ """quantbench CLI root."""
44
+ pass
45
+
46
+
47
+ @app.command()
48
+ def info():
49
+ """Display system and supported quantization capabilities."""
50
+ table = Table(title="quantbench Capabilities & Formats", border_style="cyan")
51
+ table.add_column("Category", style="bold yellow")
52
+ table.add_column("Supported Schemes", style="green")
53
+ table.add_column("Target Hardware", style="magenta")
54
+
55
+ table.add_row(
56
+ "Integer Math",
57
+ "INT8 AbsMax, INT8 Affine, INT4 Groupwise",
58
+ "CPU / Tensor Core / Metal",
59
+ )
60
+ table.add_row(
61
+ "Bit-Packing",
62
+ "INT4 (2 vals/byte), BitNet Ternary (4 or 5 vals/byte), 2:4 Sparsity",
63
+ "Vectorized Sub-Byte Runtimes",
64
+ )
65
+ table.add_row(
66
+ "Modern FP8",
67
+ "OCP E4M3FN (GEMM), OCP E5M2 (KV/Gradients)",
68
+ "Ada / Hopper / CPU Emulation",
69
+ )
70
+ table.add_row(
71
+ "Optimization",
72
+ "SmoothQuant, AWQ Grid Search, GPTQ Hessian",
73
+ "Zero-compile PyTorch",
74
+ )
75
+ table.add_row(
76
+ "Hardware Sparsity",
77
+ "2:4 Structured Sparsity (50% compression)",
78
+ "Ampere / Ada / Hopper",
79
+ )
80
+ table.add_row(
81
+ "Benchmarking",
82
+ "Sliding WikiText-2 Perplexity, SQNR (dB)",
83
+ "Local CPU & GPU",
84
+ )
85
+
86
+ console.print(table)
87
+
88
+
89
+ @app.command("pack-inspect")
90
+ def pack_inspect(
91
+ fmt: str = typer.Option(
92
+ "int4",
93
+ "--format",
94
+ "-f",
95
+ help="Format to inspect: int4, ternary, sparse24",
96
+ ),
97
+ ):
98
+ """Inspect bit-packing layout, sub-byte representation, and theoretical memory savings."""
99
+ import torch
100
+
101
+ fmt_lower = fmt.lower()
102
+ if fmt_lower == "int4":
103
+ sample = torch.tensor([5, 12], dtype=torch.int32)
104
+ packed, orig_len = pack_int4(sample, signed=False)
105
+ unpacked = unpack_int4(packed, original_len=orig_len, signed=False)
106
+ byte_val = packed.item()
107
+
108
+ panel = Panel(
109
+ f"[bold green]INT4 Sub-Byte Packing (2 values per uint8 byte)[/bold green]\n\n"
110
+ f"Input values: [cyan]{sample.tolist()}[/cyan]\n"
111
+ f"• Low nibble [0:3]: {sample[0].item()} -> [yellow]{sample[0].item():04b}[/yellow]\n"
112
+ f"• High nibble [4:7]: {sample[1].item()} -> [yellow]{sample[1].item():04b}[/yellow]\n"
113
+ f"Packed uint8 byte: [bold magenta]{byte_val}[/bold magenta] "
114
+ f"(binary: [yellow]{byte_val:08b}[/yellow], hex: [yellow]0x{byte_val:02X}[/yellow])\n"
115
+ f"Unpacked roundtrip: [cyan]{unpacked.tolist()}[/cyan]\n"
116
+ f"Memory compression: [bold green]4.0x vs FP16[/bold green] (2.0x vs INT8)",
117
+ title="INT4 Bit Layout",
118
+ border_style="cyan",
119
+ )
120
+ console.print(panel)
121
+
122
+ elif fmt_lower == "ternary":
123
+ sample = torch.tensor([-1, 0, 1, -1])
124
+ packed, orig_len = pack_ternary(sample, mode="2bit")
125
+ unpacked = unpack_ternary(packed, original_len=orig_len, mode="2bit")
126
+ byte_val = packed.item()
127
+
128
+ panel = Panel(
129
+ f"[bold green]BitNet 1.58b Ternary Packing (4 weights per uint8 byte)[/bold green]\n\n"
130
+ f"Input weights: [cyan]{sample.tolist()}[/cyan]\n"
131
+ f"Offset mapping: u = v + 1 in {{0, 1, 2}}\n"
132
+ f"Packed uint8 byte: [bold magenta]{byte_val}[/bold magenta] "
133
+ f"(binary: [yellow]{byte_val:08b}[/yellow], hex: [yellow]0x{byte_val:02X}[/yellow])\n"
134
+ f"Unpacked roundtrip: [cyan]{unpacked.tolist()}[/cyan]\n"
135
+ f"Memory compression: [bold green]8.0x vs FP16[/bold green] (Multiplication-free GEMM)",
136
+ title="BitNet 1.58b Layout",
137
+ border_style="green",
138
+ )
139
+ console.print(panel)
140
+
141
+ elif fmt_lower in ("sparse24", "2:4", "sparse"):
142
+ sample = torch.tensor([[0.0, 3.5, 0.0, -1.2, 4.0, 0.0, 0.0, -2.1]])
143
+ condensed, metadata = prune_and_compress_2_4(sample)
144
+ meta_byte = metadata.item()
145
+
146
+ panel = Panel(
147
+ f"[bold green]2:4 Structured Sparsity (NVIDIA Sparse Tensor Cores)[/bold green]\n\n"
148
+ f"Dense input row (8 values): [cyan]{sample.tolist()[0]}[/cyan]\n"
149
+ f"Condensed non-zeros (4 values): [cyan]{condensed.tolist()[0]}[/cyan]\n"
150
+ f"Packed metadata byte: [bold magenta]{meta_byte}[/bold magenta] "
151
+ f"(binary: [yellow]{meta_byte:08b}[/yellow], hex: [yellow]0x{meta_byte:02X}[/yellow])\n"
152
+ f"Storage reduction: [bold green]50% weight + 2-bit metadata/val[/bold green]\n"
153
+ f"Theoretical math throughput: [bold green]2.0x Tensor Core TFLOPS[/bold green]",
154
+ title="2:4 Sparse Tensor Layout",
155
+ border_style="magenta",
156
+ )
157
+ console.print(panel)
158
+
159
+ else:
160
+ err_msg = f"Unknown format '{fmt}'. Choose 'int4', 'ternary', or 'sparse24'."
161
+ console.print(f"[bold red]{err_msg}[/bold red]")
162
+ raise typer.Exit(code=1)
163
+
164
+
165
+ @app.command("eval")
166
+ def eval_cmd(
167
+ shape: str = typer.Option("1024x1024", "--shape", help="Weight matrix shape (MxN)."),
168
+ ):
169
+ """Evaluate tensor-level SQNR (dB) and compression factor across quantization schemes."""
170
+ import torch
171
+
172
+ from quantbench.eval.sqnr import compute_sqnr
173
+ from quantbench.quant.affine import dequantize_absmax, quantize_absmax
174
+ from quantbench.quant.blockwise import dequantize_blockwise, quantize_blockwise
175
+
176
+ try:
177
+ m_str, n_str = shape.lower().split("x")
178
+ m, n = int(m_str), int(n_str)
179
+ except Exception:
180
+ console.print(
181
+ f"[bold red]Invalid shape '{shape}'. Expected format e.g. '1024x1024'[/bold red]"
182
+ )
183
+ raise typer.Exit(code=1) from None
184
+
185
+ torch.manual_seed(42)
186
+ w = torch.randn(m, n)
187
+
188
+ # 1. INT8 AbsMax
189
+ q8, s8 = quantize_absmax(w, num_bits=8, dim=1)
190
+ w8 = dequantize_absmax(q8, s8)
191
+ sqnr8 = compute_sqnr(w, w8)
192
+
193
+ # 2. INT4 Blockwise (G=128)
194
+ q4_128, s4_128, zp4_128, meta_128 = quantize_blockwise(w, group_size=128, num_bits=4)
195
+ w4_128 = dequantize_blockwise(q4_128, s4_128, zp4_128, meta_128)
196
+ sqnr4_128 = compute_sqnr(w, w4_128)
197
+
198
+ # 3. INT4 Blockwise (G=64)
199
+ q4_64, s4_64, zp4_64, meta_64 = quantize_blockwise(w, group_size=64, num_bits=4)
200
+ w4_64 = dequantize_blockwise(q4_64, s4_64, zp4_64, meta_64)
201
+ sqnr4_64 = compute_sqnr(w, w4_64)
202
+
203
+ table = Table(
204
+ title=f"Quantization Fidelity Benchmark ({m}x{n} Weight Matrix)",
205
+ border_style="cyan",
206
+ )
207
+ table.add_column("Scheme", style="bold yellow")
208
+ table.add_column("Group Size", style="cyan")
209
+ table.add_column("Bit-Width", style="green")
210
+ table.add_column("SQNR (dB)", style="bold magenta")
211
+ table.add_column("Compression", style="bold green")
212
+
213
+ table.add_row("FP16 Baseline", "None", "16-bit", "100.0 dB", "1.00x")
214
+ table.add_row("INT8 AbsMax", "Per-Channel", "8-bit", f"{sqnr8:.2f} dB", "2.00x")
215
+ table.add_row("INT4 Blockwise", "128", "4-bit", f"{sqnr4_128:.2f} dB", "3.55x")
216
+ table.add_row("INT4 Blockwise", "64", "4-bit", f"{sqnr4_64:.2f} dB", "3.20x")
217
+
218
+ console.print(table)
219
+
220
+
221
+ @app.command("pareto")
222
+ def pareto_cmd(
223
+ vocab_size: int = typer.Option(128, "--vocab-size", help="Vocabulary cardinality."),
224
+ hidden_dim: int = typer.Option(128, "--hidden-dim", help="Model hidden dimension."),
225
+ seq_len: int = typer.Option(300, "--seq-len", help="Number of calibration tokens."),
226
+ ):
227
+ """Run an end-to-end Pareto quality-vs-compression benchmark across all quantization schemes."""
228
+ import torch
229
+ import torch.nn as nn
230
+
231
+ from quantbench.bench.pareto import ParetoBenchmark
232
+ from quantbench.eval.perplexity import generate_synthetic_corpus
233
+
234
+ class DemoTransformer(nn.Module):
235
+ def __init__(self, vocab: int, dim: int):
236
+ super().__init__()
237
+ self.embedding = nn.Embedding(vocab, dim)
238
+ self.q_proj = nn.Linear(dim, dim, bias=False)
239
+ self.k_proj = nn.Linear(dim, dim, bias=False)
240
+ self.v_proj = nn.Linear(dim, dim, bias=False)
241
+ self.o_proj = nn.Linear(dim, dim, bias=False)
242
+ self.gate_proj = nn.Linear(dim, dim * 2, bias=False)
243
+ self.down_proj = nn.Linear(dim * 2, dim, bias=False)
244
+ self.lm_head = nn.Linear(dim, vocab, bias=False)
245
+
246
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
247
+ h = self.embedding(x)
248
+ attn = self.o_proj(torch.relu(self.q_proj(h) + self.k_proj(h) + self.v_proj(h)))
249
+ mlp = self.down_proj(torch.relu(self.gate_proj(attn)))
250
+ return self.lm_head(mlp)
251
+
252
+ console.print(
253
+ f"[bold cyan]Initializing Demo Transformer[/bold cyan] "
254
+ f"(Vocab={vocab_size}, Dim={hidden_dim})..."
255
+ )
256
+ torch.manual_seed(42)
257
+ model = DemoTransformer(vocab=vocab_size, dim=hidden_dim)
258
+ tokens = generate_synthetic_corpus(vocab_size=vocab_size, seq_length=seq_len, seed=1)
259
+
260
+ bench = ParetoBenchmark(
261
+ base_model=model, test_tokens=tokens, window_size=64, stride=32
262
+ )
263
+ console.print(
264
+ "[bold yellow]Running Pareto sweep across all precision runtimes...[/bold yellow]"
265
+ )
266
+
267
+ points = bench.run_sweep(exclude_modules=["lm_head"])
268
+
269
+ table = Table(title="Pareto Frontier (Quality vs Compression)", border_style="cyan")
270
+ table.add_column("Configuration", style="bold yellow")
271
+ table.add_column("Scheme", style="cyan")
272
+ table.add_column("Group", style="magenta")
273
+ table.add_column("Memory (MB)", style="green")
274
+ table.add_column("Compression", style="bold green")
275
+ table.add_column("Perplexity", style="bold white")
276
+ table.add_column("Delta PPL", style="bold red")
277
+ table.add_column("SQNR (dB)", style="bold magenta")
278
+
279
+ for p in points:
280
+ grp = str(p.group_size) if p.group_size > 0 else "None"
281
+ delta_str = (
282
+ f"+{p.perplexity_delta:.3f}"
283
+ if p.perplexity_delta > 0
284
+ else f"{p.perplexity_delta:.3f}"
285
+ )
286
+ table.add_row(
287
+ p.name,
288
+ p.scheme,
289
+ grp,
290
+ f"{p.memory_mb:.2f}",
291
+ f"{p.compression_ratio:.2f}x",
292
+ f"{p.perplexity:.3f}",
293
+ delta_str,
294
+ f"{p.avg_sqnr_db:.1f}",
295
+ )
296
+
297
+ console.print(table)
298
+
299
+
300
+ if __name__ == "__main__":
301
+ app()
@@ -0,0 +1,27 @@
1
+ # Copyright 2026 Prajwal7-2002
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Evaluation metrics and perplexity measurement engines."""
16
+
17
+ from quantbench.eval.perplexity import (
18
+ compute_sliding_window_perplexity,
19
+ generate_synthetic_corpus,
20
+ )
21
+ from quantbench.eval.sqnr import compute_sqnr
22
+
23
+ __all__ = [
24
+ "compute_sqnr",
25
+ "compute_sliding_window_perplexity",
26
+ "generate_synthetic_corpus",
27
+ ]