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.
@@ -0,0 +1,143 @@
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
+ """Sliding-window causal language model perplexity evaluation engine.
16
+
17
+ Mathematical Foundation:
18
+ Perplexity (PPL) is the standard metric for language model evaluation:
19
+ PPL = exp( - (1 / N) * sum_{t=1}^N log P(w_t | w_{<t}) )
20
+
21
+ Standard non-overlapping chunk evaluation splits text into discrete blocks of
22
+ length L, discarding past context for the initial tokens of every block and
23
+ artificially inflating measured perplexity.
24
+
25
+ The sliding-window approach uses a context window of length L and strides forward
26
+ by S < L tokens:
27
+ - The model sees the full preceding context of length L.
28
+ - Cross-entropy loss is evaluated strictly over the trailing S tokens.
29
+ - Boundary effects are eliminated, guaranteeing statistically rigorous
30
+ comparisons across FP16, INT8, INT4, and sparse model checkpoints.
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ import math
36
+ from typing import Optional
37
+
38
+ import torch
39
+ import torch.nn as nn
40
+ import torch.nn.functional as F
41
+
42
+
43
+ def compute_sliding_window_perplexity(
44
+ model: nn.Module,
45
+ input_ids: torch.Tensor,
46
+ window_size: int = 512,
47
+ stride: int = 256,
48
+ device: Optional[torch.device] = None,
49
+ ) -> float:
50
+ """Compute exact sliding-window perplexity for a causal language model.
51
+
52
+ Args:
53
+ model: Causal language model producing logits of shape (B, S, V).
54
+ input_ids: 1D or 2D tensor of token IDs.
55
+ window_size: Maximum context window size L (e.g. 512 or 1024).
56
+ stride: Stride step S < L between consecutive evaluation windows (e.g. 256).
57
+ device: Device to run evaluation on.
58
+
59
+ Returns:
60
+ Perplexity value as a positive float.
61
+ """
62
+ if stride >= window_size:
63
+ raise ValueError(
64
+ f"stride ({stride}) must be strictly less than window_size ({window_size})"
65
+ )
66
+
67
+ model.eval()
68
+ dev = device or next(model.parameters()).device
69
+
70
+ # Ensure 1D flat token vector
71
+ flat_tokens = input_ids.detach().view(-1).to(dev)
72
+ seq_len = flat_tokens.size(0)
73
+
74
+ if seq_len <= 1:
75
+ raise ValueError("input_ids must contain at least 2 tokens for causal language modeling.")
76
+
77
+ nlls: list[torch.Tensor] = []
78
+ total_eval_tokens = 0
79
+
80
+ prev_end_loc = 0
81
+ for begin_loc in range(0, seq_len, stride):
82
+ end_loc = min(begin_loc + window_size, seq_len)
83
+ trg_len = end_loc - prev_end_loc # Number of newly evaluated tokens in this window
84
+
85
+ input_chunk = flat_tokens[begin_loc:end_loc].unsqueeze(0) # (1, chunk_len)
86
+ target_chunk = input_chunk.clone()
87
+
88
+ # Mask out context tokens that were already evaluated in previous windows
89
+ target_chunk[:, :-trg_len] = -100
90
+
91
+ with torch.no_grad():
92
+ outputs = model(input_chunk)
93
+ logits = outputs.logits if hasattr(outputs, "logits") else outputs
94
+
95
+ # Shift logits and targets for next-token causal prediction:
96
+ # logits[:, :-1, :] predicts targets[:, 1:]
97
+ shift_logits = logits[:, :-1, :].contiguous()
98
+ shift_targets = target_chunk[:, 1:].contiguous()
99
+
100
+ # Cross entropy loss with ignore_index=-100
101
+ loss = F.cross_entropy(
102
+ shift_logits.view(-1, shift_logits.size(-1)),
103
+ shift_targets.view(-1),
104
+ ignore_index=-100,
105
+ reduction="sum",
106
+ )
107
+
108
+ nlls.append(loss)
109
+ # Count non-masked target tokens
110
+ num_valid = (shift_targets != -100).sum().item()
111
+ total_eval_tokens += num_valid
112
+
113
+ prev_end_loc = end_loc
114
+ if end_loc == seq_len:
115
+ break
116
+
117
+ if total_eval_tokens == 0:
118
+ return float("inf")
119
+
120
+ total_nll = torch.stack(nlls).sum().item()
121
+ mean_nll = total_nll / total_eval_tokens
122
+ perplexity = math.exp(mean_nll)
123
+
124
+ return float(perplexity)
125
+
126
+
127
+ def generate_synthetic_corpus(
128
+ vocab_size: int = 1000,
129
+ seq_length: int = 1024,
130
+ seed: int = 42,
131
+ ) -> torch.Tensor:
132
+ """Generate a deterministic synthetic token sequence for offline testing.
133
+
134
+ Args:
135
+ vocab_size: Vocabulary cardinality.
136
+ seq_length: Number of tokens to generate.
137
+ seed: Random seed for repeatability.
138
+
139
+ Returns:
140
+ Tensor of shape (seq_length,) containing token IDs.
141
+ """
142
+ g = torch.Generator().manual_seed(seed)
143
+ return torch.randint(low=0, high=vocab_size, size=(seq_length,), generator=g)
@@ -0,0 +1,66 @@
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
+ """Signal-to-Quantization-Noise Ratio (SQNR) evaluation metrics."""
16
+
17
+ from __future__ import annotations
18
+
19
+ import torch
20
+
21
+
22
+ def compute_sqnr(
23
+ original: torch.Tensor,
24
+ quantized: torch.Tensor,
25
+ eps: float = 1e-12,
26
+ ) -> float:
27
+ """Compute Signal-to-Quantization-Noise Ratio (SQNR) in decibels (dB).
28
+
29
+ Mathematical Formulation:
30
+ SQNR (dB) = 10 * log10( P_signal / P_noise )
31
+ = 10 * log10( sum(|W|^2) / (sum(|W - W_hat|^2) + eps) )
32
+
33
+ Interpretation:
34
+ - Higher SQNR indicates superior numerical fidelity.
35
+ - FP16 -> INT8 typically achieves 40 dB to 55 dB.
36
+ - FP16 -> INT4 blockwise typically achieves 22 dB to 32 dB.
37
+ - Severe breakdown or NaN yields <= 0 dB.
38
+
39
+ Args:
40
+ original: Reference unquantized floating-point tensor.
41
+ quantized: Reconstructed/dequantized floating-point tensor of matching shape.
42
+ eps: Numerical stability constant to prevent division by zero for exact equality.
43
+
44
+ Returns:
45
+ Float value of SQNR in decibels (dB).
46
+ """
47
+ if original.shape != quantized.shape:
48
+ raise ValueError(
49
+ f"Shape mismatch in SQNR calculation: {original.shape} vs {quantized.shape}"
50
+ )
51
+
52
+ orig_f = original.detach().float()
53
+ quant_f = quantized.detach().float()
54
+
55
+ signal_power = torch.sum(orig_f.pow(2)).item()
56
+ noise_power = torch.sum((orig_f - quant_f).pow(2)).item()
57
+
58
+ if noise_power <= eps:
59
+ return 100.0 # Cap at +100 dB for near-lossless / exact reconstruction
60
+
61
+ ratio = signal_power / (noise_power + eps)
62
+ if ratio <= 0.0:
63
+ return 0.0
64
+
65
+ sqnr_db = 10.0 * torch.log10(torch.tensor(ratio)).item()
66
+ return float(sqnr_db)
@@ -0,0 +1,9 @@
1
+ """Drop-in quantized neural network modules and model conversion runtimes."""
2
+
3
+ from quantbench.nn.model_convert import convert_model_to_quant
4
+ from quantbench.nn.quant_linear import QuantLinear
5
+
6
+ __all__ = [
7
+ "QuantLinear",
8
+ "convert_model_to_quant",
9
+ ]
@@ -0,0 +1,109 @@
1
+ """In-place model conversion runtime for PyTorch transformer architectures.
2
+
3
+ Recursively scans any PyTorch neural network (such as Hugging Face CausalLM models)
4
+ and replaces target torch.nn.Linear layers with QuantLinear modules.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import time
10
+ from typing import Any
11
+
12
+ import torch.nn as nn
13
+
14
+ from quantbench.nn.quant_linear import QuantLinear
15
+
16
+
17
+ def convert_model_to_quant(
18
+ model: nn.Module,
19
+ target_modules: list[str] | None = None,
20
+ exclude_modules: list[str] | None = None,
21
+ scheme: str = "int4",
22
+ group_size: int = 64,
23
+ symmetric: bool = True,
24
+ ) -> tuple[nn.Module, dict[str, Any]]:
25
+ """In-place recursive conversion of model linear layers to QuantLinear.
26
+
27
+ Args:
28
+ model: PyTorch model (e.g. Hugging Face model or custom transformer).
29
+ target_modules: Optional list of substring names to convert (e.g. ['q_proj', 'v_proj']).
30
+ If None, all linear layers are targeted except those in exclude_modules.
31
+ exclude_modules: Module names to explicitly preserve unquantized (defaults to ['lm_head']).
32
+ scheme: Target quantization scheme ('int4', 'int8', 'ternary', 'sparse24').
33
+ group_size: Group size for blockwise quantization (-1 for per-channel).
34
+ symmetric: Whether to use symmetric quantization.
35
+
36
+ Returns:
37
+ Tuple of (converted_model, conversion_statistics_dictionary).
38
+ """
39
+ if exclude_modules is None:
40
+ exclude_modules = ["lm_head"]
41
+
42
+ t0 = time.perf_counter()
43
+
44
+ # Calculate pre-conversion memory
45
+ orig_bytes = sum(p.nelement() * p.element_size() for p in model.parameters())
46
+
47
+ converted_count = 0
48
+ skipped_count = 0
49
+ layer_names: list[str] = []
50
+
51
+ # Helper function to recursively replace layers
52
+ def _replace_layers(parent: nn.Module, prefix: str = ""):
53
+ nonlocal converted_count, skipped_count
54
+ for name, child in list(parent.named_children()):
55
+ full_name = f"{prefix}.{name}" if prefix else name
56
+
57
+ # Check if this child is an explicit exclusion
58
+ if any(exc in full_name for exc in exclude_modules):
59
+ skipped_count += 1
60
+ continue
61
+
62
+ if isinstance(child, nn.Linear):
63
+ # Check if target matches
64
+ if target_modules is not None:
65
+ is_target = any(tgt in full_name for tgt in target_modules)
66
+ else:
67
+ is_target = True
68
+
69
+ if is_target:
70
+ # Convert to QuantLinear
71
+ quant_child = QuantLinear.from_linear(
72
+ child,
73
+ scheme=scheme,
74
+ group_size=group_size,
75
+ symmetric=symmetric,
76
+ )
77
+ setattr(parent, name, quant_child)
78
+ converted_count += 1
79
+ layer_names.append(full_name)
80
+ else:
81
+ skipped_count += 1
82
+ else:
83
+ _replace_layers(child, full_name)
84
+
85
+ _replace_layers(model)
86
+
87
+ dt = time.perf_counter() - t0
88
+
89
+ # Calculate post-conversion memory (buffers + parameters)
90
+ quant_bytes = sum(p.nelement() * p.element_size() for p in model.parameters())
91
+ quant_bytes += sum(b.nelement() * b.element_size() for b in model.buffers())
92
+
93
+ orig_mb = orig_bytes / (1024 * 1024)
94
+ quant_mb = quant_bytes / (1024 * 1024)
95
+ ratio = orig_mb / max(quant_mb, 1e-6)
96
+
97
+ stats = {
98
+ "converted_layers": converted_count,
99
+ "skipped_layers": skipped_count,
100
+ "scheme": scheme,
101
+ "group_size": group_size,
102
+ "orig_memory_mb": round(orig_mb, 2),
103
+ "quant_memory_mb": round(quant_mb, 2),
104
+ "compression_ratio": round(ratio, 2),
105
+ "duration_sec": round(dt, 4),
106
+ "layer_names": layer_names,
107
+ }
108
+
109
+ return model, stats
@@ -0,0 +1,296 @@
1
+ """QuantLinear: Drop-in PyTorch Linear layer with sub-byte packed weights.
2
+
3
+ Replaces standard torch.nn.Linear by storing weights as packed integer buffers:
4
+ - INT4: Vectorized uint8 packed nibbles (2 weights/byte) + blockwise/channel scales.
5
+ - BitNet Ternary: 2-bit offset-binary packed uint8 buffers (4 weights/byte) + scale.
6
+ - 2:4 Sparsity: 50% condensed float values + 2-bit position metadata nibbles.
7
+ - INT8: Signed 8-bit integers + per-channel or per-tensor scales.
8
+
9
+ Weights are registered as persistent buffers rather than trainable nn.Parameters,
10
+ eliminating optimizer memory overhead and enabling direct serialization into safetensors.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import Any
16
+
17
+ import torch
18
+ import torch.nn as nn
19
+ import torch.nn.functional as F
20
+
21
+ from quantbench.quant.affine import (
22
+ dequantize_absmax,
23
+ dequantize_affine,
24
+ quantize_absmax,
25
+ quantize_affine,
26
+ )
27
+ from quantbench.quant.blockwise import dequantize_blockwise, quantize_blockwise
28
+ from quantbench.quant.packing import (
29
+ decompress_2_4,
30
+ pack_int4,
31
+ pack_ternary,
32
+ prune_and_compress_2_4,
33
+ unpack_int4,
34
+ unpack_ternary,
35
+ )
36
+
37
+
38
+ class QuantLinear(nn.Module):
39
+ """Drop-in quantized linear layer storing packed weights in memory buffers.
40
+
41
+ Args:
42
+ in_features: Size of each input sample.
43
+ out_features: Size of each output sample.
44
+ scheme: Quantization scheme: 'int4', 'int8', 'ternary', 'sparse24'.
45
+ group_size: Group size for blockwise quantization (-1 for per-channel).
46
+ bias: Whether to include an additive bias parameter.
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ in_features: int,
52
+ out_features: int,
53
+ scheme: str = "int4",
54
+ group_size: int = 64,
55
+ bias: bool = True,
56
+ ) -> None:
57
+ super().__init__()
58
+ self.in_features = in_features
59
+ self.out_features = out_features
60
+ self.scheme = scheme.lower()
61
+ self.group_size = group_size
62
+
63
+ if self.scheme not in ("int4", "int8", "ternary", "sparse24"):
64
+ raise ValueError(
65
+ f"Unknown scheme '{scheme}'. Supported: 'int4', 'int8', 'ternary', 'sparse24'."
66
+ )
67
+
68
+ # Register bias as parameter if present
69
+ if bias:
70
+ self.bias = nn.Parameter(torch.zeros(out_features))
71
+ else:
72
+ self.register_parameter("bias", None)
73
+
74
+ # Buffers are allocated upon calling from_linear or loading state_dict
75
+ self.register_buffer("packed_weight", None)
76
+ self.register_buffer("scales", None)
77
+ self.register_buffer("zero_points", None)
78
+ self.register_buffer("condensed_weight", None)
79
+ self.register_buffer("packed_metadata", None)
80
+
81
+ def _load_from_state_dict(
82
+ self,
83
+ state_dict: dict[str, Any],
84
+ prefix: str,
85
+ local_metadata: dict[str, Any],
86
+ strict: bool,
87
+ missing_keys: list[str],
88
+ unexpected_keys: list[str],
89
+ error_msgs: list[str],
90
+ ) -> None:
91
+ """Register incoming serialized buffer tensors dynamically during load_state_dict."""
92
+ for buf_name in (
93
+ "packed_weight",
94
+ "scales",
95
+ "zero_points",
96
+ "condensed_weight",
97
+ "packed_metadata",
98
+ ):
99
+ full_key = prefix + buf_name
100
+ if full_key in state_dict and state_dict[full_key] is not None:
101
+ self.register_buffer(buf_name, state_dict[full_key])
102
+ super()._load_from_state_dict(
103
+ state_dict,
104
+ prefix,
105
+ local_metadata,
106
+ strict,
107
+ missing_keys,
108
+ unexpected_keys,
109
+ error_msgs,
110
+ )
111
+
112
+ @classmethod
113
+ def from_linear(
114
+ cls,
115
+ linear: nn.Linear,
116
+ scheme: str = "int4",
117
+ group_size: int = 64,
118
+ symmetric: bool = True,
119
+ ) -> QuantLinear:
120
+ """Create a QuantLinear module from an unquantized torch.nn.Linear layer.
121
+
122
+ Args:
123
+ linear: Source torch.nn.Linear layer.
124
+ scheme: Target quantization scheme: 'int4', 'int8', 'ternary', 'sparse24'.
125
+ group_size: Group size for blockwise quantization (e.g. 32, 64, 128, or -1).
126
+ symmetric: Whether to use symmetric quantization (zero-point = 0).
127
+
128
+ Returns:
129
+ QuantLinear instance with compressed weight buffers.
130
+ """
131
+ in_features = linear.in_features
132
+ out_features = linear.out_features
133
+ has_bias = linear.bias is not None
134
+
135
+ quant_layer = cls(
136
+ in_features=in_features,
137
+ out_features=out_features,
138
+ scheme=scheme,
139
+ group_size=group_size,
140
+ bias=has_bias,
141
+ )
142
+
143
+ w = linear.weight.data.clone().to(torch.float32)
144
+ if has_bias:
145
+ quant_layer.bias.data.copy_(linear.bias.data)
146
+
147
+ scheme_lower = scheme.lower()
148
+
149
+ if scheme_lower == "int4":
150
+ if group_size > 0:
151
+ # Group-wise INT4
152
+ q_blocks, s, zp, _ = quantize_blockwise(
153
+ w, group_size=group_size, num_bits=4, symmetric=symmetric
154
+ )
155
+ # Pack each block along dim=-1
156
+ packed_w, _ = pack_int4(q_blocks, signed=symmetric, dim=-1)
157
+ quant_layer.register_buffer("packed_weight", packed_w)
158
+ quant_layer.register_buffer("scales", s)
159
+ if zp is not None:
160
+ quant_layer.register_buffer("zero_points", zp)
161
+ else:
162
+ # Per-channel INT4 along output channels (reduce across input dim 1)
163
+ if symmetric:
164
+ q_w, s = quantize_absmax(w, num_bits=4, dim=1)
165
+ zp = None
166
+ else:
167
+ q_w, s, zp = quantize_affine(w, num_bits=4, dim=1)
168
+ packed_w, _ = pack_int4(q_w, signed=symmetric, dim=-1)
169
+ quant_layer.register_buffer("packed_weight", packed_w)
170
+ quant_layer.register_buffer("scales", s)
171
+ if zp is not None:
172
+ quant_layer.register_buffer("zero_points", zp)
173
+
174
+ elif scheme_lower == "int8":
175
+ if symmetric:
176
+ q_w, s = quantize_absmax(w, num_bits=8, dim=1)
177
+ zp = None
178
+ else:
179
+ q_w, s, zp = quantize_affine(w, num_bits=8, dim=1)
180
+ quant_layer.register_buffer("packed_weight", q_w.to(torch.int8))
181
+ quant_layer.register_buffer("scales", s)
182
+ if zp is not None:
183
+ quant_layer.register_buffer("zero_points", zp)
184
+
185
+ elif scheme_lower == "ternary":
186
+ # BitNet ternary scaling: gamma = mean(|W|)
187
+ gamma = w.abs().mean().clamp(min=1e-8)
188
+ scaled_w = w / gamma
189
+ ternary_w = torch.clamp(torch.round(scaled_w), -1.0, 1.0)
190
+ packed_w, _ = pack_ternary(ternary_w, mode="2bit", dim=-1)
191
+ quant_layer.register_buffer("packed_weight", packed_w)
192
+ quant_layer.register_buffer("scales", gamma.view(1))
193
+
194
+ elif scheme_lower in ("sparse24", "2:4"):
195
+ condensed, metadata = prune_and_compress_2_4(w)
196
+ quant_layer.register_buffer("condensed_weight", condensed)
197
+ quant_layer.register_buffer("packed_metadata", metadata)
198
+
199
+ return quant_layer
200
+
201
+ def dequantize_weight(self) -> torch.Tensor:
202
+ """Dequantize packed buffers back to a standard floating-point weight matrix."""
203
+ if self.scheme == "int4":
204
+ is_signed = self.zero_points is None
205
+ if self.group_size > 0:
206
+ # Group-wise reconstruction
207
+ unpacked_q = unpack_int4(
208
+ self.packed_weight, original_len=self.group_size, signed=is_signed, dim=-1
209
+ )
210
+ n_elements = self.out_features * self.in_features
211
+ remainder = n_elements % self.group_size
212
+ pad_len = (self.group_size - remainder) % self.group_size
213
+ meta = {
214
+ "original_shape": (self.out_features, self.in_features),
215
+ "n_elements": n_elements,
216
+ "pad_len": pad_len,
217
+ "group_size": self.group_size,
218
+ "symmetric": is_signed,
219
+ "dtype": torch.float32,
220
+ "device": self.packed_weight.device,
221
+ }
222
+ zp = self.zero_points if self.zero_points is not None else None
223
+ return dequantize_blockwise(unpacked_q, self.scales, zp, meta)
224
+ else:
225
+ # Per-channel reconstruction
226
+ unpacked_q = unpack_int4(
227
+ self.packed_weight, original_len=self.in_features, signed=is_signed, dim=-1
228
+ )
229
+ if self.zero_points is not None:
230
+ return dequantize_affine(unpacked_q, self.scales, self.zero_points)
231
+ else:
232
+ return dequantize_absmax(unpacked_q, self.scales)
233
+
234
+ elif self.scheme == "int8":
235
+ if self.zero_points is not None:
236
+ return dequantize_affine(self.packed_weight, self.scales, self.zero_points)
237
+ else:
238
+ return dequantize_absmax(self.packed_weight, self.scales)
239
+
240
+ elif self.scheme == "ternary":
241
+ unpacked_t = unpack_ternary(
242
+ self.packed_weight, original_len=self.in_features, mode="2bit", dim=-1
243
+ )
244
+ return unpacked_t.to(torch.float32) * self.scales
245
+
246
+ elif self.scheme in ("sparse24", "2:4"):
247
+ return decompress_2_4(
248
+ self.condensed_weight,
249
+ self.packed_metadata,
250
+ original_shape=(self.out_features, self.in_features),
251
+ )
252
+
253
+ raise RuntimeError(f"Unsupported scheme '{self.scheme}'.")
254
+
255
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
256
+ """Forward pass with dynamic vector dequantization.
257
+
258
+ Args:
259
+ x: Input activation tensor with shape (..., in_features).
260
+
261
+ Returns:
262
+ Projected activation tensor with shape (..., out_features).
263
+ """
264
+ w_hat = self.dequantize_weight().to(x.dtype)
265
+ return F.linear(x, w_hat, self.bias)
266
+
267
+ def memory_footprint_bytes(self) -> dict[str, float]:
268
+ """Compute exact memory footprint in bytes and compression factor relative to FP16.
269
+
270
+ Returns:
271
+ Dictionary containing 'compressed_bytes', 'fp16_bytes', and 'compression_ratio'.
272
+ """
273
+ compressed_bytes = 0
274
+ for buf in self.buffers():
275
+ if buf is not None:
276
+ compressed_bytes += buf.nelement() * buf.element_size()
277
+ if self.bias is not None:
278
+ compressed_bytes += self.bias.nelement() * self.bias.element_size()
279
+
280
+ fp16_bytes = (self.in_features * self.out_features * 2) + (
281
+ (self.out_features * 2) if self.bias is not None else 0
282
+ )
283
+ ratio = fp16_bytes / max(compressed_bytes, 1)
284
+
285
+ return {
286
+ "compressed_bytes": compressed_bytes,
287
+ "fp16_bytes": fp16_bytes,
288
+ "compression_ratio": ratio,
289
+ }
290
+
291
+ def extra_repr(self) -> str:
292
+ return (
293
+ f"in_features={self.in_features}, out_features={self.out_features}, "
294
+ f"scheme={self.scheme}, group_size={self.group_size}, "
295
+ f"bias={self.bias is not None}"
296
+ )
@@ -0,0 +1,58 @@
1
+ """First-principles quantization primitives, algorithms, and bit-packing runtimes."""
2
+
3
+ from quantbench.quant.affine import (
4
+ dequantize_absmax,
5
+ dequantize_affine,
6
+ quantize_absmax,
7
+ quantize_affine,
8
+ )
9
+ from quantbench.quant.algorithms import (
10
+ awq_quantize_weights,
11
+ compute_activation_channel_means,
12
+ compute_activation_hessian,
13
+ compute_smoothquant_scale,
14
+ extract_activation_channel_scales,
15
+ gptq_quantize_weights,
16
+ search_best_awq_scale,
17
+ smooth_linear_weights,
18
+ )
19
+ from quantbench.quant.blockwise import dequantize_blockwise, quantize_blockwise
20
+ from quantbench.quant.fp8 import FP8Format, dequantize_fp8, quantize_fp8
21
+ from quantbench.quant.packing import (
22
+ decompress_2_4,
23
+ pack_2_4_indices,
24
+ pack_int4,
25
+ pack_ternary,
26
+ prune_and_compress_2_4,
27
+ unpack_2_4_indices,
28
+ unpack_int4,
29
+ unpack_ternary,
30
+ )
31
+
32
+ __all__ = [
33
+ "quantize_affine",
34
+ "dequantize_affine",
35
+ "quantize_absmax",
36
+ "dequantize_absmax",
37
+ "quantize_blockwise",
38
+ "dequantize_blockwise",
39
+ "quantize_fp8",
40
+ "dequantize_fp8",
41
+ "FP8Format",
42
+ "pack_int4",
43
+ "unpack_int4",
44
+ "pack_ternary",
45
+ "unpack_ternary",
46
+ "pack_2_4_indices",
47
+ "unpack_2_4_indices",
48
+ "prune_and_compress_2_4",
49
+ "decompress_2_4",
50
+ "compute_smoothquant_scale",
51
+ "extract_activation_channel_scales",
52
+ "smooth_linear_weights",
53
+ "compute_activation_channel_means",
54
+ "search_best_awq_scale",
55
+ "awq_quantize_weights",
56
+ "compute_activation_hessian",
57
+ "gptq_quantize_weights",
58
+ ]